Skip to content

Commit 75d7a66

Browse files
feat: add support for dependentRequired
1 parent 5db8757 commit 75d7a66

File tree

15 files changed

+191
-3
lines changed

15 files changed

+191
-3
lines changed

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: 5 additions & 0 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,6 +412,7 @@ 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)

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();

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ public virtual string GetScalarValue()
8484
public virtual List<JsonNode> CreateListOfAny()
8585
{
8686
throw new OpenApiReaderException("Cannot create a list from this type of node.", Context);
87-
}
87+
}
88+
89+
public virtual Dictionary<string, ISet<T>> CreateArrayMap<T>(Func<ValueNode, OpenApiDocument, T> map, OpenApiDocument openApiDocument)
90+
{
91+
throw new OpenApiReaderException("Cannot create array map from this type of node.", Context);
92+
}
8893
}
8994
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using Microsoft.OpenApi.Extensions;
@@ -234,6 +234,13 @@ internal static partial class OpenApiV31Deserializer
234234
"deprecated",
235235
(o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue())
236236
},
237+
{
238+
"dependentRequired",
239+
(o, n, doc) =>
240+
{
241+
o.DependentRequired = n.CreateArrayMap((n2, p) => n2.GetScalarValue(), doc);
242+
}
243+
},
237244
};
238245

239246
private static readonly PatternFieldMap<OpenApiSchema> _openApiSchemaPatternFields = new()

src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,21 @@ public virtual void WriteValue(bool value)
210210
Writer.Write(value.ToString().ToLower());
211211
}
212212

213+
/// <summary>
214+
/// Writes an enumerable collection as an array
215+
/// </summary>
216+
/// <param name="collection">The enumerable collection to write.</param>
217+
/// <typeparam name="T">The type of elements in the collection.</typeparam>
218+
public virtual void WriteEnumerable<T>(IEnumerable<T> collection)
219+
{
220+
WriteStartArray();
221+
foreach (var item in collection)
222+
{
223+
WriteValue(item);
224+
}
225+
WriteEndArray();
226+
}
227+
213228
/// <summary>
214229
/// Write object value.
215230
/// </summary>
@@ -264,6 +279,10 @@ public virtual void WriteValue(object value)
264279
{
265280
WriteValue((DateTimeOffset)value);
266281
}
282+
else if (value is IEnumerable<object> enumerable)
283+
{
284+
WriteEnumerable(enumerable);
285+
}
267286
else
268287
{
269288
throw new OpenApiWriterException(string.Format(SRResource.OpenApiUnsupportedValueType, type.FullName));

src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,25 @@ public static void WriteOptionalMap(
311311
}
312312
}
313313

314+
/// <summary>
315+
/// Write the optional Open API element map (string to array mapping).
316+
/// </summary>
317+
/// <param name="writer">The Open API writer.</param>
318+
/// <param name="name">The property name.</param>
319+
/// <param name="elements">The map values.</param>
320+
/// <param name="action">The map element writer action.</param>
321+
public static void WriteOptionalMap(
322+
this IOpenApiWriter writer,
323+
string name,
324+
IDictionary<string, ISet<string>> elements,
325+
Action<IOpenApiWriter, ISet<string>> action)
326+
{
327+
if (elements != null && elements.Any())
328+
{
329+
writer.WriteMapInternal(name, elements, action);
330+
}
331+
}
332+
314333
/// <summary>
315334
/// Write the optional Open API element map.
316335
/// </summary>

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
4646
"id",
4747
"name"
4848
},
49+
DependentRequired = new Dictionary<string, ISet<string>>
50+
{
51+
{ "tag", new HashSet<string> { "category" } }
52+
},
4953
Properties = new Dictionary<string, IOpenApiSchema>
5054
{
5155
["id"] = new OpenApiSchema()
@@ -61,6 +65,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
6165
{
6266
Type = JsonSchemaType.String
6367
},
68+
["category"] = new OpenApiSchema()
69+
{
70+
Type = JsonSchemaType.String,
71+
},
6472
}
6573
},
6674
["newPetSchema"] = new OpenApiSchema()
@@ -70,6 +78,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
7078
{
7179
"name"
7280
},
81+
DependentRequired = new Dictionary<string, ISet<string>>
82+
{
83+
{ "tag", new HashSet<string> { "category" } }
84+
},
7385
Properties = new Dictionary<string, IOpenApiSchema>
7486
{
7587
["id"] = new OpenApiSchema()
@@ -85,6 +97,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
8597
{
8698
Type = JsonSchemaType.String
8799
},
100+
["category"] = new OpenApiSchema()
101+
{
102+
Type = JsonSchemaType.String,
103+
},
88104
}
89105
}
90106
}
@@ -222,6 +238,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
222238
"id",
223239
"name"
224240
},
241+
DependentRequired = new Dictionary<string, ISet<string>>
242+
{
243+
{ "tag", new HashSet<string> { "category" } }
244+
},
225245
Properties = new Dictionary<string, IOpenApiSchema>
226246
{
227247
["id"] = new OpenApiSchema()
@@ -237,6 +257,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
237257
{
238258
Type = JsonSchemaType.String
239259
},
260+
["category"] = new OpenApiSchema()
261+
{
262+
Type = JsonSchemaType.String,
263+
},
240264
}
241265
},
242266
["newPetSchema"] = new OpenApiSchema()
@@ -246,6 +270,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
246270
{
247271
"name"
248272
},
273+
DependentRequired = new Dictionary<string, ISet<string>>
274+
{
275+
{ "tag", new HashSet<string> { "category" } }
276+
},
249277
Properties = new Dictionary<string, IOpenApiSchema>
250278
{
251279
["id"] = new OpenApiSchema()
@@ -261,6 +289,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
261289
{
262290
Type = JsonSchemaType.String
263291
},
292+
["category"] = new OpenApiSchema()
293+
{
294+
Type = JsonSchemaType.String,
295+
},
264296
}
265297
}
266298
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ public async Task ParseBasicV31SchemaShouldSucceed()
6868
"veggieName",
6969
"veggieLike"
7070
},
71+
DependentRequired = new Dictionary<string, ISet<string>>
72+
{
73+
{ "veggieType", new HashSet<string> { "veggieColor", "veggieSize" } }
74+
},
7175
Properties = new Dictionary<string, IOpenApiSchema>
7276
{
7377
["veggieName"] = new OpenApiSchema
@@ -79,6 +83,21 @@ public async Task ParseBasicV31SchemaShouldSucceed()
7983
{
8084
Type = JsonSchemaType.Boolean,
8185
Description = "Do I like this vegetable?"
86+
},
87+
["veggieType"] = new OpenApiSchema
88+
{
89+
Type = JsonSchemaType.String,
90+
Description = "The type of vegetable (e.g., root, leafy, etc.)."
91+
},
92+
["veggieColor"] = new OpenApiSchema
93+
{
94+
Type = JsonSchemaType.String,
95+
Description = "The color of the vegetable."
96+
},
97+
["veggieSize"] = new OpenApiSchema
98+
{
99+
Type = JsonSchemaType.String,
100+
Description = "The size of the vegetable."
82101
}
83102
}
84103
}

test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ components:
1313
required:
1414
- id
1515
- name
16+
dependentRequired:
17+
tag:
18+
- category
1619
properties:
1720
id:
1821
type: integer
@@ -21,10 +24,15 @@ components:
2124
type: string
2225
tag:
2326
type: string
27+
category:
28+
type: string
2429
newPetSchema:
2530
type: object
2631
required:
2732
- name
33+
dependentRequired:
34+
tag:
35+
- category
2836
properties:
2937
id:
3038
type: integer
@@ -33,6 +41,8 @@ components:
3341
type: string
3442
tag:
3543
type: string
44+
category:
45+
type: string
3646
pathItems:
3747
pets:
3848
get:

test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ components:
5959
required:
6060
- id
6161
- name
62+
dependentRequired:
63+
tag:
64+
- category
6265
properties:
6366
id:
6467
type: integer
@@ -67,15 +70,22 @@ components:
6770
type: string
6871
tag:
6972
type: string
73+
category:
74+
type: string
7075
newPetSchema:
7176
type: object
7277
required:
7378
- name
79+
dependentRequired:
80+
tag:
81+
- category
7482
properties:
7583
id:
7684
type: integer
7785
format: int64
7886
name:
7987
type: string
8088
tag:
89+
type: string
90+
category:
8191
type: string

0 commit comments

Comments
 (0)