Generated the JSON contracts of providers, the runtime, and services at compile time (#1150)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-03 07:59:30 +01:00
1 parent e954881fda
commit 24e3c22db3
140 files changed
+1415 -292

No files matched your search

+18 -7
View File
@@ -7,11 +7,12 @@ namespace AIStudio.Tests.Serialization;
/// Checks that a property missing in the JSON keeps the value a new object starts with.
/// </summary>
/// <remarks>
/// Files written by an older version of AI Studio lack every property added since. Reading them has to give
/// those properties their defaults, as a new object has them, e.g., a sub-object of the settings instead of
/// null. Source generation in .NET 10 breaks this for init-only properties: it sets them all while creating
/// the object, and a property missing in the JSON gets the default of its type instead (dotnet/runtime#84484).
/// Hence, the types AI Studio stores use set instead of init.<br/><br/>
/// Files written by an older version of AI Studio lack every property added since, and an answer of a
/// provider, of the runtime, or of a service leaves out whatever it has nothing to say about. Reading them has
/// to give those properties their defaults, as a new object has them, e.g., a sub-object of the settings
/// instead of null. Source generation in .NET 10 breaks this for init-only properties: it sets them all while
/// creating the object, and a property missing in the JSON gets the default of its type instead
/// (dotnet/runtime#84484). Hence, the types AI Studio reads use set instead of init.<br/><br/>
///
/// A new object is created the way reflection creates it: with its parameterless constructor, or, for a
/// struct, as the default of the struct. Source generation passes the values of a positional record struct
@@ -21,6 +22,9 @@ namespace AIStudio.Tests.Serialization;
public sealed class JsonDefaultsTests
{
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.Stored))]
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ProviderAnswers))]
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeAnswers))]
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ServiceAnswers))]
public void AnEmptyObjectGetsTheDefaultsOfANewOne(JsonTestRoot root)
{
var baseline = JsonReflectionBaseline.Of(root.Options);
@@ -60,7 +64,14 @@ public sealed class JsonDefaultsTests
Assert.That(failures, Is.Empty);
}
private static string ValueAsJson(JsonPropertyInfo property, object instance, JsonSerializerOptions options) => JsonSerializer.Serialize(property.Get!(instance), property.PropertyType, options);
private static string ValueAsJson(JsonPropertyInfo property, object instance, JsonSerializerOptions options) => AsJson(property.Get!(instance), property.PropertyType, options);
private static string DefaultAsJson(Type type, JsonSerializerOptions options) => type.IsValueType ? JsonSerializer.Serialize(Activator.CreateInstance(type), type, options) : "null";
private static string DefaultAsJson(Type type, JsonSerializerOptions options) => type.IsValueType ? AsJson(Activator.CreateInstance(type), type, options) : "null";
private static string AsJson(object? value, Type type, JsonSerializerOptions options) => value switch
{
// The default of a JsonElement holds no JSON at all, so it cannot be written:
JsonElement { ValueKind: JsonValueKind.Undefined } => "no JSON",
_ => JsonSerializer.Serialize(value, type, options),
};
}