Skip to content

Commit 80fc0f6

Browse files
Copilotbaywet
andcommitted
fix: address PR feedback on UnevaluatedProperties implementation
- Simplify copy constructor using pattern matching - Add V3.2 deserialization support for UnevaluatedPropertiesSchema - Add test for default value (no property set, defaults to true) - Emit as x-jsonschema-unevaluatedProperties extension for versions < 3.1 - Add comprehensive tests for extension serialization in V2 and V3.0 - Add V3.2 deserialization tests Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent a4d66fc commit 80fc0f6

File tree

3 files changed

+110
-5
lines changed

3 files changed

+110
-5
lines changed

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,9 @@ internal OpenApiSchema(IOpenApiSchema schema)
280280
DynamicRef = schema.DynamicRef ?? DynamicRef;
281281
Definitions = schema.Definitions != null ? new Dictionary<string, IOpenApiSchema>(schema.Definitions) : null;
282282
UnevaluatedProperties = schema.UnevaluatedProperties;
283-
if (schema is IOpenApiSchemaWithUnevaluatedProperties schemaWithUnevaluated)
283+
if (schema is IOpenApiSchemaWithUnevaluatedProperties { UnevaluatedPropertiesSchema: { } unevaluatedSchema })
284284
{
285-
UnevaluatedPropertiesSchema = schemaWithUnevaluated.UnevaluatedPropertiesSchema?.CreateShallowCopy();
285+
UnevaluatedPropertiesSchema = unevaluatedSchema.CreateShallowCopy();
286286
}
287287
ExclusiveMaximum = schema.ExclusiveMaximum ?? ExclusiveMaximum;
288288
ExclusiveMinimum = schema.ExclusiveMinimum ?? ExclusiveMinimum;
@@ -539,9 +539,29 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
539539
// deprecated
540540
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
541541

542+
// For versions < 3.1, write unevaluatedProperties as an extension
543+
if (version < OpenApiSpecVersion.OpenApi3_1)
544+
{
545+
// Write UnevaluatedPropertiesSchema as extension if present
546+
if (UnevaluatedPropertiesSchema is not null)
547+
{
548+
writer.WriteOptionalObject(
549+
"x-jsonschema-unevaluatedProperties",
550+
UnevaluatedPropertiesSchema,
551+
callback);
552+
}
553+
// Write boolean false as extension if explicitly set to false
554+
else if (!UnevaluatedProperties)
555+
{
556+
writer.WritePropertyName("x-jsonschema-unevaluatedProperties");
557+
writer.WriteValue(false);
558+
}
559+
}
560+
542561
// extensions
543562
writer.WriteExtensions(Extensions, version);
544563

564+
545565
// Unrecognized keywords
546566
if (UnrecognizedKeywords is not null && UnrecognizedKeywords.Any())
547567
{
@@ -795,6 +815,21 @@ private void SerializeAsV2(
795815
// x-nullable extension
796816
SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0);
797817

818+
// Write UnevaluatedPropertiesSchema as extension if present
819+
if (UnevaluatedPropertiesSchema is not null)
820+
{
821+
writer.WriteOptionalObject(
822+
"x-jsonschema-unevaluatedProperties",
823+
UnevaluatedPropertiesSchema,
824+
(w, s) => s.SerializeAsV2(w));
825+
}
826+
// Write boolean false as extension if explicitly set to false
827+
else if (!UnevaluatedProperties)
828+
{
829+
writer.WritePropertyName("x-jsonschema-unevaluatedProperties");
830+
writer.WriteValue(false);
831+
}
832+
798833
// extensions
799834
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);
800835

test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,5 +815,34 @@ public void ParseSchemaWithUnevaluatedPropertiesComplexSchema()
815815
// Assert
816816
Assert.Equivalent(expected, actual);
817817
}
818+
819+
[Fact]
820+
public void ParseSchemaWithoutUnevaluatedPropertiesDefaultsToTrue()
821+
{
822+
// Arrange - no unevaluatedProperties property should default to true (allow all)
823+
var schema = @"{
824+
""type"": ""object"",
825+
""properties"": {
826+
""name"": { ""type"": ""string"" }
827+
}
828+
}";
829+
830+
var expected = new OpenApiSchema()
831+
{
832+
Type = JsonSchemaType.Object,
833+
Properties = new Dictionary<string, IOpenApiSchema>
834+
{
835+
["name"] = new OpenApiSchema { Type = JsonSchemaType.String }
836+
},
837+
UnevaluatedProperties = true // Default value
838+
};
839+
840+
// Act
841+
var actual = OpenApiModelFactory.Parse<OpenApiSchema>(schema, OpenApiSpecVersion.OpenApi3_1, new(), out _);
842+
843+
// Assert
844+
Assert.Equivalent(expected, actual);
845+
Assert.True(actual.UnevaluatedProperties); // Explicitly verify the default
846+
}
818847
}
819848
}

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,10 +1234,10 @@ public async Task SerializeUnevaluatedPropertiesSchemaTakesPrecedenceOverBoolean
12341234
[Theory]
12351235
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
12361236
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
1237-
public async Task SerializeUnevaluatedPropertiesNotEmittedInEarlierVersions(OpenApiSpecVersion version)
1237+
public async Task SerializeUnevaluatedPropertiesAsExtensionInEarlierVersions(OpenApiSpecVersion version)
12381238
{
1239-
var expected = @"{ }";
1240-
// Given - UnevaluatedProperties should not be emitted in versions < 3.1
1239+
var expected = @"{ ""x-jsonschema-unevaluatedProperties"": false }";
1240+
// Given - UnevaluatedProperties should be emitted as extension in versions < 3.1
12411241
var schema = new OpenApiSchema
12421242
{
12431243
UnevaluatedProperties = false
@@ -1250,6 +1250,47 @@ public async Task SerializeUnevaluatedPropertiesNotEmittedInEarlierVersions(Open
12501250
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
12511251
}
12521252

1253+
[Theory]
1254+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
1255+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
1256+
public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInEarlierVersions(OpenApiSpecVersion version)
1257+
{
1258+
var expected = @"{ ""x-jsonschema-unevaluatedProperties"": { ""type"": ""string"" } }";
1259+
// Given - UnevaluatedPropertiesSchema should be emitted as extension in versions < 3.1
1260+
var schema = new OpenApiSchema
1261+
{
1262+
UnevaluatedPropertiesSchema = new OpenApiSchema
1263+
{
1264+
Type = JsonSchemaType.String
1265+
}
1266+
};
1267+
1268+
// When
1269+
var actual = await schema.SerializeAsJsonAsync(version);
1270+
1271+
// Then
1272+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1273+
}
1274+
1275+
[Theory]
1276+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
1277+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
1278+
public async Task SerializeUnevaluatedPropertiesTrueNotEmittedInEarlierVersions(OpenApiSpecVersion version)
1279+
{
1280+
var expected = @"{ }";
1281+
// Given - UnevaluatedProperties true (default) should not be emitted even as extension
1282+
var schema = new OpenApiSchema
1283+
{
1284+
UnevaluatedProperties = true
1285+
};
1286+
1287+
// When
1288+
var actual = await schema.SerializeAsJsonAsync(version);
1289+
1290+
// Then
1291+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1292+
}
1293+
12531294
internal class SchemaVisitor : OpenApiVisitorBase
12541295
{
12551296
public List<string> Titles = new();

0 commit comments

Comments
 (0)