mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-03 22:07:21 +00:00
Generated the JSON contracts of the Visual Briefing assistant at compile time
This commit is contained in:
7 files changed
+118
-13
No files matched your search
+1
-1
@@ -204,7 +204,7 @@ public sealed partial class VisualBriefingArtifactService
|
||||
return context;
|
||||
|
||||
if (path is "$index")
|
||||
return JsonSerializer.SerializeToElement(0);
|
||||
return JsonSerializer.SerializeToElement(0, JSON_OPTIONS);
|
||||
|
||||
if (path.StartsWith(".", StringComparison.Ordinal))
|
||||
return GetDataAtPath(context, path[1..]);
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json writes and reads the canonical JSON of visual briefings, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Much of what these contracts write goes into the hashes of stored briefings, see VisualBriefingJson.Canonical.
|
||||
/// The hashes of the artifacts are generic: every list they hash needs a contract of its own. A test fails for a type
|
||||
/// which is missing here.
|
||||
/// </remarks>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
|
||||
// The header and the data of an exported briefing, and the build which the progress view shows:
|
||||
[JsonSerializable(typeof(VisualBriefingExportManifest))]
|
||||
[JsonSerializable(typeof(VisualBriefingBuildRecord))]
|
||||
[JsonSerializable(typeof(VisualBriefingCompiledData))]
|
||||
[JsonSerializable(typeof(VisualBriefingContentData))]
|
||||
[JsonSerializable(typeof(VisualBriefingInteractionData))]
|
||||
[JsonSerializable(typeof(VisualBriefingProtectedData))]
|
||||
[JsonSerializable(typeof(Dictionary<string, JsonElement>))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(JsonObject))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(int))]
|
||||
|
||||
// The payload hashes of the artifacts:
|
||||
[JsonSerializable(typeof(List<VisualBriefingEvidenceFact>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingEvidenceMetric>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingEvidenceTable>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingSourceCoverage>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingAssetPlanItem>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingPlanSection>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingSlotValue>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingChartSpec>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingControlSpec>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingFormulaSpec>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, List<string>>))]
|
||||
[JsonSerializable(typeof(VisualBriefingLayoutNode))]
|
||||
|
||||
// The prompts of the stages:
|
||||
[JsonSerializable(typeof(IEnumerable<VisualBriefingSourcePrompt>))]
|
||||
[JsonSerializable(typeof(string[]))]
|
||||
[JsonSerializable(typeof(VisualBriefingRequiredSlot[]))]
|
||||
[JsonSerializable(typeof(VisualBriefingControlRequirement[]))]
|
||||
[JsonSerializable(typeof(VisualBriefingEvidencePrompt))]
|
||||
[JsonSerializable(typeof(VisualBriefingParentDesign))]
|
||||
|
||||
// The answers of the models:
|
||||
[JsonSerializable(typeof(VisualBriefingEvidenceResponse))]
|
||||
[JsonSerializable(typeof(VisualBriefingPlanResponse))]
|
||||
[JsonSerializable(typeof(VisualBriefingContentResponse))]
|
||||
[JsonSerializable(typeof(VisualBriefingDesignResponse))]
|
||||
|
||||
// The fingerprint which decides whether the editor holds unsaved changes:
|
||||
[JsonSerializable(typeof(VisualBriefingPersistenceFingerprint))]
|
||||
internal sealed partial class VisualBriefingCanonicalJsonContext : JsonSerializerContext;
|
||||
@@ -28,7 +28,9 @@ internal static class VisualBriefingJson
|
||||
/// and a mismatch makes the store discard the stored artifact. Any change here — a converter, a
|
||||
/// naming policy, an encoder — therefore invalidates every briefing that was ever written, which
|
||||
/// surfaces as a failed integrity check rather than as a build error. This is why enums stay numeric
|
||||
/// here even though the persisted manifest writes their member names.
|
||||
/// here even though the persisted manifest writes their member names. Unlike other options of the app,
|
||||
/// these are not made read-only right here, because MWAIS0010 wants nothing but the object creation;
|
||||
/// System.Text.Json locks them with their first use anyway.
|
||||
/// </remarks>
|
||||
[CanonicalJsonConfiguration]
|
||||
internal static JsonSerializerOptions Canonical { get; } = new()
|
||||
@@ -38,6 +40,7 @@ internal static class VisualBriefingJson
|
||||
WriteIndented = false,
|
||||
Encoder = JavaScriptEncoder.Default,
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
||||
TypeInfoResolver = VisualBriefingCanonicalJsonContext.Default,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -52,7 +55,7 @@ internal static class VisualBriefingJson
|
||||
/// defined outside the feature, such as the target language and the audience enums. Reading accepts
|
||||
/// numbers as well, so manifests written before this distinction existed keep loading.
|
||||
/// </remarks>
|
||||
internal static JsonSerializerOptions Persistence { get; } = new()
|
||||
internal static JsonSerializerOptions Persistence { get; } = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = false,
|
||||
@@ -60,5 +63,6 @@ internal static class VisualBriefingJson
|
||||
Encoder = JavaScriptEncoder.Default,
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
TypeInfoResolver = VisualBriefingPersistenceJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes the files of visual briefings, built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(VisualBriefingManifest))]
|
||||
[JsonSerializable(typeof(VisualBriefingEvidenceArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingPlanArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingContentArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingPresentationArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingBuildRecord))]
|
||||
[JsonSerializable(typeof(VisualBriefingOperationDiagnostics))]
|
||||
|
||||
// The briefing selected last:
|
||||
[JsonSerializable(typeof(Guid?))]
|
||||
internal sealed partial class VisualBriefingPersistenceJsonContext : JsonSerializerContext;
|
||||
@@ -24,7 +24,7 @@ public sealed partial class VisualBriefingStore
|
||||
try
|
||||
{
|
||||
this.LastSelectedBriefingId = briefingId;
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(briefingId), overwrite: true, token);
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(briefingId, JSON_OPTIONS), overwrite: true, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -47,7 +47,7 @@ public sealed partial class VisualBriefingStore
|
||||
return;
|
||||
|
||||
this.LastSelectedBriefingId = null;
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(null), overwrite: true, token);
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(null, JSON_OPTIONS), overwrite: true, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -67,7 +67,7 @@ public sealed partial class VisualBriefingStore
|
||||
try
|
||||
{
|
||||
var serialized = await File.ReadAllTextAsync(path, token);
|
||||
var selected = JsonSerializer.Deserialize<Guid?>(serialized);
|
||||
var selected = JsonSerializer.Deserialize<Guid?>(serialized, JSON_OPTIONS);
|
||||
this.LastSelectedBriefingId = selected is not null &&
|
||||
Directory.Exists(this.BriefingDirectory(selected.Value))
|
||||
? selected
|
||||
|
||||
@@ -20,6 +20,9 @@ public sealed class JsonCoverageTests
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeAnswers))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ServiceRequests))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ServiceAnswers))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.VisualBriefingStored))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.VisualBriefingHashed))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.VisualBriefingAnswers))]
|
||||
public void EveryContractComesFromSourceGeneration(JsonTestRoot root)
|
||||
{
|
||||
var typesWithoutGeneratedContract = JsonTypeGraph.Collect(root.Type, root.Options)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
@@ -22,8 +23,8 @@ public static partial class JsonTestRoots
|
||||
yield return Case(new(typeof(VisualBriefingPresentationArtifact), options));
|
||||
yield return Case(new(typeof(VisualBriefingBuildRecord), options));
|
||||
|
||||
// The briefing selected last, which gets written without options of its own:
|
||||
yield return Case(new(typeof(Guid?), JsonSerializerOptions.Default), "selected briefing");
|
||||
// The briefing selected last:
|
||||
yield return Case(new(typeof(Guid?), options), "selected briefing");
|
||||
|
||||
//
|
||||
// The canonical JSON which gets read back: the header and the data of an exported briefing, the version
|
||||
@@ -60,12 +61,28 @@ public static partial class JsonTestRoots
|
||||
yield return Case(new(typeof(Dictionary<string, List<string>>), options));
|
||||
yield return Case(new(typeof(VisualBriefingLayoutNode), options));
|
||||
|
||||
// The data of a briefing, and the IDs the prompts list:
|
||||
// The data of a briefing, with what the compilers and the stages put into it:
|
||||
yield return Case(new(typeof(JsonElement), options));
|
||||
yield return Case(new(typeof(string[]), options));
|
||||
yield return Case(new(typeof(JsonObject), options));
|
||||
yield return Case(new(typeof(VisualBriefingCompiledData), options));
|
||||
yield return Case(new(typeof(VisualBriefingContentData), options));
|
||||
yield return Case(new(typeof(VisualBriefingInteractionData), options));
|
||||
yield return Case(new(typeof(VisualBriefingProtectedData), options));
|
||||
yield return Case(new(typeof(string), options), "value of a control");
|
||||
|
||||
// The position inside a repeated element, which the data bindings write without options of their own:
|
||||
yield return Case(new(typeof(int), JsonSerializerOptions.Default), "index");
|
||||
// The position inside a repeated element, which the data bindings write:
|
||||
yield return Case(new(typeof(int), options), "index");
|
||||
|
||||
// What the prompts of the stages carry:
|
||||
yield return Case(new(typeof(IEnumerable<VisualBriefingSourcePrompt>), options));
|
||||
yield return Case(new(typeof(string[]), options));
|
||||
yield return Case(new(typeof(VisualBriefingRequiredSlot[]), options));
|
||||
yield return Case(new(typeof(VisualBriefingControlRequirement[]), options));
|
||||
yield return Case(new(typeof(VisualBriefingEvidencePrompt), options));
|
||||
yield return Case(new(typeof(VisualBriefingParentDesign), options));
|
||||
|
||||
// The fingerprint which decides whether the editor holds unsaved changes:
|
||||
yield return Case(new(typeof(VisualBriefingPersistenceFingerprint), options));
|
||||
|
||||
// The diagnostics a user copies to the clipboard:
|
||||
yield return Case(new(typeof(VisualBriefingOperationDiagnostics), VisualBriefingJson.Persistence));
|
||||
|
||||
Reference in new issue
Block a user