Skip to content

Commit 11d0a4e

Browse files
committed
chore: merge main
2 parents e133de0 + 9c10c44 commit 11d0a4e

22 files changed

+371
-69
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.0.0-preview8"
2+
".": "2.0.0-preview9"
33
}

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## [2.0.0-preview9](https://github.com/microsoft/OpenAPI.NET/compare/v2.0.0-preview8...v2.0.0-preview9) (2025-02-21)
4+
5+
6+
### Features
7+
8+
* add support for dependentRequired ([75d7a66](https://github.com/microsoft/OpenAPI.NET/commit/75d7a662fc873566e50191127e4082b4ecf5ca7a))
9+
10+
11+
### Bug Fixes
12+
13+
* an issue where deprecation extension parsing would fail ([5db8757](https://github.com/microsoft/OpenAPI.NET/commit/5db8757df642dbe651552ce4a7c740e94474eafc))
14+
* an issue where deprecation extension parsing would fail ([b59864c](https://github.com/microsoft/OpenAPI.NET/commit/b59864c2387c9410e71b0caa8d439e7f122ddc24))
15+
* refactor ToIdentifier() to normalize flaggable enums ([#2156](https://github.com/microsoft/OpenAPI.NET/issues/2156)) ([b80e934](https://github.com/microsoft/OpenAPI.NET/commit/b80e9342018cf136cc54b900bb95832a6867e982))
16+
317
## [2.0.0-preview8](https://github.com/microsoft/OpenAPI.NET/compare/v2.0.0-preview7...v2.0.0-preview8) (2025-02-17)
418

519

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<PackageProjectUrl>https://github.com/Microsoft/OpenAPI.NET</PackageProjectUrl>
1313
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
1414
<PackageTags>OpenAPI .NET</PackageTags>
15-
<Version>2.0.0-preview8</Version>
15+
<Version>2.0.0-preview9</Version>
1616
</PropertyGroup>
1717
<!-- https://github.com/clairernovotny/DeterministicBuilds#deterministic-builds -->
1818
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">

src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

Lines changed: 91 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
67
using Microsoft.OpenApi.Exceptions;
78
using Microsoft.OpenApi.Models;
89

@@ -19,34 +20,61 @@ public static class OpenApiTypeMapper
1920
/// </summary>
2021
/// <param name="schemaType"></param>
2122
/// <returns></returns>
22-
public static string? ToIdentifier(this JsonSchemaType? schemaType)
23+
public static string[]? ToIdentifiers(this JsonSchemaType? schemaType)
2324
{
2425
if (schemaType is null)
2526
{
2627
return null;
2728
}
28-
return schemaType.Value.ToIdentifier();
29+
return schemaType.Value.ToIdentifiers();
2930
}
3031

3132
/// <summary>
3233
/// Maps a JsonSchema data type to an identifier.
3334
/// </summary>
3435
/// <param name="schemaType"></param>
3536
/// <returns></returns>
36-
public static string? ToIdentifier(this JsonSchemaType schemaType)
37+
public static string[] ToIdentifiers(this JsonSchemaType schemaType)
3738
{
38-
return schemaType switch
39-
{
40-
JsonSchemaType.Null => "null",
41-
JsonSchemaType.Boolean => "boolean",
42-
JsonSchemaType.Integer => "integer",
43-
JsonSchemaType.Number => "number",
44-
JsonSchemaType.String => "string",
45-
JsonSchemaType.Array => "array",
46-
JsonSchemaType.Object => "object",
47-
_ => null,
48-
};
39+
return schemaType.ToIdentifiersInternal().ToArray();
40+
}
41+
42+
private static readonly Dictionary<JsonSchemaType, string> allSchemaTypes = new()
43+
{
44+
{ JsonSchemaType.Boolean, "boolean" },
45+
{ JsonSchemaType.Integer, "integer" },
46+
{ JsonSchemaType.Number, "number" },
47+
{ JsonSchemaType.String, "string" },
48+
{ JsonSchemaType.Object, "object" },
49+
{ JsonSchemaType.Array, "array" },
50+
{ JsonSchemaType.Null, "null" }
51+
};
52+
53+
private static IEnumerable<string> ToIdentifiersInternal(this JsonSchemaType schemaType)
54+
{
55+
return allSchemaTypes.Where(kvp => schemaType.HasFlag(kvp.Key)).Select(static kvp => kvp.Value);
56+
}
57+
58+
/// <summary>
59+
/// Returns the first identifier from a string array.
60+
/// </summary>
61+
/// <param name="schemaType"></param>
62+
/// <returns></returns>
63+
internal static string ToFirstIdentifier(this JsonSchemaType schemaType)
64+
{
65+
return schemaType.ToIdentifiersInternal().First();
66+
}
67+
68+
/// <summary>
69+
/// Returns a single identifier from an array with only one item.
70+
/// </summary>
71+
/// <param name="schemaType"></param>
72+
/// <returns></returns>
73+
internal static string ToSingleIdentifier(this JsonSchemaType schemaType)
74+
{
75+
return schemaType.ToIdentifiersInternal().Single();
4976
}
77+
5078
#nullable restore
5179

5280
/// <summary>
@@ -70,6 +98,26 @@ public static JsonSchemaType ToJsonSchemaType(this string identifier)
7098
};
7199
}
72100

101+
/// <summary>
102+
/// Converts a schema type's identifier into the enum equivalent
103+
/// </summary>
104+
/// <param name="identifier"></param>
105+
/// <returns></returns>
106+
public static JsonSchemaType? ToJsonSchemaType(this string[] identifier)
107+
{
108+
if (identifier == null)
109+
{
110+
return null;
111+
}
112+
113+
JsonSchemaType type = 0;
114+
foreach (var id in identifier)
115+
{
116+
type |= id.ToJsonSchemaType();
117+
}
118+
return type;
119+
}
120+
73121
private static readonly Dictionary<Type, Func<OpenApiSchema>> _simpleTypeToOpenApiSchema = new()
74122
{
75123
[typeof(bool)] = () => new() { Type = JsonSchemaType.Boolean },
@@ -141,7 +189,7 @@ public static OpenApiSchema MapTypeToOpenApiPrimitiveType(this Type type)
141189
}
142190

143191
/// <summary>
144-
/// Maps an JsonSchema data type and format to a simple type.
192+
/// Maps a JsonSchema data type and format to a simple type.
145193
/// </summary>
146194
/// <param name="schema">The OpenApi data type</param>
147195
/// <returns>The simple type</returns>
@@ -153,37 +201,36 @@ public static Type MapOpenApiPrimitiveTypeToSimpleType(this OpenApiSchema schema
153201
throw new ArgumentNullException(nameof(schema));
154202
}
155203

156-
var type = ((schema.Type & ~JsonSchemaType.Null).ToIdentifier(), schema.Format?.ToLowerInvariant(), schema.Type & JsonSchemaType.Null) switch
204+
var type = (schema.Type, schema.Format?.ToLowerInvariant()) switch
157205
{
158-
("integer" or "number", "int32", JsonSchemaType.Null) => typeof(int?),
159-
("integer" or "number", "int64", JsonSchemaType.Null) => typeof(long?),
160-
("integer", null, JsonSchemaType.Null) => typeof(long?),
161-
("number", "float", JsonSchemaType.Null) => typeof(float?),
162-
("number", "double", JsonSchemaType.Null) => typeof(double?),
163-
("number", null, JsonSchemaType.Null) => typeof(double?),
164-
("number", "decimal", JsonSchemaType.Null) => typeof(decimal?),
165-
("string", "byte", JsonSchemaType.Null) => typeof(byte?),
166-
("string", "date-time", JsonSchemaType.Null) => typeof(DateTimeOffset?),
167-
("string", "uuid", JsonSchemaType.Null) => typeof(Guid?),
168-
("string", "char", JsonSchemaType.Null) => typeof(char?),
169-
("boolean", null, JsonSchemaType.Null) => typeof(bool?),
170-
("boolean", null, _) => typeof(bool),
206+
(JsonSchemaType.Integer | JsonSchemaType.Null or JsonSchemaType.Number | JsonSchemaType.Null, "int32") => typeof(int?),
207+
(JsonSchemaType.Integer | JsonSchemaType.Null or JsonSchemaType.Number | JsonSchemaType.Null, "int64") => typeof(long?),
208+
(JsonSchemaType.Integer | JsonSchemaType.Null, null) => typeof(long?),
209+
(JsonSchemaType.Number | JsonSchemaType.Null, "float") => typeof(float?),
210+
(JsonSchemaType.Number | JsonSchemaType.Null, "double") => typeof(double?),
211+
(JsonSchemaType.Number | JsonSchemaType.Null, null) => typeof(double?),
212+
(JsonSchemaType.Number | JsonSchemaType.Null, "decimal") => typeof(decimal?),
213+
(JsonSchemaType.String | JsonSchemaType.Null, "byte") => typeof(byte?),
214+
(JsonSchemaType.String | JsonSchemaType.Null, "date-time") => typeof(DateTimeOffset?),
215+
(JsonSchemaType.String | JsonSchemaType.Null, "uuid") => typeof(Guid?),
216+
(JsonSchemaType.String | JsonSchemaType.Null, "char") => typeof(char?),
217+
(JsonSchemaType.Boolean | JsonSchemaType.Null, null) => typeof(bool?),
218+
(JsonSchemaType.Boolean, null) => typeof(bool),
171219
// integer is technically not valid with format, but we must provide some compatibility
172-
("integer" or "number", "int32", _) => typeof(int),
173-
("integer" or "number", "int64", _) => typeof(long),
174-
("integer", null, _) => typeof(long),
175-
("number", "float", _) => typeof(float),
176-
("number", "double", _) => typeof(double),
177-
("number", "decimal", _) => typeof(decimal),
178-
("number", null, _) => typeof(double),
179-
("string", "byte", _) => typeof(byte),
180-
("string", "date-time", _) => typeof(DateTimeOffset),
181-
("string", "uuid", _) => typeof(Guid),
182-
("string", "duration", _) => typeof(TimeSpan),
183-
("string", "char", _) => typeof(char),
184-
("string", null, _) => typeof(string),
185-
("object", null, _) => typeof(object),
186-
("string", "uri", _) => typeof(Uri),
220+
(JsonSchemaType.Integer or JsonSchemaType.Number, "int32") => typeof(int),
221+
(JsonSchemaType.Integer or JsonSchemaType.Number, "int64") => typeof(long),
222+
(JsonSchemaType.Integer, null) => typeof(long),
223+
(JsonSchemaType.Number, "float") => typeof(float),
224+
(JsonSchemaType.Number, "double") => typeof(double),
225+
(JsonSchemaType.Number, "decimal") => typeof(decimal),
226+
(JsonSchemaType.Number, null) => typeof(double),
227+
(JsonSchemaType.String, "byte") => typeof(byte),
228+
(JsonSchemaType.String, "date-time") => typeof(DateTimeOffset),
229+
(JsonSchemaType.String, "uuid") => typeof(Guid),
230+
(JsonSchemaType.String, "char") => typeof(char),
231+
(JsonSchemaType.String, null) => typeof(string),
232+
(JsonSchemaType.Object, null) => typeof(object),
233+
(JsonSchemaType.String, "uri") => typeof(Uri),
187234
_ => typeof(string),
188235
};
189236

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using System.Text.Json.Nodes;
33
using Microsoft.OpenApi.Interfaces;
44

@@ -299,4 +299,9 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable
299299
/// Annotations are NOT (de)serialized with the schema and can be used for custom properties.
300300
/// </summary>
301301
public IDictionary<string, object> Annotations { get; }
302+
303+
/// <summary>
304+
/// Follow JSON Schema definition:https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.4
305+
/// </summary>
306+
public IDictionary<string, ISet<string>> DependentRequired { get; }
302307
}

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,11 @@ public static class OpenApiConstants
720720
/// </summary>
721721
public const string NullableExtension = "x-nullable";
722722

723+
/// <summary>
724+
/// Field: DependentRequired
725+
/// </summary>
726+
public const string DependentRequired = "dependentRequired";
727+
723728
#region V2.0
724729

725730
/// <summary>

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ public class OpenApiSchema : IOpenApiReferenceable, IOpenApiExtensible, IOpenApi
176176
/// <inheritdoc />
177177
public IDictionary<string, object> Annotations { get; set; }
178178

179+
/// <inheritdoc />
180+
public IDictionary<string, ISet<string>> DependentRequired { get; set; } = new Dictionary<string, ISet<string>>();
181+
179182
/// <summary>
180183
/// Parameterless constructor
181184
/// </summary>
@@ -239,6 +242,7 @@ internal OpenApiSchema(IOpenApiSchema schema)
239242
Extensions = schema.Extensions != null ? new Dictionary<string, IOpenApiExtension>(schema.Extensions) : null;
240243
Annotations = schema.Annotations != null ? new Dictionary<string, object>(schema.Annotations) : null;
241244
UnrecognizedKeywords = schema.UnrecognizedKeywords != null ? new Dictionary<string, JsonNode>(schema.UnrecognizedKeywords) : null;
245+
DependentRequired = schema.DependentRequired != null ? new Dictionary<string, ISet<string>>(schema.DependentRequired) : null;
242246
}
243247

244248
/// <inheritdoc />
@@ -408,12 +412,13 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer)
408412
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false);
409413
writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s));
410414
writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w));
415+
writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s));
411416
}
412417

413418
internal void WriteAsItemsProperties(IOpenApiWriter writer)
414419
{
415420
// type
416-
writer.WriteProperty(OpenApiConstants.Type, (Type & ~JsonSchemaType.Null).ToIdentifier());
421+
writer.WriteProperty(OpenApiConstants.Type, (Type & ~JsonSchemaType.Null)?.ToFirstIdentifier());
417422

418423
// format
419424
WriteFormatProperty(writer);
@@ -629,10 +634,10 @@ private void SerializeAsV2(
629634
private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version)
630635
{
631636
// check whether nullable is true for upcasting purposes
632-
var isNullable = (Type.HasValue && Type.Value.HasFlag(JsonSchemaType.Null)) ||
637+
var isNullable = (Type.HasValue && Type.Value.HasFlag(JsonSchemaType.Null)) ||
633638
Extensions is not null &&
634639
Extensions.TryGetValue(OpenApiConstants.NullableExtension, out var nullExtRawValue) &&
635-
nullExtRawValue is OpenApiAny { Node: JsonNode jsonNode} &&
640+
nullExtRawValue is OpenApiAny { Node: JsonNode jsonNode } &&
636641
jsonNode.GetValueKind() is JsonValueKind.True;
637642
if (type is null)
638643
{
@@ -651,14 +656,14 @@ Extensions is not null &&
651656
break;
652657
case OpenApiSpecVersion.OpenApi3_0 when isNullable && type.Value == JsonSchemaType.Null:
653658
writer.WriteProperty(OpenApiConstants.Nullable, true);
654-
writer.WriteProperty(OpenApiConstants.Type, JsonSchemaType.Object.ToIdentifier());
659+
writer.WriteProperty(OpenApiConstants.Type, JsonSchemaType.Object.ToFirstIdentifier());
655660
break;
656661
case OpenApiSpecVersion.OpenApi3_0 when isNullable && type.Value != JsonSchemaType.Null:
657662
writer.WriteProperty(OpenApiConstants.Nullable, true);
658-
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier());
663+
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToFirstIdentifier());
659664
break;
660665
default:
661-
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier());
666+
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToFirstIdentifier());
662667
break;
663668
}
664669
}
@@ -674,7 +679,13 @@ Extensions is not null &&
674679
var list = (from JsonSchemaType flag in jsonSchemaTypeValues
675680
where type.Value.HasFlag(flag)
676681
select flag).ToList();
677-
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier()));
682+
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) =>
683+
{
684+
foreach(var item in s.ToIdentifiers())
685+
{
686+
w.WriteValue(item);
687+
}
688+
});
678689
}
679690
}
680691
}
@@ -697,7 +708,7 @@ private static void UpCastSchemaTypeToV31(JsonSchemaType type, IOpenApiWriter wr
697708
var temporaryType = type | JsonSchemaType.Null;
698709
var list = (from JsonSchemaType flag in jsonSchemaTypeValues// Check if the flag is set in 'type' using a bitwise AND operation
699710
where temporaryType.HasFlag(flag)
700-
select flag.ToIdentifier()).ToList();
711+
select flag.ToFirstIdentifier()).ToList();
701712
if (list.Count > 1)
702713
{
703714
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s));
@@ -734,7 +745,7 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
734745
if (schemaType.HasFlag(flag) && flag != JsonSchemaType.Null)
735746
{
736747
// Write the non-null flag value to the writer
737-
writer.WriteProperty(OpenApiConstants.Type, flag.ToIdentifier());
748+
writer.WriteProperty(OpenApiConstants.Type, flag.ToFirstIdentifier());
738749
}
739750
}
740751
writer.WriteProperty(nullableProp, true);
@@ -747,7 +758,7 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
747758
}
748759
else
749760
{
750-
writer.WriteProperty(OpenApiConstants.Type, schemaType.ToIdentifier());
761+
writer.WriteProperty(OpenApiConstants.Type, schemaType.ToFirstIdentifier());
751762
}
752763
}
753764
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ public string Description
154154
/// <inheritdoc/>
155155
public IDictionary<string, object> Annotations { get => Target?.Annotations; }
156156

157+
/// <inheritdoc/>
158+
public IDictionary<string, ISet<string>> DependentRequired { get => Target?.DependentRequired; }
159+
157160
/// <inheritdoc/>
158161
public override void SerializeAsV31(IOpenApiWriter writer)
159162
{

src/Microsoft.OpenApi/Reader/ParseNodes/MapNode.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,34 @@ public override Dictionary<string, T> CreateSimpleMap<T>(Func<ValueNode, T> map)
103103
return nodes.ToDictionary(k => k.key, v => v.value);
104104
}
105105

106+
public override Dictionary<string, ISet<T>> CreateArrayMap<T>(Func<ValueNode, OpenApiDocument, T> map, OpenApiDocument openApiDocument)
107+
{
108+
var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context);
109+
110+
var nodes = jsonMap.Select(n =>
111+
{
112+
var key = n.Key;
113+
try
114+
{
115+
Context.StartObject(key);
116+
JsonArray arrayNode = n.Value is JsonArray value
117+
? value
118+
: throw new OpenApiReaderException($"Expected array while parsing {typeof(T).Name}", Context);
119+
120+
ISet<T> values = new HashSet<T>(arrayNode.Select(item => map(new ValueNode(Context, item), openApiDocument)));
121+
122+
return (key, values);
123+
124+
}
125+
finally
126+
{
127+
Context.EndObject();
128+
}
129+
});
130+
131+
return nodes.ToDictionary(kvp => kvp.key, kvp => kvp.values);
132+
}
133+
106134
public IEnumerator<PropertyNode> GetEnumerator()
107135
{
108136
return _nodes.GetEnumerator();

0 commit comments

Comments
 (0)