Added tests which guard the JSON of providers, the runtime, and services before source generation

This commit is contained in:
Thorsten Sommer committed 2026-11-02 18:42:28 +01:00
1 parent e954881fda
commit 9e5aee766c
31 files changed
+577 -93

No files matched your search

@@ -215,31 +215,35 @@ public sealed class QdrantEdgeClientImplementation(
return client;
}
// ReSharper disable NotAccessedPositionalProperty.Local
private sealed record EnsureVectorStoreRequest(string StoreName, string DataSourceName, int VectorSize);
//
// The requests are internal rather than private, so that the JSON context of the runtime can name them. It
// generates the contracts they get written with, and a context sees no private type of another class.
//
// ReSharper disable NotAccessedPositionalProperty.Global
internal sealed record EnsureVectorStoreRequest(string StoreName, string DataSourceName, int VectorSize);
private sealed record CheckVectorStoreRequest(string StoreName);
internal sealed record CheckVectorStoreRequest(string StoreName);
private sealed record InsertEmbeddingRequest(string StoreName, IReadOnlyList<VectorStoragePoint> Points);
internal sealed record InsertEmbeddingRequest(string StoreName, IReadOnlyList<VectorStoragePoint> Points);
/// <param name="PointIds">The only points the search may return, or null for the whole store.</param>
private sealed record SearchEmbeddingRequest(string StoreName, IReadOnlyList<float> Vector, int MaxMatches, IReadOnlyList<string>? PointIds, IReadOnlyList<HiddenChunks> HiddenChunks);
internal sealed record SearchEmbeddingRequest(string StoreName, IReadOnlyList<float> Vector, int MaxMatches, IReadOnlyList<string>? PointIds, IReadOnlyList<HiddenChunks> HiddenChunks);
private sealed record RetrieveVectorsRequest(string StoreName, IReadOnlyList<string> PointIds);
internal sealed record RetrieveVectorsRequest(string StoreName, IReadOnlyList<string> PointIds);
private sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath, string ParentFileId, IReadOnlyCollection<string> KeepPointIds);
internal sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath, string ParentFileId, IReadOnlyCollection<string> KeepPointIds);
private sealed record RelocateEmbeddingFileRequest(string StoreName, string ParentFileId, string AbsolutePath, string FileName, string RelativePath, string Fingerprint);
internal sealed record RelocateEmbeddingFileRequest(string StoreName, string ParentFileId, string AbsolutePath, string FileName, string RelativePath, string Fingerprint);
private sealed record OptimizeVectorStoreRequest(string StoreName);
private sealed record DeleteVectorStoreRequest(string StoreName);
internal sealed record OptimizeVectorStoreRequest(string StoreName);
internal sealed record DeleteVectorStoreRequest(string StoreName);
/// <summary>
/// Preparing a backup needs nothing but the request itself.
/// </summary>
private sealed record PrepareBackupRequest;
internal sealed record PrepareBackupRequest;
private sealed record CopyStoreForBackupRequest(string BackupId, string StoreName);
// ReSharper restore NotAccessedPositionalProperty.Local
internal sealed record CopyStoreForBackupRequest(string BackupId, string StoreName);
// ReSharper restore NotAccessedPositionalProperty.Global
}
@@ -9,7 +9,7 @@ public abstract class ERIClientBase(IERIDataSource dataSource) : IDisposable
{
protected readonly IERIDataSource DataSource = dataSource;
protected static readonly JsonSerializerOptions JSON_OPTIONS = new()
internal static readonly JsonSerializerOptions JSON_OPTIONS = new()
{
WriteIndented = true,
AllowTrailingCommas = true,
@@ -63,7 +63,7 @@ public sealed partial class RustService
private async Task<RequestedSecret> GetAPIKeyByKey(string secretKey, bool isTrying)
{
var secretRequest = new SelectSecretRequest(secretKey, Environment.UserName, isTrying);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
if(!isTrying)
@@ -71,7 +71,7 @@ public sealed partial class RustService
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the API key due to an API issue."));
}
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(JSON_OPTIONS);
if (secret.Success)
this.logger!.LogDebug($"Successfully retrieved the API key for '{secretKey}'.");
else if (isTrying)
@@ -106,14 +106,14 @@ public sealed partial class RustService
private async Task<StoreSecretResponse> StoreEncryptedAPIKeyByKey(string secretKey, EncryptedText encryptedKey)
{
var request = new StoreSecretRequest(secretKey, Environment.UserName, encryptedKey);
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/secrets/store", request, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to store the API key for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new StoreSecretResponse(false, TB("Failed to store the API key due to an API issue."));
}
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(JSON_OPTIONS);
if (!state.Success)
this.logger!.LogError($"Failed to store the API key for '{secretKey}': '{state.Issue}'");
else
@@ -149,14 +149,14 @@ public sealed partial class RustService
private async Task<DeleteSecretResponse> DeleteAPIKeyByKey(string secretKey, bool isTrying = false)
{
var request = new SelectSecretRequest(secretKey, Environment.UserName, false);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to delete the API key for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = TB("Failed to delete the API key due to an API issue.")};
}
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(JSON_OPTIONS);
if (!state.Success && !isTrying)
this.logger!.LogError($"Failed to delete the API key for '{secretKey}': '{state.Issue}'");
@@ -122,7 +122,7 @@ public sealed partial class RustService
public async Task<RuntimeInfoResponse> GetRuntimeInfo()
{
var response = await this.http.GetFromJsonAsync<RuntimeInfoResponse>("/system/runtime/info", this.jsonRustSerializerOptions);
var response = await this.http.GetFromJsonAsync<RuntimeInfoResponse>("/system/runtime/info", JSON_OPTIONS);
return response;
}
@@ -140,7 +140,7 @@ public sealed partial class RustService
return false;
}
var result = await response.Content.ReadFromJsonAsync<AppExitResponse>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<AppExitResponse>(JSON_OPTIONS);
if (result is null || !result.Success)
{
this.logger?.LogError("Failed to exit the app: {Error}", result?.ErrorMessage ?? "Unknown error");
@@ -177,7 +177,7 @@ public sealed partial class RustService
return false;
}
var result = await response.Content.ReadFromJsonAsync<AppRestartResponse>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<AppRestartResponse>(JSON_OPTIONS);
if (result is null || !result.Success)
{
this.logger?.LogError("Failed to restart the app: {Error}", result?.ErrorMessage ?? "Unknown error");
@@ -203,7 +203,7 @@ public sealed partial class RustService
{
try
{
var response = await this.http.PostAsJsonAsync("/app/interface-size", request, this.jsonRustSerializerOptions);
var response = await this.http.PostAsJsonAsync("/app/interface-size", request, JSON_OPTIONS);
var result = await this.ReadInterfaceSizeResponse(response, "set the interface size");
if (result is not null)
this.logger?.LogInformation("Set the interface sizes of {ScreenCount} screens. AI Studio shows with the size '{Size}' at {CurrentPercent} % and a zoom of {Zoom:0.###} on the screen '{ScreenKey}'.", request.Screens.Count, result.Size, result.CurrentPercent, result.Zoom, result.ScreenKey);
@@ -243,7 +243,7 @@ public sealed partial class RustService
return null;
}
var result = await response.Content.ReadFromJsonAsync<InterfaceSizeResponse>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<InterfaceSizeResponse>(JSON_OPTIONS);
if (result is null || !result.Success)
{
this.logger?.LogError("Failed to {Action}: {Error}", action, result?.ErrorMessage ?? "Unknown error");
@@ -28,7 +28,7 @@ public sealed partial class RustService
return;
}
var state = await response.Content.ReadFromJsonAsync<SetClipboardResponse>(this.jsonRustSerializerOptions);
var state = await response.Content.ReadFromJsonAsync<SetClipboardResponse>(JSON_OPTIONS);
if (!state.Success)
{
this.logger!.LogError("Failed to copy the text to the clipboard.");
@@ -24,7 +24,7 @@ public sealed partial class RustService
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(45));
var databaseInfo = await this.http.GetFromJsonAsync<TDatabaseInfo>(infoPath, this.jsonRustSerializerOptions, cts.Token);
var databaseInfo = await this.http.GetFromJsonAsync<TDatabaseInfo>(infoPath, JSON_OPTIONS, cts.Token);
return databaseInfo ?? unavailableFactory("The database information response was empty.");
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
@@ -52,10 +52,10 @@ public sealed partial class RustService
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromMinutes(5));
using var response = await this.http.PostAsJsonAsync(path, request, this.jsonRustSerializerOptions, cts.Token);
using var response = await this.http.PostAsJsonAsync(path, request, JSON_OPTIONS, cts.Token);
response.EnsureSuccessStatusCode();
var operation = await response.Content.ReadFromJsonAsync<DatabaseOperationResponse>(this.jsonRustSerializerOptions, cts.Token);
var operation = await response.Content.ReadFromJsonAsync<DatabaseOperationResponse>(JSON_OPTIONS, cts.Token);
if (operation is not { Success: true })
throw CreateDatabaseException(operation?.Issue, operation?.IssueCode, $"The {databaseName} operation failed.");
}
@@ -65,10 +65,10 @@ public sealed partial class RustService
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromMinutes(5));
using var response = await this.http.PostAsJsonAsync(path, request, this.jsonRustSerializerOptions, cts.Token);
using var response = await this.http.PostAsJsonAsync(path, request, JSON_OPTIONS, cts.Token);
response.EnsureSuccessStatusCode();
var operation = await response.Content.ReadFromJsonAsync<DatabaseQueryResponse<TResult>>(this.jsonRustSerializerOptions, cts.Token);
var operation = await response.Content.ReadFromJsonAsync<DatabaseQueryResponse<TResult>>(JSON_OPTIONS, cts.Token);
if (operation is not { Success: true })
throw CreateDatabaseException(operation?.Issue, operation?.IssueCode, $"The {databaseName} query failed.");
@@ -94,7 +94,10 @@ public sealed partial class RustService
};
}
private sealed record DatabaseOperationResponse(bool Success, string Issue, string IssueCode);
//
// Internal rather than private, so that the JSON context of the runtime can name them:
//
internal sealed record DatabaseOperationResponse(bool Success, string Issue, string IssueCode);
private sealed record DatabaseQueryResponse<TResult>(bool Success, string Issue, string IssueCode, TResult? Data);
internal sealed record DatabaseQueryResponse<TResult>(bool Success, string Issue, string IssueCode, TResult? Data);
}
@@ -39,7 +39,7 @@ public sealed partial class RustService
throw new HttpRequestException($"Failed to query the enterprise configurations: '{result.StatusCode}'");
}
var configs = await result.Content.ReadFromJsonAsync<List<EnterpriseConfig>>(this.jsonRustSerializerOptions);
var configs = await result.Content.ReadFromJsonAsync<List<EnterpriseConfig>>(JSON_OPTIONS);
if (configs is null)
throw new InvalidOperationException("Failed to parse the enterprise configurations from Rust.");
@@ -35,7 +35,7 @@ public partial class RustService
continue;
// Deserialize the Tauri event:
var tauriEvent = JsonSerializer.Deserialize<TauriEvent>(line, this.jsonRustSerializerOptions);
var tauriEvent = JsonSerializer.Deserialize<TauriEvent>(line, JSON_OPTIONS);
// Forward relevant events to the message bus:
if (tauriEvent != default && tauriEvent.EventType
@@ -16,10 +16,10 @@ public sealed partial class RustService
var startDirectory = initialDirectory is null ? null : PathTools.FindNearestExistingPath(initialDirectory);
var result = startDirectory is null
? await this.http.PostAsync($"/select/directory?title={encodedTitle}", null)
: await this.http.PostAsJsonAsync($"/select/directory?title={encodedTitle}", new PreviousDirectory(startDirectory), this.jsonRustSerializerOptions);
: await this.http.PostAsJsonAsync($"/select/directory?title={encodedTitle}", new PreviousDirectory(startDirectory), JSON_OPTIONS);
if (result.IsSuccessStatusCode)
return await result.Content.ReadFromJsonAsync<DirectorySelectionResponse>(this.jsonRustSerializerOptions);
return await result.Content.ReadFromJsonAsync<DirectorySelectionResponse>(JSON_OPTIONS);
this.logger!.LogError("Failed to select a directory: '{StatusCode}'", result.StatusCode);
return new DirectorySelectionResponse(true, string.Empty);
@@ -63,9 +63,9 @@ public sealed partial class RustService
Filter = FileTypes.ToFileDialogFilter(filter)
};
var result = await this.http.PostAsJsonAsync("/select/file", payload, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/select/file", payload, JSON_OPTIONS);
if (result.IsSuccessStatusCode)
return await result.Content.ReadFromJsonAsync<FileSelectionResponse>(this.jsonRustSerializerOptions);
return await result.Content.ReadFromJsonAsync<FileSelectionResponse>(JSON_OPTIONS);
this.logger!.LogError("Failed to select a file: '{StatusCode}'", result.StatusCode);
return new FileSelectionResponse(true, string.Empty);
@@ -87,9 +87,9 @@ public sealed partial class RustService
Filter = FileTypes.ToFileDialogFilter(filter)
};
var result = await this.http.PostAsJsonAsync("/select/files", payload, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/select/files", payload, JSON_OPTIONS);
if (result.IsSuccessStatusCode)
return await result.Content.ReadFromJsonAsync<FilesSelectionResponse>(this.jsonRustSerializerOptions);
return await result.Content.ReadFromJsonAsync<FilesSelectionResponse>(JSON_OPTIONS);
this.logger!.LogError("Failed to select files: '{StatusCode}'", result.StatusCode);
return new FilesSelectionResponse(true, Array.Empty<string>());
@@ -118,9 +118,9 @@ public sealed partial class RustService
Filter = FileTypes.ToFileDialogFilter(filter)
};
var result = await this.http.PostAsJsonAsync("/save/file", payload, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/save/file", payload, JSON_OPTIONS);
if (result.IsSuccessStatusCode)
return await result.Content.ReadFromJsonAsync<FileSaveResponse>(this.jsonRustSerializerOptions);
return await result.Content.ReadFromJsonAsync<FileSaveResponse>(JSON_OPTIONS);
this.logger!.LogError("Failed to select a file for writing operation: '{StatusCode}'", result.StatusCode);
return new FileSaveResponse(true, string.Empty);
@@ -133,7 +133,7 @@ public sealed partial class RustService
HttpResponseMessage result;
try
{
result = await this.http.PostAsJsonAsync("/open/path", new OpenPathRequest(path), this.jsonRustSerializerOptions);
result = await this.http.PostAsJsonAsync("/open/path", new OpenPathRequest(path), JSON_OPTIONS);
}
catch (HttpRequestException e)
{
@@ -154,7 +154,7 @@ public sealed partial class RustService
return new OpenPathResponse(false, string.Format(TB("The runtime file manager endpoint returned '{0}'."), result.StatusCode));
}
var response = await result.Content.ReadFromJsonAsync<OpenPathResponse>(this.jsonRustSerializerOptions);
var response = await result.Content.ReadFromJsonAsync<OpenPathResponse>(JSON_OPTIONS);
var normalizedResponse = response.Success
? response
: new OpenPathResponse(false, string.IsNullOrWhiteSpace(response.Issue) ? TB("The runtime file manager endpoint failed without details.") : response.Issue);
@@ -187,7 +187,7 @@ public sealed partial class RustService
HttpResponseMessage result;
try
{
result = await this.http.PostAsJsonAsync("/open/document", new OpenDocumentRequest(path, pageNumber), this.jsonRustSerializerOptions);
result = await this.http.PostAsJsonAsync("/open/document", new OpenDocumentRequest(path, pageNumber), JSON_OPTIONS);
}
catch (HttpRequestException e)
{
@@ -208,7 +208,7 @@ public sealed partial class RustService
return new OpenDocumentResponse(false, false, string.Format(TB("The runtime document endpoint returned '{0}'."), result.StatusCode));
}
var response = await result.Content.ReadFromJsonAsync<OpenDocumentResponse>(this.jsonRustSerializerOptions);
var response = await result.Content.ReadFromJsonAsync<OpenDocumentResponse>(JSON_OPTIONS);
if (response.Success)
{
//
@@ -20,10 +20,10 @@ public sealed partial class RustService
bool optimize,
CancellationToken token = default)
{
using var response = await this.http.PostAsJsonAsync("/image/prepare", new { path, optimize }, this.jsonRustSerializerOptions, token);
using var response = await this.http.PostAsJsonAsync("/image/prepare", new { path, optimize }, JSON_OPTIONS, token);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<ImagePrepareResponse>(this.jsonRustSerializerOptions, token)
return await response.Content.ReadFromJsonAsync<ImagePrepareResponse>(JSON_OPTIONS, token)
?? throw new InvalidDataException("The Rust image preparation returned an empty response.");
}
@@ -44,10 +44,10 @@ public sealed partial class RustService
{
try
{
using var response = await this.http.PostAsJsonAsync("/image/prepare-for-model", new { path, maxBytes }, this.jsonRustSerializerOptions, token);
using var response = await this.http.PostAsJsonAsync("/image/prepare-for-model", new { path, maxBytes }, JSON_OPTIONS, token);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<PreparedModelImage>(this.jsonRustSerializerOptions, token)
return await response.Content.ReadFromJsonAsync<PreparedModelImage>(JSON_OPTIONS, token)
?? PreparedModelImage.Failed(ModelImageErrorCode.UNKNOWN);
}
catch (Exception e) when (e is not OperationCanceledException)
@@ -10,7 +10,7 @@ public sealed partial class RustService
/// <returns>The paths of the log files.</returns>
public async Task<GetLogPathsResponse> GetLogPaths()
{
return await this.http.GetFromJsonAsync<GetLogPathsResponse>("/log/paths", this.jsonRustSerializerOptions);
return await this.http.GetFromJsonAsync<GetLogPathsResponse>("/log/paths", JSON_OPTIONS);
}
/// <summary>
@@ -33,7 +33,7 @@ public sealed partial class RustService
// feed itself. The unobserved task exception handler in Program.cs remains the safety net here.
//
var request = new LogEventRequest(timestamp, level, category, message, exception, stackTrace);
_ = this.http.PostAsJsonAsync("/log/event", request, this.jsonRustSerializerOptions);
_ = this.http.PostAsJsonAsync("/log/event", request, JSON_OPTIONS);
}
catch
{
@@ -18,11 +18,11 @@ public partial class RustService
using var response = await this.http.PostAsJsonAsync(
"/media/jobs",
new CreateMediaJobRequest(inputPath, outputPath, OpusBitrateBps: opusBitrateBps),
this.jsonRustSerializerOptions,
JSON_OPTIONS,
token);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<CreateMediaJobResponse>(this.jsonRustSerializerOptions, token);
var result = await response.Content.ReadFromJsonAsync<CreateMediaJobResponse>(JSON_OPTIONS, token);
return result?.JobId ?? throw new InvalidOperationException("The Rust runtime did not return a media job ID.");
}
@@ -49,7 +49,7 @@ public partial class RustService
continue;
var json = line["data:".Length..].Trim();
var mediaEvent = JsonSerializer.Deserialize<MediaJobEvent>(json, this.jsonRustSerializerOptions);
var mediaEvent = JsonSerializer.Deserialize<MediaJobEvent>(json, JSON_OPTIONS);
if (mediaEvent is not null)
yield return mediaEvent;
@@ -65,14 +65,14 @@ public sealed partial class RustService
var secretKey = SecretKey(secretId, storeType);
var encryptedSecret = await this.encryptor!.Encrypt(secretData);
var request = new StoreSecretRequest(secretKey, Environment.UserName, encryptedSecret);
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/secrets/store", request, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to store the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new StoreSecretResponse(false, TB("Failed to store the secret data due to an API issue."));
}
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(JSON_OPTIONS);
if (!state.Success)
this.logger!.LogError($"Failed to store the secret data for '{secretKey}': '{state.Issue}'");
@@ -104,7 +104,7 @@ public sealed partial class RustService
private async Task<RequestedSecret> GetSecretByKey(string secretKey, bool isTrying)
{
var secretRequest = new SelectSecretRequest(secretKey, Environment.UserName, isTrying);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
if(isTrying)
@@ -114,7 +114,7 @@ public sealed partial class RustService
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the secret data due to an API issue."));
}
var state = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
var state = await result.Content.ReadFromJsonAsync<RequestedSecret>(JSON_OPTIONS);
if (!state.Success)
{
//
@@ -138,14 +138,14 @@ public sealed partial class RustService
private async Task<DeleteSecretResponse> DeleteSecretByKey(string secretKey)
{
var request = new SelectSecretRequest(secretKey, Environment.UserName, false);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to delete the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = TB("Failed to delete the secret data due to an API issue.")};
}
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(JSON_OPTIONS);
if (!state.Success)
this.logger!.LogError($"Failed to delete the secret data for '{secretKey}': '{state.Issue}'");
@@ -1,4 +1,4 @@
// ReSharper disable NotAccessedPositionalProperty.Local
// ReSharper disable NotAccessedPositionalProperty.Global
using AIStudio.Tools.Rust;
namespace AIStudio.Tools.Services;
@@ -14,14 +14,14 @@ public sealed partial class RustService
{
try
{
using var response = await this.http.PostAsJsonAsync("/share/file", new ShareFileRequest(filePath), this.jsonRustSerializerOptions);
using var response = await this.http.PostAsJsonAsync("/share/file", new ShareFileRequest(filePath), JSON_OPTIONS);
if (!response.IsSuccessStatusCode)
{
this.logger?.LogError($"The Rust runtime rejected the share request: {response.StatusCode}.");
return new(ShareSheetOutcome.FAILED, $"The Rust runtime rejected the share request: {response.StatusCode}.");
}
var result = await response.Content.ReadFromJsonAsync<ShareFileResponse>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<ShareFileResponse>(JSON_OPTIONS);
if (result is null)
{
this.logger?.LogError("The Rust runtime answered the share request without a result.");
@@ -50,5 +50,8 @@ public sealed partial class RustService
}
}
private sealed record ShareFileRequest(string FilePath);
/// <remarks>
/// Internal rather than private, so that the JSON context of the runtime can name it.
/// </remarks>
internal sealed record ShareFileRequest(string FilePath);
}
@@ -18,7 +18,7 @@ public sealed partial class RustService
try
{
var request = new RegisterShortcutRequest(shortcutId, shortcut, description, reconfigure);
var response = await this.http.PostAsJsonAsync("/shortcuts/register", request, this.jsonRustSerializerOptions);
var response = await this.http.PostAsJsonAsync("/shortcuts/register", request, JSON_OPTIONS);
if (!response.IsSuccessStatusCode)
{
@@ -26,7 +26,7 @@ public sealed partial class RustService
return ShortcutRegistrationResult.Failed(TB("The global shortcut could not be registered because the desktop service is unavailable."));
}
var result = await response.Content.ReadFromJsonAsync<ShortcutRegistrationResult>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<ShortcutRegistrationResult>(JSON_OPTIONS);
if (result is null || !result.Success)
{
this.logger?.LogError("Failed to register global shortcut '{ShortcutId}': {Error}", shortcutId, result?.ErrorMessage ?? "Unknown error");
@@ -53,7 +53,7 @@ public sealed partial class RustService
try
{
var request = new ValidateShortcutRequest(shortcut);
var response = await this.http.PostAsJsonAsync("/shortcuts/validate", request, this.jsonRustSerializerOptions);
var response = await this.http.PostAsJsonAsync("/shortcuts/validate", request, JSON_OPTIONS);
if (!response.IsSuccessStatusCode)
{
@@ -61,7 +61,7 @@ public sealed partial class RustService
return new ShortcutValidationResult(false, "Network error during validation", false, string.Empty);
}
var result = await response.Content.ReadFromJsonAsync<ShortcutValidationResponse>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<ShortcutValidationResponse>(JSON_OPTIONS);
if (result is null)
return new ShortcutValidationResult(false, "Invalid response from server", false, string.Empty);
@@ -91,7 +91,7 @@ public sealed partial class RustService
return false;
}
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(JSON_OPTIONS);
if (result is null || !result.Success)
{
this.logger?.LogError("Failed to suspend shortcut processing: {Error}", result?.ErrorMessage ?? "Unknown error");
@@ -123,7 +123,7 @@ public sealed partial class RustService
return false;
}
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(this.jsonRustSerializerOptions);
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(JSON_OPTIONS);
if (result is null || !result.Success)
{
this.logger?.LogError("Failed to resume shortcut processing: {Error}", result?.ErrorMessage ?? "Unknown error");
@@ -17,7 +17,7 @@ public sealed partial class RustService : ITokenizerStorage
{
var result = await this.http.PostAsJsonAsync("/tokenizer/validate", new {
file_path = filePath,
}, this.jsonRustSerializerOptions);
}, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
@@ -25,7 +25,7 @@ public sealed partial class RustService : ITokenizerStorage
return CreateUnavailableTokenizerResponse("An error occured while sending the path to the Rust framework for validation: "+result.StatusCode);
}
var response = await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions);
var response = await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS);
return response;
}
@@ -36,7 +36,7 @@ public sealed partial class RustService : ITokenizerStorage
var result = await this.http.PostAsJsonAsync("/tokenizer/store", new {
model_id = modelId,
file_path = filePath,
}, this.jsonRustSerializerOptions);
}, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
@@ -44,7 +44,7 @@ public sealed partial class RustService : ITokenizerStorage
return CreateUnavailableTokenizerResponse("An error occured while sending the path to the Rust framework for storing: "+result.StatusCode);
}
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions);
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS);
}
public async Task<TokenizerResponse> DeleteTokenizer(string modelId)
@@ -52,7 +52,7 @@ public sealed partial class RustService : ITokenizerStorage
this.logger!.LogInformation($"Deleting tokenizer for model '{modelId}'");
var result = await this.http.PostAsJsonAsync("/tokenizer/delete", new {
model_id = modelId,
}, this.jsonRustSerializerOptions);
}, JSON_OPTIONS);
if (!result.IsSuccessStatusCode)
{
@@ -60,7 +60,7 @@ public sealed partial class RustService : ITokenizerStorage
return CreateUnavailableTokenizerResponse("An error occured while sending the tokenizer delete request to the Rust framework: "+result.StatusCode);
}
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions);
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS);
}
public Task<TokenizerResponse?> GetTokenCount(AIStudio.Settings.Provider provider, string text, CancellationToken cancellationToken = default) =>
@@ -74,7 +74,7 @@ public sealed partial class RustService : ITokenizerStorage
var result = await this.http.PostAsJsonAsync("/tokenizer/count", new {
text,
tokenizer_path = tokenizerPath,
}, this.jsonRustSerializerOptions, cancellationToken);
}, JSON_OPTIONS, cancellationToken);
if (!result.IsSuccessStatusCode)
{
@@ -82,6 +82,6 @@ public sealed partial class RustService : ITokenizerStorage
return CreateUnavailableTokenizerResponse("Error while getting token count from Rust service: "+result.StatusCode);
}
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions, cancellationToken);
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS, cancellationToken);
}
}
@@ -9,7 +9,7 @@ public sealed partial class RustService
try
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(45));
var response = await this.http.GetFromJsonAsync<UpdateResponse>("/updates/check", this.jsonRustSerializerOptions, cts.Token);
var response = await this.http.GetFromJsonAsync<UpdateResponse>("/updates/check", JSON_OPTIONS, cts.Token);
if (response == default)
{
@@ -34,7 +34,10 @@ public sealed partial class RustService : BackgroundService
private readonly SemaphoreSlim userLanguageLock = new(1, 1);
private readonly SemaphoreSlim userNameLock = new(1, 1);
private readonly JsonSerializerOptions jsonRustSerializerOptions = new()
/// <summary>
/// The JSON options of nearly every call to the runtime, which names its fields in snake case.
/// </summary>
internal static readonly JsonSerializerOptions JSON_OPTIONS = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
Converters =