Checked every JSON type of every options of the app in the trim smoke test

This commit is contained in:
Thorsten Sommer committed 2026-11-03 15:02:51 +01:00
1 parent ee6436d81d
commit 7ab79b491e
4 files changed
+43 -56

No files matched your search

+4 -1
View File
@@ -45,7 +45,10 @@ public static class JsonContractWalker
return;
}
foreach (var property in typeInfo.Properties)
//
// System.Text.Json never meets the type of an ignored property, e.g., one marked with JsonIgnore:
//
foreach (var property in typeInfo.Properties.Where(property => property is not { Get: null, Set: null, AssociatedParameter: null }))
Visit(property.PropertyType, $"{path} → {property.Name}", options, visited, failures);
if (typeInfo.KeyType is { } keyType)
+26 -53
View File
@@ -1,65 +1,38 @@
using System.Text.Json;
using AIStudio.Agents;
using AIStudio.Agents.AssistantAudit;
using AIStudio.Assistants.Builder;
using AIStudio.Assistants.PromptOptimizer;
using AIStudio.Assistants.SWOTAnalysis;
using AIStudio.Assistants.VisualBriefing;
using AIStudio.Chat;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Settings.DataModel.PreviousModels;
using AIStudio.Tools;
using AIStudio.Tools.DataBackup;
using System.Text.Json.Serialization;
namespace AIStudio.TrimSmokeTest;
/// <summary>
/// The types which AI Studio reads or writes as JSON on their own, not only as part of another type.
/// The types which AI Studio reads or writes as JSON, each with the options it uses for them.
/// </summary>
/// <remarks>
/// Whatever these types hold, i.e., the types of their properties, the elements of their collections, and
/// their derived types, gets checked along with them, so only the outermost types belong here. Add a type
/// whenever AI Studio starts to store a new kind of file or to read a new kind of model answer.<br/><br/>
///
/// The answers of models are read with options of their own, which are private and only change names and
/// leniency. The default options build the same contracts for the types of .NET they hold. A type which is
/// private to its parser, such as the DTO of a slide deck, is out of reach here.
/// The options of AI Studio each name the context which knows their types, so the types registered in that
/// context are exactly those which System.Text.Json gets to read or write with these options. Whatever these
/// types hold, i.e., the types of their properties, the elements of their collections, and their derived
/// types, gets checked along with them. A new type, or new options, need nothing here: they are found
/// through their context.
/// </remarks>
public static class JsonRoots
{
public static readonly IReadOnlyList<JsonRoot> ALL =
[
// Chats, in the workspaces, among the temporary chats, and in chat archives:
new(typeof(ChatThread), WorkspaceBehaviour.JSON_OPTIONS),
/// <summary>
/// Returns every type registered in a context of the options of AI Studio, together with these options.
/// </summary>
/// <remarks>
/// Options which several members share are checked once. Like AppJsonOptions.FindAll, this needs the
/// static state of the app to be filled before.
/// </remarks>
public static IReadOnlyList<JsonRoot> FindAll() => AppJsonOptions.FindAll().Values
.Distinct(ReferenceEqualityComparer.Instance)
.Cast<JsonSerializerOptions>()
.SelectMany(options => options.TypeInfoResolverChain
.OfType<JsonSerializerContext>()
.SelectMany(RegisteredTypes)
.Select(type => new JsonRoot(type, options)))
.ToList();
// The settings, and their older versions, which the migrations still read:
new(typeof(Data), SettingsManager.JSON_OPTIONS),
new(typeof(DataV1V3), SettingsManager.JSON_OPTIONS),
new(typeof(DataV4), SettingsManager.JSON_OPTIONS),
new(typeof(DataV5), SettingsManager.JSON_OPTIONS),
// The projects of the Visual Briefing assistant:
new(typeof(VisualBriefingManifest), VisualBriefingJson.Persistence),
new(typeof(VisualBriefingBuildRecord), VisualBriefingJson.Persistence),
new(typeof(VisualBriefingEvidenceArtifact), VisualBriefingJson.Persistence),
new(typeof(VisualBriefingPlanArtifact), VisualBriefingJson.Persistence),
new(typeof(VisualBriefingContentArtifact), VisualBriefingJson.Persistence),
new(typeof(VisualBriefingPresentationArtifact), VisualBriefingJson.Persistence),
// Backups and their restore:
new(typeof(DataBackupManifest), DataBackupFormat.JSON_OPTIONS),
new(typeof(DataRestorePlan), DataBackupFormat.JSON_OPTIONS),
new(typeof(DataRestoreReport), DataBackupFormat.JSON_OPTIONS),
// Answers of models:
new(typeof(LuaResponse), JsonSerializerOptions.Default),
new(typeof(LauncherTextsResponse), JsonSerializerOptions.Default),
new(typeof(PromptOptimizationResult), JsonSerializerOptions.Default),
new(typeof(RetrievalContextValidationResult), JsonSerializerOptions.Default),
new(typeof(AssistantAuditResult), JsonSerializerOptions.Default),
new(typeof(SwotCategoryAnalysisResult), JsonSerializerOptions.Default),
new(typeof(SwotModerationResult), JsonSerializerOptions.Default),
];
private static IEnumerable<Type> RegisteredTypes(JsonSerializerContext context) => context.GetType()
.GetCustomAttributesData()
.Where(attribute => attribute.AttributeType == typeof(JsonSerializableAttribute))
.Select(attribute => (Type)attribute.ConstructorArguments[0].Value!);
}
+3 -2
View File
@@ -15,14 +15,15 @@ public static class Program
//
AIStudio.Program.LOGGER_FACTORY = NullLoggerFactory.Instance;
var roots = JsonRoots.FindAll();
var failures = new List<string>();
foreach (var root in JsonRoots.ALL)
foreach (var root in roots)
failures.AddRange(JsonContractWalker.FindBrokenContracts(root));
failures.AddRange(JsonRoundTrips.FindLosses());
if (failures.Count is 0)
{
Console.WriteLine($"- All {JsonRoots.ALL.Count} JSON roots can be read and written.");
Console.WriteLine($"- All {roots.Count} JSON roots can be read and written.");
return 0;
}
+10
View File
@@ -21,6 +21,16 @@
<Import Project="..\Trimming.props" />
<!--
AI Studio reaches nearly all of its code from its start, and with it the assemblies it depends on. This
program starts nowhere near that, so the trimmer would remove, e.g., the Blazor server and SQLite, and the
types of the app which use them could not even be loaded, let alone their options be read. Rooting the
app keeps what the app itself keeps, while code of .NET which only reflection reaches is still removed.
-->
<ItemGroup>
<TrimmerRootAssembly Include="mindworkAIStudio" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MindWork AI Studio\MindWork AI Studio.csproj" />
</ItemGroup>