Skip to content

Commit 90a2b8d

Browse files
committed
Merge branch 'main' into pr/2706
2 parents 97ed211 + fb40442 commit 90a2b8d

File tree

12 files changed

+611
-15
lines changed

12 files changed

+611
-15
lines changed

.github/copilot-instructions.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copilot Instructions
2+
3+
## Commit Message Format
4+
5+
Always use conventional commits format when creating commits. Follow this structure:
6+
7+
```
8+
<type>(<scope>): <description>
9+
10+
[optional body]
11+
12+
[optional footer(s)]
13+
```
14+
15+
### Types
16+
17+
- **feat**: A new feature
18+
- **fix**: A bug fix
19+
- **docs**: Documentation only changes
20+
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc)
21+
- **refactor**: A code change that neither fixes a bug nor adds a feature
22+
- **perf**: A code change that improves performance
23+
- **test**: Adding missing tests or correcting existing tests
24+
- **build**: Changes that affect the build system or external dependencies
25+
- **ci**: Changes to CI configuration files and scripts
26+
- **chore**: Other changes that don't modify src or test files
27+
28+
### Scope
29+
30+
The scope should indicate the package or area affected (e.g., `library`, `yaml-reader`, `hidi`).
31+
32+
### Examples
33+
34+
```
35+
feat(library): add support for pattern properties
36+
fix(yaml-reader): updates boolean serialization
37+
docs(README): update installation instructions
38+
ci(release): configure automated release workflow
39+
```
40+
41+
### Breaking Changes
42+
43+
If a commit introduces a breaking change, add `BREAKING CHANGE:` in the footer or append `!` after the type/scope:
44+
45+
```
46+
feat(identity-emitter)!: change output format for models
47+
48+
BREAKING CHANGE: The emitter now generates TypeScript interfaces instead of types
49+
```
50+
51+
52+
## Updating the benchmark information
53+
54+
The user might request you update the benchmark information. You might do it on your own if a previous change added new properties to models under **src/Microsoft.OpenApi/Models**. Always use a separate commit for this change.
55+
56+
To do so, run the following script:
57+
58+
```shell
59+
cd performance/benchmark
60+
dotnet run -c Release
61+
```
62+
63+
Then commit the report files using a "chore" commit.

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/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ public static class OpenApiConstants
130130
/// </summary>
131131
public const string UnevaluatedProperties = "unevaluatedProperties";
132132

133+
/// <summary>
134+
/// Extension: x-jsonschema-unevaluatedProperties
135+
/// </summary>
136+
public const string UnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties";
137+
133138
/// <summary>
134139
/// Field: Version
135140
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 58 additions & 3 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; }
@@ -232,7 +232,10 @@ public string? Minimum
232232
public IList<JsonNode>? Enum { get; set; }
233233

234234
/// <inheritdoc />
235-
public bool UnevaluatedProperties { get; set; }
235+
public bool UnevaluatedProperties { get; set; } = true;
236+
237+
/// <inheritdoc />
238+
public IOpenApiSchema? UnevaluatedPropertiesSchema { get; set; }
236239

237240
/// <inheritdoc />
238241
public OpenApiExternalDocs? ExternalDocs { get; set; }
@@ -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 { UnevaluatedPropertiesSchema: { } unevaluatedSchema })
284+
{
285+
UnevaluatedPropertiesSchema = unevaluatedSchema.CreateShallowCopy();
286+
}
280287
ExclusiveMaximum = schema.ExclusiveMaximum ?? ExclusiveMaximum;
281288
ExclusiveMinimum = schema.ExclusiveMinimum ?? ExclusiveMinimum;
282289
if (schema is OpenApiSchema eMSchema)
@@ -537,9 +544,29 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
537544
// deprecated
538545
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
539546

547+
// For versions < 3.1, write unevaluatedProperties as an extension
548+
if (version < OpenApiSpecVersion.OpenApi3_1)
549+
{
550+
// Write UnevaluatedPropertiesSchema as extension if present
551+
if (UnevaluatedPropertiesSchema is not null)
552+
{
553+
writer.WriteOptionalObject(
554+
OpenApiConstants.UnevaluatedPropertiesExtension,
555+
UnevaluatedPropertiesSchema,
556+
callback);
557+
}
558+
// Write boolean false as extension if explicitly set to false
559+
else if (!UnevaluatedProperties)
560+
{
561+
writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension);
562+
writer.WriteValue(false);
563+
}
564+
}
565+
540566
// extensions
541567
writer.WriteExtensions(Extensions, version);
542568

569+
543570
// Unrecognized keywords
544571
if (UnrecognizedKeywords is not null && UnrecognizedKeywords.Any())
545572
{
@@ -565,7 +592,20 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer)
565592
writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w));
566593
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
567594
writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor);
568-
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false);
595+
596+
// UnevaluatedProperties: similar to AdditionalProperties, serialize as schema if present, else as boolean
597+
if (UnevaluatedPropertiesSchema is not null)
598+
{
599+
writer.WriteOptionalObject(
600+
OpenApiConstants.UnevaluatedProperties,
601+
UnevaluatedPropertiesSchema,
602+
(w, s) => s.SerializeAsV31(w));
603+
}
604+
else if (!UnevaluatedProperties)
605+
{
606+
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties);
607+
}
608+
// true is the default, no need to write it out
569609
writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s));
570610
writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w));
571611
writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s));
@@ -780,6 +820,21 @@ private void SerializeAsV2(
780820
// x-nullable extension
781821
SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0);
782822

823+
// Write UnevaluatedPropertiesSchema as extension if present
824+
if (UnevaluatedPropertiesSchema is not null)
825+
{
826+
writer.WriteOptionalObject(
827+
OpenApiConstants.UnevaluatedPropertiesExtension,
828+
UnevaluatedPropertiesSchema,
829+
(w, s) => s.SerializeAsV2(w));
830+
}
831+
// Write boolean false as extension if explicitly set to false
832+
else if (!UnevaluatedProperties)
833+
{
834+
writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension);
835+
writer.WriteValue(false);
836+
}
837+
783838
// extensions
784839
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);
785840

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

Lines changed: 4 additions & 2 deletions
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>
@@ -144,7 +144,9 @@ public IList<JsonNode>? Examples
144144
/// <inheritdoc/>
145145
public IList<JsonNode>? Enum { get => Target?.Enum; }
146146
/// <inheritdoc/>
147-
public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? false; }
147+
public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? true; }
148+
/// <inheritdoc/>
149+
public IOpenApiSchema? UnevaluatedPropertiesSchema { get => (Target as IOpenApiSchemaWithUnevaluatedProperties)?.UnevaluatedPropertiesSchema; }
148150
/// <inheritdoc/>
149151
public OpenApiExternalDocs? ExternalDocs { get => Target?.ExternalDocs; }
150152
/// <inheritdoc/>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
const Microsoft.OpenApi.OpenApiConstants.UnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties" -> string!
3+
Microsoft.OpenApi.IOpenApiSchemaWithUnevaluatedProperties
4+
Microsoft.OpenApi.IOpenApiSchemaWithUnevaluatedProperties.UnevaluatedPropertiesSchema.get -> Microsoft.OpenApi.IOpenApiSchema?
5+
Microsoft.OpenApi.OpenApiSchema.UnevaluatedPropertiesSchema.get -> Microsoft.OpenApi.IOpenApiSchema?
6+
Microsoft.OpenApi.OpenApiSchema.UnevaluatedPropertiesSchema.set -> void
7+
Microsoft.OpenApi.OpenApiSchemaReference.UnevaluatedPropertiesSchema.get -> Microsoft.OpenApi.IOpenApiSchema?

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,21 @@ 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+
if (n is ValueNode)
153+
{
154+
var value = n.GetScalarValue();
155+
if (value is not null)
156+
{
157+
o.UnevaluatedProperties = bool.Parse(value);
158+
}
159+
}
160+
else
153161
{
154-
o.UnevaluatedProperties = bool.Parse(unevaluatedProps);
162+
// Schema object case: deserialize as schema
163+
o.UnevaluatedPropertiesSchema = LoadSchema(n, t);
155164
}
156165
}
157166
},

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,21 @@ internal static partial class OpenApiV32Deserializer
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+
if (n is ValueNode)
153+
{
154+
var value = n.GetScalarValue();
155+
if (value is not null)
156+
{
157+
o.UnevaluatedProperties = bool.Parse(value);
158+
}
159+
}
160+
else
153161
{
154-
o.UnevaluatedProperties = bool.Parse(unevaluatedProps);
162+
// Schema object case: deserialize as schema
163+
o.UnevaluatedPropertiesSchema = LoadSchema(n, t);
155164
}
156165
}
157166
},

0 commit comments

Comments
 (0)