From cf27ffa00cbf9698b6231ee99ad67e3d46e7e6c1 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 25 Jul 2024 11:28:28 +0300 Subject: [PATCH 01/27] Add an OpenApiSchema model with known keywords --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/Microsoft.OpenApi/Models/OpenApiSchema.cs diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs new file mode 100644 index 000000000..24a990f66 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -0,0 +1,267 @@ +using System.Collections.Generic; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; + +namespace Microsoft.OpenApi.Models +{ + internal class OpenApiSchema + { + /// + /// Follow JSON Schema definition. Short text providing information about the data. + /// + public string Title { get; set; } + + public string Schema { get; set; } + + public string Id { get; set; } + + public string Comment { get; set; } + + public string Vocabulary { get; set; } + + public string DynamicRef { get; set; } + + public string DynamicAnchor { get; set; } + + public string RecursiveAnchor { get; set; } + + public string RecursiveRef { get; set; } + + public IDictionary Definitions { get; set; } + + public bool UnevaluatedProperties { get; set; } + + public decimal V31ExclusiveMaximum { get; set; } + + public decimal V31ExclusiveMinimum { get; set; } + + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Value MUST be a string in V2 and V3. + /// + public string Type { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Multiple types via an array are supported in V31. + /// + public string[] TypeArray { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// While relying on JSON Schema's defined formats, + /// the OAS offers a few additional predefined formats. + /// + public string Format { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// CommonMark syntax MAY be used for rich text representation. + /// + public string Description { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public decimal? Maximum { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public bool? ExclusiveMaximum { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public decimal? Minimum { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public bool? ExclusiveMinimum { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public int? MaxLength { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public int? MinLength { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect + /// + public string Pattern { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public decimal? MultipleOf { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// The default value represents what would be assumed by the consumer of the input as the value of the schema if one is not provided. + /// Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object defined at the same level. + /// For example, if type is string, then default can be "foo" but cannot be 1. + /// + public OpenApiAny Default { get; set; } + + /// + /// Relevant only for Schema "properties" definitions. Declares the property as "read only". + /// This means that it MAY be sent as part of a response but SHOULD NOT be sent as part of the request. + /// If the property is marked as readOnly being true and is in the required list, + /// the required will take effect on the response only. + /// A property MUST NOT be marked as both readOnly and writeOnly being true. + /// Default value is false. + /// + public bool ReadOnly { get; set; } + + /// + /// Relevant only for Schema "properties" definitions. Declares the property as "write only". + /// Therefore, it MAY be sent as part of a request but SHOULD NOT be sent as part of the response. + /// If the property is marked as writeOnly being true and is in the required list, + /// the required will take effect on the request only. + /// A property MUST NOT be marked as both readOnly and writeOnly being true. + /// Default value is false. + /// + public bool WriteOnly { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. + /// + public IList AllOf { get; set; } = new List(); + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. + /// + public IList OneOf { get; set; } = new List(); + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. + /// + public IList AnyOf { get; set; } = new List(); + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. + /// + public OpenApiSchema Not { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public ISet Required { get; set; } = new HashSet(); + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object + /// and not a standard JSON Schema. items MUST be present if the type is array. + /// + public OpenApiSchema Items { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public int? MaxItems { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public int? MinItems { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public bool? UniqueItems { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Property definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced). + /// + public IDictionary Properties { get; set; } = new Dictionary(); + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public int? MaxProperties { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public int? MinProperties { get; set; } + + /// + /// Indicates if the schema can contain properties other than those defined by the properties map. + /// + public bool AdditionalPropertiesAllowed { get; set; } = true; + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Value can be boolean or object. Inline or referenced schema + /// MUST be of a Schema Object and not a standard JSON Schema. + /// + public OpenApiSchema AdditionalProperties { get; set; } + + /// + /// Adds support for polymorphism. The discriminator is an object name that is used to differentiate + /// between other schemas which may satisfy the payload description. + /// + public OpenApiDiscriminator Discriminator { get; set; } + + /// + /// A free-form property to include an example of an instance for this schema. + /// To represent examples that cannot be naturally represented in JSON or YAML, + /// a string value can be used to contain the example with escaping where necessary. + /// + public OpenApiAny Example { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public IList Enum { get; set; } = new List(); + + /// + /// Allows sending a null value for the defined schema. Default value is false. + /// + public bool Nullable { get; set; } + + /// + /// Additional external documentation for this schema. + /// + public OpenApiExternalDocs ExternalDocs { get; set; } + + /// + /// Specifies that a schema is deprecated and SHOULD be transitioned out of usage. + /// Default value is false. + /// + public bool Deprecated { get; set; } + + /// + /// This MAY be used only on properties schemas. It has no effect on root schemas. + /// Adds additional metadata to describe the XML representation of this property. + /// + public OpenApiXml Xml { get; set; } + + /// + /// This object MAY be extended with Specification Extensions. + /// + public IDictionary Extensions { get; set; } = new Dictionary(); + + /// + /// Indicates object is a placeholder reference to an actual object and does not contain valid data. + /// + public bool UnresolvedReference { get; set; } + + /// + /// Reference object. + /// + public OpenApiReference Reference { get; set; } + } +} From 6fed3851ee1c07b301cab566ed05218755e7bc94 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 25 Jul 2024 11:29:03 +0300 Subject: [PATCH 02/27] Add a copy constructor --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 24a990f66..f924a8aaa 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -263,5 +263,68 @@ internal class OpenApiSchema /// Reference object. /// public OpenApiReference Reference { get; set; } + + /// + /// Parameterless constructor + /// + public OpenApiSchema() { } + + /// + /// Initializes a copy of object + /// + public OpenApiSchema(OpenApiSchema schema) + { + Title = schema?.Title ?? Title; + Id = schema?.Id ?? Id; + Schema = schema?.Schema ?? Schema; + Comment = schema?.Comment ?? Comment; + Vocabulary = schema?.Vocabulary ?? Vocabulary; + DynamicAnchor = schema?.DynamicAnchor ?? DynamicAnchor; + DynamicRef = schema?.DynamicRef ?? DynamicRef; + RecursiveAnchor = schema?.RecursiveAnchor ?? RecursiveAnchor; + RecursiveRef = schema?.RecursiveRef ?? RecursiveRef; + Definitions = schema?.Definitions != null ? new Dictionary(schema.Definitions) : null; + UnevaluatedProperties = schema?.UnevaluatedProperties ?? UnevaluatedProperties; + V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum; + V31ExclusiveMinimum = schema?.V31ExclusiveMinimum ?? V31ExclusiveMinimum; + Type = schema?.Type ?? Type; + Format = schema?.Format ?? Format; + Description = schema?.Description ?? Description; + Maximum = schema?.Maximum ?? Maximum; + ExclusiveMaximum = schema?.ExclusiveMaximum ?? ExclusiveMaximum; + Minimum = schema?.Minimum ?? Minimum; + ExclusiveMinimum = schema?.ExclusiveMinimum ?? ExclusiveMinimum; + MaxLength = schema?.MaxLength ?? MaxLength; + MinLength = schema?.MinLength ?? MinLength; + Pattern = schema?.Pattern ?? Pattern; + MultipleOf = schema?.MultipleOf ?? MultipleOf; + Default = schema?.Default != null ? new(schema?.Default.Node) : null; + ReadOnly = schema?.ReadOnly ?? ReadOnly; + WriteOnly = schema?.WriteOnly ?? WriteOnly; + AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; + OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; + AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; + Not = schema?.Not != null ? new(schema?.Not) : null; + Required = schema?.Required != null ? new HashSet(schema.Required) : null; + Items = schema?.Items != null ? new(schema?.Items) : null; + MaxItems = schema?.MaxItems ?? MaxItems; + MinItems = schema?.MinItems ?? MinItems; + UniqueItems = schema?.UniqueItems ?? UniqueItems; + Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + MaxProperties = schema?.MaxProperties ?? MaxProperties; + MinProperties = schema?.MinProperties ?? MinProperties; + AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? AdditionalPropertiesAllowed; + AdditionalProperties = schema?.AdditionalProperties != null ? new(schema?.AdditionalProperties) : null; + Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null; + Example = schema?.Example != null ? new(schema?.Example.Node) : null; + Enum = schema?.Enum != null ? new List(schema.Enum) : null; + Nullable = schema?.Nullable ?? Nullable; + ExternalDocs = schema?.ExternalDocs != null ? new(schema?.ExternalDocs) : null; + Deprecated = schema?.Deprecated ?? Deprecated; + Xml = schema?.Xml != null ? new(schema?.Xml) : null; + Extensions = schema?.Extensions != null ? new Dictionary(schema.Extensions) : null; + UnresolvedReference = schema?.UnresolvedReference ?? UnresolvedReference; + Reference = schema?.Reference != null ? new(schema?.Reference) : null; + } } } From 71215ac1d2394f810c313d495a8be9437dc4319c Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Mon, 29 Jul 2024 11:54:30 +0300 Subject: [PATCH 03/27] Add an OpenApiSchema model with all known keywords --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index f924a8aaa..b974d6148 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -1,40 +1,80 @@ using System.Collections.Generic; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; namespace Microsoft.OpenApi.Models { - internal class OpenApiSchema + /// + /// The Schema Object allows the definition of input and output data types. + /// + public class OpenApiSchema : IOpenApiExtensible { /// /// Follow JSON Schema definition. Short text providing information about the data. /// public string Title { get; set; } + /// + /// $schema, a JSON Schema dialect identifier. Value must be a URI + /// public string Schema { get; set; } + /// + /// $id - Identifies a schema resource with its canonical URI. + /// public string Id { get; set; } + /// + /// $comment - reserves a location for comments from schema authors to readers or maintainers of the schema. + /// public string Comment { get; set; } + /// + /// $vocabulary- used in meta-schemas to identify the vocabularies available for use in schemas described by that meta-schema. + /// public string Vocabulary { get; set; } + /// + /// $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 + /// public string DynamicRef { get; set; } + /// + /// $dynamicAnchor - used to create plain name fragments that are not tied to any particular structural location for referencing purposes, which are taken into consideration for dynamic referencing. + /// public string DynamicAnchor { get; set; } + /// + /// $recursiveAnchor - used to construct recursive schemas i.e one that has a reference to its own root, identified by the empty fragment URI reference ("#") + /// public string RecursiveAnchor { get; set; } + /// + /// $recursiveRef - used to construct recursive schemas i.e one that has a reference to its own root, identified by the empty fragment URI reference ("#") + /// public string RecursiveRef { get; set; } + /// + /// $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 + /// public IDictionary Definitions { get; set; } - public bool UnevaluatedProperties { get; set; } - + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// public decimal V31ExclusiveMaximum { get; set; } + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// public decimal V31ExclusiveMinimum { get; set; } + /// + /// + /// + public bool UnEvaluatedProperties { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 @@ -225,13 +265,18 @@ internal class OpenApiSchema /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public IList Enum { get; set; } = new List(); + public IList Enum { get; set; } = new List(); /// /// Allows sending a null value for the defined schema. Default value is false. /// public bool Nullable { get; set; } + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public bool UnevaluatedProperties { get; set;} + /// /// Additional external documentation for this schema. /// @@ -317,7 +362,7 @@ public OpenApiSchema(OpenApiSchema schema) AdditionalProperties = schema?.AdditionalProperties != null ? new(schema?.AdditionalProperties) : null; Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null; Example = schema?.Example != null ? new(schema?.Example.Node) : null; - Enum = schema?.Enum != null ? new List(schema.Enum) : null; + Enum = schema?.Enum != null ? new List(schema.Enum) : null; Nullable = schema?.Nullable ?? Nullable; ExternalDocs = schema?.ExternalDocs != null ? new(schema?.ExternalDocs) : null; Deprecated = schema?.Deprecated ?? Deprecated; From 3000226a93dd506a8e0d745d587c19ab0c27b997 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 01:48:46 +0300 Subject: [PATCH 04/27] Add V2 schema deserializer --- .../Reader/V2/OpenApiSchemaDeserializer.cs | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs new file mode 100644 index 000000000..b3d49a9d4 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V2 +{ + /// + /// Class containing logic to deserialize Open API V2 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV2Deserializer + { + private static readonly FixedFieldMap _schemaFixedFields = new() + { + { + "title", + (o, n) => o.Title = n.GetScalarValue() + }, + { + "multipleOf", + (o, n) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + }, + { + "maximum", + (o, n) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "exclusiveMaximum", + (o, n) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) + }, + { + "minimum", + (o, n) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + }, + { + "exclusiveMinimum", + (o, n) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) + }, + { + "maxLength", + (o, n) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minLength", + (o, n) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "pattern", + (o, n) => o.Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minItems", + (o, n) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "uniqueItems", + (o, n) => o.UniqueItems = bool.Parse(n.GetScalarValue()) + }, + { + "maxProperties", + (o, n) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minProperties", + (o, n) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "required", + (o, n) => o.Required = new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue())) + }, + { + "enum", + (o, n) => o.Enum = n.CreateListOfAny() + }, + + { + "type", + (o, n) => o.Type = n.GetScalarValue() + }, + { + "allOf", + (o, n) => o.AllOf = n.CreateList(LoadSchema) + }, + { + "items", + (o, n) => o.Items = LoadSchema(n) + }, + { + "properties", + (o, n) => o.Properties = n.CreateMap(LoadSchema) + }, + { + "additionalProperties", (o, n) => + { + if (n is ValueNode) + { + o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); + } + else + { + o.AdditionalProperties = LoadSchema(n); + } + } + }, + { + "description", + (o, n) => o.Description = n.GetScalarValue() + }, + { + "format", + (o, n) => o.Format = n.GetScalarValue() + }, + { + "default", + (o, n) => o.Default = n.CreateAny() + }, + { + "discriminator", (o, n) => + { + o.Discriminator = new() + { + PropertyName = n.GetScalarValue() + }; + } + }, + { + "readOnly", + (o, n) => o.ReadOnly = bool.Parse(n.GetScalarValue()) + }, + { + "xml", + (o, n) => o.Xml = LoadXml(n) + }, + { + "externalDocs", + (o, n) => o.ExternalDocs = LoadExternalDocs(n) + }, + { + "example", + (o, n) => o.Example = n.CreateAny() + }, + }; + + private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiSchema LoadSchema(ParseNode node) + { + var mapNode = node.CheckMapNode("schema"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + return mapNode.GetReferencedObject(ReferenceType.Schema, pointer); + } + + var schema = new OpenApiSchema(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schema, _schemaFixedFields, _schemaPatternFields); + } + + return schema; + } + + private static Dictionary LoadExtensions(string value, IOpenApiExtension extension) + { + var extensions = new Dictionary + { + { value, extension } + }; + return extensions; + } + } +} From 5d3580c6e8d352b11ff1cab80f122ccd172517a7 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 12:35:29 +0300 Subject: [PATCH 05/27] Add v31 schema property names as constants --- .../Models/OpenApiConstants.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 90d5c545b..8ed048427 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -60,6 +60,66 @@ public static class OpenApiConstants /// public const string Format = "format"; + /// + /// Field: Schema + /// + public const string DollarSchema = "$schema"; + + /// + /// Field: Id + /// + public const string Id = "$id"; + + /// + /// Field: Comment + /// + public const string Comment = "$comment"; + + /// + /// Field: Vocabulary + /// + public const string Vocabulary = "$vocabulary"; + + /// + /// Field: DynamicRef + /// + public const string DynamicRef = "$dynamicRef"; + + /// + /// Field: DynamicAnchor + /// + public const string DynamicAnchor = "$dynamicAnchor"; + + /// + /// Field: RecursiveRef + /// + public const string RecursiveRef = "$recursiveRef"; + + /// + /// Field: RecursiveAnchor + /// + public const string RecursiveAnchor = "$recursiveAnchor"; + + /// + /// Field: Definitions + /// + public const string Defs = "$defs"; + + /// + /// Field: V31ExclusiveMaximum + /// + public const string V31ExclusiveMaximum = "exclusiveMaximum"; + + /// + /// Field: V31ExclusiveMinimum + /// + public const string V31ExclusiveMinimum = "exclusiveMinimum"; + + /// + /// Field: UnevaluatedProperties + /// + public const string UnevaluatedProperties = "unevaluatedProperties"; + /// /// Field: Version /// From 77616513119d27045ab57d61014f5e1a24fabd3a Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 12:36:36 +0300 Subject: [PATCH 06/27] Add license info; implement IOpenApiReferenceable interface --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index b974d6148..d24b69220 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -1,14 +1,20 @@ -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; namespace Microsoft.OpenApi.Models { /// /// The Schema Object allows the definition of input and output data types. /// - public class OpenApiSchema : IOpenApiExtensible + public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable { /// /// Follow JSON Schema definition. Short text providing information about the data. From 6bb2546ca33929a59098245bae331849013f38a6 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 12:36:59 +0300 Subject: [PATCH 07/27] Add serialization logic --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 380 +++++++++++++++++- 1 file changed, 379 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index d24b69220..0f8eaef7f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -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; @@ -377,5 +377,383 @@ public OpenApiSchema(OpenApiSchema schema) UnresolvedReference = schema?.UnresolvedReference ?? UnresolvedReference; Reference = schema?.Reference != null ? new(schema?.Reference) : null; } + + /// + /// Serialize to Open Api v3.1 + /// + public virtual void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), + (writer, element) => element.SerializeAsV31WithoutReference(writer)); + } + + /// + /// Serialize to Open Api v3.0 + /// + public virtual void SerializeAsV3(IOpenApiWriter writer) + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + (writer, element) => element.SerializeAsV3WithoutReference(writer)); + } + + private void SerializeInternal(IOpenApiWriter writer, Action callback, + Action action) + { + Utils.CheckArgumentNull(writer); + var target = this; + action(writer, target); + } + + /// + /// Serialize to OpenAPI V3 document without using reference. + /// + public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) + { + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + (writer, element) => element.SerializeAsV31(writer)); + } + + /// + /// Serialize to OpenAPI V3 document without using reference. + /// + public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) + { + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + (writer, element) => element.SerializeAsV3(writer)); + } + +/// + + public void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) + { + writer.WriteStartObject(); + + if (version == OpenApiSpecVersion.OpenApi3_1) + { + WriteV31Properties(writer); + } + + // title + writer.WriteProperty(OpenApiConstants.Title, Title); + + // multipleOf + writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf); + + // maximum + writer.WriteProperty(OpenApiConstants.Maximum, Maximum); + + // exclusiveMaximum + writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum); + + // minimum + writer.WriteProperty(OpenApiConstants.Minimum, Minimum); + + // exclusiveMinimum + writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum); + + // maxLength + writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength); + + // minLength + writer.WriteProperty(OpenApiConstants.MinLength, MinLength); + + // pattern + writer.WriteProperty(OpenApiConstants.Pattern, Pattern); + + // maxItems + writer.WriteProperty(OpenApiConstants.MaxItems, MaxItems); + + // minItems + writer.WriteProperty(OpenApiConstants.MinItems, MinItems); + + // uniqueItems + writer.WriteProperty(OpenApiConstants.UniqueItems, UniqueItems); + + // maxProperties + writer.WriteProperty(OpenApiConstants.MaxProperties, MaxProperties); + + // minProperties + writer.WriteProperty(OpenApiConstants.MinProperties, MinProperties); + + // required + writer.WriteOptionalCollection(OpenApiConstants.Required, Required, (w, s) => w.WriteValue(s)); + + // enum + writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (nodeWriter, s) => nodeWriter.WriteAny(new OpenApiAny(s))); + + // type + writer.WriteProperty(OpenApiConstants.Type, Type); + + // allOf + writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV3(w)); + + // anyOf + writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, (w, s) => s.SerializeAsV3(w)); + + // oneOf + writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, (w, s) => s.SerializeAsV3(w)); + + // not + writer.WriteOptionalObject(OpenApiConstants.Not, Not, (w, s) => s.SerializeAsV3(w)); + + // items + writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV3(w)); + + // properties + writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, s) => s.SerializeAsV3(w)); + + // additionalProperties + if (AdditionalPropertiesAllowed) + { + writer.WriteOptionalObject( + OpenApiConstants.AdditionalProperties, + AdditionalProperties, + (w, s) => s.SerializeAsV3(w)); + } + else + { + writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); + } + + // description + writer.WriteProperty(OpenApiConstants.Description, Description); + + // format + writer.WriteProperty(OpenApiConstants.Format, Format); + + // default + writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); + + // nullable + writer.WriteProperty(OpenApiConstants.Nullable, Nullable, false); + + // discriminator + writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, (w, s) => s.SerializeAsV3(w)); + + // readOnly + writer.WriteProperty(OpenApiConstants.ReadOnly, ReadOnly, false); + + // writeOnly + writer.WriteProperty(OpenApiConstants.WriteOnly, WriteOnly, false); + + // xml + writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, (w, s) => s.SerializeAsV2(w)); + + // externalDocs + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV3(w)); + + // example + writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); + + // deprecated + writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); + + // extensions + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + + writer.WriteEndObject(); + } + +/// + + public void SerializeAsV2WithoutReference(IOpenApiWriter writer) + { + SerializeAsV2WithoutReference( + writer: writer, + parentRequiredProperties: new HashSet(), + propertyName: null); + } + +/// + + public void SerializeAsV2(IOpenApiWriter writer) + { + SerializeAsV2(writer: writer, parentRequiredProperties: new HashSet(), propertyName: null); + } + + internal void WriteV31Properties(IOpenApiWriter writer) + { + writer.WriteProperty(OpenApiConstants.DollarSchema, Schema); + writer.WriteProperty(OpenApiConstants.Id, Id); + writer.WriteProperty(OpenApiConstants.Comment, Comment); + writer.WriteProperty(OpenApiConstants.Vocabulary, Vocabulary); + writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV3(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); + } + + /// + /// Serialize to Open Api v2.0 and handles not marking the provided property + /// as readonly if its included in the provided list of required properties of parent schema. + /// + /// The open api writer. + /// The list of required properties in parent schema. + /// The property name that will be serialized. + internal void SerializeAsV2( + IOpenApiWriter writer, + ISet parentRequiredProperties, + string propertyName) + { + var target = this; + parentRequiredProperties ??= new HashSet(); + + target.SerializeAsV2WithoutReference(writer, parentRequiredProperties, propertyName); + } + + /// + /// Serialize to OpenAPI V2 document without using reference and handles not marking the provided property + /// as readonly if its included in the provided list of required properties of parent schema. + /// + /// The open api writer. + /// The list of required properties in parent schema. + /// The property name that will be serialized. + internal void SerializeAsV2WithoutReference( + IOpenApiWriter writer, + ISet parentRequiredProperties, + string propertyName) + { + writer.WriteStartObject(); + WriteAsSchemaProperties(writer, parentRequiredProperties, propertyName); + writer.WriteEndObject(); + } + + internal void WriteAsSchemaProperties( + IOpenApiWriter writer, + ISet parentRequiredProperties, + string propertyName) + { + // format + if (string.IsNullOrEmpty(Format)) + { + Format = AllOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format ?? + AnyOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format ?? + OneOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format; + } + + writer.WriteProperty(OpenApiConstants.Format, Format); + + // title + writer.WriteProperty(OpenApiConstants.Title, Title); + + // description + writer.WriteProperty(OpenApiConstants.Description, Description); + + // default + writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); + + // multipleOf + writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf); + + // maximum + writer.WriteProperty(OpenApiConstants.Maximum, Maximum); + + // exclusiveMaximum + writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum); + + // minimum + writer.WriteProperty(OpenApiConstants.Minimum, Minimum); + + // exclusiveMinimum + writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum); + + // maxLength + writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength); + + // minLength + writer.WriteProperty(OpenApiConstants.MinLength, MinLength); + + // pattern + writer.WriteProperty(OpenApiConstants.Pattern, Pattern); + + // maxItems + writer.WriteProperty(OpenApiConstants.MaxItems, MaxItems); + + // minItems + writer.WriteProperty(OpenApiConstants.MinItems, MinItems); + + // uniqueItems + writer.WriteProperty(OpenApiConstants.UniqueItems, UniqueItems); + + // maxProperties + writer.WriteProperty(OpenApiConstants.MaxProperties, MaxProperties); + + // minProperties + writer.WriteProperty(OpenApiConstants.MinProperties, MinProperties); + + // required + writer.WriteOptionalCollection(OpenApiConstants.Required, Required, (w, s) => w.WriteValue(s)); + + // enum + writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, s) => w.WriteAny(new OpenApiAny(s))); + + // type + writer.WriteProperty(OpenApiConstants.Type, Type); + + // items + writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV2(w)); + + // allOf + writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV2(w)); + + // If there isn't already an allOf, and the schema contains a oneOf or anyOf write an allOf with the first + // schema in the list as an attempt to guess at a graceful downgrade situation. + if (AllOf == null || AllOf.Count == 0) + { + // anyOf (Not Supported in V2) - Write the first schema only as an allOf. + writer.WriteOptionalCollection(OpenApiConstants.AllOf, AnyOf?.Take(1), (w, s) => s.SerializeAsV2(w)); + + if (AnyOf == null || AnyOf.Count == 0) + { + // oneOf (Not Supported in V2) - Write the first schema only as an allOf. + writer.WriteOptionalCollection(OpenApiConstants.AllOf, OneOf?.Take(1), (w, s) => s.SerializeAsV2(w)); + } + } + + // properties + writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, key, s) => + s.SerializeAsV2(w, Required, key)); + + // additionalProperties + if (AdditionalPropertiesAllowed) + { + writer.WriteOptionalObject( + OpenApiConstants.AdditionalProperties, + AdditionalProperties, + (w, s) => s.SerializeAsV2(w)); + } + else + { + writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); + } + + // discriminator + writer.WriteProperty(OpenApiConstants.Discriminator, Discriminator?.PropertyName); + + // readOnly + // In V2 schema if a property is part of required properties of parent schema, + // it cannot be marked as readonly. + if (!parentRequiredProperties.Contains(propertyName)) + { + writer.WriteProperty(name: OpenApiConstants.ReadOnly, value: ReadOnly, defaultValue: false); + } + + // xml + writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, (w, s) => s.SerializeAsV2(w)); + + // externalDocs + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w)); + + // example + writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); + + // extensions + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); + } } } From f76fddf2a1b50c100bfdc54d0a6b6f60ab2d575d Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 12:57:37 +0300 Subject: [PATCH 08/27] clean up file --- .../Reader/V2/OpenApiSchemaDeserializer.cs | 80 ++++++++----------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs index b3d49a9d4..d606b6af5 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; -using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Reader.ParseNodes; @@ -17,91 +16,91 @@ namespace Microsoft.OpenApi.Reader.V2 /// internal static partial class OpenApiV2Deserializer { - private static readonly FixedFieldMap _schemaFixedFields = new() + private static readonly FixedFieldMap _openApiSchemaFixedFields = new() { { "title", - (o, n) => o.Title = n.GetScalarValue() + (o, n, _) => o.Title = n.GetScalarValue() }, { "multipleOf", - (o, n) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + (o, n, _) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) }, { "maximum", - (o, n) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + (o, n,_) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) }, { "exclusiveMaximum", - (o, n) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) }, { "minimum", - (o, n) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + (o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) }, { "exclusiveMinimum", - (o, n) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) }, { "maxLength", - (o, n) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, _) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "minLength", - (o, n) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, _) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "pattern", - (o, n) => o.Pattern = n.GetScalarValue() + (o, n, _) => o.Pattern = n.GetScalarValue() }, { "maxItems", - (o, n) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, _) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "minItems", - (o, n) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, _) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "uniqueItems", - (o, n) => o.UniqueItems = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.UniqueItems = bool.Parse(n.GetScalarValue()) }, { "maxProperties", - (o, n) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, _) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "minProperties", - (o, n) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, _) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "required", - (o, n) => o.Required = new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue())) + (o, n, _) => o.Required = new HashSet(n.CreateSimpleList((n2, p) => n2.GetScalarValue())) }, { "enum", - (o, n) => o.Enum = n.CreateListOfAny() + (o, n, _) => o.Enum = n.CreateListOfAny() }, { "type", - (o, n) => o.Type = n.GetScalarValue() + (o, n, _) => o.Type = n.GetScalarValue() }, { "allOf", - (o, n) => o.AllOf = n.CreateList(LoadSchema) + (o, n, t) => o.AllOf = n.CreateList(LoadOpenApiSchema, t) }, { "items", - (o, n) => o.Items = LoadSchema(n) + (o, n, _) => o.Items = LoadOpenApiSchema(n) }, { "properties", - (o, n) => o.Properties = n.CreateMap(LoadSchema) + (o, n, t) => o.Properties = n.CreateMap(LoadOpenApiSchema, t) }, { - "additionalProperties", (o, n) => + "additionalProperties", (o, n, _) => { if (n is ValueNode) { @@ -109,24 +108,24 @@ internal static partial class OpenApiV2Deserializer } else { - o.AdditionalProperties = LoadSchema(n); + o.AdditionalProperties = LoadOpenApiSchema(n); } } }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "format", - (o, n) => o.Format = n.GetScalarValue() + (o, n, _) => o.Format = n.GetScalarValue() }, { "default", - (o, n) => o.Default = n.CreateAny() + (o, n, _) => o.Default = n.CreateAny() }, { - "discriminator", (o, n) => + "discriminator", (o, n, _) => { o.Discriminator = new() { @@ -136,28 +135,28 @@ internal static partial class OpenApiV2Deserializer }, { "readOnly", - (o, n) => o.ReadOnly = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.ReadOnly = bool.Parse(n.GetScalarValue()) }, { "xml", - (o, n) => o.Xml = LoadXml(n) + (o, n, _) => o.Xml = LoadXml(n) }, { "externalDocs", - (o, n) => o.ExternalDocs = LoadExternalDocs(n) + (o, n, _) => o.ExternalDocs = LoadExternalDocs(n) }, { "example", - (o, n) => o.Example = n.CreateAny() + (o, n, _) => o.Example = n.CreateAny() }, }; - private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap + private static readonly PatternFieldMap _openApiSchemaPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiSchema LoadSchema(ParseNode node) + public static OpenApiSchema LoadOpenApiSchema(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("schema"); @@ -171,19 +170,10 @@ public static OpenApiSchema LoadSchema(ParseNode node) foreach (var propertyNode in mapNode) { - propertyNode.ParseField(schema, _schemaFixedFields, _schemaPatternFields); + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); } return schema; } - - private static Dictionary LoadExtensions(string value, IOpenApiExtension extension) - { - var extensions = new Dictionary - { - { value, extension } - }; - return extensions; - } } } From 5cfce6e17a8a6e0f04994996244d40a4ed9974a4 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 12:57:55 +0300 Subject: [PATCH 09/27] Add a V3 schema deserializer --- .../Reader/V3/OpenApiSchemaDeserializer.cs | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs new file mode 100644 index 000000000..268cf636d --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Readers.ParseNodes; +using System.Collections.Generic; +using System.Globalization; + +namespace Microsoft.OpenApi.Reader.V3 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV3Deserializer + { + private static readonly FixedFieldMap _openApiSchemaFixedFields = new() + { + { + "title", + (o, n, _) => o.Title = n.GetScalarValue() + }, + { + "multipleOf", + (o, n, _) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + }, + { + "maximum", + (o, n, _) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "exclusiveMaximum", + (o, n, _) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) + }, + { + "minimum", + (o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + }, + { + "exclusiveMinimum", + (o, n, _) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) + }, + { + "maxLength", + (o, n, _) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minLength", + (o, n, _) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "pattern", + (o, n, _) => o.Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n, _) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minItems", + (o, n, _) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "uniqueItems", + (o, n, _) => o.UniqueItems = bool.Parse(n.GetScalarValue()) + }, + { + "maxProperties", + (o, n, _) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minProperties", + (o, n, _) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "required", + (o, n, _) => o.Required = new HashSet(n.CreateSimpleList((n2, p) => n2.GetScalarValue())) + }, + { + "enum", + (o, n, _) => o.Enum = n.CreateListOfAny() + }, + { + "type", + (o, n, _) => o.Type = n.GetScalarValue() + }, + { + "allOf", + (o, n, t) => o.AllOf = n.CreateList(LoadOpenApiSchema, t) + }, + { + "oneOf", + (o, n, _) => o.OneOf = n.CreateList(LoadOpenApiSchema) + }, + { + "anyOf", + (o, n, t) => o.AnyOf = n.CreateList(LoadOpenApiSchema, t) + }, + { + "not", + (o, n, _) => o.Not = LoadOpenApiSchema(n) + }, + { + "items", + (o, n, _) => o.Items = LoadOpenApiSchema(n) + }, + { + "properties", + (o, n, t) => o.Properties = n.CreateMap(LoadOpenApiSchema, t) + }, + { + "additionalProperties", (o, n, _) => + { + if (n is ValueNode) + { + o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); + } + else + { + o.AdditionalProperties = LoadOpenApiSchema(n); + } + } + }, + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + { + "format", + (o, n, _) => o.Format = n.GetScalarValue() + }, + { + "default", + (o, n, _) => o.Default = n.CreateAny() + }, + { + "nullable", + (o, n, _) => o.Nullable = bool.Parse(n.GetScalarValue()) + }, + { + "discriminator", + (o, n, _) => o.Discriminator = LoadDiscriminator(n) + }, + { + "readOnly", + (o, n, _) => o.ReadOnly = bool.Parse(n.GetScalarValue()) + }, + { + "writeOnly", + (o, n, _) => o.WriteOnly = bool.Parse(n.GetScalarValue()) + }, + { + "xml", + (o, n, _) => o.Xml = LoadXml(n) + }, + { + "externalDocs", + (o, n, _) => o.ExternalDocs = LoadExternalDocs(n) + }, + { + "example", + (o, n, _) => o.Example = n.CreateAny() + }, + { + "deprecated", + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) + }, + }; + + private static readonly PatternFieldMap _openApiSchemaPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiSchema LoadOpenApiSchema(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + + var pointer = mapNode.GetReferencePointer(); + + if (pointer != null) + { + return new() + { + UnresolvedReference = true, + Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.Schema) + }; + } + + var schema = new OpenApiSchema(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); + } + + return schema; + } + } +} From a124aa2cc868ed939c5a26e03e44c06354dee6f8 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 12:58:22 +0300 Subject: [PATCH 10/27] Make the host document an optional param --- src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs index 50b0321c7..54c584df2 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs @@ -212,7 +212,7 @@ public static OpenApiParameter LoadParameter(ParseNode node, OpenApiDocument hos return LoadParameter(node, false, hostDocument); } - public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBody, OpenApiDocument hostDocument) + public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBody, OpenApiDocument hostDocument = null) { // Reset the local variables every time this method is called. node.Context.SetTempStorage(TempStorageKeys.ParameterIsBodyOrFormData, false); From 06000026e5a88ea4cea7673caf7644bc45565076 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 15:30:00 +0300 Subject: [PATCH 11/27] Serialize type array in v31 --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 0f8eaef7f..84c7a73cc 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -339,6 +339,7 @@ public OpenApiSchema(OpenApiSchema schema) V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum; V31ExclusiveMinimum = schema?.V31ExclusiveMinimum ?? V31ExclusiveMinimum; Type = schema?.Type ?? Type; + TypeArray = schema.TypeArray != null ? new string[schema.TypeArray.Length] : null; Format = schema?.Format ?? Format; Description = schema?.Description ?? Description; Maximum = schema?.Maximum ?? Maximum; @@ -575,6 +576,7 @@ public void SerializeAsV2(IOpenApiWriter writer) internal void WriteV31Properties(IOpenApiWriter writer) { writer.WriteProperty(OpenApiConstants.DollarSchema, Schema); + writer.WriteOptionalCollection(OpenApiConstants.TypeArray, TypeArray, (w, s) => w.WriteRaw(s)); writer.WriteProperty(OpenApiConstants.Id, Id); writer.WriteProperty(OpenApiConstants.Comment, Comment); writer.WriteProperty(OpenApiConstants.Vocabulary, Vocabulary); From 902bd62e214c38db14f4be71eaaf089af34cba2d Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 15:30:18 +0300 Subject: [PATCH 12/27] Update namespace --- src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs index 9dd05ebdd..030572f68 100644 --- a/src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { /// /// Useful tools to parse data From a59ba31f6b7baaa0a92bff57211962aed7860bd4 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 15:30:41 +0300 Subject: [PATCH 13/27] Remove unnecessary usings --- src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs | 1 - src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs index d606b6af5..868ea2d32 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs @@ -6,7 +6,6 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Reader.ParseNodes; -using Microsoft.OpenApi.Readers.ParseNodes; namespace Microsoft.OpenApi.Reader.V2 { diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 268cf636d..51b427321 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -4,7 +4,6 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Reader.ParseNodes; -using Microsoft.OpenApi.Readers.ParseNodes; using System.Collections.Generic; using System.Globalization; From 902936e728b9ce338fef87a8c36bd4dacae2b3d3 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 15:55:06 +0300 Subject: [PATCH 14/27] Add a v31 schema deserializer --- .../Reader/V31/OpenApiSchemaDeserializer.cs | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs new file mode 100644 index 000000000..d46c94004 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; +using System.Collections.Generic; +using System.Globalization; + +namespace Microsoft.OpenApi.Reader.V31 +{ + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _openApiSchemaFixedFields = new() + { + { + "title", + (o, n, _) => o.Title = n.GetScalarValue() + }, + { + "$schema", + (o, n, _) => o.Schema = n.GetScalarValue() + }, + { + "$id", + (o, n, _) => o.Id = n.GetScalarValue() + }, + { + "$comment", + (o, n, _) => o.Comment = n.GetScalarValue() + }, + { + "$vocabulary", + (o, n, _) => o.Vocabulary = n.GetScalarValue() + }, + { + "$dynamicRef", + (o, n, _) => o.DynamicRef = n.GetScalarValue() + }, + { + "$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(LoadOpenApiSchema, t) + }, + { + "multipleOf", + (o, n, _) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + }, + { + "maximum", + (o, n, _) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "exclusiveMaximum", + (o, n, _) => o.V31ExclusiveMaximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "minimum", + (o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + }, + { + "exclusiveMinimum", + (o, n, _) => o.V31ExclusiveMinimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "maxLength", + (o, n, _) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minLength", + (o, n, _) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "pattern", + (o, n, _) => o.Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n, _) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minItems", + (o, n, _) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "uniqueItems", + (o, n, _) => o.UniqueItems = bool.Parse(n.GetScalarValue()) + }, + { + "unevaluatedProperties", + (o, n, _) => o.UnevaluatedProperties = bool.Parse(n.GetScalarValue()) + }, + { + "maxProperties", + (o, n, _) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minProperties", + (o, n, _) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "required", + (o, n, _) => o.Required = new HashSet(n.CreateSimpleList((n2, p) => n2.GetScalarValue())) + }, + { + "enum", + (o, n, _) => o.Enum = n.CreateListOfAny() + }, + { + "type", + (o, n, _) => o.TypeArray = n.CreateSimpleList((n2, p) => n2.GetScalarValue()).ToArray() + + }, + { + "allOf", + (o, n, t) => o.AllOf = n.CreateList(LoadOpenApiSchema, t) + }, + { + "oneOf", + (o, n, t) => o.OneOf = n.CreateList(LoadOpenApiSchema, t) + }, + { + "anyOf", + (o, n, t) => o.AnyOf = n.CreateList(LoadOpenApiSchema, t) + }, + { + "not", + (o, n, _) => o.Not = LoadOpenApiSchema(n) + }, + { + "items", + (o, n, _) => o.Items = LoadOpenApiSchema(n) + }, + { + "properties", + (o, n, t) => o.Properties = n.CreateMap(LoadOpenApiSchema, t) + }, + { + "additionalProperties", (o, n, _) => + { + if (n is ValueNode) + { + o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); + } + else + { + o.AdditionalProperties = LoadOpenApiSchema(n); + } + } + }, + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + { + "format", + (o, n, _) => o.Format = n.GetScalarValue() + }, + { + "default", + (o, n, _) => o.Default = n.CreateAny() + }, + { + "nullable", + (o, n, _) => o.Nullable = bool.Parse(n.GetScalarValue()) + }, + { + "discriminator", + (o, n, _) => o.Discriminator = LoadDiscriminator(n) + }, + { + "readOnly", + (o, n, _) => o.ReadOnly = bool.Parse(n.GetScalarValue()) + }, + { + "writeOnly", + (o, n, _) => o.WriteOnly = bool.Parse(n.GetScalarValue()) + }, + { + "xml", + (o, n, _) => o.Xml = LoadXml(n) + }, + { + "externalDocs", + (o, n, _) => o.ExternalDocs = LoadExternalDocs(n) + }, + { + "example", + (o, n, _) => o.Example = n.CreateAny() + }, + { + "deprecated", + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) + }, + }; + + private static readonly PatternFieldMap _openApiSchemaPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiSchema LoadOpenApiSchema(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + + var pointer = mapNode.GetReferencePointer(); + + if (pointer != null) + { + return new() + { + UnresolvedReference = true, + Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.Schema) + }; + } + + var schema = new OpenApiSchema(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); + } + + return schema; + } + } +} From 266896bd32eaac76c22e0aa1b19f618c819ffbc8 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 16:05:34 +0300 Subject: [PATCH 15/27] Update import, code cleanup --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 2 +- .../ParseNodes/ParserHelperTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 84c7a73cc..9d5e79288 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -576,7 +576,7 @@ public void SerializeAsV2(IOpenApiWriter writer) internal void WriteV31Properties(IOpenApiWriter writer) { writer.WriteProperty(OpenApiConstants.DollarSchema, Schema); - writer.WriteOptionalCollection(OpenApiConstants.TypeArray, TypeArray, (w, s) => w.WriteRaw(s)); + writer.WriteOptionalCollection(OpenApiConstants.Type, TypeArray, (w, s) => w.WriteRaw(s)); writer.WriteProperty(OpenApiConstants.Id, Id); writer.WriteProperty(OpenApiConstants.Comment, Comment); writer.WriteProperty(OpenApiConstants.Vocabulary, Vocabulary); diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs index 1368e103d..4e3500d6b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System.Globalization; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.ParseNodes From 536218cc92c62b09068ef29571ca1a2939768971 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 16:16:52 +0300 Subject: [PATCH 16/27] Add null conditional operator --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 9d5e79288..0c7df07b5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -339,7 +339,7 @@ public OpenApiSchema(OpenApiSchema schema) V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum; V31ExclusiveMinimum = schema?.V31ExclusiveMinimum ?? V31ExclusiveMinimum; Type = schema?.Type ?? Type; - TypeArray = schema.TypeArray != null ? new string[schema.TypeArray.Length] : null; + TypeArray = schema?.TypeArray != null ? new string[schema.TypeArray.Length] : null; Format = schema?.Format ?? Format; Description = schema?.Description ?? Description; Maximum = schema?.Maximum ?? Maximum; From 2ffebe7f9bb9dad71e3a84bc2f4cc62d6df3a441 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 30 Jul 2024 16:19:38 +0300 Subject: [PATCH 17/27] Update public API interface --- .../PublicApi/PublicApi.approved.txt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 7e0730600..1bc477c5a 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -459,6 +459,7 @@ namespace Microsoft.OpenApi.Models public const string BodyName = "x-bodyName"; public const string Callbacks = "callbacks"; public const string ClientCredentials = "clientCredentials"; + public const string Comment = "$comment"; public const string Components = "components"; public const string ComponentsSegment = "/components/"; public const string Consumes = "consumes"; @@ -471,11 +472,15 @@ namespace Microsoft.OpenApi.Models public const string DefaultName = "Default Name"; public const string DefaultTitle = "Default Title"; public const string Definitions = "definitions"; + public const string Defs = "$defs"; public const string Delete = "delete"; public const string Deprecated = "deprecated"; public const string Description = "description"; public const string Discriminator = "discriminator"; public const string DollarRef = "$ref"; + public const string DollarSchema = "$schema"; + public const string DynamicAnchor = "$dynamicAnchor"; + public const string DynamicRef = "$dynamicRef"; public const string Email = "email"; public const string Encoding = "encoding"; public const string Enum = "enum"; @@ -495,6 +500,7 @@ namespace Microsoft.OpenApi.Models public const string Head = "head"; public const string Headers = "headers"; public const string Host = "host"; + public const string Id = "$id"; public const string Identifier = "identifier"; public const string Implicit = "implicit"; public const string In = "in"; @@ -539,6 +545,8 @@ namespace Microsoft.OpenApi.Models public const string PropertyName = "propertyName"; public const string Put = "put"; public const string ReadOnly = "readOnly"; + public const string RecursiveAnchor = "$recursiveAnchor"; + public const string RecursiveRef = "$recursiveRef"; public const string RefreshUrl = "refreshUrl"; public const string RequestBodies = "requestBodies"; public const string RequestBody = "requestBody"; @@ -563,13 +571,17 @@ namespace Microsoft.OpenApi.Models public const string TokenUrl = "tokenUrl"; public const string Trace = "trace"; public const string Type = "type"; + public const string UnevaluatedProperties = "unevaluatedProperties"; public const string UniqueItems = "uniqueItems"; public const string Url = "url"; public const string V2ReferenceUri = "https://registry/definitions/"; + public const string V31ExclusiveMaximum = "exclusiveMaximum"; + public const string V31ExclusiveMinimum = "exclusiveMinimum"; public const string V3ReferenceUri = "https://registry/components/schemas/"; public const string Value = "value"; public const string Variables = "variables"; public const string Version = "version"; + public const string Vocabulary = "$vocabulary"; public const string Webhooks = "webhooks"; public const string Wrapped = "wrapped"; public const string WriteOnly = "writeOnly"; @@ -945,6 +957,71 @@ namespace Microsoft.OpenApi.Models public OpenApiResponses() { } public OpenApiResponses(Microsoft.OpenApi.Models.OpenApiResponses openApiResponses) { } } + public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + { + public OpenApiSchema() { } + public OpenApiSchema(Microsoft.OpenApi.Models.OpenApiSchema schema) { } + public Microsoft.OpenApi.Models.OpenApiSchema AdditionalProperties { get; set; } + public bool AdditionalPropertiesAllowed { get; set; } + public System.Collections.Generic.IList AllOf { get; set; } + public System.Collections.Generic.IList AnyOf { get; set; } + public string Comment { get; set; } + public Microsoft.OpenApi.Any.OpenApiAny Default { get; set; } + public System.Collections.Generic.IDictionary Definitions { get; set; } + public bool Deprecated { get; set; } + public string Description { get; set; } + public Microsoft.OpenApi.Models.OpenApiDiscriminator Discriminator { get; set; } + public string DynamicAnchor { get; set; } + public string DynamicRef { get; set; } + public System.Collections.Generic.IList Enum { get; set; } + public Microsoft.OpenApi.Any.OpenApiAny Example { get; set; } + public bool? ExclusiveMaximum { get; set; } + public bool? ExclusiveMinimum { get; set; } + public System.Collections.Generic.IDictionary Extensions { get; set; } + public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } + public string Format { get; set; } + public string Id { get; set; } + public Microsoft.OpenApi.Models.OpenApiSchema Items { get; set; } + public int? MaxItems { get; set; } + public int? MaxLength { get; set; } + public int? MaxProperties { get; set; } + public decimal? Maximum { get; set; } + public int? MinItems { get; set; } + public int? MinLength { get; set; } + public int? MinProperties { get; set; } + public decimal? Minimum { get; set; } + public decimal? MultipleOf { get; set; } + public Microsoft.OpenApi.Models.OpenApiSchema Not { get; set; } + public bool Nullable { get; set; } + public System.Collections.Generic.IList OneOf { get; set; } + public string Pattern { get; set; } + public System.Collections.Generic.IDictionary Properties { get; set; } + public bool ReadOnly { get; set; } + public string RecursiveAnchor { get; set; } + public string RecursiveRef { get; set; } + public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } + public System.Collections.Generic.ISet Required { get; set; } + public string Schema { get; set; } + public string Title { get; set; } + public string Type { get; set; } + public string[] TypeArray { get; set; } + public bool UnEvaluatedProperties { get; set; } + public bool UnevaluatedProperties { get; set; } + public bool? UniqueItems { get; set; } + public bool UnresolvedReference { get; set; } + public decimal V31ExclusiveMaximum { get; set; } + public decimal V31ExclusiveMinimum { get; set; } + public string Vocabulary { get; set; } + public bool WriteOnly { get; set; } + public Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; } + public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeInternalWithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion version, System.Action callback) { } + } public class OpenApiSecurityRequirement : System.Collections.Generic.Dictionary>, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiSecurityRequirement() { } From 81b41e97b56a0521afd525afafad9feed97e156b Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 7 Aug 2024 11:58:11 +0300 Subject: [PATCH 18/27] Write string value for Type for V2 and V3, allow type array for V3 --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 0c7df07b5..9cf294b05 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -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; @@ -14,8 +14,11 @@ namespace Microsoft.OpenApi.Models /// /// The Schema Object allows the definition of input and output data types. /// - public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable + public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable, IOpenApiSerializable { + private string[] _typeArray; + private string _type; + /// /// Follow JSON Schema definition. Short text providing information about the data. /// @@ -86,13 +89,21 @@ public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Value MUST be a string in V2 and V3. /// - public string Type { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// Multiple types via an array are supported in V31. - /// - public string[] TypeArray { get; set; } + public object Type + { + get => _type; + set + { + if (value is string || value is JsonNode) + { + _type = (string)value; + } + else + { + _typeArray = (string[])value; + } + } + } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 @@ -484,7 +495,14 @@ public void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpec writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (nodeWriter, s) => nodeWriter.WriteAny(new OpenApiAny(s))); // type - writer.WriteProperty(OpenApiConstants.Type, Type); + if (Type.GetType() == typeof(string)) + { + writer.WriteProperty(OpenApiConstants.Type, _type); + } + else + { + writer.WriteOptionalCollection(OpenApiConstants.Type, _typeArray, (w, s) => w.WriteRaw(s)); + } // allOf writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV3(w)); @@ -695,7 +713,7 @@ internal void WriteAsSchemaProperties( writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, s) => w.WriteAny(new OpenApiAny(s))); // type - writer.WriteProperty(OpenApiConstants.Type, Type); + writer.WriteProperty(OpenApiConstants.Type, _type); // items writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV2(w)); From be9e5a2429fd9200ad5a1a34b00305bacadf5da6 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 7 Aug 2024 11:58:56 +0300 Subject: [PATCH 19/27] code cleanup --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 9cf294b05..fa2728a1f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -73,12 +73,12 @@ public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable, IOpenApi /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public decimal V31ExclusiveMaximum { get; set; } + public decimal? V31ExclusiveMaximum { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public decimal V31ExclusiveMinimum { get; set; } + public decimal? V31ExclusiveMinimum { get; set; } /// /// @@ -593,9 +593,8 @@ public void SerializeAsV2(IOpenApiWriter writer) internal void WriteV31Properties(IOpenApiWriter writer) { - writer.WriteProperty(OpenApiConstants.DollarSchema, Schema); - writer.WriteOptionalCollection(OpenApiConstants.Type, TypeArray, (w, s) => w.WriteRaw(s)); 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)); @@ -604,8 +603,8 @@ internal void WriteV31Properties(IOpenApiWriter writer) 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); + writer.WriteProperty(OpenApiConstants.V31ExclusiveMinimum, V31ExclusiveMinimum); + writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false); } /// From 13d5061c7d12bc5ad6884b89a190c5284c5c48b5 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 7 Aug 2024 11:59:33 +0300 Subject: [PATCH 20/27] Add schema loader --- src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs index 202e4e905..5e47f03b6 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs @@ -57,6 +57,7 @@ public OpenApiV31VersionService(OpenApiDiagnostic diagnostic) [typeof(OpenApiResponse)] = OpenApiV31Deserializer.LoadResponse, [typeof(OpenApiResponses)] = OpenApiV31Deserializer.LoadResponses, [typeof(JsonSchema)] = OpenApiV31Deserializer.LoadSchema, + [typeof(OpenApiSchema)] = OpenApiV31Deserializer.LoadOpenApiSchema, [typeof(OpenApiSecurityRequirement)] = OpenApiV31Deserializer.LoadSecurityRequirement, [typeof(OpenApiSecurityScheme)] = OpenApiV31Deserializer.LoadSecurityScheme, [typeof(OpenApiServer)] = OpenApiV31Deserializer.LoadServer, From ee2495f989582c0a09ca3c07d2972fac8b4f5cba Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 7 Aug 2024 12:01:34 +0300 Subject: [PATCH 21/27] if node value is string, get the scalar value, else cast it into an array and assign it to the type property --- .../Reader/V31/OpenApiSchemaDeserializer.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index d46c94004..94c266b15 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -119,8 +119,17 @@ internal static partial class OpenApiV31Deserializer }, { "type", - (o, n, _) => o.TypeArray = n.CreateSimpleList((n2, p) => n2.GetScalarValue()).ToArray() - + (o, n, _) => + { + if (n is ValueNode) + { + o.Type = n.GetScalarValue(); + } + else + { + o.Type = n.CreateSimpleList((n2, p) => n2.GetScalarValue()).ToArray(); + } + } }, { "allOf", From 57ca1cc27114e0ac361bfb9799748dc44e44f346 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 8 Aug 2024 15:24:11 +0300 Subject: [PATCH 22/27] Clean up type array support logic --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index fa2728a1f..7a2c7656b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -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; @@ -16,9 +16,6 @@ namespace Microsoft.OpenApi.Models /// public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable, IOpenApiSerializable { - private string[] _typeArray; - private string _type; - /// /// Follow JSON Schema definition. Short text providing information about the data. /// @@ -81,7 +78,7 @@ public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable, IOpenApi public decimal? V31ExclusiveMinimum { get; set; } /// - /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// public bool UnEvaluatedProperties { get; set; } @@ -89,21 +86,7 @@ public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable, IOpenApi /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Value MUST be a string in V2 and V3. /// - public object Type - { - get => _type; - set - { - if (value is string || value is JsonNode) - { - _type = (string)value; - } - else - { - _typeArray = (string[])value; - } - } - } + public object Type { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 @@ -349,8 +332,7 @@ public OpenApiSchema(OpenApiSchema schema) UnevaluatedProperties = schema?.UnevaluatedProperties ?? UnevaluatedProperties; V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum; V31ExclusiveMinimum = schema?.V31ExclusiveMinimum ?? V31ExclusiveMinimum; - Type = schema?.Type ?? Type; - TypeArray = schema?.TypeArray != null ? new string[schema.TypeArray.Length] : null; + Type = DeepCloneType(schema?.Type); Format = schema?.Format ?? Format; Description = schema?.Description ?? Description; Maximum = schema?.Maximum ?? Maximum; @@ -497,11 +479,11 @@ public void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpec // type if (Type.GetType() == typeof(string)) { - writer.WriteProperty(OpenApiConstants.Type, _type); + writer.WriteProperty(OpenApiConstants.Type, (string)Type); } else { - writer.WriteOptionalCollection(OpenApiConstants.Type, _typeArray, (w, s) => w.WriteRaw(s)); + writer.WriteOptionalCollection(OpenApiConstants.Type, (string[])Type, (w, s) => w.WriteRaw(s)); } // allOf @@ -712,7 +694,7 @@ internal void WriteAsSchemaProperties( writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, s) => w.WriteAny(new OpenApiAny(s))); // type - writer.WriteProperty(OpenApiConstants.Type, _type); + writer.WriteProperty(OpenApiConstants.Type, (string)Type); // items writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV2(w)); @@ -774,5 +756,28 @@ internal void WriteAsSchemaProperties( // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); } + + private object DeepCloneType(object type) + { + if (type == null) + return null; + + if (type is string) + { + return type; // Return the string as is + } + + else + { + var array = type as Array; + Type elementType = type.GetType().GetElementType(); + Array copiedArray = Array.CreateInstance(elementType, array.Length); + for (int i = 0; i < array.Length; i++) + { + copiedArray.SetValue(DeepCloneType(array.GetValue(i)), i); + } + return copiedArray; + } + } } } From 58e3cd646625f544f255eeed743d4a0c3f71cc16 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 8 Aug 2024 15:24:44 +0300 Subject: [PATCH 23/27] Add tests and update public API --- .../V31Tests/OpenApiSchemaTests.cs | 137 ++++++++++++++++++ .../Samples/OpenApiSchema/jsonSchema.json | 33 +++++ .../Models/OpenApiSchemaTests.cs | 108 ++++++++++++++ .../PublicApi/PublicApi.approved.txt | 7 +- 4 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/jsonSchema.json create mode 100644 test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs new file mode 100644 index 000000000..72c5289e5 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.IO; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + public class OpenApiSchemaTests + { + private const string SampleFolderPath = "V31Tests/Samples/OpenApiSchema/"; + + [Fact] + public void ParseBasicV31SchemaShouldSucceed() + { + var expectedObject = new OpenApiSchema() + { + Id = "https://example.com/arrays.schema.json", + Schema = "https://json-schema.org/draft/2020-12/schema", + Description = "A representation of a person, company, organization, or place", + Type = "object", + Properties = new Dictionary + { + ["fruits"] = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Type = "string" + } + }, + ["vegetables"] = new OpenApiSchema + { + Type = "array" + } + }, + Definitions = new Dictionary + { + ["veggie"] = new OpenApiSchema + { + Type = "object", + Required = new HashSet + { + "veggieName", + "veggieLike" + }, + Properties = new Dictionary + { + ["veggieName"] = new OpenApiSchema + { + Type = "string", + Description = "The name of the vegetable." + }, + ["veggieLike"] = new OpenApiSchema + { + Type = "boolean", + Description = "Do I like this vegetable?" + } + } + } + } + }; + + // Act + var schema = OpenApiModelFactory.Load( + Path.Combine(SampleFolderPath, "jsonSchema.json"), OpenApiSpecVersion.OpenApi3_1, out _); + + // Assert + schema.Should().BeEquivalentTo(expectedObject); + } + + [Fact] + public void ParseSchemaWithTypeArrayWorks() + { + // Arrange + var schema = @"{ + ""$id"": ""https://example.com/arrays.schema.json"", + ""$schema"": ""https://json-schema.org/draft/2020-12/schema"", + ""description"": ""A representation of a person, company, organization, or place"", + ""type"": [""object"", ""null""] +}"; + + var expected = new OpenApiSchema() + { + Id = "https://example.com/arrays.schema.json", + Schema = "https://json-schema.org/draft/2020-12/schema", + Description = "A representation of a person, company, organization, or place", + Type = new string[] { "object", "null" } + }; + + // Act + var actual = OpenApiModelFactory.Parse(schema, OpenApiSpecVersion.OpenApi3_1, out _); + + // Assert + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void TestSchemaCopyConstructorWithTypeArrayWorks() + { + /* Arrange + * Test schema's copy constructor for deep-cloning type array + */ + var schemaWithTypeArray = new OpenApiSchema() + { + Type = new string[] { "array", "null" }, + Items = new OpenApiSchema + { + Type = "string" + } + }; + + var simpleSchema = new OpenApiSchema() + { + Type = "string" + }; + + // Act + var schemaWithArrayCopy = new OpenApiSchema(schemaWithTypeArray); + schemaWithArrayCopy.Type = "string"; + + var simpleSchemaCopy = new OpenApiSchema(simpleSchema); + simpleSchemaCopy.Type = new string[] { "string", "null" }; + + // Assert + schemaWithArrayCopy.Type.Should().NotBeEquivalentTo(schemaWithTypeArray.Type); + schemaWithTypeArray.Type = new string[] { "string", "null" }; + + simpleSchemaCopy.Type.Should().NotBeEquivalentTo(simpleSchema.Type); + simpleSchema.Type = "string"; + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/jsonSchema.json b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/jsonSchema.json new file mode 100644 index 000000000..84b1ea211 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/jsonSchema.json @@ -0,0 +1,33 @@ +{ + "$id": "https://example.com/arrays.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "A representation of a person, company, organization, or place", + "type": "object", + "properties": { + "fruits": { + "type": "array", + "items": { + "type": "string" + } + }, + "vegetables": { + "type": "array" + } + }, + "$defs": { + "veggie": { + "type": "object", + "required": [ "veggieName", "veggieLike" ], + "properties": { + "veggieName": { + "type": "string", + "description": "The name of the vegetable." + }, + "veggieLike": { + "type": "boolean", + "description": "Do I like this vegetable?" + } + } + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs new file mode 100644 index 000000000..b67f64de1 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; +using Xunit; +using FluentAssertions; +using Microsoft.OpenApi.Extensions; + +namespace Microsoft.OpenApi.Tests.Models +{ + public class OpenApiSchemaTests + { + public static OpenApiSchema BasicV31Schema = new() + { + Id = "https://example.com/arrays.schema.json", + Schema = "https://json-schema.org/draft/2020-12/schema", + Description = "A representation of a person, company, organization, or place", + Type = "object", + Properties = new Dictionary + { + ["fruits"] = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Type = "string" + } + }, + ["vegetables"] = new OpenApiSchema + { + Type = "array" + } + }, + Definitions = new Dictionary + { + ["veggie"] = new OpenApiSchema + { + Type = "object", + Required = new HashSet{ "veggieName", "veggieLike" }, + Properties = new Dictionary + { + ["veggieName"] = new OpenApiSchema + { + Type = "string", + Description = "The name of the vegetable." + }, + ["veggieLike"] = new OpenApiSchema + { + Type = "boolean", + Description = "Do I like this vegetable?" + } + } + } + } + }; + + [Fact] + public void SerializeBasicV31SchemaWorks() + { + // Arrange + var expected = @"{ + ""$id"": ""https://example.com/arrays.schema.json"", + ""$schema"": ""https://json-schema.org/draft/2020-12/schema"", + ""$defs"": { + ""veggie"": { + ""required"": [ + ""veggieName"", + ""veggieLike"" + ], + ""type"": ""object"", + ""properties"": { + ""veggieName"": { + ""type"": ""string"", + ""description"": ""The name of the vegetable."" + }, + ""veggieLike"": { + ""type"": ""boolean"", + ""description"": ""Do I like this vegetable?"" + } + } + } + }, + ""type"": ""object"", + ""properties"": { + ""fruits"": { + ""type"": ""array"", + ""items"": { + ""type"": ""string"" + } + }, + ""vegetables"": { + ""type"": ""array"" + } + }, + ""description"": ""A representation of a person, company, organization, or place"" +}"; + + // Act + var actual = BasicV31Schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 1bc477c5a..5d8f06a7c 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1003,14 +1003,13 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.ISet Required { get; set; } public string Schema { get; set; } public string Title { get; set; } - public string Type { get; set; } - public string[] TypeArray { get; set; } + public object Type { get; set; } public bool UnEvaluatedProperties { get; set; } public bool UnevaluatedProperties { get; set; } public bool? UniqueItems { get; set; } public bool UnresolvedReference { get; set; } - public decimal V31ExclusiveMaximum { get; set; } - public decimal V31ExclusiveMinimum { get; set; } + public decimal? V31ExclusiveMaximum { get; set; } + public decimal? V31ExclusiveMinimum { get; set; } public string Vocabulary { get; set; } public bool WriteOnly { get; set; } public Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; } From 4ff2176859c1592dea572a64633a3da8ed97d02c Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 8 Aug 2024 15:40:52 +0300 Subject: [PATCH 24/27] clean up code block --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 7a2c7656b..af9b5e037 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -767,17 +767,18 @@ private object DeepCloneType(object type) return type; // Return the string as is } - else + if (type is Array array) { - var array = type as Array; Type elementType = type.GetType().GetElementType(); Array copiedArray = Array.CreateInstance(elementType, array.Length); - for (int i = 0; i < array.Length; i++) + for (int i = 0; i < array?.Length; i++) { - copiedArray.SetValue(DeepCloneType(array.GetValue(i)), i); + copiedArray.SetValue(DeepCloneType(array?.GetValue(i)), i); } return copiedArray; } + + return null; } } } From 6f28adfc7976837e79b7d0224741931810484d00 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 8 Aug 2024 15:41:02 +0300 Subject: [PATCH 25/27] Use ternary operator --- .../Reader/V31/OpenApiSchemaDeserializer.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 94c266b15..9cb2ffa77 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -121,14 +121,9 @@ internal static partial class OpenApiV31Deserializer "type", (o, n, _) => { - if (n is ValueNode) - { - o.Type = n.GetScalarValue(); - } - else - { - o.Type = n.CreateSimpleList((n2, p) => n2.GetScalarValue()).ToArray(); - } + o.Type = n is ValueNode + ? n.GetScalarValue() + : n.CreateSimpleList((n2, p) => n2.GetScalarValue()).ToArray(); } }, { From f5811e95f9a76ef41043461bc5af327a238a5584 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 13 Aug 2024 17:51:48 +0300 Subject: [PATCH 26/27] Add support for pattern properties --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 15 +++++++++++++-- .../Reader/V31/OpenApiSchemaDeserializer.cs | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index af9b5e037..66fa00acd 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -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; @@ -227,6 +227,15 @@ public class OpenApiSchema : IOpenApiExtensible, IOpenApiReferenceable, IOpenApi /// public IDictionary Properties { get; set; } = new Dictionary(); + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// PatternProperty definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced) + /// Each property name of this object SHOULD be a valid regular expression according to the ECMA 262 r + /// egular expression dialect. Each property value of this object MUST be an object, and each object MUST + /// be a valid Schema Object not a standard JSON Schema. + /// + public IDictionary PatternProperties { get; set; } = new Dictionary(); + /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// @@ -356,11 +365,12 @@ public OpenApiSchema(OpenApiSchema schema) MinItems = schema?.MinItems ?? MinItems; UniqueItems = schema?.UniqueItems ?? UniqueItems; Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + PatternProperties = schema?.PatternProperties != null ? new Dictionary(schema.PatternProperties) : null; MaxProperties = schema?.MaxProperties ?? MaxProperties; MinProperties = schema?.MinProperties ?? MinProperties; AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? AdditionalPropertiesAllowed; AdditionalProperties = schema?.AdditionalProperties != null ? new(schema?.AdditionalProperties) : null; - Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null; + Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null; Example = schema?.Example != null ? new(schema?.Example.Node) : null; Enum = schema?.Enum != null ? new List(schema.Enum) : null; Nullable = schema?.Nullable ?? Nullable; @@ -587,6 +597,7 @@ internal void WriteV31Properties(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.V31ExclusiveMaximum, V31ExclusiveMaximum); writer.WriteProperty(OpenApiConstants.V31ExclusiveMinimum, V31ExclusiveMinimum); writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false); + writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w)); } /// diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 9cb2ffa77..fa9d7dd93 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -150,6 +150,10 @@ internal static partial class OpenApiV31Deserializer "properties", (o, n, t) => o.Properties = n.CreateMap(LoadOpenApiSchema, t) }, + { + "patternProperties", + (o, n, t) => o.PatternProperties = n.CreateMap(LoadOpenApiSchema, t) + }, { "additionalProperties", (o, n, _) => { From e09fe0043443c8e8354b7b260e6f1263c7306acb Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Mon, 19 Aug 2024 11:37:41 +0300 Subject: [PATCH 27/27] Update public API surface --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 5d8f06a7c..58d7a576e 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -995,6 +995,7 @@ namespace Microsoft.OpenApi.Models public bool Nullable { get; set; } public System.Collections.Generic.IList OneOf { get; set; } public string Pattern { get; set; } + public System.Collections.Generic.IDictionary PatternProperties { get; set; } public System.Collections.Generic.IDictionary Properties { get; set; } public bool ReadOnly { get; set; } public string RecursiveAnchor { get; set; }