Generated the JSON contracts of ERI servers and search services at compile time

This commit is contained in:
Thorsten Sommer committed 2026-11-02 19:20:02 +01:00
1 parent 53f358eb41
commit 9e78f5055e
5 files changed
+53 -5

No files matched your search

@@ -9,7 +9,7 @@ public abstract class ERIClientBase(IERIDataSource dataSource) : IDisposable
{
protected readonly IERIDataSource DataSource = dataSource;
internal static readonly JsonSerializerOptions JSON_OPTIONS = new()
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
{
WriteIndented = true,
AllowTrailingCommas = true,
@@ -20,8 +20,9 @@ public abstract class ERIClientBase(IERIDataSource dataSource) : IDisposable
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
}
};
},
TypeInfoResolver = ERIClientJsonContext.Default,
}.AsReadOnly();
protected readonly HttpClient HttpClient = ExternalHttpClientTimeout.CreateHttpClient(new Uri($"{dataSource.Hostname}:{dataSource.Port}"), ExternalHttpTrustPolicy.ALLOW_CUSTOM_ROOTS_WHEN_HOST_WHITELISTED);
@@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Tools.ERIClient;
/// <summary>
/// The contracts with which System.Text.Json reads and writes the calls to ERI servers, built at compile time.
/// </summary>
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
// The request:
[JsonSerializable(typeof(RetrievalRequest))]
// The answers:
[JsonSerializable(typeof(List<AuthScheme>))]
[JsonSerializable(typeof(AuthResponse))]
[JsonSerializable(typeof(DataSourceInfo))]
[JsonSerializable(typeof(List<EmbeddingInfo>))]
[JsonSerializable(typeof(List<RetrievalInfo>))]
[JsonSerializable(typeof(List<Context>))]
[JsonSerializable(typeof(SecurityRequirements))]
internal sealed partial class ERIClientJsonContext : JsonSerializerContext;
@@ -14,10 +14,11 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch;
/// </remarks>
internal static class WebSearchJson
{
public static readonly JsonSerializerOptions OPTIONS = new()
public static readonly JsonSerializerOptions OPTIONS = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
TypeInfoResolver = WebSearchJsonContext.Default,
}.AsReadOnly();
}
@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
using AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.Staan;
using AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.Tavily;
namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch;
/// <summary>
/// The contracts with which System.Text.Json reads and writes the calls to the search services, built at
/// compile time.
/// </summary>
/// <remarks>
/// SearXNG is missing on purpose: its answers differ between instances, so its client reads them as a tree
/// of JSON nodes instead.
/// </remarks>
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(StaanSearchRequest))]
[JsonSerializable(typeof(StaanSearchResponse))]
[JsonSerializable(typeof(TavilySearchRequest))]
[JsonSerializable(typeof(TavilySearchResponse))]
internal sealed partial class WebSearchJsonContext : JsonSerializerContext;
@@ -18,6 +18,8 @@ public sealed class JsonCoverageTests
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ProviderAnswers))]
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeRequests))]
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeAnswers))]
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ServiceRequests))]
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ServiceAnswers))]
public void EveryContractComesFromSourceGeneration(JsonTestRoot root)
{
var typesWithoutGeneratedContract = JsonTypeGraph.Collect(root.Type, root.Options)