Adopted further C# 14 and .NET 10 features (#1158)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-04 14:04:10 +01:00
1 parent f649febf88
commit f0fa1411c8
40 files changed
+545 -56

No files matched your search

@@ -31,6 +31,9 @@ internal static class VisualBriefingJson
/// 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.
///
/// The answers of the model are read with these options, too, which therefore refuse a field named
/// twice. That rule only concerns reading and changes no byte of the output.
/// </remarks>
[CanonicalJsonConfiguration]
internal static JsonSerializerOptions Canonical { get; } = new()
@@ -40,6 +43,7 @@ internal static class VisualBriefingJson
WriteIndented = false,
Encoder = JavaScriptEncoder.Default,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
AllowDuplicateProperties = false,
TypeInfoResolver = VisualBriefingCanonicalJsonContext.Default,
};
@@ -40,4 +40,7 @@ public enum VisualBriefingStructuredResponseIssueKind
/// <summary>The parsed response violated a semantic stage contract.</summary>
SEMANTIC_CONTRACT_INVALID,
/// <summary>An object of the response named the same field more than once.</summary>
DUPLICATE_FIELD,
}
@@ -20,7 +20,7 @@ internal static partial class VisualBriefingStructuredResponseProcessor
.DisableHtml()
.Build();
private static readonly NullabilityInfoContext NULLABILITY = new();
private static readonly object CONTRACT_LOCK = new();
private static readonly Lock CONTRACT_LOCK = new();
private static readonly Dictionary<Type, ContractShape> CONTRACTS = [];
/// <summary>
@@ -159,6 +159,18 @@ internal static partial class VisualBriefingStructuredResponseProcessor
"The structured response root must be a JSON object.",
expected: "JSON object");
var duplicateIssue = FindDuplicateField(document.RootElement, "$");
if (duplicateIssue is not null)
return Rejected<T>(
candidate,
duplicateIssue.Kind,
VisualBriefingFailureCode.RESPONSE_JSON_INVALID,
VisualBriefingValidationRule.JSON_INVALID,
ContractIssueMessage(duplicateIssue),
duplicateIssue.Path,
fieldName: duplicateIssue.FieldName,
expected: duplicateIssue.Expected);
var contractIssue = Inspect(
document.RootElement,
GetContract(typeof(T)),
@@ -377,6 +389,58 @@ internal static partial class VisualBriefingStructuredResponseProcessor
private static long? ToResponseLine(ResponseCandidate candidate, long? candidateLine) =>
candidateLine is null ? null : candidate.StartLine + candidateLine;
/// <summary>
/// Finds the first field which an object of the response names a second time.
/// </summary>
/// <remarks>
/// JSON allows a name twice, and readers disagree on which of the values counts: the checks below
/// see one of them, while the browser which runs the finished briefing may see the other. The
/// response is refused instead, and the repair names the field. This looks into every value, also
/// into those the contract leaves open, such as the data of a chart.
/// </remarks>
private static ContractInspectionIssue? FindDuplicateField(JsonElement element, string path)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
{
var names = new HashSet<string>(StringComparer.Ordinal);
foreach (var property in element.EnumerateObject())
{
var safeField = SafeIdentifier(property.Name);
var fieldPath = string.IsNullOrEmpty(safeField) ? path : $"{path}.{safeField}";
if (!names.Add(property.Name))
return new(
VisualBriefingStructuredResponseIssueKind.DUPLICATE_FIELD,
fieldPath,
safeField,
"every field name only once per object");
var issue = FindDuplicateField(property.Value, fieldPath);
if (issue is not null)
return issue;
}
return null;
}
case JsonValueKind.Array:
{
var index = 0;
foreach (var item in element.EnumerateArray())
{
var issue = FindDuplicateField(item, $"{path}[{index}]");
if (issue is not null)
return issue;
index++;
}
return null;
}
default:
return null;
}
}
private static ContractInspectionIssue? Inspect(
JsonElement element,
ContractShape shape,
@@ -496,6 +560,10 @@ internal static partial class VisualBriefingStructuredResponseProcessor
$"The model response contains the unknown field '{issue.FieldName}' at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.UNKNOWN_FIELD =>
$"The model response contains an unknown field at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.DUPLICATE_FIELD when !string.IsNullOrEmpty(issue.FieldName) =>
$"The model response contains the field '{issue.FieldName}' more than once at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.DUPLICATE_FIELD =>
$"The model response contains a field more than once at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.REQUIRED_FIELD_MISSING =>
$"The required field '{issue.FieldName}' is missing at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.ENUM_VALUE_INVALID =>