Skip to content

Commit

Permalink
Fix handling of appending keywords to boolean schemas. (dotnet#108246)
Browse files Browse the repository at this point in the history
* Fix handling of appending keywords to boolean schemas.

* Add a few comments documenting boolean schemas.
  • Loading branch information
eiriktsarpalis authored and sirntar committed Sep 30, 2024
1 parent d477268 commit aaeb350
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ public JsonSchema() { }

public bool IsTrue => _trueOrFalse is true;
public bool IsFalse => _trueOrFalse is false;

/// <summary>
/// Per the JSON schema core specification section 4.3
/// (https://json-schema.org/draft/2020-12/json-schema-core#name-json-schema-documents)
/// A JSON schema must either be an object or a boolean.
/// We represent false and true schemas using this flag.
/// It is not possible to specify keywords in boolean schemas.
/// </summary>
private readonly bool? _trueOrFalse;

public string? Ref { get => _ref; set { VerifyMutable(); _ref = value; } }
Expand Down Expand Up @@ -95,6 +103,7 @@ public int KeywordCount
{
if (_trueOrFalse != null)
{
// Boolean schemas admit no keywords
return 0;
}

Expand Down Expand Up @@ -129,6 +138,7 @@ public void MakeNullable()
{
if (_trueOrFalse != null)
{
// boolean schemas do not admit type keywords.
return;
}

Expand Down Expand Up @@ -260,6 +270,23 @@ JsonNode CompleteSchema(JsonNode schema)
}
}

/// <summary>
/// If the schema is boolean, replaces it with a semantically
/// equivalent object schema that allows appending keywords.
/// </summary>
public static void EnsureMutable(ref JsonSchema schema)
{
switch (schema._trueOrFalse)
{
case false:
schema = new JsonSchema { Not = True };
break;
case true:
schema = new JsonSchema();
break;
}
}

private static ReadOnlySpan<JsonSchemaType> s_schemaValues =>
[
// NB the order of these values influences order of types in the rendered schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ private static JsonSchema MapJsonSchemaCore(

if (property.AssociatedParameter is { HasDefaultValue: true } parameterInfo)
{
JsonSchema.EnsureMutable(ref propertySchema);
propertySchema.DefaultValue = JsonSerializer.SerializeToNode(parameterInfo.DefaultValue, property.JsonTypeInfo);
propertySchema.HasDefaultValue = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,18 @@ public static IEnumerable<ITestData> GetTestDataCore()
}
""");

yield return new TestData<ClassWithOptionalObjectParameter>(
Value: new(value: null),
AdditionalValues: [new(true), new(42), new(""), new(new object()), new(Array.Empty<int>())],
ExpectedJsonSchema: """
{
"type": ["object","null"],
"properties": {
"Value": { "default": null }
}
}
""");

// Collection types
yield return new TestData<int[]>([1, 2, 3], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"integer"}}""");
yield return new TestData<List<bool>>([false, true, false], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"boolean"}}""");
Expand Down Expand Up @@ -1441,6 +1453,11 @@ public class ClassWithJsonPointerEscapablePropertyNames
public PocoWithRecursiveMembers Value { get; set; }
}

public class ClassWithOptionalObjectParameter(object? value = null)
{
public object? Value { get; } = value;
}

public readonly struct StructDictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> values)
: IReadOnlyDictionary<TKey, TValue>
where TKey : notnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public sealed partial class JsonSchemaExporterTests_SourceGen()
[JsonSerializable(typeof(PocoCombiningPolymorphicTypeAndDerivedTypes))]
[JsonSerializable(typeof(ClassWithComponentModelAttributes))]
[JsonSerializable(typeof(ClassWithJsonPointerEscapablePropertyNames))]
[JsonSerializable(typeof(ClassWithOptionalObjectParameter))]
// Collection types
[JsonSerializable(typeof(int[]))]
[JsonSerializable(typeof(List<bool>))]
Expand Down

0 comments on commit aaeb350

Please sign in to comment.