Skip to content

Commit fb691d6

Browse files
Copilotbaywet
andcommitted
feat: add UnevaluatedPropertiesSchema support to IOpenApiSchema
- Create IOpenApiSchemaWithUnevaluatedProperties interface for binary compatibility - Add UnevaluatedPropertiesSchema property to OpenApiSchema - Update OpenApiSchemaReference to implement new interface - Update serialization to handle both boolean and schema cases - Update V31 deserialization to handle both boolean and schema cases - Add comprehensive documentation with TODO markers for next major version Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent 259e669 commit fb691d6

File tree

5 files changed

+80
-9
lines changed

5 files changed

+80
-9
lines changed

src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,15 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte
258258
public IList<JsonNode>? Enum { get; }
259259

260260
/// <summary>
261-
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
262-
/// </summary>
261+
/// Indicates whether unevaluated properties are allowed. When false, no unevaluated properties are permitted.
262+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties
263+
/// Only serialized when false and UnevaluatedPropertiesSchema (from IOpenApiSchemaWithUnevaluatedProperties) is null.
264+
/// </summary>
265+
/// <remarks>
266+
/// NOTE: This property differs from the naming pattern of AdditionalPropertiesAllowed for binary compatibility reasons.
267+
/// In the next major version, this will be renamed to UnevaluatedPropertiesAllowed.
268+
/// TODO: Rename to UnevaluatedPropertiesAllowed in the next major version.
269+
/// </remarks>
263270
public bool UnevaluatedProperties { get; }
264271

265272
/// <summary>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace Microsoft.OpenApi;
2+
3+
/// <summary>
4+
/// Compatibility interface for UnevaluatedProperties schema support.
5+
/// This interface provides access to the UnevaluatedPropertiesSchema property, which represents
6+
/// the schema for unevaluated properties as defined in JSON Schema draft 2020-12.
7+
///
8+
/// NOTE: This is a temporary compatibility solution. In the next major version:
9+
/// - This interface will be merged into IOpenApiSchema
10+
/// - The UnevaluatedPropertiesSchema property will be renamed to UnevaluatedProperties
11+
/// - The current UnevaluatedProperties boolean property will be renamed to UnevaluatedPropertiesAllowed
12+
/// </summary>
13+
/// <remarks>
14+
/// TODO: Remove this interface in the next major version and merge its content into IOpenApiSchema.
15+
/// </remarks>
16+
public interface IOpenApiSchemaWithUnevaluatedProperties
17+
{
18+
/// <summary>
19+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties
20+
/// This is a schema that unevaluated properties must validate against.
21+
/// When serialized, this takes precedence over the UnevaluatedProperties boolean property.
22+
/// </summary>
23+
/// <remarks>
24+
/// NOTE: This property differs from the naming pattern of AdditionalProperties/AdditionalPropertiesAllowed
25+
/// for binary compatibility reasons. In the next major version:
26+
/// - This property will be renamed to UnevaluatedProperties
27+
/// - The current boolean UnevaluatedProperties property will be renamed to UnevaluatedPropertiesAllowed
28+
///
29+
/// TODO: Rename this property to UnevaluatedProperties in the next major version.
30+
/// </remarks>
31+
IOpenApiSchema? UnevaluatedPropertiesSchema { get; }
32+
}

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.OpenApi
1212
/// <summary>
1313
/// The Schema Object allows the definition of input and output data types.
1414
/// </summary>
15-
public class OpenApiSchema : IOpenApiExtensible, IOpenApiSchema, IMetadataContainer
15+
public class OpenApiSchema : IOpenApiExtensible, IOpenApiSchema, IOpenApiSchemaWithUnevaluatedProperties, IMetadataContainer
1616
{
1717
/// <inheritdoc />
1818
public string? Title { get; set; }
@@ -234,6 +234,9 @@ public string? Minimum
234234
/// <inheritdoc />
235235
public bool UnevaluatedProperties { get; set; }
236236

237+
/// <inheritdoc />
238+
public IOpenApiSchema? UnevaluatedPropertiesSchema { get; set; }
239+
237240
/// <inheritdoc />
238241
public OpenApiExternalDocs? ExternalDocs { get; set; }
239242

@@ -277,6 +280,10 @@ internal OpenApiSchema(IOpenApiSchema schema)
277280
DynamicRef = schema.DynamicRef ?? DynamicRef;
278281
Definitions = schema.Definitions != null ? new Dictionary<string, IOpenApiSchema>(schema.Definitions) : null;
279282
UnevaluatedProperties = schema.UnevaluatedProperties;
283+
if (schema is IOpenApiSchemaWithUnevaluatedProperties schemaWithUnevaluated)
284+
{
285+
UnevaluatedPropertiesSchema = schemaWithUnevaluated.UnevaluatedPropertiesSchema?.CreateShallowCopy();
286+
}
280287
ExclusiveMaximum = schema.ExclusiveMaximum ?? ExclusiveMaximum;
281288
ExclusiveMinimum = schema.ExclusiveMinimum ?? ExclusiveMinimum;
282289
if (schema is OpenApiSchema eMSchema)
@@ -560,7 +567,20 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer)
560567
writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w));
561568
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
562569
writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor);
563-
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false);
570+
571+
// UnevaluatedProperties: similar to AdditionalProperties, serialize as schema if present, else as boolean
572+
if (UnevaluatedPropertiesSchema is not null)
573+
{
574+
writer.WriteOptionalObject(
575+
OpenApiConstants.UnevaluatedProperties,
576+
UnevaluatedPropertiesSchema,
577+
(w, s) => s.SerializeAsV31(w));
578+
}
579+
else if (!UnevaluatedProperties)
580+
{
581+
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties);
582+
}
583+
// true is the default, no need to write it out
564584
writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s));
565585
writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w));
566586
writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s));

src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.OpenApi
1010
/// <summary>
1111
/// Schema reference object
1212
/// </summary>
13-
public class OpenApiSchemaReference : BaseOpenApiReferenceHolder<OpenApiSchema, IOpenApiSchema, JsonSchemaReference>, IOpenApiSchema
13+
public class OpenApiSchemaReference : BaseOpenApiReferenceHolder<OpenApiSchema, IOpenApiSchema, JsonSchemaReference>, IOpenApiSchema, IOpenApiSchemaWithUnevaluatedProperties
1414
{
1515

1616
/// <summary>
@@ -146,6 +146,8 @@ public IList<JsonNode>? Examples
146146
/// <inheritdoc/>
147147
public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? false; }
148148
/// <inheritdoc/>
149+
public IOpenApiSchema? UnevaluatedPropertiesSchema { get => (Target as IOpenApiSchemaWithUnevaluatedProperties)?.UnevaluatedPropertiesSchema; }
150+
/// <inheritdoc/>
149151
public OpenApiExternalDocs? ExternalDocs { get => Target?.ExternalDocs; }
150152
/// <inheritdoc/>
151153
public bool Deprecated

src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,22 @@ internal static partial class OpenApiV31Deserializer
146146
},
147147
{
148148
"unevaluatedProperties",
149-
(o, n, _) =>
149+
(o, n, t) =>
150150
{
151-
var unevaluatedProps = n.GetScalarValue();
152-
if (unevaluatedProps != null)
151+
// Handle both boolean (false/true) and schema object cases
152+
var scalarValue = n.GetScalarValue();
153+
if (scalarValue != null)
154+
{
155+
// Boolean case: false means no unevaluated properties, true is default (ignore)
156+
if (bool.TryParse(scalarValue, out var boolValue))
157+
{
158+
o.UnevaluatedProperties = boolValue;
159+
}
160+
}
161+
else
153162
{
154-
o.UnevaluatedProperties = bool.Parse(unevaluatedProps);
163+
// Schema object case: deserialize as schema
164+
o.UnevaluatedPropertiesSchema = LoadSchema(n, t);
155165
}
156166
}
157167
},

0 commit comments

Comments
 (0)