Skip to content

Commit

Permalink
Merge pull request #1899 from microsoft/mk/remove-recursive-keywords
Browse files Browse the repository at this point in the history
OpenApiSchema refactor to remove recursive keywords
  • Loading branch information
MaggieKimani1 authored Oct 29, 2024
2 parents 5c35c7b + f45de4c commit 6987f7b
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 49 deletions.
42 changes: 14 additions & 28 deletions src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiRe
/// <summary>
/// $vocabulary- used in meta-schemas to identify the vocabularies available for use in schemas described by that meta-schema.
/// </summary>
public virtual string Vocabulary { get; set; }
public virtual IDictionary<string, bool> Vocabulary { get; set; }

/// <summary>
/// $dynamicRef - an applicator that allows for deferring the full resolution until runtime, at which point it is resolved each time it is encountered while evaluating an instance
Expand All @@ -55,16 +55,6 @@ public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiRe
/// </summary>
public virtual string DynamicAnchor { get; set; }

/// <summary>
/// $recursiveAnchor - used to construct recursive schemas i.e one that has a reference to its own root, identified by the empty fragment URI reference ("#")
/// </summary>
public virtual string RecursiveAnchor { get; set; }

/// <summary>
/// $recursiveRef - used to construct recursive schemas i.e one that has a reference to its own root, identified by the empty fragment URI reference ("#")
/// </summary>
public virtual string RecursiveRef { get; set; }

/// <summary>
/// $defs - reserves a location for schema authors to inline re-usable JSON Schemas into a more general schema.
/// The keyword does not directly affect the validation result
Expand Down Expand Up @@ -358,11 +348,9 @@ public OpenApiSchema(OpenApiSchema schema)
Id = schema?.Id ?? Id;
Schema = schema?.Schema ?? Schema;
Comment = schema?.Comment ?? Comment;
Vocabulary = schema?.Vocabulary ?? Vocabulary;
Vocabulary = schema?.Vocabulary != null ? new Dictionary<string, bool>(schema.Vocabulary) : null;
DynamicAnchor = schema?.DynamicAnchor ?? DynamicAnchor;
DynamicRef = schema?.DynamicRef ?? DynamicRef;
RecursiveAnchor = schema?.RecursiveAnchor ?? RecursiveAnchor;
RecursiveRef = schema?.RecursiveRef ?? RecursiveRef;
Definitions = schema?.Definitions != null ? new Dictionary<string, OpenApiSchema>(schema.Definitions) : null;
UnevaluatedProperties = schema?.UnevaluatedProperties ?? UnevaluatedProperties;
V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum;
Expand Down Expand Up @@ -490,30 +478,30 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
SerializeTypeProperty(Type, writer, version);

// allOf
writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback);

// anyOf
writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, callback);

// oneOf
writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, callback);

// not
writer.WriteOptionalObject(OpenApiConstants.Not, Not, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalObject(OpenApiConstants.Not, Not, callback);

// items
writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalObject(OpenApiConstants.Items, Items, callback);

// properties
writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, callback);

// additionalProperties
if (AdditionalPropertiesAllowed)
{
writer.WriteOptionalObject(
OpenApiConstants.AdditionalProperties,
AdditionalProperties,
(w, s) => s.SerializeAsV3(w));
callback);
}
else
{
Expand All @@ -536,7 +524,7 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
}

// discriminator
writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, callback);

// readOnly
writer.WriteProperty(OpenApiConstants.ReadOnly, ReadOnly, false);
Expand All @@ -548,7 +536,7 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, (w, s) => s.SerializeAsV2(w));

// externalDocs
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback);

// example
writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e));
Expand All @@ -557,7 +545,7 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);

// extensions
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0);
writer.WriteExtensions(Extensions, version);

writer.WriteEndObject();
}
Expand All @@ -574,12 +562,10 @@ internal void WriteV31Properties(IOpenApiWriter writer)
writer.WriteProperty(OpenApiConstants.Id, Id);
writer.WriteProperty(OpenApiConstants.DollarSchema, Schema);
writer.WriteProperty(OpenApiConstants.Comment, Comment);
writer.WriteProperty(OpenApiConstants.Vocabulary, Vocabulary);
writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV3(w));
writer.WriteOptionalMap(OpenApiConstants.Vocabulary, Vocabulary, (w, s) => w.WriteValue(s));
writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w));
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor);
writer.WriteProperty(OpenApiConstants.RecursiveAnchor, RecursiveAnchor);
writer.WriteProperty(OpenApiConstants.RecursiveRef, RecursiveRef);
writer.WriteProperty(OpenApiConstants.V31ExclusiveMaximum, V31ExclusiveMaximum);
writer.WriteProperty(OpenApiConstants.V31ExclusiveMinimum, V31ExclusiveMinimum);
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,12 @@ internal OpenApiSchemaReference(OpenApiSchema target, string referenceId)
/// <inheritdoc/>
public override string Comment { get => Target.Comment; set => Target.Comment = value; }
/// <inheritdoc/>
public override string Vocabulary { get => Target.Vocabulary; set => Target.Vocabulary = value; }
public override IDictionary<string, bool> Vocabulary { get => Target.Vocabulary; set => Target.Vocabulary = value; }
/// <inheritdoc/>
public override string DynamicRef { get => Target.DynamicRef; set => Target.DynamicRef = value; }
/// <inheritdoc/>
public override string DynamicAnchor { get => Target.DynamicAnchor; set => Target.DynamicAnchor = value; }
/// <inheritdoc/>
public override string RecursiveAnchor { get => Target.RecursiveAnchor; set => Target.RecursiveAnchor = value; }
/// <inheritdoc/>
public override string RecursiveRef { get => Target.RecursiveRef; set => Target.RecursiveRef = value; }
/// <inheritdoc/>
public override IDictionary<string, OpenApiSchema> Definitions { get => Target.Definitions; set => Target.Definitions = value; }
/// <inheritdoc/>
public override decimal? V31ExclusiveMaximum { get => Target.V31ExclusiveMaximum; set => Target.V31ExclusiveMaximum = value; }
Expand Down
10 changes: 1 addition & 9 deletions src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal static partial class OpenApiV31Deserializer
},
{
"$vocabulary",
(o, n, _) => o.Vocabulary = n.GetScalarValue()
(o, n, _) => o.Vocabulary = n.CreateSimpleMap(LoadBool)
},
{
"$dynamicRef",
Expand All @@ -42,14 +42,6 @@ internal static partial class OpenApiV31Deserializer
"$dynamicAnchor",
(o, n, _) => o.DynamicAnchor = n.GetScalarValue()
},
{
"$recursiveAnchor",
(o, n, _) => o.RecursiveAnchor = n.GetScalarValue()
},
{
"$recursiveRef",
(o, n, _) => o.RecursiveRef = n.GetScalarValue()
},
{
"$defs",
(o, n, t) => o.Definitions = n.CreateMap(LoadSchema, t)
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ private static string LoadString(ParseNode node)
return node.GetScalarValue();
}

private static bool LoadBool(ParseNode node)
{
return bool.Parse(node.GetScalarValue());
}

private static (string, string) GetReferenceIdAndExternalResource(string pointer)
{
/* Check whether the reference pointer is a URL
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 @@ -272,6 +272,25 @@ public static void WriteOptionalMap(
}
}

/// <summary>
/// Write the optional Open API element map (string to string 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, bool> elements,
Action<IOpenApiWriter, bool> 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
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Collections.Generic;
Expand Down Expand Up @@ -404,5 +404,51 @@ public void LoadSchemaWithNullableExtensionAsV31Works(string filePath)
// Assert
schema.Type.Should().BeEquivalentTo(new string[] { "string", "null" });
}

[Fact]
public void SerializeSchemaWithJsonSchemaKeywordsWorks()
{
// Arrange
var expected = @"$id: https://example.com/schemas/person.schema.yaml
$schema: https://json-schema.org/draft/2020-12/schema
$comment: A schema defining a person object with optional references to dynamic components.
$vocabulary:
https://json-schema.org/draft/2020-12/vocab/core: true
https://json-schema.org/draft/2020-12/vocab/applicator: true
https://json-schema.org/draft/2020-12/vocab/validation: true
https://json-schema.org/draft/2020-12/vocab/meta-data: false
https://json-schema.org/draft/2020-12/vocab/format-annotation: false
$dynamicAnchor: addressDef
title: Person
required:
- name
type: object
properties:
name:
$comment: The person's full name
type: string
age:
$comment: Age must be a non-negative integer
minimum: 0
type: integer
address:
$comment: Reference to an address definition which can change dynamically
$dynamicRef: '#addressDef'
description: Schema for a person object
";
var path = Path.Combine(SampleFolderPath, "schemaWithJsonSchemaKeywords.yaml");

// Act
var schema = OpenApiModelFactory.Load<OpenApiSchema>(path, OpenApiSpecVersion.OpenApi3_1, out _);

// serialization
var writer = new StringWriter();
schema.SerializeAsV31(new OpenApiYamlWriter(writer));
var schemaString = writer.ToString();

// Assert
schema.Vocabulary.Keys.Count.Should().Be(5);
schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "https://example.com/schemas/person.schema.yaml"
$comment: "A schema defining a person object with optional references to dynamic components."
$vocabulary:
"https://json-schema.org/draft/2020-12/vocab/core": true
"https://json-schema.org/draft/2020-12/vocab/applicator": true
"https://json-schema.org/draft/2020-12/vocab/validation": true
"https://json-schema.org/draft/2020-12/vocab/meta-data": false
"https://json-schema.org/draft/2020-12/vocab/format-annotation": false

title: "Person"
description: "Schema for a person object"
type: "object"

properties:
name:
type: "string"
$comment: "The person's full name"
age:
type: "integer"
minimum: 0
$comment: "Age must be a non-negative integer"
address:
$dynamicRef: "#addressDef"
$comment: "Reference to an address definition which can change dynamically"

required:
- name

$dynamicAnchor: "addressDef"
definitions:
address:
$dynamicAnchor: "addressDef"
type: "object"
properties:
street:
type: "string"
city:
type: "string"
postalCode:
type: "string"
9 changes: 3 additions & 6 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -901,8 +901,6 @@ namespace Microsoft.OpenApi.Models
public virtual System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Models.OpenApiSchema> PatternProperties { get; set; }
public virtual System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Models.OpenApiSchema> Properties { get; set; }
public virtual bool ReadOnly { get; set; }
public virtual string RecursiveAnchor { get; set; }
public virtual string RecursiveRef { get; set; }
public virtual Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; }
public virtual System.Collections.Generic.ISet<string> Required { get; set; }
public virtual string Schema { get; set; }
Expand All @@ -914,7 +912,7 @@ namespace Microsoft.OpenApi.Models
public virtual bool UnresolvedReference { get; set; }
public virtual decimal? V31ExclusiveMaximum { get; set; }
public virtual decimal? V31ExclusiveMinimum { get; set; }
public virtual string Vocabulary { get; set; }
public virtual System.Collections.Generic.IDictionary<string, bool> Vocabulary { get; set; }
public virtual bool WriteOnly { get; set; }
public virtual Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; }
public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
Expand Down Expand Up @@ -1243,8 +1241,6 @@ namespace Microsoft.OpenApi.Models.References
public override System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Models.OpenApiSchema> PatternProperties { get; set; }
public override System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.Models.OpenApiSchema> Properties { get; set; }
public override bool ReadOnly { get; set; }
public override string RecursiveAnchor { get; set; }
public override string RecursiveRef { get; set; }
public override System.Collections.Generic.ISet<string> Required { get; set; }
public override string Schema { get; set; }
public override string Title { get; set; }
Expand All @@ -1254,7 +1250,7 @@ namespace Microsoft.OpenApi.Models.References
public override bool? UniqueItems { get; set; }
public override decimal? V31ExclusiveMaximum { get; set; }
public override decimal? V31ExclusiveMinimum { get; set; }
public override string Vocabulary { get; set; }
public override System.Collections.Generic.IDictionary<string, bool> Vocabulary { get; set; }
public override bool WriteOnly { get; set; }
public override Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; }
public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }
Expand Down Expand Up @@ -1849,6 +1845,7 @@ namespace Microsoft.OpenApi.Writers
{
public static void WriteOptionalCollection(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable<string> elements, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, string> action) { }
public static void WriteOptionalCollection<T>(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable<T> elements, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, T> action) { }
public static void WriteOptionalMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary<string, bool> elements, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, bool> action) { }
public static void WriteOptionalMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary<string, string> elements, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, string> action) { }
public static void WriteOptionalMap<T>(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary<string, T> elements, System.Action<Microsoft.OpenApi.Writers.IOpenApiWriter, T> action)
where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { }
Expand Down

0 comments on commit 6987f7b

Please sign in to comment.