Skip to content

Commit

Permalink
feat: add support for dependentRequired
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael-Wamae authored Feb 20, 2025
1 parent 5db8757 commit 75d7a66
Show file tree
Hide file tree
Showing 15 changed files with 191 additions and 3 deletions.
7 changes: 6 additions & 1 deletion src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Interfaces;

Expand Down Expand Up @@ -299,4 +299,9 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable
/// Annotations are NOT (de)serialized with the schema and can be used for custom properties.
/// </summary>
public IDictionary<string, object> Annotations { get; }

/// <summary>
/// Follow JSON Schema definition:https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.4
/// </summary>
public IDictionary<string, ISet<string>> DependentRequired { get; }
}
5 changes: 5 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,11 @@ public static class OpenApiConstants
/// </summary>
public const string NullableExtension = "x-nullable";

/// <summary>
/// Field: DependentRequired
/// </summary>
public const string DependentRequired = "dependentRequired";

#region V2.0

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ public class OpenApiSchema : IOpenApiReferenceable, IOpenApiExtensible, IOpenApi
/// <inheritdoc />
public IDictionary<string, object> Annotations { get; set; }

/// <inheritdoc />
public IDictionary<string, ISet<string>> DependentRequired { get; set; } = new Dictionary<string, ISet<string>>();

/// <summary>
/// Parameterless constructor
/// </summary>
Expand Down Expand Up @@ -239,6 +242,7 @@ internal OpenApiSchema(IOpenApiSchema schema)
Extensions = schema.Extensions != null ? new Dictionary<string, IOpenApiExtension>(schema.Extensions) : null;
Annotations = schema.Annotations != null ? new Dictionary<string, object>(schema.Annotations) : null;
UnrecognizedKeywords = schema.UnrecognizedKeywords != null ? new Dictionary<string, JsonNode>(schema.UnrecognizedKeywords) : null;
DependentRequired = schema.DependentRequired != null ? new Dictionary<string, ISet<string>>(schema.DependentRequired) : null;
}

/// <inheritdoc />
Expand Down Expand Up @@ -408,6 +412,7 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer)
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false);
writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s));
writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w));
writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s));
}

internal void WriteAsItemsProperties(IOpenApiWriter writer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ public string Description
/// <inheritdoc/>
public IDictionary<string, object> Annotations { get => Target?.Annotations; }

/// <inheritdoc/>
public IDictionary<string, ISet<string>> DependentRequired { get => Target?.DependentRequired; }

/// <inheritdoc/>
public override void SerializeAsV31(IOpenApiWriter writer)
{
Expand Down
28 changes: 28 additions & 0 deletions src/Microsoft.OpenApi/Reader/ParseNodes/MapNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,34 @@ public override Dictionary<string, T> CreateSimpleMap<T>(Func<ValueNode, T> map)
return nodes.ToDictionary(k => k.key, v => v.value);
}

public override Dictionary<string, ISet<T>> CreateArrayMap<T>(Func<ValueNode, OpenApiDocument, T> map, OpenApiDocument openApiDocument)
{
var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context);

var nodes = jsonMap.Select(n =>
{
var key = n.Key;
try
{
Context.StartObject(key);
JsonArray arrayNode = n.Value is JsonArray value
? value
: throw new OpenApiReaderException($"Expected array while parsing {typeof(T).Name}", Context);

ISet<T> values = new HashSet<T>(arrayNode.Select(item => map(new ValueNode(Context, item), openApiDocument)));

return (key, values);

}
finally
{
Context.EndObject();
}
});

return nodes.ToDictionary(kvp => kvp.key, kvp => kvp.values);
}

public IEnumerator<PropertyNode> GetEnumerator()
{
return _nodes.GetEnumerator();
Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.OpenApi/Reader/ParseNodes/ParseNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ public virtual string GetScalarValue()
public virtual List<JsonNode> CreateListOfAny()
{
throw new OpenApiReaderException("Cannot create a list from this type of node.", Context);
}
}

public virtual Dictionary<string, ISet<T>> CreateArrayMap<T>(Func<ValueNode, OpenApiDocument, T> map, OpenApiDocument openApiDocument)
{
throw new OpenApiReaderException("Cannot create array map from this type of node.", Context);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using Microsoft.OpenApi.Extensions;
Expand Down Expand Up @@ -234,6 +234,13 @@ internal static partial class OpenApiV31Deserializer
"deprecated",
(o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue())
},
{
"dependentRequired",
(o, n, doc) =>
{
o.DependentRequired = n.CreateArrayMap((n2, p) => n2.GetScalarValue(), doc);
}
},
};

private static readonly PatternFieldMap<OpenApiSchema> _openApiSchemaPatternFields = new()
Expand Down
19 changes: 19 additions & 0 deletions src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,21 @@ public virtual void WriteValue(bool value)
Writer.Write(value.ToString().ToLower());
}

/// <summary>
/// Writes an enumerable collection as an array
/// </summary>
/// <param name="collection">The enumerable collection to write.</param>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public virtual void WriteEnumerable<T>(IEnumerable<T> collection)
{
WriteStartArray();
foreach (var item in collection)
{
WriteValue(item);
}
WriteEndArray();
}

/// <summary>
/// Write object value.
/// </summary>
Expand Down Expand Up @@ -264,6 +279,10 @@ public virtual void WriteValue(object value)
{
WriteValue((DateTimeOffset)value);
}
else if (value is IEnumerable<object> enumerable)
{
WriteEnumerable(enumerable);
}
else
{
throw new OpenApiWriterException(string.Format(SRResource.OpenApiUnsupportedValueType, type.FullName));
Expand Down
19 changes: 19 additions & 0 deletions src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,25 @@ public static void WriteOptionalMap(
}
}

/// <summary>
/// Write the optional Open API element map (string to array mapping).
/// </summary>
/// <param name="writer">The Open API writer.</param>
/// <param name="name">The property name.</param>
/// <param name="elements">The map values.</param>
/// <param name="action">The map element writer action.</param>
public static void WriteOptionalMap(
this IOpenApiWriter writer,
string name,
IDictionary<string, ISet<string>> elements,
Action<IOpenApiWriter, ISet<string>> action)
{
if (elements != null && elements.Any())
{
writer.WriteMapInternal(name, elements, action);
}
}

/// <summary>
/// Write the optional Open API element map.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
"id",
"name"
},
DependentRequired = new Dictionary<string, ISet<string>>
{
{ "tag", new HashSet<string> { "category" } }
},
Properties = new Dictionary<string, IOpenApiSchema>
{
["id"] = new OpenApiSchema()
Expand All @@ -61,6 +65,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
{
Type = JsonSchemaType.String
},
["category"] = new OpenApiSchema()
{
Type = JsonSchemaType.String,
},
}
},
["newPetSchema"] = new OpenApiSchema()
Expand All @@ -70,6 +78,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
{
"name"
},
DependentRequired = new Dictionary<string, ISet<string>>
{
{ "tag", new HashSet<string> { "category" } }
},
Properties = new Dictionary<string, IOpenApiSchema>
{
["id"] = new OpenApiSchema()
Expand All @@ -85,6 +97,10 @@ public async Task ParseDocumentWithWebhooksShouldSucceed()
{
Type = JsonSchemaType.String
},
["category"] = new OpenApiSchema()
{
Type = JsonSchemaType.String,
},
}
}
}
Expand Down Expand Up @@ -222,6 +238,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
"id",
"name"
},
DependentRequired = new Dictionary<string, ISet<string>>
{
{ "tag", new HashSet<string> { "category" } }
},
Properties = new Dictionary<string, IOpenApiSchema>
{
["id"] = new OpenApiSchema()
Expand All @@ -237,6 +257,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
{
Type = JsonSchemaType.String
},
["category"] = new OpenApiSchema()
{
Type = JsonSchemaType.String,
},
}
},
["newPetSchema"] = new OpenApiSchema()
Expand All @@ -246,6 +270,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
{
"name"
},
DependentRequired = new Dictionary<string, ISet<string>>
{
{ "tag", new HashSet<string> { "category" } }
},
Properties = new Dictionary<string, IOpenApiSchema>
{
["id"] = new OpenApiSchema()
Expand All @@ -261,6 +289,10 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
{
Type = JsonSchemaType.String
},
["category"] = new OpenApiSchema()
{
Type = JsonSchemaType.String,
},
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public async Task ParseBasicV31SchemaShouldSucceed()
"veggieName",
"veggieLike"
},
DependentRequired = new Dictionary<string, ISet<string>>
{
{ "veggieType", new HashSet<string> { "veggieColor", "veggieSize" } }
},
Properties = new Dictionary<string, IOpenApiSchema>
{
["veggieName"] = new OpenApiSchema
Expand All @@ -79,6 +83,21 @@ public async Task ParseBasicV31SchemaShouldSucceed()
{
Type = JsonSchemaType.Boolean,
Description = "Do I like this vegetable?"
},
["veggieType"] = new OpenApiSchema
{
Type = JsonSchemaType.String,
Description = "The type of vegetable (e.g., root, leafy, etc.)."
},
["veggieColor"] = new OpenApiSchema
{
Type = JsonSchemaType.String,
Description = "The color of the vegetable."
},
["veggieSize"] = new OpenApiSchema
{
Type = JsonSchemaType.String,
Description = "The size of the vegetable."
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ components:
required:
- id
- name
dependentRequired:
tag:
- category
properties:
id:
type: integer
Expand All @@ -21,10 +24,15 @@ components:
type: string
tag:
type: string
category:
type: string
newPetSchema:
type: object
required:
- name
dependentRequired:
tag:
- category
properties:
id:
type: integer
Expand All @@ -33,6 +41,8 @@ components:
type: string
tag:
type: string
category:
type: string
pathItems:
pets:
get:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ components:
required:
- id
- name
dependentRequired:
tag:
- category
properties:
id:
type: integer
Expand All @@ -67,15 +70,22 @@ components:
type: string
tag:
type: string
category:
type: string
newPetSchema:
type: object
required:
- name
dependentRequired:
tag:
- category
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
category:
type: string
Loading

0 comments on commit 75d7a66

Please sign in to comment.