From c8d87d73470e74026cbe28ab2c828731282a4cd2 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 23 Sep 2025 21:05:41 -0400 Subject: [PATCH 001/146] feat: adds the new spec version to the enum Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/OpenApiSpecVersion.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/OpenApiSpecVersion.cs b/src/Microsoft.OpenApi/OpenApiSpecVersion.cs index 6138b84c6..a1107d9c9 100644 --- a/src/Microsoft.OpenApi/OpenApiSpecVersion.cs +++ b/src/Microsoft.OpenApi/OpenApiSpecVersion.cs @@ -20,6 +20,10 @@ public enum OpenApiSpecVersion /// /// Represents OpenAPI V3.1 spec /// - OpenApi3_1 + OpenApi3_1, + /// + /// Represents OpenAPI V3.2 spec + /// + OpenApi3_2 } From 7f869d06ef89fea20cb14ac4bdb7c8213afb4792 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 23 Sep 2025 21:34:04 -0400 Subject: [PATCH 002/146] feat: adds the new 3.2 serialization infrastructure Signed-off-by: Vincent Biret --- .../Interfaces/IOpenApiSerializable.cs | 5 ++++ .../Models/BaseOpenApiReference.cs | 15 ++++++++++++ .../Models/JsonSchemaReference.cs | 11 ++++++++- .../Models/OpenApiCallback.cs | 10 ++++++++ .../Models/OpenApiComponents.cs | 22 +++++++++++++---- .../Models/OpenApiContact.cs | 8 +++++++ .../Models/OpenApiDiscriminator.cs | 24 +++++++++++++------ .../Models/OpenApiDocument.cs | 21 ++++++++++++---- .../Models/OpenApiEncoding.cs | 9 +++++++ .../Models/OpenApiExample.cs | 6 +++++ .../Models/OpenApiExtensibleDictionary.cs | 8 +++++++ .../Models/OpenApiExternalDocs.cs | 8 +++++++ src/Microsoft.OpenApi/Models/OpenApiHeader.cs | 10 +++++++- src/Microsoft.OpenApi/Models/OpenApiInfo.cs | 12 ++++++++++ .../Models/OpenApiLicense.cs | 10 ++++++++ src/Microsoft.OpenApi/Models/OpenApiLink.cs | 5 ++++ .../Models/OpenApiMediaType.cs | 7 ++++++ .../Models/OpenApiOAuthFlow.cs | 8 +++++++ .../Models/OpenApiOAuthFlows.cs | 8 +++++++ .../Models/OpenApiOperation.cs | 8 +++++++ .../Models/OpenApiParameter.cs | 6 +++++ .../Models/OpenApiPathItem.cs | 8 +++++++ .../Models/OpenApiReferenceWithDescription.cs | 12 +++++++++- ...enApiReferenceWithDescriptionAndSummary.cs | 12 +++++++++- .../Models/OpenApiRequestBody.cs | 8 +++++++ .../Models/OpenApiResponse.cs | 8 +++++++ src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 5 ++++ .../Models/OpenApiSecurityRequirement.cs | 16 ++++++++++++- .../Models/OpenApiSecurityScheme.cs | 7 ++++++ src/Microsoft.OpenApi/Models/OpenApiServer.cs | 8 +++++++ .../Models/OpenApiServerVariable.cs | 8 +++++++ src/Microsoft.OpenApi/Models/OpenApiTag.cs | 11 ++++++++- src/Microsoft.OpenApi/Models/OpenApiXml.cs | 10 +++++++- .../References/BaseOpenApiReferenceHolder.cs | 13 ++++++++++ 34 files changed, 324 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs index 34c289bc5..d3d46431c 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs @@ -8,6 +8,11 @@ namespace Microsoft.OpenApi /// public interface IOpenApiSerializable : IOpenApiElement { + /// + /// Serialize OpenAPI element into v3.2 + /// + /// + void SerializeAsV32(IOpenApiWriter writer); /// /// Serialize OpenAPI element into v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs b/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs index f47438644..b45d18787 100644 --- a/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs @@ -150,12 +150,27 @@ public BaseOpenApiReference(BaseOpenApiReference reference) HostDocument = reference.HostDocument; } + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, SerializeAdditionalV32Properties); + } + /// public virtual void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, SerializeAdditionalV31Properties); } + /// + /// Serialize additional properties for Open Api v3.2. + /// + /// + protected virtual void SerializeAdditionalV32Properties(IOpenApiWriter writer) + { + // noop for the base type + } + /// /// Serialize additional properties for Open Api v3.1. /// diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 1e7f788df..b724dbd1f 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -71,11 +71,20 @@ public JsonSchemaReference(JsonSchemaReference reference) : base(reference) /// protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer) + { + SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV31Properties); + } + /// + protected override void SerializeAdditionalV32Properties(IOpenApiWriter writer) + { + SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV32Properties); + } + private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action baseSerializer) { if (Type != ReferenceType.Schema) throw new InvalidOperationException( $"JsonSchemaReference can only be serialized for ReferenceType.Schema, but was {Type}."); - base.SerializeAdditionalV31Properties(writer); + baseSerializer(writer); // Additional schema metadata annotations in 3.1 writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); writer.WriteProperty(OpenApiConstants.Title, Title); diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index 7a67e3bdf..4832619f7 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -50,6 +50,16 @@ public void AddPathItem(RuntimeExpression expression, IOpenApiPathItem pathItem) PathItems.Add(expression, pathItem); } + /// + /// Serialize to Open Api v3.2 + /// + /// + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index a27cae914..9f544f1bd 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -90,11 +90,24 @@ public OpenApiComponents(OpenApiComponents? components) Extensions = components?.Extensions != null ? new Dictionary(components.Extensions) : null; } + /// + /// Serialize to Open API v3.2. + /// + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeAsV3X(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer), (writer, referenceElement) => referenceElement.SerializeAsV32(writer)); + } + /// /// Serialize to Open API v3.1. /// /// public virtual void SerializeAsV31(IOpenApiWriter writer) + { + SerializeAsV3X(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer), (writer, referenceElement) => referenceElement.SerializeAsV31(writer)); + } + private void SerializeAsV3X(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback, Action action) { Utils.CheckArgumentNull(writer); @@ -102,7 +115,7 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) // however if they have cycles, then we will need a component rendered if (writer.GetSettings().InlineLocalReferences) { - RenderComponents(writer, (writer, element) => element.SerializeAsV31(writer), OpenApiSpecVersion.OpenApi3_1); + RenderComponents(writer, callback, version); return; } @@ -116,16 +129,15 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) { if (component is OpenApiPathItemReference reference) { - reference.SerializeAsV31(w); + action(w, reference); } else { - component.SerializeAsV31(w); + callback(w, component); } }); - SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer), - (writer, referenceElement) => referenceElement.SerializeAsV31(writer)); + SerializeInternal(writer, version, callback, action); } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiContact.cs b/src/Microsoft.OpenApi/Models/OpenApiContact.cs index 276566d56..385129af7 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiContact.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiContact.cs @@ -47,6 +47,14 @@ public OpenApiContact(OpenApiContact contact) Email = contact?.Email ?? Email; Extensions = contact?.Extensions != null ? new Dictionary(contact.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_2); + } /// /// Serialize to Open Api v3.1 diff --git a/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs b/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs index 96361d3d4..eda3b4b21 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs @@ -40,13 +40,27 @@ public OpenApiDiscriminator(OpenApiDiscriminator discriminator) Extensions = discriminator?.Extensions != null ? new Dictionary(discriminator.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + /// + public void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2); + + // extensions + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_2); + + writer.WriteEndObject(); + } + /// /// Serialize to Open Api v3.1 /// /// public void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1); // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1); @@ -59,16 +73,12 @@ public void SerializeAsV31(IOpenApiWriter writer) /// public void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } - /// - /// Serialize to Open Api v3.0 - /// - /// - private void SerializeInternal(IOpenApiWriter writer) + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version) { Utils.CheckArgumentNull(writer); diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 031eadb5d..f2d2cf623 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -167,23 +167,36 @@ public void SerializeAs(OpenApiSpecVersion version, IOpenApiWriter writer) } } + /// + /// Serialize to Open API v3.2 document. + /// + /// + public void SerializeAsV32(IOpenApiWriter writer) + { + SerializeAsV3X(writer, "3.2.0", OpenApiSpecVersion.OpenApi3_2, (w, element) => element.SerializeAsV32(w), (w, referenceElement) => referenceElement.SerializeAsV32(w)); + } + /// /// Serialize to Open API v3.1 document. /// /// public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeAsV3X(writer, "3.1.2", OpenApiSpecVersion.OpenApi3_1, (w, element) => element.SerializeAsV31(w), (w, referenceElement) => referenceElement.SerializeAsV31(w)); + } + private void SerializeAsV3X(IOpenApiWriter writer, string versionString, OpenApiSpecVersion version, Action callback, Action referenceCallback) { Utils.CheckArgumentNull(writer); writer.WriteStartObject(); // openApi - writer.WriteProperty(OpenApiConstants.OpenApi, "3.1.1"); + writer.WriteProperty(OpenApiConstants.OpenApi, versionString); // jsonSchemaDialect writer.WriteProperty(OpenApiConstants.JsonSchemaDialect, JsonSchemaDialect?.ToString()); - SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (w, element) => element.SerializeAsV31(w)); + SerializeInternal(writer, version, callback); // webhooks writer.WriteOptionalMap( @@ -193,11 +206,11 @@ public void SerializeAsV31(IOpenApiWriter writer) { if (component is OpenApiPathItemReference reference) { - reference.SerializeAsV31(w); + referenceCallback(w, reference); } else { - component.SerializeAsV31(w); + callback(w, component); } }); diff --git a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs index d12a24b5f..f5df9026f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs @@ -76,6 +76,15 @@ public OpenApiEncoding(OpenApiEncoding encoding) Extensions = encoding?.Extensions != null ? new Dictionary(encoding.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index b23befd08..6c0352664 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -45,6 +45,12 @@ internal OpenApiExample(IOpenApiExample example) Extensions = example.Extensions != null ? new Dictionary(example.Extensions) : null; } + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2); + } + /// public virtual void SerializeAsV31(IOpenApiWriter writer) { diff --git a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs index bf49f9099..966516eb4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs @@ -36,6 +36,14 @@ protected OpenApiExtensibleDictionary( /// public IDictionary? Extensions { get; set; } + /// + /// Serialize to Open Api v3.2 + /// + /// + public void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } /// /// Serialize to Open Api v3.1 diff --git a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs index 8c16397c0..e7737b011 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs @@ -41,6 +41,14 @@ public OpenApiExternalDocs(OpenApiExternalDocs externalDocs) Extensions = externalDocs?.Extensions != null ? new Dictionary(externalDocs.Extensions) : null; } + /// + /// Serialize to Open Api v3.2. + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_2); + } + /// /// Serialize to Open Api v3.1. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index 8bb155f85..3165730f0 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -74,12 +74,20 @@ internal OpenApiHeader(IOpenApiHeader header) Extensions = header.Extensions != null ? new Dictionary(header.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// public virtual void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV31(writer)); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs index 8b004bf74..9a5a7828b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs @@ -71,6 +71,18 @@ public OpenApiInfo(OpenApiInfo info) Extensions = info?.Extensions != null ? new Dictionary(info.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + + // summary - present in 3.2 + writer.WriteProperty(OpenApiConstants.Summary, Summary); + writer.WriteEndObject(); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs index 04a267bdc..6645cc8d3 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs @@ -47,6 +47,16 @@ public OpenApiLicense(OpenApiLicense license) Extensions = license?.Extensions != null ? new Dictionary(license.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_2); + writer.WriteProperty(OpenApiConstants.Identifier, Identifier); + writer.WriteEndObject(); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiLink.cs b/src/Microsoft.OpenApi/Models/OpenApiLink.cs index 1571dcadd..585cab160 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLink.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLink.cs @@ -51,6 +51,11 @@ internal OpenApiLink(IOpenApiLink link) Server = link.Server != null ? new(link.Server) : null; Extensions = link.Extensions != null ? new Dictionary(link.Extensions) : null; } + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV32(writer)); + } /// public virtual void SerializeAsV31(IOpenApiWriter writer) diff --git a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs index b901eacd1..0ae0fbca5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs @@ -60,6 +60,13 @@ public OpenApiMediaType(OpenApiMediaType? mediaType) Extensions = mediaType?.Extensions != null ? new Dictionary(mediaType.Extensions) : null; } + /// + /// Serialize to Open Api v3.2. + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (w, element) => element.SerializeAsV32(w)); + } /// /// Serialize to Open Api v3.1. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs index 5bfeba500..8272e7034 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs @@ -55,6 +55,14 @@ public OpenApiOAuthFlow(OpenApiOAuthFlow oAuthFlow) Extensions = oAuthFlow?.Extensions != null ? new Dictionary(oAuthFlow.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs index 16a6f17b3..42c9cf935 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs @@ -54,6 +54,14 @@ public OpenApiOAuthFlows(OpenApiOAuthFlows oAuthFlows) Extensions = oAuthFlows?.Extensions != null ? new Dictionary(oAuthFlows.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 80aa1e429..e4625ff1d 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -145,6 +145,14 @@ public OpenApiOperation(OpenApiOperation operation) Metadata = operation.Metadata != null ? new Dictionary(operation.Metadata) : null; } + /// + /// Serialize to Open Api v3.2. + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 63b3735e2..f1300f027 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -93,6 +93,12 @@ internal OpenApiParameter(IOpenApiParameter parameter) Deprecated = parameter.Deprecated; } + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// public virtual void SerializeAsV31(IOpenApiWriter writer) { diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index 0be48961d..fe3322837 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -60,6 +60,14 @@ internal OpenApiPathItem(IOpenApiPathItem pathItem) Extensions = pathItem.Extensions != null ? new Dictionary(pathItem.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescription.cs b/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescription.cs index a6338b761..0085cd085 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescription.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescription.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Text.Json.Nodes; namespace Microsoft.OpenApi; @@ -33,7 +34,16 @@ public OpenApiReferenceWithDescription(OpenApiReferenceWithDescription reference /// protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer) { - base.SerializeAdditionalV31Properties(writer); + SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV31Properties); + } + /// + protected override void SerializeAdditionalV32Properties(IOpenApiWriter writer) + { + SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV32Properties); + } + private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action baseSerializer) + { + baseSerializer(writer); // summary and description are in 3.1 but not in 3.0 writer.WriteProperty(OpenApiConstants.Description, Description); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescriptionAndSummary.cs b/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescriptionAndSummary.cs index 6096fb4c5..f9c3a7703 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescriptionAndSummary.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReferenceWithDescriptionAndSummary.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Text.Json.Nodes; namespace Microsoft.OpenApi; @@ -31,10 +32,19 @@ public OpenApiReferenceWithDescriptionAndSummary(OpenApiReferenceWithDescription } /// protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer) + { + SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV31Properties); + } + /// + protected override void SerializeAdditionalV32Properties(IOpenApiWriter writer) + { + SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV32Properties); + } + private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action baseSerializer) { // summary and description are in 3.1 but not in 3.0 writer.WriteProperty(OpenApiConstants.Summary, Summary); - base.SerializeAdditionalV31Properties(writer); + baseSerializer(writer); } /// protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject) diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 8f4c1bd90..844303f0b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -41,6 +41,14 @@ internal OpenApiRequestBody(IOpenApiRequestBody requestBody) Extensions = requestBody.Extensions != null ? new Dictionary(requestBody.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 6c1af10f1..e15ea5742 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -45,6 +45,14 @@ internal OpenApiResponse(IOpenApiResponse response) Extensions = response.Extensions != null ? new Dictionary(response.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index db3064b00..0ad6a7274 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -323,6 +323,11 @@ internal OpenApiSchema(IOpenApiSchema schema) UnrecognizedKeywords = schema.UnrecognizedKeywords != null ? new Dictionary(schema.UnrecognizedKeywords) : null; DependentRequired = schema.DependentRequired != null ? new Dictionary>(schema.DependentRequired) : null; } + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } /// public virtual void SerializeAsV31(IOpenApiWriter writer) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs index fe2d78145..4a912c0de 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs @@ -27,6 +27,20 @@ public OpenApiSecurityRequirement() : base(new OpenApiSecuritySchemeReferenceEqualityComparer()) { } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, (w, s) => + { + if (!string.IsNullOrEmpty(s.Reference.ReferenceV3) && s.Reference.ReferenceV3 is not null) + { + w.WritePropertyName(s.Reference.ReferenceV3); + } + }); + } + /// /// Serialize to Open Api v3.1 @@ -35,7 +49,7 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, (w, s) => { - if(!string.IsNullOrEmpty(s.Reference.ReferenceV3) && s.Reference.ReferenceV3 is not null) + if (!string.IsNullOrEmpty(s.Reference.ReferenceV3) && s.Reference.ReferenceV3 is not null) { w.WritePropertyName(s.Reference.ReferenceV3); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index 7d82f3219..320d64504 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -59,6 +59,13 @@ internal OpenApiSecurityScheme(IOpenApiSecurityScheme securityScheme) OpenIdConnectUrl = securityScheme.OpenIdConnectUrl != null ? new Uri(securityScheme.OpenIdConnectUrl.OriginalString, UriKind.RelativeOrAbsolute) : null; Extensions = securityScheme.Extensions != null ? new Dictionary(securityScheme.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } /// /// Serialize to Open Api v3.1 diff --git a/src/Microsoft.OpenApi/Models/OpenApiServer.cs b/src/Microsoft.OpenApi/Models/OpenApiServer.cs index 6a05057c1..0733428af 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServer.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServer.cs @@ -49,6 +49,14 @@ public OpenApiServer(OpenApiServer server) Extensions = server?.Extensions != null ? new Dictionary(server.Extensions) : null; } + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, (writer, element) => element.SerializeAsV32(writer)); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs index 8f743b756..f07990b17 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs @@ -50,6 +50,14 @@ public OpenApiServerVariable(OpenApiServerVariable serverVariable) Extensions = serverVariable?.Extensions != null ? new Dictionary(serverVariable.Extensions) : serverVariable?.Extensions; } + /// + /// Serialize to Open Api v3.2 + /// + public void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2); + } + /// /// Serialize to Open Api v3.1 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiTag.cs b/src/Microsoft.OpenApi/Models/OpenApiTag.cs index 91dabb976..b74f68392 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiTag.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiTag.cs @@ -39,11 +39,20 @@ internal OpenApiTag(IOpenApiTag tag) ExternalDocs = tag.ExternalDocs != null ? new(tag.ExternalDocs) : null; Extensions = tag.Extensions != null ? new Dictionary(tag.Extensions) : null; } + + /// + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_2, + (writer, element) => element.SerializeAsV32(writer)); + } /// /// Serialize to Open Api v3.1 /// - public virtual void SerializeAsV31(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index 76f4e0d13..62c1b64de 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -62,7 +62,15 @@ public OpenApiXml(OpenApiXml xml) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.2 + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + Write(writer, OpenApiSpecVersion.OpenApi3_2); + } + + /// + /// Serialize to Open Api v3.1 /// public virtual void SerializeAsV31(IOpenApiWriter writer) { diff --git a/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs b/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs index 634c5a723..3ea599592 100644 --- a/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs +++ b/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs @@ -99,6 +99,19 @@ public virtual void SerializeAsV3(IOpenApiWriter writer) } } + /// + public virtual void SerializeAsV32(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV32(writer); + } + else + { + SerializeInternal(writer, (writer, element) => CopyReferenceAsTargetElementWithOverrides(element).SerializeAsV32(writer)); + } + } + /// public virtual void SerializeAsV31(IOpenApiWriter writer) { From 5d365c0b0ad854b84d000bb14be02cf5aaf27aa5 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 23 Sep 2025 21:47:39 -0400 Subject: [PATCH 003/146] feat: adds parsing infrastructure for version 3.2 Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 2 +- .../OpenApiSpecVersionHelper.cs | 6 +- src/Microsoft.OpenApi.Workbench/MainModel.cs | 7 + .../OpenApiSerializableExtensions.cs | 4 + .../Models/OpenApiDocument.cs | 4 + .../Reader/OpenApiVersionExtensionMethods.cs | 16 + .../Reader/ParsingContext.cs | 11 + .../Reader/V31/OpenApiV31VersionService.cs | 2 +- .../Reader/V32/OpenApiCallbackDeserializer.cs | 41 ++ .../V32/OpenApiComponentsDeserializer.cs | 45 ++ .../Reader/V32/OpenApiContactDeserializer.cs | 54 +++ .../V32/OpenApiDiscriminatorDeserializer.cs | 55 +++ .../Reader/V32/OpenApiDocumentDeserializer.cs | 53 +++ .../Reader/V32/OpenApiEncodingDeserializer.cs | 77 ++++ .../Reader/V32/OpenApiExampleDeserializer.cs | 69 +++ .../V32/OpenApiExternalDocsDeserializer.cs | 53 +++ .../Reader/V32/OpenApiHeaderDeserializer.cs | 132 ++++++ .../Reader/V32/OpenApiInfoDeserializer.cs | 77 ++++ .../Reader/V32/OpenApiLicenseDeserializer.cs | 55 +++ .../Reader/V32/OpenApiLinkDeserializer.cs | 71 +++ .../V32/OpenApiMediaTypeDeserializer.cs | 86 ++++ .../V32/OpenApiOAuthFlowDeserializer.cs | 71 +++ .../V32/OpenApiOAuthFlowsDeserializer.cs | 40 ++ .../V32/OpenApiOperationDeserializer.cs | 135 ++++++ .../V32/OpenApiParameterDeserializer.cs | 179 ++++++++ .../Reader/V32/OpenApiPathItemDeserializer.cs | 71 +++ .../Reader/V32/OpenApiPathsDeserializer.cs | 31 ++ .../V32/OpenApiRequestBodyDeserializer.cs | 67 +++ .../Reader/V32/OpenApiResponseDeserializer.cs | 65 +++ .../V32/OpenApiResponsesDeserializer.cs | 34 ++ .../Reader/V32/OpenApiSchemaDeserializer.cs | 412 ++++++++++++++++++ .../OpenApiSecurityRequirementDeserializer.cs | 47 ++ .../V32/OpenApiSecuritySchemeDeserializer.cs | 108 +++++ .../Reader/V32/OpenApiServerDeserializer.cs | 53 +++ .../V32/OpenApiServerVariableDeserializer.cs | 56 +++ .../Reader/V32/OpenApiTagDeserializer.cs | 56 +++ .../Reader/V32/OpenApiV32Deserializer.cs | 172 ++++++++ .../Reader/V32/OpenApiV32VersionService.cs | 65 +++ .../Reader/V32/OpenApiXmlDeserializer.cs | 81 ++++ 39 files changed, 2660 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiComponentsDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiContactDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiDocumentDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiEncodingDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiExternalDocsDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiInfoDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiLicenseDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowsDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiOperationDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiPathsDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiResponsesDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiSecurityRequirementDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiServerDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiServerVariableDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiTagDeserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs create mode 100644 src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 8787e7697..d63d559f0 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -69,7 +69,7 @@ public static async Task TransformOpenApiDocumentAsync(HidiOptions options, ILog // Default to yaml and OpenApiVersion 3_1 during csdl to OpenApi conversion var openApiFormat = options.OpenApiFormat ?? (!string.IsNullOrEmpty(options.OpenApi) ? GetOpenApiFormat(options.OpenApi, logger) : OpenApiConstants.Yaml); - var openApiVersion = options.Version != null ? TryParseOpenApiSpecVersion(options.Version) : OpenApiSpecVersion.OpenApi3_1; + var openApiVersion = options.Version != null ? TryParseOpenApiSpecVersion(options.Version) : OpenApiSpecVersion.OpenApi3_2; // If ApiManifest is provided, set the referenced OpenAPI document var apiDependency = await FindApiDependencyAsync(options.FilterOptions.FilterByApiManifest, logger, cancellationToken).ConfigureAwait(false); diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs index 222f7a8c6..b8bff3195 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs @@ -35,8 +35,12 @@ public static OpenApiSpecVersion TryParseOpenApiSpecVersion(string value) { return OpenApiSpecVersion.OpenApi3_1; } + else if (majorVersion == 3 && minorVersion == 2) + { + return OpenApiSpecVersion.OpenApi3_2; + } - return OpenApiSpecVersion.OpenApi3_1; // default + return OpenApiSpecVersion.OpenApi3_2; // default } } } diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs index 4af1cbb4a..660fcc5ed 100644 --- a/src/Microsoft.OpenApi.Workbench/MainModel.cs +++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs @@ -155,6 +155,7 @@ public OpenApiSpecVersion Version OnPropertyChanged(nameof(IsV2_0)); OnPropertyChanged(nameof(IsV3_0)); OnPropertyChanged(nameof(IsV3_1)); + OnPropertyChanged(nameof(IsV3_2)); } } @@ -188,6 +189,12 @@ public bool IsV3_1 set => Version = value ? OpenApiSpecVersion.OpenApi3_1 : Version; } + public bool IsV3_2 + { + get => Version == OpenApiSpecVersion.OpenApi3_2; + set => Version = value ? OpenApiSpecVersion.OpenApi3_2 : Version; + } + /// /// Handling method when the property with given name has changed. /// diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs index 1ed0aacab..f320e920d 100755 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs @@ -111,6 +111,10 @@ public static Task SerializeAsync(this T element, IOpenApiWriter writer, Open switch (specVersion) { + case OpenApiSpecVersion.OpenApi3_2: + element.SerializeAsV32(writer); + break; + case OpenApiSpecVersion.OpenApi3_1: element.SerializeAsV31(writer); break; diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index f2d2cf623..7a90105d5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -162,6 +162,10 @@ public void SerializeAs(OpenApiSpecVersion version, IOpenApiWriter writer) SerializeAsV31(writer); break; + case OpenApiSpecVersion.OpenApi3_2: + SerializeAsV32(writer); + break; + default: throw new ArgumentOutOfRangeException(nameof(version), version, string.Format(SRResource.OpenApiSpecVersionNotSupported, version)); } diff --git a/src/Microsoft.OpenApi/Reader/OpenApiVersionExtensionMethods.cs b/src/Microsoft.OpenApi/Reader/OpenApiVersionExtensionMethods.cs index 24f32ef5f..580b34fde 100644 --- a/src/Microsoft.OpenApi/Reader/OpenApiVersionExtensionMethods.cs +++ b/src/Microsoft.OpenApi/Reader/OpenApiVersionExtensionMethods.cs @@ -57,5 +57,21 @@ public static bool is3_1(this string version) return result; } + + /// + /// Extension method for Spec version 3.2 + /// + /// + /// + public static bool is3_2(this string version) + { + bool result = false; + if (version.StartsWith("3.2", StringComparison.OrdinalIgnoreCase)) + { + result = true; + } + + return result; + } } } diff --git a/src/Microsoft.OpenApi/Reader/ParsingContext.cs b/src/Microsoft.OpenApi/Reader/ParsingContext.cs index 504014152..2515d8809 100644 --- a/src/Microsoft.OpenApi/Reader/ParsingContext.cs +++ b/src/Microsoft.OpenApi/Reader/ParsingContext.cs @@ -8,6 +8,7 @@ using Microsoft.OpenApi.Reader.V2; using Microsoft.OpenApi.Reader.V3; using Microsoft.OpenApi.Reader.V31; +using Microsoft.OpenApi.Reader.V32; namespace Microsoft.OpenApi.Reader { @@ -88,6 +89,12 @@ public OpenApiDocument Parse(JsonNode jsonNode, Uri location) this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_1; ValidateRequiredFields(doc, version); break; + case string version when version.is3_2(): + VersionService = new OpenApiV32VersionService(Diagnostic); + doc = VersionService.LoadDocument(RootNode, location); + this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_2; + ValidateRequiredFields(doc, version); + break; default: throw new OpenApiUnsupportedSpecVersionException(inputVersion); } @@ -123,6 +130,10 @@ public OpenApiDocument Parse(JsonNode jsonNode, Uri location) this.VersionService = new OpenApiV31VersionService(Diagnostic); element = this.VersionService.LoadElement(node, openApiDocument); break; + case OpenApiSpecVersion.OpenApi3_2: + this.VersionService = new OpenApiV32VersionService(Diagnostic); + element = this.VersionService.LoadElement(node, openApiDocument); + break; } return element; diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs index 548f5de76..24a9ba5f2 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs @@ -15,7 +15,7 @@ internal class OpenApiV31VersionService : BaseOpenApiVersionService /// /// Create Parsing Context /// - /// Provide instance for diagnotic object for collecting and accessing information about the parsing. + /// Provide instance for diagnostic object for collecting and accessing information about the parsing. public OpenApiV31VersionService(OpenApiDiagnostic diagnostic):base(diagnostic) { } diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs new file mode 100644 index 000000000..c3510d8bd --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs @@ -0,0 +1,41 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _callbackFixedFields = + new(); + + private static readonly PatternFieldMap _callbackPatternFields = + new() + { + {s => !s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, t) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n, t))}, + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + public static IOpenApiCallback LoadCallback(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("callback"); + + if (mapNode.GetReferencePointer() is { } pointer) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var callbackReference = new OpenApiCallbackReference(reference.Item1, hostDocument, reference.Item2); + callbackReference.Reference.SetMetadataFromMapNode(mapNode); + return callbackReference; + } + + var domainObject = new OpenApiCallback(); + + ParseMap(mapNode, domainObject, _callbackFixedFields, _callbackPatternFields, hostDocument); + + return domainObject; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiComponentsDeserializer.cs new file mode 100644 index 000000000..4f0b74554 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiComponentsDeserializer.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _componentsFixedFields = new() + { + {"schemas", (o, n, t) => o.Schemas = n.CreateMap(LoadSchema, t)}, + {"responses", (o, n, t) => o.Responses = n.CreateMap(LoadResponse, t)}, + {"parameters", (o, n, t) => o.Parameters = n.CreateMap(LoadParameter, t)}, + {"examples", (o, n, t) => o.Examples = n.CreateMap(LoadExample, t)}, + {"requestBodies", (o, n, t) => o.RequestBodies = n.CreateMap(LoadRequestBody, t)}, + {"headers", (o, n, t) => o.Headers = n.CreateMap(LoadHeader, t)}, + {"securitySchemes", (o, n, t) => o.SecuritySchemes = n.CreateMap(LoadSecurityScheme, t)}, + {"links", (o, n, t) => o.Links = n.CreateMap(LoadLink, t)}, + {"callbacks", (o, n, t) => o.Callbacks = n.CreateMap(LoadCallback, t)}, + {"pathItems", (o, n, t) => o.PathItems = n.CreateMap(LoadPathItem, t)} + }; + + private static readonly PatternFieldMap _componentsPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiComponents LoadComponents(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("components"); + var components = new OpenApiComponents(); + + ParseMap(mapNode, components, _componentsFixedFields, _componentsPatternFields, hostDocument); + + return components; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiContactDeserializer.cs new file mode 100644 index 000000000..d0d6493e7 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiContactDeserializer.cs @@ -0,0 +1,54 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _contactFixedFields = new() + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "email", (o, n, _) => + { + o.Email = n.GetScalarValue(); + } + }, + { + "url", + (o, n, t) => + { + var url = n.GetScalarValue(); + if (url != null) + { + o.Url = new(url, UriKind.RelativeOrAbsolute); + } + } + }, + }; + + private static readonly PatternFieldMap _contactPatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiContact LoadContact(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node as MapNode; + var contact = new OpenApiContact(); + + ParseMap(mapNode, contact, _contactFixedFields, _contactPatternFields, hostDocument); + + return contact; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs new file mode 100644 index 000000000..285aa025f --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs @@ -0,0 +1,55 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _discriminatorFixedFields = + new() + { + { + "propertyName", (o, n, _) => + { + o.PropertyName = n.GetScalarValue(); + } + }, + { + "mapping", (o, n, doc) => + { + o.Mapping = n.CreateSimpleMap((node) => LoadMapping(node, doc)); + } + } + }; + + private static readonly PatternFieldMap _discriminatorPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiDiscriminator LoadDiscriminator(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("discriminator"); + + var discriminator = new OpenApiDiscriminator(); + foreach (var property in mapNode) + { + property.ParseField(discriminator, _discriminatorFixedFields, _discriminatorPatternFields, hostDocument); + } + + return discriminator; + } + + public static OpenApiSchemaReference LoadMapping(ParseNode node, OpenApiDocument hostDocument) + { + var pointer = node.GetScalarValue() ?? throw new InvalidOperationException("Could not get a pointer reference"); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiDocumentDeserializer.cs new file mode 100644 index 000000000..113ff8da2 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiDocumentDeserializer.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _openApiFixedFields = new() + { + { + "openapi", (o, n, _) => + { + } /* Version is valid field but we already parsed it */ + }, + {"info", (o, n, _) => o.Info = LoadInfo(n, o)}, + {"jsonSchemaDialect", (o, n, _) => { if (n.GetScalarValue() is string {} sjsd && Uri.TryCreate(sjsd, UriKind.Absolute, out var jsd)) {o.JsonSchemaDialect = jsd;}} }, + {"servers", (o, n, _) => o.Servers = n.CreateList(LoadServer, o)}, + {"paths", (o, n, _) => o.Paths = LoadPaths(n, o)}, + {"webhooks", (o, n, _) => o.Webhooks = n.CreateMap(LoadPathItem, o)}, + {"components", (o, n, _) => o.Components = LoadComponents(n, o)}, + {"tags", (o, n, _) => { if (n.CreateList(LoadTag, o) is {Count:> 0} tags) {o.Tags = new HashSet(tags, OpenApiTagComparer.Instance); } } }, + {"externalDocs", (o, n, _) => o.ExternalDocs = LoadExternalDocs(n, o)}, + {"security", (o, n, _) => o.Security = n.CreateList(LoadSecurityRequirement, o)} + }; + + private static readonly PatternFieldMap _openApiPatternFields = new() + { + // We have no semantics to verify X- nodes, therefore treat them as just values. + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiDocument LoadOpenApi(RootNode rootNode, Uri location) + { + var openApiDoc = new OpenApiDocument + { + BaseUri = location + }; + var openApiNode = rootNode.GetMap(); + + ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc); + + // Register components + openApiDoc.Workspace?.RegisterComponents(openApiDoc); + + return openApiDoc; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiEncodingDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiEncodingDeserializer.cs new file mode 100644 index 000000000..f5dbd6904 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiEncodingDeserializer.cs @@ -0,0 +1,77 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _encodingFixedFields = new() + { + { + "contentType", (o, n, _) => + { + o.ContentType = n.GetScalarValue(); + } + }, + { + "headers", (o, n, t) => + { + o.Headers = n.CreateMap(LoadHeader, t); + } + }, + { + "style", (o, n, _) => + { + if(!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var style)) + { + return; + } + o.Style = style; + } + }, + { + "explode", (o, n, _) => + { + var explode = n.GetScalarValue(); + if (explode is not null) + { + o.Explode = bool.Parse(explode); + } + } + }, + { + "allowedReserved", (o, n, _) => + { + var allowReserved = n.GetScalarValue(); + if (allowReserved is not null) + { + o.AllowReserved = bool.Parse(allowReserved); + } + } + }, + }; + + private static readonly PatternFieldMap _encodingPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiEncoding LoadEncoding(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("encoding"); + + var encoding = new OpenApiEncoding(); + foreach (var property in mapNode) + { + property.ParseField(encoding, _encodingFixedFields, _encodingPatternFields, hostDocument); + } + + return encoding; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs new file mode 100644 index 000000000..b5287f982 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs @@ -0,0 +1,69 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _exampleFixedFields = new() + { + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "value", (o, n, _) => + { + o.Value = n.CreateAny(); + } + }, + { + "externalValue", (o, n, _) => + { + o.ExternalValue = n.GetScalarValue(); + } + }, + + }; + + private static readonly PatternFieldMap _examplePatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static IOpenApiExample LoadExample(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("example"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var exampleReference = new OpenApiExampleReference(reference.Item1, hostDocument, reference.Item2); + exampleReference.Reference.SetMetadataFromMapNode(mapNode); + return exampleReference; + } + + var example = new OpenApiExample(); + foreach (var property in mapNode) + { + property.ParseField(example, _exampleFixedFields, _examplePatternFields, hostDocument); + } + + return example; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiExternalDocsDeserializer.cs new file mode 100644 index 000000000..46325d6bb --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiExternalDocsDeserializer.cs @@ -0,0 +1,53 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _externalDocsFixedFields = + new() + { + // $ref + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "url", + (o, n, t) => + { + var url = n.GetScalarValue(); + if (url != null) + { + o.Url = new(url, UriKind.RelativeOrAbsolute); + } + } + }, + }; + + private static readonly PatternFieldMap _externalDocsPatternFields = + new() + { + + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiExternalDocs LoadExternalDocs(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("externalDocs"); + + var externalDocs = new OpenApiExternalDocs(); + + ParseMap(mapNode, externalDocs, _externalDocsFixedFields, _externalDocsPatternFields, hostDocument); + + return externalDocs; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs new file mode 100644 index 000000000..05edeee1a --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs @@ -0,0 +1,132 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _headerFixedFields = new() + { + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "required", + (o, n, _) => + { + var required = n.GetScalarValue(); + if (required != null) + { + o.Required = bool.Parse(required); + } + } + }, + { + "deprecated", + (o, n, _) => + { + var deprecated = n.GetScalarValue(); + if (deprecated != null) + { + o.Deprecated = bool.Parse(deprecated); + } + } + }, + { + "allowEmptyValue", + (o, n, _) => + { + var allowEmptyVal = n.GetScalarValue(); + if (allowEmptyVal != null) + { + o.AllowEmptyValue = bool.Parse(allowEmptyVal); + } + } + }, + { + "allowReserved", + (o, n, _) => + { + var allowReserved = n.GetScalarValue(); + if (allowReserved != null) + { + o.AllowReserved = bool.Parse(allowReserved); + } + } + }, + { + "style", (o, n, _) => + { + if(!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var style)) + { + return; + } + o.Style = style; + } + }, + { + "explode", + (o, n, _) => + { + var explode = n.GetScalarValue(); + if (explode != null) + { + o.Explode = bool.Parse(explode); + } + } + }, + { + "schema", (o, n, t) => + { + o.Schema = LoadSchema(n, t); + } + }, + { + "examples", (o, n, t) => + { + o.Examples = n.CreateMap(LoadExample, t); + } + }, + { + "example", (o, n, _) => + { + o.Example = n.CreateAny(); + } + }, + }; + + private static readonly PatternFieldMap _headerPatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static IOpenApiHeader LoadHeader(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("header"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var headerReference = new OpenApiHeaderReference(reference.Item1, hostDocument, reference.Item2); + headerReference.Reference.SetMetadataFromMapNode(mapNode); + return headerReference; + } + + var header = new OpenApiHeader(); + foreach (var property in mapNode) + { + property.ParseField(header, _headerFixedFields, _headerPatternFields, hostDocument); + } + + return header; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiInfoDeserializer.cs new file mode 100644 index 000000000..e0426a2ac --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiInfoDeserializer.cs @@ -0,0 +1,77 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + public static readonly FixedFieldMap InfoFixedFields = new() + { + { + "title", (o, n, _) => + { + o.Title = n.GetScalarValue(); + } + }, + { + "version", (o, n, _) => + { + o.Version = n.GetScalarValue(); + } + }, + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "termsOfService", + (o, n, _) => + { + var terms = n.GetScalarValue(); + if (terms != null) + { + o.TermsOfService = new(terms, UriKind.RelativeOrAbsolute); + } + } + }, + { + "contact", (o, n, t) => + { + o.Contact = LoadContact(n, t); + } + }, + { + "license", (o, n, t) => + { + o.License = LoadLicense(n, t); + } + } + }; + + public static readonly PatternFieldMap InfoPatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, k, n, _) => o.AddExtension(k,LoadExtension(k, n))} + }; + + public static OpenApiInfo LoadInfo(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("Info"); + var info = new OpenApiInfo(); + ParseMap(mapNode, info, InfoFixedFields, InfoPatternFields, hostDocument); + + return info; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiLicenseDeserializer.cs new file mode 100644 index 000000000..59376695f --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiLicenseDeserializer.cs @@ -0,0 +1,55 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _licenseFixedFields = new() + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "identifier", (o, n, _) => + { + o.Identifier = n.GetScalarValue(); + } + }, + { + "url", + (o, n, _) => + { + var url = n.GetScalarValue(); + if (url != null) + { + o.Url = new(url, UriKind.RelativeOrAbsolute); + } + } + } + }; + + private static readonly PatternFieldMap _licensePatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + internal static OpenApiLicense LoadLicense(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("License"); + + var license = new OpenApiLicense(); + + ParseMap(mapNode, license, _licenseFixedFields, _licensePatternFields, hostDocument); + + return license; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs new file mode 100644 index 000000000..c926b447e --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs @@ -0,0 +1,71 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _linkFixedFields = new() + { + { + "operationRef", (o, n, _) => + { + o.OperationRef = n.GetScalarValue(); + } + }, + { + "operationId", (o, n, _) => + { + o.OperationId = n.GetScalarValue(); + } + }, + { + "parameters", (o, n, _) => + { + o.Parameters = n.CreateSimpleMap(LoadRuntimeExpressionAnyWrapper); + } + }, + { + "requestBody", (o, n, _) => + { + o.RequestBody = LoadRuntimeExpressionAnyWrapper(n); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + {"server", (o, n, t) => o.Server = LoadServer(n, t)} + }; + + private static readonly PatternFieldMap _linkPatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + public static IOpenApiLink LoadLink(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("link"); + var link = new OpenApiLink(); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var linkReference = new OpenApiLinkReference(reference.Item1, hostDocument, reference.Item2); + linkReference.Reference.SetMetadataFromMapNode(mapNode); + return linkReference; + } + + ParseMap(mapNode, link, _linkFixedFields, _linkPatternFields, hostDocument); + + return link; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs new file mode 100644 index 000000000..dca434f8b --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs @@ -0,0 +1,86 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _mediaTypeFixedFields = + new() + { + { + OpenApiConstants.Schema, (o, n, t) => + { + o.Schema = LoadSchema(n, t); + } + }, + { + OpenApiConstants.Examples, (o, n, t) => + { + o.Examples = n.CreateMap(LoadExample, t); + } + }, + { + OpenApiConstants.Example, (o, n, _) => + { + o.Example = n.CreateAny(); + } + }, + { + OpenApiConstants.Encoding, (o, n, t) => + { + o.Encoding = n.CreateMap(LoadEncoding, t); + } + }, + }; + + private static readonly PatternFieldMap _mediaTypePatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + private static readonly AnyFieldMap _mediaTypeAnyFields = new AnyFieldMap + { + { + OpenApiConstants.Example, + new AnyFieldMapParameter( + s => s.Example, + (s, v) => s.Example = v, + s => s.Schema) + } + }; + + + private static readonly AnyMapFieldMap _mediaTypeAnyMapOpenApiExampleFields = + new AnyMapFieldMap + { + { + OpenApiConstants.Examples, + new AnyMapFieldMapParameter( + m => m.Examples, + e => e.Value, + (e, v) => {if (e is OpenApiExample ex) {ex.Value = v;}}, + m => m.Schema) + } + }; + + public static OpenApiMediaType LoadMediaType(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Content); + + var mediaType = new OpenApiMediaType(); + + ParseMap(mapNode, mediaType, _mediaTypeFixedFields, _mediaTypePatternFields, hostDocument); + + ProcessAnyFields(mapNode, mediaType, _mediaTypeAnyFields); + ProcessAnyMapFields(mapNode, mediaType, _mediaTypeAnyMapOpenApiExampleFields); + + return mediaType; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowDeserializer.cs new file mode 100644 index 000000000..d3b9ae370 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowDeserializer.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _oAuthFlowFixedFileds = + new() + { + { + "authorizationUrl", + (o, n, _) => + { + var url = n.GetScalarValue(); + if (url != null) + { + o.AuthorizationUrl = new(url, UriKind.RelativeOrAbsolute); + } + } + }, + { + "tokenUrl", + (o, n, _) => + { + var url = n.GetScalarValue(); + if (url != null) + { + o.TokenUrl = new(url, UriKind.RelativeOrAbsolute); + } + } + }, + { + "refreshUrl", + (o, n, _) => + { + var url = n.GetScalarValue(); + if (url != null) + { + o.RefreshUrl = new(url, UriKind.RelativeOrAbsolute); + } + } + }, + {"scopes", (o, n, _) => o.Scopes = n.CreateSimpleMap(LoadString).Where(kv => kv.Value is not null).ToDictionary(kv => kv.Key, kv => kv.Value!)} + }; + + private static readonly PatternFieldMap _oAuthFlowPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("OAuthFlow"); + + var oauthFlow = new OpenApiOAuthFlow(); + foreach (var property in mapNode) + { + property.ParseField(oauthFlow, _oAuthFlowFixedFileds, _oAuthFlowPatternFields, hostDocument); + } + + return oauthFlow; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowsDeserializer.cs new file mode 100644 index 000000000..e6071400a --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowsDeserializer.cs @@ -0,0 +1,40 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _oAuthFlowsFixedFields = + new() + { + {"implicit", (o, n, t) => o.Implicit = LoadOAuthFlow(n, t)}, + {"password", (o, n, t) => o.Password = LoadOAuthFlow(n, t)}, + {"clientCredentials", (o, n, t) => o.ClientCredentials = LoadOAuthFlow(n, t)}, + {"authorizationCode", (o, n, t) => o.AuthorizationCode = LoadOAuthFlow(n, t)} + }; + + private static readonly PatternFieldMap _oAuthFlowsPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiOAuthFlows LoadOAuthFlows(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("OAuthFlows"); + + var oAuthFlows = new OpenApiOAuthFlows(); + foreach (var property in mapNode) + { + property.ParseField(oAuthFlows, _oAuthFlowsFixedFields, _oAuthFlowsPatternFields, hostDocument); + } + + return oAuthFlows; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiOperationDeserializer.cs new file mode 100644 index 000000000..370731bd5 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiOperationDeserializer.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _operationFixedFields = + new() + { + { + "tags", (o, n, doc) => { + if (n.CreateSimpleList( + (valueNode, doc) => + { + var val = valueNode.GetScalarValue(); + if (string.IsNullOrEmpty(val)) + return null; // Avoid exception on empty tag, we'll remove these from the list further on + return LoadTagByReference(val , doc); + }, + doc) + // Filter out empty tags instead of excepting on them + .OfType().ToList() is {Count: > 0} tags) + { + o.Tags = new HashSet(tags, OpenApiTagComparer.Instance); + } + } + }, + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "externalDocs", (o, n, t) => + { + o.ExternalDocs = LoadExternalDocs(n, t); + } + }, + { + "operationId", (o, n, _) => + { + o.OperationId = n.GetScalarValue(); + } + }, + { + "parameters", (o, n, t) => + { + o.Parameters = n.CreateList(LoadParameter, t); + } + }, + { + "requestBody", (o, n, t) => + { + o.RequestBody = LoadRequestBody(n, t); + } + }, + { + "responses", (o, n, t) => + { + o.Responses = LoadResponses(n, t); + } + }, + { + "callbacks", (o, n, t) => + { + o.Callbacks = n.CreateMap(LoadCallback, t); + } + }, + { + "deprecated", + (o, n, _) => + { + var deprecated = n.GetScalarValue(); + if (deprecated != null) + { + o.Deprecated = bool.Parse(deprecated); + } + } + }, + { + "security", (o, n, t) => + { + if (n.JsonNode is JsonArray) + { + o.Security = n.CreateList(LoadSecurityRequirement, t); + } + } + }, + { + "servers", (o, n, t) => + { + o.Servers = n.CreateList(LoadServer, t); + } + }, + }; + + private static readonly PatternFieldMap _operationPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + internal static OpenApiOperation LoadOperation(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("Operation"); + + var operation = new OpenApiOperation(); + + ParseMap(mapNode, operation, _operationFixedFields, _operationPatternFields, hostDocument); + + return operation; + } + + private static OpenApiTagReference LoadTagByReference(string tagName, OpenApiDocument? hostDocument) + { + var tagObject = new OpenApiTagReference(tagName, hostDocument); + return tagObject; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs new file mode 100644 index 000000000..0cdb4a7bd --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs @@ -0,0 +1,179 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _parameterFixedFields = + new() + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "in", (o, n, _) => + { + if (!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var _in)) + { + return; + } + o.In = _in; + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "required", + (o, n, t) => + { + var required = n.GetScalarValue(); + if (required != null) + { + o.Required = bool.Parse(required); + } + } + }, + { + "deprecated", + (o, n, t) => + { + var deprecated = n.GetScalarValue(); + if (deprecated != null) + { + o.Deprecated = bool.Parse(deprecated); + } + } + }, + { + "allowEmptyValue", + (o, n, t) => + { + var allowEmptyValue = n.GetScalarValue(); + if (allowEmptyValue != null) + { + o.AllowEmptyValue = bool.Parse(allowEmptyValue); + } + } + }, + { + "allowReserved", + (o, n, _) => + { + var allowReserved = n.GetScalarValue(); + if (allowReserved != null) + { + o.AllowReserved = bool.Parse(allowReserved); + } + } + }, + { + "style", (o, n, _) => + { + if (!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var style)) + { + return; + } + o.Style = style; + } + }, + { + "explode", (o, n, _) => + { + var explode = n.GetScalarValue(); + if (explode != null) + { + o.Explode = bool.Parse(explode); + } + } + }, + { + "schema", (o, n, t) => + { + o.Schema = LoadSchema(n, t); + } + }, + { + "content", (o, n, t) => + { + o.Content = n.CreateMap(LoadMediaType, t); + } + }, + { + "examples", (o, n, t) => + { + o.Examples = n.CreateMap(LoadExample, t); + } + }, + { + "example", (o, n, _) => + { + o.Example = n.CreateAny(); + } + }, + }; + + private static readonly PatternFieldMap _parameterPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + private static readonly AnyFieldMap _parameterAnyFields = new AnyFieldMap + { + { + OpenApiConstants.Example, + new AnyFieldMapParameter( + s => s.Example, + (s, v) => s.Example = v, + s => s.Schema) + } + }; + + private static readonly AnyMapFieldMap _parameterAnyMapOpenApiExampleFields = + new AnyMapFieldMap + { + { + OpenApiConstants.Examples, + new AnyMapFieldMapParameter( + m => m.Examples, + e => e.Value, + (e, v) => {if (e is OpenApiExample ex) {ex.Value = v;}}, + m => m.Schema) + } + }; + + public static IOpenApiParameter LoadParameter(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("parameter"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var parameterReference = new OpenApiParameterReference(reference.Item1, hostDocument, reference.Item2); + parameterReference.Reference.SetMetadataFromMapNode(mapNode); + return parameterReference; + } + + var parameter = new OpenApiParameter(); + + ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields, hostDocument); + ProcessAnyFields(mapNode, parameter, _parameterAnyFields); + ProcessAnyMapFields(mapNode, parameter, _parameterAnyMapOpenApiExampleFields); + + return parameter; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs new file mode 100644 index 000000000..e5a4a7e5f --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs @@ -0,0 +1,71 @@ +using System; +using System.Net.Http; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _pathItemFixedFields = new() + { + + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + {"get", (o, n, t) => o.AddOperation(HttpMethod.Get, LoadOperation(n, t))}, + {"put", (o, n, t) => o.AddOperation(HttpMethod.Put, LoadOperation(n, t))}, + {"post", (o, n, t) => o.AddOperation(HttpMethod.Post, LoadOperation(n, t))}, + {"delete", (o, n, t) => o.AddOperation(HttpMethod.Delete, LoadOperation(n, t))}, + {"options", (o, n, t) => o.AddOperation(HttpMethod.Options, LoadOperation(n, t))}, + {"head", (o, n, t) => o.AddOperation(HttpMethod.Head, LoadOperation(n, t))}, +#if NETSTANDARD2_1_OR_GREATER + {"patch", (o, n, t) => o.AddOperation(HttpMethod.Patch, LoadOperation(n, t))}, +#else + {"patch", (o, n, t) => o.AddOperation(new HttpMethod("PATCH"), LoadOperation(n, t))}, +#endif + {"trace", (o, n, t) => o.AddOperation(HttpMethod.Trace, LoadOperation(n, t))}, + {"servers", (o, n, t) => o.Servers = n.CreateList(LoadServer, t)}, + {"parameters", (o, n, t) => o.Parameters = n.CreateList(LoadParameter, t)} + }; + + private static readonly PatternFieldMap _pathItemPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static IOpenApiPathItem LoadPathItem(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("PathItem"); + + var pointer = mapNode.GetReferencePointer(); + + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var pathItemReference = new OpenApiPathItemReference(reference.Item1, hostDocument, reference.Item2); + pathItemReference.Reference.SetMetadataFromMapNode(mapNode); + return pathItemReference; + } + + var pathItem = new OpenApiPathItem(); + + ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields, hostDocument); + + return pathItem; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiPathsDeserializer.cs new file mode 100644 index 000000000..1790553e6 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiPathsDeserializer.cs @@ -0,0 +1,31 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _pathsFixedFields = new(); + + private static readonly PatternFieldMap _pathsPatternFields = new() + { + {s => s.StartsWith("/", StringComparison.OrdinalIgnoreCase), (o, k, n, t) => o.Add(k, LoadPathItem(n, t))}, + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiPaths LoadPaths(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("Paths"); + + var domainObject = new OpenApiPaths(); + + ParseMap(mapNode, domainObject, _pathsFixedFields, _pathsPatternFields, hostDocument); + + return domainObject; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs new file mode 100644 index 000000000..0dc03c99d --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs @@ -0,0 +1,67 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _requestBodyFixedFields = + new() + { + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "content", (o, n, t) => + { + o.Content = n.CreateMap(LoadMediaType, t); + } + }, + { + "required", (o, n, _) => + { + var required = n.GetScalarValue(); + if (required != null) + { + o.Required = bool.Parse(required); + } + } + }, + }; + + private static readonly PatternFieldMap _requestBodyPatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static IOpenApiRequestBody LoadRequestBody(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("requestBody"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var requestBodyReference = new OpenApiRequestBodyReference(reference.Item1, hostDocument, reference.Item2); + requestBodyReference.Reference.SetMetadataFromMapNode(mapNode); + return requestBodyReference; + } + + var requestBody = new OpenApiRequestBody(); + foreach (var property in mapNode) + { + property.ParseField(requestBody, _requestBodyFixedFields, _requestBodyPatternFields, hostDocument); + } + + return requestBody; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs new file mode 100644 index 000000000..eec9a03d1 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs @@ -0,0 +1,65 @@ +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _responseFixedFields = new() + { + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "headers", (o, n, t) => + { + o.Headers = n.CreateMap(LoadHeader, t); + } + }, + { + "content", (o, n, t) => + { + o.Content = n.CreateMap(LoadMediaType, t); + } + }, + { + "links", (o, n, t) => + { + o.Links = n.CreateMap(LoadLink, t); + } + } + }; + + private static readonly PatternFieldMap _responsePatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("response"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var responseReference = new OpenApiResponseReference(reference.Item1, hostDocument, reference.Item2); + responseReference.Reference.SetMetadataFromMapNode(mapNode); + return responseReference; + } + + var response = new OpenApiResponse(); + ParseMap(mapNode, response, _responseFixedFields, _responsePatternFields, hostDocument); + + return response; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiResponsesDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponsesDeserializer.cs new file mode 100644 index 000000000..972000b0a --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponsesDeserializer.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + public static readonly FixedFieldMap ResponsesFixedFields = new(); + + public static readonly PatternFieldMap ResponsesPatternFields = new() + { + {s => !s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, t) => o.Add(p, LoadResponse(n, t))}, + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiResponses LoadResponses(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("Responses"); + + var domainObject = new OpenApiResponses(); + + ParseMap(mapNode, domainObject, ResponsesFixedFields, ResponsesPatternFields, hostDocument); + + return domainObject; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs new file mode 100644 index 000000000..ecc36afee --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -0,0 +1,412 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json.Nodes; + +namespace Microsoft.OpenApi.Reader.V32; +internal static partial class OpenApiV32Deserializer +{ + private static readonly FixedFieldMap _openApiSchemaFixedFields = new() + { + { + "title", + (o, n, _) => o.Title = n.GetScalarValue() + }, + { + "$schema", + (o, n, _) => { if (n.GetScalarValue() is string {} sSchema && Uri.TryCreate(sSchema, UriKind.Absolute, out var schema)) {o.Schema = schema;}} + }, + { + "$id", + (o, n, _) => o.Id = n.GetScalarValue() + }, + { + "$comment", + (o, n, _) => o.Comment = n.GetScalarValue() + }, + { + "$vocabulary", + (o, n, _) => o.Vocabulary = n.CreateSimpleMap(LoadBool).ToDictionary(kvp => kvp.Key, kvp => kvp.Value ?? false) + }, + { + "$dynamicRef", + (o, n, _) => o.DynamicRef = n.GetScalarValue() + }, + { + "$dynamicAnchor", + (o, n, _) => o.DynamicAnchor = n.GetScalarValue() + }, + { + "$defs", + (o, n, t) => o.Definitions = n.CreateMap(LoadSchema, t) + }, + { + "multipleOf", + (o, n, _) => + { + var multipleOf = n.GetScalarValue(); + if (multipleOf != null) + { + o.MultipleOf = decimal.Parse(multipleOf, NumberStyles.Float, CultureInfo.InvariantCulture); + } + } + }, + { + "maximum", + (o, n,_) => + { + var max = n.GetScalarValue(); + if (!string.IsNullOrEmpty(max)) + { + o.Maximum = max; + } + } + }, + { + "exclusiveMaximum", + (o, n, _) => o.ExclusiveMaximum = n.GetScalarValue() + }, + { + "minimum", + (o, n, _) => + { + var min = n.GetScalarValue(); + if (!string.IsNullOrEmpty(min)) + { + o.Minimum = min; + } + } + }, + { + "exclusiveMinimum", + (o, n, _) => o.ExclusiveMinimum = n.GetScalarValue() + }, + { + "maxLength", + (o, n, _) => + { + var maxLength = n.GetScalarValue(); + if (maxLength != null) + { + o.MaxLength = int.Parse(maxLength, CultureInfo.InvariantCulture); + } + } + }, + { + "minLength", + (o, n, _) => + { + var minLength = n.GetScalarValue(); + if (minLength != null) + { + o.MinLength = int.Parse(minLength, CultureInfo.InvariantCulture); + } + } + }, + { + "pattern", + (o, n, _) => o.Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n, _) => + { + var maxItems = n.GetScalarValue(); + if (maxItems != null) + { + o.MaxItems = int.Parse(maxItems, CultureInfo.InvariantCulture); + } + } + }, + { + "minItems", + (o, n, _) => + { + var minItems = n.GetScalarValue(); + if (minItems != null) + { + o.MinItems = int.Parse(minItems, CultureInfo.InvariantCulture); + } + } + }, + { + "uniqueItems", + (o, n, _) => + { + var uniqueItems = n.GetScalarValue(); + if (uniqueItems != null) + { + o.UniqueItems = bool.Parse(uniqueItems); + } + } + }, + { + "unevaluatedProperties", + (o, n, _) => + { + var unevaluatedProps = n.GetScalarValue(); + if (unevaluatedProps != null) + { + o.UnevaluatedProperties = bool.Parse(unevaluatedProps); + } + } + }, + { + "maxProperties", + (o, n, _) => + { + var maxProps = n.GetScalarValue(); + if (maxProps != null) + { + o.MaxProperties = int.Parse(maxProps, CultureInfo.InvariantCulture); + } + } + }, + { + "minProperties", + (o, n, _) => + { + var minProps = n.GetScalarValue(); + if (minProps != null) + { + o.MinProperties = int.Parse(minProps, CultureInfo.InvariantCulture); + } + } + }, + { + "required", + (o, n, doc) => o.Required = new HashSet(n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc).Where(s => s != null)) + }, + { + "enum", + (o, n, _) => o.Enum = n.CreateListOfAny() + }, + { + "type", + (o, n, doc) => + { + if (n is ValueNode) + { + o.Type = n.GetScalarValue()?.ToJsonSchemaType(); + } + else + { + var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc); + JsonSchemaType combinedType = 0; + foreach(var type in list) + { + if (type is not null) + { + var schemaType = type.ToJsonSchemaType(); + combinedType |= schemaType; + } + } + o.Type = combinedType; + } + } + }, + { + "const", + (o, n, _) => o.Const = n.GetScalarValue() + }, + { + "allOf", + (o, n, t) => o.AllOf = n.CreateList(LoadSchema, t) + }, + { + "oneOf", + (o, n, t) => o.OneOf = n.CreateList(LoadSchema, t) + }, + { + "anyOf", + (o, n, t) => o.AnyOf = n.CreateList(LoadSchema, t) + }, + { + "not", + (o, n, doc) => o.Not = LoadSchema(n, doc) + }, + { + "items", + (o, n, doc) => o.Items = LoadSchema(n, doc) + }, + { + "properties", + (o, n, t) => o.Properties = n.CreateMap(LoadSchema, t) + }, + { + "patternProperties", + (o, n, t) => o.PatternProperties = n.CreateMap(LoadSchema, t) + }, + { + "additionalProperties", (o, n, doc) => + { + if (n is ValueNode) + { + var value = n.GetScalarValue(); + if (value is not null) + { + o.AdditionalPropertiesAllowed = bool.Parse(value); + } + } + else + { + o.AdditionalProperties = LoadSchema(n, doc); + } + } + }, + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + { + "format", + (o, n, _) => o.Format = n.GetScalarValue() + }, + { + "default", + (o, n, _) => o.Default = n.CreateAny() + }, + { + "nullable", + (o, n, _) => + { + var value = n.GetScalarValue(); + if (value is not null) + { + var nullable = bool.Parse(value); + if (nullable) // if nullable, convert type into an array of type(s) and null + { + if (o.Type.HasValue) + o.Type |= JsonSchemaType.Null; + else + o.Type = JsonSchemaType.Null; + } + } + } + }, + { + "discriminator", + (o, n, doc) => o.Discriminator = LoadDiscriminator(n, doc) + }, + { + "readOnly", + (o, n, _) => + { + var readOnly = n.GetScalarValue(); + if (readOnly != null) + { + o.ReadOnly = bool.Parse(readOnly); + } + } + }, + { + "writeOnly", + (o, n, _) => + { + var writeOnly = n.GetScalarValue(); + if (writeOnly != null) + { + o.WriteOnly = bool.Parse(writeOnly); + } + } + }, + { + "xml", + (o, n, doc) => o.Xml = LoadXml(n, doc) + }, + { + "externalDocs", + (o, n, doc) => o.ExternalDocs = LoadExternalDocs(n, doc) + }, + { + "example", + (o, n, _) => o.Example = n.CreateAny() + }, + { + "examples", + (o, n, _) => o.Examples = n.CreateListOfAny() + }, + { + "deprecated", + (o, n, t) => + { + var deprecated = n.GetScalarValue(); + if (deprecated != null) + { + o.Deprecated = bool.Parse(deprecated); + } + } + }, + { + "dependentRequired", + (o, n, doc) => + { + o.DependentRequired = n.CreateArrayMap((n2, p) => n2.GetScalarValue(), doc); + } + }, + }; + + private static readonly PatternFieldMap _openApiSchemaPatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static IOpenApiSchema LoadSchema(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + + var pointer = mapNode.GetReferencePointer(); + var identifier = mapNode.GetJsonSchemaIdentifier(); + var nodeLocation = node.Context.GetLocation(); + + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var result = new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); + result.Reference.SetMetadataFromMapNode(mapNode); + result.Reference.SetJsonPointerPath(pointer, nodeLocation); + return result; + } + + var schema = new OpenApiSchema(); + + foreach (var propertyNode in mapNode) + { + bool isRecognized = _openApiSchemaFixedFields.ContainsKey(propertyNode.Name) || + _openApiSchemaPatternFields.Any(p => p.Key(propertyNode.Name)); + + if (isRecognized) + { + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields, hostDocument); + } + else if (propertyNode.JsonNode is not null) + { + schema.UnrecognizedKeywords ??= new Dictionary(StringComparer.Ordinal); + schema.UnrecognizedKeywords[propertyNode.Name] = propertyNode.JsonNode; + } + } + + if (schema.Extensions is not null && schema.Extensions.ContainsKey(OpenApiConstants.NullableExtension)) + { + if (schema.Type.HasValue) + schema.Type |= JsonSchemaType.Null; + else + schema.Type = JsonSchemaType.Null; + + schema.Extensions.Remove(OpenApiConstants.NullableExtension); + } + + if (!string.IsNullOrEmpty(identifier) && hostDocument.Workspace is not null) + { + // register the schema in our registry using the identifier's URL + hostDocument.Workspace.RegisterComponentForDocument(hostDocument, schema, identifier!); + } + + return schema; + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecurityRequirementDeserializer.cs new file mode 100644 index 000000000..a2f1326a4 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecurityRequirementDeserializer.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Linq; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("security"); + + var securityRequirement = new OpenApiSecurityRequirement(); + + foreach (var property in mapNode) + { + var scheme = LoadSecuritySchemeByReference(property.Name, hostDocument); + + var scopes = property.Value.CreateSimpleList((n2, p) => n2.GetScalarValue(), hostDocument) + .OfType() + .ToList(); + if (scheme != null) + { + securityRequirement.Add(scheme, scopes); + } + else + { + mapNode.Context.Diagnostic.Errors.Add( + new OpenApiError(node.Context.GetLocation(), $"Scheme {property.Name} is not found")); + } + } + + return securityRequirement; + } + + private static OpenApiSecuritySchemeReference LoadSecuritySchemeByReference(string schemeName, OpenApiDocument? hostDocument) + { + return new OpenApiSecuritySchemeReference(schemeName, hostDocument); + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs new file mode 100644 index 000000000..7278df227 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _securitySchemeFixedFields = + new() + { + { + "type", (o, n, _) => + { + if (!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var type)) + { + return; + } + o.Type = type; + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "in", (o, n, _) => + { + if (!n.GetScalarValue().TryGetEnumFromDisplayName(n.Context, out var _in)) + { + return; + } + o.In = _in; + } + }, + { + "scheme", (o, n, _) => + { + o.Scheme = n.GetScalarValue(); + } + }, + { + "bearerFormat", (o, n, _) => + { + o.BearerFormat = n.GetScalarValue(); + } + }, + { + "openIdConnectUrl", (o, n, _) => + { + var connectUrl = n.GetScalarValue(); + if (connectUrl != null) + { + o.OpenIdConnectUrl = new(connectUrl, UriKind.RelativeOrAbsolute); + } + } + }, + { + "flows", (o, n, t) => + { + o.Flows = LoadOAuthFlows(n, t); + } + } + }; + + private static readonly PatternFieldMap _securitySchemePatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static IOpenApiSecurityScheme LoadSecurityScheme(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("securityScheme"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + var securitySchemeReference = new OpenApiSecuritySchemeReference(reference.Item1, hostDocument, reference.Item2); + securitySchemeReference.Reference.SetMetadataFromMapNode(mapNode); + return securitySchemeReference; + } + + var securityScheme = new OpenApiSecurityScheme(); + foreach (var property in mapNode) + { + property.ParseField(securityScheme, _securitySchemeFixedFields, _securitySchemePatternFields, hostDocument); + } + + return securityScheme; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiServerDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiServerDeserializer.cs new file mode 100644 index 000000000..428818c0a --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiServerDeserializer.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _serverFixedFields = new() + { + { + "url", (o, n, _) => + { + o.Url = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "variables", (o, n, t) => + { + o.Variables = n.CreateMap(LoadServerVariable, t); + } + } + }; + + private static readonly PatternFieldMap _serverPatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiServer LoadServer(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("server"); + + var server = new OpenApiServer(); + + ParseMap(mapNode, server, _serverFixedFields, _serverPatternFields, hostDocument); + + return server; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiServerVariableDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiServerVariableDeserializer.cs new file mode 100644 index 000000000..08169d474 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiServerVariableDeserializer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Linq; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _serverVariableFixedFields = + new() + { + { + "enum", (o, n, doc) => + { + o.Enum = n.CreateSimpleList((s, p) => s.GetScalarValue(), doc).OfType().ToList(); + } + }, + { + "default", (o, n, _) => + { + o.Default = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + }; + + private static readonly PatternFieldMap _serverVariablePatternFields = + new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiServerVariable LoadServerVariable(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("serverVariable"); + + var serverVariable = new OpenApiServerVariable(); + + ParseMap(mapNode, serverVariable, _serverVariableFixedFields, _serverVariablePatternFields, hostDocument); + + return serverVariable; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiTagDeserializer.cs new file mode 100644 index 000000000..9a5468cc6 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiTagDeserializer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _tagFixedFields = new() + { + { + OpenApiConstants.Name, (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + OpenApiConstants.Description, (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + OpenApiConstants.ExternalDocs, (o, n, t) => + { + o.ExternalDocs = LoadExternalDocs(n, t); + } + } + }; + + private static readonly PatternFieldMap _tagPatternFields = new() + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiTag LoadTag(ParseNode n, OpenApiDocument hostDocument) + { + var mapNode = n.CheckMapNode("tag"); + + var domainObject = new OpenApiTag(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(domainObject, _tagFixedFields, _tagPatternFields, hostDocument); + } + + return domainObject; + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs new file mode 100644 index 000000000..9b062931d --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Linq; +using System.Text.Json.Nodes; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static void ParseMap( + MapNode? mapNode, + T domainObject, + FixedFieldMap fixedFieldMap, + PatternFieldMap patternFieldMap, + OpenApiDocument doc) + { + if (mapNode == null) + { + return; + } + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(domainObject, fixedFieldMap, patternFieldMap, doc); + } + + } + + private static void ProcessAnyFields( + MapNode mapNode, + T domainObject, + AnyFieldMap anyFieldMap) + { + foreach (var anyFieldName in anyFieldMap.Keys.ToList()) + { + try + { + mapNode.Context.StartObject(anyFieldName); + + var any = anyFieldMap[anyFieldName].PropertyGetter(domainObject); + + if (any == null) + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, null); + } + else + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, any); + } + } + catch (OpenApiException exception) + { + exception.Pointer = mapNode.Context.GetLocation(); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); + } + finally + { + mapNode.Context.EndObject(); + } + } + } + + private static void ProcessAnyMapFields( + MapNode mapNode, + T domainObject, + AnyMapFieldMap anyMapFieldMap) + { + foreach (var anyMapFieldName in anyMapFieldMap.Keys.ToList()) + { + try + { + mapNode.Context.StartObject(anyMapFieldName); + var propertyMapGetter = anyMapFieldMap[anyMapFieldName].PropertyMapGetter(domainObject); + if (propertyMapGetter != null) + { + foreach (var propertyMapElement in propertyMapGetter) + { + mapNode.Context.StartObject(propertyMapElement.Key); + + if (propertyMapElement.Value != null) + { + var any = anyMapFieldMap[anyMapFieldName].PropertyGetter(propertyMapElement.Value); + if (any is not null) + { + anyMapFieldMap[anyMapFieldName].PropertySetter(propertyMapElement.Value, any); + } + } + } + } + } + catch (OpenApiException exception) + { + exception.Pointer = mapNode.Context.GetLocation(); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); + } + finally + { + mapNode.Context.EndObject(); + } + } + } + + private static RuntimeExpressionAnyWrapper LoadRuntimeExpressionAnyWrapper(ParseNode node) + { + var value = node.GetScalarValue(); + + if (value != null && value.StartsWith("$", StringComparison.OrdinalIgnoreCase)) + { + return new RuntimeExpressionAnyWrapper + { + Expression = RuntimeExpression.Build(value) + }; + } + + return new RuntimeExpressionAnyWrapper + { + Any = node.CreateAny() + }; + } + + public static JsonNode LoadAny(ParseNode node, OpenApiDocument hostDocument) + { + return node.CreateAny(); + } + + private static IOpenApiExtension LoadExtension(string name, ParseNode node) + { + return node.Context.ExtensionParsers is not null && node.Context.ExtensionParsers.TryGetValue(name, out var parser) + ? parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_2) + : new JsonNodeExtension(node.CreateAny()); + } + + private static string? LoadString(ParseNode node) + { + return node.GetScalarValue(); + } + + private static bool? LoadBool(ParseNode node) + { + var value = node.GetScalarValue(); + return value is not null ? bool.Parse(value) : null; + } + + private static (string, string?) GetReferenceIdAndExternalResource(string pointer) + { + /* Check whether the reference pointer is a URL + * (id keyword allows you to supply a URL for the schema as a target for referencing) + * E.g. $ref: 'https://example.com/schemas/resource.json' + * or its a normal json pointer fragment syntax + * E.g. $ref: '#/components/schemas/pet' + */ + var refSegments = pointer.Split('/'); + string refId = !pointer.Contains('#') ? pointer : refSegments[refSegments.Count()-1]; + + var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase); + string? externalResource = null; + if (isExternalResource && pointer.Contains('#')) + { + externalResource = pointer.Split('#')[0].TrimEnd('#'); + } + + return (refId, externalResource); + } + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs new file mode 100644 index 000000000..51f00b86d --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// The version service for the Open API V3.1. + /// + internal class OpenApiV32VersionService : BaseOpenApiVersionService + { + + /// + /// Create Parsing Context + /// + /// Provide instance for diagnostic object for collecting and accessing information about the parsing. + public OpenApiV32VersionService(OpenApiDiagnostic diagnostic):base(diagnostic) + { + } + + private readonly Dictionary> _loaders = new Dictionary> + { + [typeof(JsonNodeExtension)] = OpenApiV32Deserializer.LoadAny, + [typeof(OpenApiCallback)] = OpenApiV32Deserializer.LoadCallback, + [typeof(OpenApiComponents)] = OpenApiV32Deserializer.LoadComponents, + [typeof(OpenApiContact)] = OpenApiV32Deserializer.LoadContact, + [typeof(OpenApiDiscriminator)] = OpenApiV32Deserializer.LoadDiscriminator, + [typeof(OpenApiEncoding)] = OpenApiV32Deserializer.LoadEncoding, + [typeof(OpenApiExample)] = OpenApiV32Deserializer.LoadExample, + [typeof(OpenApiExternalDocs)] = OpenApiV32Deserializer.LoadExternalDocs, + [typeof(OpenApiHeader)] = OpenApiV32Deserializer.LoadHeader, + [typeof(OpenApiInfo)] = OpenApiV32Deserializer.LoadInfo, + [typeof(OpenApiLicense)] = OpenApiV32Deserializer.LoadLicense, + [typeof(OpenApiLink)] = OpenApiV32Deserializer.LoadLink, + [typeof(OpenApiMediaType)] = OpenApiV32Deserializer.LoadMediaType, + [typeof(OpenApiOAuthFlow)] = OpenApiV32Deserializer.LoadOAuthFlow, + [typeof(OpenApiOAuthFlows)] = OpenApiV32Deserializer.LoadOAuthFlows, + [typeof(OpenApiOperation)] = OpenApiV32Deserializer.LoadOperation, + [typeof(OpenApiParameter)] = OpenApiV32Deserializer.LoadParameter, + [typeof(OpenApiPathItem)] = OpenApiV32Deserializer.LoadPathItem, + [typeof(OpenApiPaths)] = OpenApiV32Deserializer.LoadPaths, + [typeof(OpenApiRequestBody)] = OpenApiV32Deserializer.LoadRequestBody, + [typeof(OpenApiResponse)] = OpenApiV32Deserializer.LoadResponse, + [typeof(OpenApiResponses)] = OpenApiV32Deserializer.LoadResponses, + [typeof(OpenApiSchema)] = OpenApiV32Deserializer.LoadSchema, + [typeof(OpenApiSecurityRequirement)] = OpenApiV32Deserializer.LoadSecurityRequirement, + [typeof(OpenApiSecurityScheme)] = OpenApiV32Deserializer.LoadSecurityScheme, + [typeof(OpenApiServer)] = OpenApiV32Deserializer.LoadServer, + [typeof(OpenApiServerVariable)] = OpenApiV32Deserializer.LoadServerVariable, + [typeof(OpenApiTag)] = OpenApiV32Deserializer.LoadTag, + [typeof(OpenApiXml)] = OpenApiV32Deserializer.LoadXml, + [typeof(OpenApiSchemaReference)] = OpenApiV32Deserializer.LoadMapping + }; + + public override OpenApiDocument LoadDocument(RootNode rootNode, Uri location) + { + return OpenApiV32Deserializer.LoadOpenApi(rootNode, location); + } + internal override Dictionary> Loaders => _loaders; + + } +} + diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs new file mode 100644 index 000000000..a0169629a --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiXmlDeserializer.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Reader.V32 +{ + /// + /// Class containing logic to deserialize Open API V32 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV32Deserializer + { + private static readonly FixedFieldMap _xmlFixedFields = new FixedFieldMap + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "namespace", + (o, n, _) => + { + var value = n.GetScalarValue(); + if (value != null) + { + o.Namespace = new(value, UriKind.Absolute); + } + } + }, + { + "prefix", + (o, n, _) => o.Prefix = n.GetScalarValue() + }, + { + "attribute", + (o, n, _) => + { + var attribute = n.GetScalarValue(); + if (attribute is not null) + { + o.Attribute = bool.Parse(attribute); + } + } + }, + { + "wrapped", + (o, n, _) => + { + var wrapped = n.GetScalarValue(); + if (wrapped is not null) + { + o.Wrapped = bool.Parse(wrapped); + } + } + } + }; + + private static readonly PatternFieldMap _xmlPatternFields = + new PatternFieldMap + { + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiXml LoadXml(ParseNode node, OpenApiDocument hostDocument) + { + var mapNode = node.CheckMapNode("xml"); + + var xml = new OpenApiXml(); + foreach (var property in mapNode) + { + property.ParseField(xml, _xmlFixedFields, _xmlPatternFields, hostDocument); + } + + return xml; + } + } +} + From e992717df036ac502e3876cfad470109b9adb9b4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 23 Sep 2025 22:01:36 -0400 Subject: [PATCH 004/146] chore: updates existing failing tests --- ...DocumentWith31PropertiesWorks.verified.txt | 2 +- .../OpenApiDocument/docWith31properties.json | 2 +- .../OpenApiDocument/docWithExample.yaml | 2 +- .../docWithPatternPropertiesInSchema.yaml | 2 +- .../OpenApiDocument/docWithReferenceById.yaml | 2 +- ...docWithReferencedExampleInSchemaWorks.yaml | 2 +- .../documentWith31Properties.yaml | 2 +- .../documentWithReusablePaths.yaml | 2 +- ...tWithSummaryAndDescriptionInReference.yaml | 2 +- .../OpenApiDocument/documentWithWebhooks.yaml | 2 +- .../OpenApiDocument/externalRefById.yaml | 2 +- .../externalRefByJsonPointer.yaml | 2 +- .../OpenApiDocument/externalResource.yaml | 2 +- .../V3Tests/OpenApiDocumentTests.cs | 2 +- ...WorksAsync_version=OpenApi3_1.verified.txt | 2 +- ...WorksAsync_version=OpenApi3_2.verified.txt | 495 ++++++++++++++++++ ...orks_produceTerseOutput=False.verified.txt | 2 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- .../Models/OpenApiDocumentTests.cs | 6 +- .../OpenApiPathItemReferenceTests.cs | 4 +- .../References/OpenApiTagReferenceTest.cs | 4 +- .../Models/Samples/docWithDollarId.yaml | 2 +- .../Samples/docWithReusableWebhooks.yaml | 2 +- .../PublicApi/PublicApi.approved.txt | 37 ++ .../Reader/OpenApiModelFactoryTests.cs | 4 +- 25 files changed, 560 insertions(+), 28 deletions(-) create mode 100644 test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_2.verified.txt diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.ParseDocumentWith31PropertiesWorks.verified.txt b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.ParseDocumentWith31PropertiesWorks.verified.txt index 71e0e5887..e47eb7eff 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.ParseDocumentWith31PropertiesWorks.verified.txt +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.ParseDocumentWith31PropertiesWorks.verified.txt @@ -1,4 +1,4 @@ -openapi: '3.1.1' +openapi: '3.1.2' jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema info: title: Sample OpenAPI 3.1 API diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWith31properties.json b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWith31properties.json index aabf7b10e..f326be6cb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWith31properties.json +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWith31properties.json @@ -1,5 +1,5 @@ { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": { "title": "Sample OpenAPI 3.1 API", "description": "A sample API demonstrating OpenAPI 3.1 features", diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml index f2a5dae79..df4a5947a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 # The version of the OpenAPI Specification +openapi: 3.1.2 # The version of the OpenAPI Specification info: # Metadata about the API title: A simple OpenAPI 3.1 example version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml index bc70bb95e..d019c619f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: Example API version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferenceById.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferenceById.yaml index b02ce3e57..83c4a9be0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferenceById.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferenceById.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: ReferenceById version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml index 2df12cda5..343f8f724 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: ReferenceById version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWith31Properties.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWith31Properties.yaml index b64b5aa7e..2ac973e5f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWith31Properties.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWith31Properties.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: Sample OpenAPI 3.1 API description: A sample API demonstrating OpenAPI 3.1 features diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml index 148ff40c2..15453cb28 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml @@ -1,4 +1,4 @@ -openapi : 3.1.1 +openapi : 3.1.2 info: title: Webhook Example version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml index d789b48e9..6f443f7d3 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml @@ -1,4 +1,4 @@ -openapi: '3.1.1' +openapi: '3.1.2' info: version: '1.0.0' title: Swagger Petstore (Simple) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml index ee15f6849..cf9b8f62a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: Webhook Example version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml index 35eece7f8..37dcdee78 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: ReferenceById version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml index 0903bc27b..b2875c60b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: ReferenceById version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml index 19bb025d0..ea122bb70 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: ReferencedById version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 10e4c4e0e..26cd13a99 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -1187,7 +1187,7 @@ public async Task ValidateExampleShouldNotHaveDataTypeMismatch() "title": "Pet Store with double hop references", "version": "1.0.0" }, - "openapi": "3.1.1", + "openapi": "3.1.2", "paths": { "/pets": { "get": { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_1.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_1.verified.txt index 3c9768fe9..c8d2a76d9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_1.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_1.verified.txt @@ -1,5 +1,5 @@ { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": { "title": "Swagger Petstore (Simple)", "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_2.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_2.verified.txt new file mode 100644 index 000000000..a29f040b0 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsVersionJsonWorksAsync_version=OpenApi3_2.verified.txt @@ -0,0 +1,495 @@ +{ + "openapi": "3.2.0", + "info": { + "title": "Swagger Petstore (Simple)", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Swagger API team", + "url": "http://swagger.io", + "email": "foo@example.com" + }, + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/MIT" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://petstore.swagger.io/api" + } + ], + "paths": { + "/pets": { + "get": { + "description": "Returns all pets from the system that the user has access to", + "operationId": "findPets", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "tags to filter by", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "limit", + "in": "query", + "description": "maximum number of results to return", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + } + } + } + }, + "4XX": { + "description": "unexpected client error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "5XX": { + "description": "unexpected server error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "post": { + "description": "Creates a new pet in the store. Duplicates are allowed", + "operationId": "addPet", + "requestBody": { + "description": "Pet to add to the store", + "content": { + "application/json": { + "schema": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + } + } + }, + "4XX": { + "description": "unexpected client error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "5XX": { + "description": "unexpected server error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/pets/{id}": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "findPetById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to fetch", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + }, + "application/xml": { + "schema": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + } + } + }, + "4XX": { + "description": "unexpected client error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "5XX": { + "description": "unexpected server error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "description": "deletes a single pet based on the ID supplied", + "operationId": "deletePet", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "204": { + "description": "pet deleted" + }, + "4XX": { + "description": "unexpected client error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "5XX": { + "description": "unexpected server error", + "content": { + "text/html": { + "schema": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "pet": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "newPet": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "errorModel": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=False.verified.txt index 417f9cbea..e4a3ca482 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -1,5 +1,5 @@ { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": { "title": "Webhook Example", "version": "1.0.0" diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=True.verified.txt index 5c0c4058d..d07fac270 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"openapi":"3.1.1","info":{"title":"Webhook Example","version":"1.0.0"},"paths":{},"components":{"schemas":{"Pet":{"required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"webhooks":{"newPet":{"post":{"requestBody":{"description":"Information about a new pet in the system","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}}}},"responses":{"200":{"description":"Return a 200 status to indicate that the data was received successfully"}}}}}} \ No newline at end of file +{"openapi":"3.1.2","info":{"title":"Webhook Example","version":"1.0.0"},"paths":{},"components":{"schemas":{"Pet":{"required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"webhooks":{"newPet":{"post":{"requestBody":{"description":"Information about a new pet in the system","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}}}},"responses":{"200":{"description":"Return a 200 status to indicate that the data was received successfully"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 5e3644f1b..fefd8ce0a 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -1892,7 +1892,7 @@ public async Task SerializeDocumentWithWebhooksAsV3JsonWorks(bool produceTerseOu public async Task SerializeDocumentWithWebhooksAsV3YamlWorks() { // Arrange - var expected = @"openapi: '3.1.1' + var expected = @"openapi: '3.1.2' info: title: Webhook Example version: 1.0.0 @@ -1947,7 +1947,7 @@ public async Task SerializeDocumentWithRootJsonSchemaDialectPropertyWorks() JsonSchemaDialect = new Uri("http://json-schema.org/draft-07/schema#") }; - var expected = @"openapi: '3.1.1' + var expected = @"openapi: '3.1.2' jsonSchemaDialect: http://json-schema.org/draft-07/schema# info: title: JsonSchemaDialectTest @@ -1990,7 +1990,7 @@ public async Task SerializeV31DocumentWithRefsInWebhooksWorks() [Fact] public async Task SerializeDocWithDollarIdInDollarRefSucceeds() { - var expected = @"openapi: '3.1.1' + var expected = @"openapi: '3.1.2' info: title: Simple API version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index e4a98a0a1..5c70a1ffa 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.OpenApi.Tests.Models.References public class OpenApiPathItemReferenceTests { private const string OpenApi = @" -openapi: 3.1.1 +openapi: 3.1.2 info: title: Sample API version: 1.0.0 @@ -36,7 +36,7 @@ public class OpenApiPathItemReferenceTests "; private const string OpenApi_2 = @" -openapi: 3.1.1 +openapi: 3.1.2 info: title: Sample API version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs index df008d516..e760982d2 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs @@ -234,7 +234,7 @@ public async Task SerializesADescriptionWithNoMatchingTagDefinition(OpenApiSpecV OpenApiSpecVersion.OpenApi3_1 => """ { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": {}, "paths": { "/test": { @@ -275,7 +275,7 @@ public async Task SerializesADescriptionWithNoMatchingTagDefinition(OpenApiSpecV [InlineData( """ { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": {}, "paths": { "/test": { diff --git a/test/Microsoft.OpenApi.Tests/Models/Samples/docWithDollarId.yaml b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithDollarId.yaml index 8ac0816fa..ee6ee6df5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/Samples/docWithDollarId.yaml +++ b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithDollarId.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.1 +openapi: 3.1.2 info: title: Simple API version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Tests/Models/Samples/docWithReusableWebhooks.yaml b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithReusableWebhooks.yaml index 844c9caf0..f4c0c35c5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/Samples/docWithReusableWebhooks.yaml +++ b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithReusableWebhooks.yaml @@ -1,4 +1,4 @@ -openapi : 3.1.1 +openapi : 3.1.2 info: title: Webhook Example version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 408ddb15e..a00be2a9b 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -20,9 +20,11 @@ namespace Microsoft.OpenApi public string? ReferenceV3 { get; } public Microsoft.OpenApi.ReferenceType Type { get; init; } protected virtual void SerializeAdditionalV31Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } + protected virtual void SerializeAdditionalV32Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } protected virtual void SetAdditional31MetadataFromMapNode(System.Text.Json.Nodes.JsonObject jsonObject) { } protected static string? GetPropertyValueFromNode(System.Text.Json.Nodes.JsonObject jsonObject, string key) { } } @@ -42,6 +44,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public sealed class BodyExpression : Microsoft.OpenApi.SourceExpression { @@ -277,6 +280,7 @@ namespace Microsoft.OpenApi void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer); void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer); void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer); + void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer); } public interface IOpenApiSummarizedElement : Microsoft.OpenApi.IOpenApiElement { @@ -343,6 +347,7 @@ namespace Microsoft.OpenApi public string? Title { get; set; } public bool? WriteOnly { get; set; } protected override void SerializeAdditionalV31Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } + protected override void SerializeAdditionalV32Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } protected override void SetAdditional31MetadataFromMapNode(System.Text.Json.Nodes.JsonObject jsonObject) { } } [System.Flags] @@ -372,6 +377,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiCallbackReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiCallback, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -401,6 +407,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } [Microsoft.OpenApi.OpenApiRule] public static class OpenApiComponentsRules @@ -576,6 +583,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } [Microsoft.OpenApi.OpenApiRule] public static class OpenApiContactRules @@ -592,6 +600,7 @@ namespace Microsoft.OpenApi public void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiDocument : Microsoft.OpenApi.IMetadataContainer, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiSerializable { @@ -617,6 +626,7 @@ namespace Microsoft.OpenApi public void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SetReferenceHostDocument() { } public static Microsoft.OpenApi.Reader.ReadResult Load(System.IO.MemoryStream stream, string? format = null, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null) { } public static System.Threading.Tasks.Task LoadAsync(string url, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null, System.Threading.CancellationToken token = default) { } @@ -646,6 +656,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiError { @@ -668,6 +679,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiExampleReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExample, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable { @@ -698,6 +710,7 @@ namespace Microsoft.OpenApi public void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public static class OpenApiExtensibleExtensions { @@ -719,6 +732,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } [Microsoft.OpenApi.OpenApiRule] public static class OpenApiExternalDocsRules @@ -750,6 +764,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiHeaderReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiHeader, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -785,6 +800,7 @@ namespace Microsoft.OpenApi public void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } [Microsoft.OpenApi.OpenApiRule] public static class OpenApiInfoRules @@ -824,6 +840,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } [Microsoft.OpenApi.OpenApiRule] public static class OpenApiLicenseRules @@ -844,6 +861,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiLinkReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiLink, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -872,6 +890,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public static class OpenApiNonDefaultRules { @@ -892,6 +911,7 @@ namespace Microsoft.OpenApi public void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } [Microsoft.OpenApi.OpenApiRule] public static class OpenApiOAuthFlowRules @@ -910,6 +930,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiOperation : Microsoft.OpenApi.IMetadataContainer, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiSerializable { @@ -932,6 +953,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiParameter : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiParameter, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -954,6 +976,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiParameterReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiParameter, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -997,6 +1020,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiPathItemReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiPathItem, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable { @@ -1047,6 +1071,7 @@ namespace Microsoft.OpenApi public OpenApiReferenceWithDescription(Microsoft.OpenApi.OpenApiReferenceWithDescription reference) { } public string? Description { get; set; } protected override void SerializeAdditionalV31Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } + protected override void SerializeAdditionalV32Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } protected override void SetAdditional31MetadataFromMapNode(System.Text.Json.Nodes.JsonObject jsonObject) { } } public class OpenApiReferenceWithDescriptionAndSummary : Microsoft.OpenApi.OpenApiReferenceWithDescription, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiSummarizedElement @@ -1055,6 +1080,7 @@ namespace Microsoft.OpenApi public OpenApiReferenceWithDescriptionAndSummary(Microsoft.OpenApi.OpenApiReferenceWithDescriptionAndSummary reference) { } public string? Summary { get; set; } protected override void SerializeAdditionalV31Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } + protected override void SerializeAdditionalV32Properties(Microsoft.OpenApi.IOpenApiWriter writer) { } protected override void SetAdditional31MetadataFromMapNode(System.Text.Json.Nodes.JsonObject jsonObject) { } } public class OpenApiRequestBody : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiRequestBody, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable @@ -1070,6 +1096,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiRequestBodyReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiRequestBody, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -1097,6 +1124,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiResponseReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -1188,6 +1216,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiSchemaReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSchema, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -1262,6 +1291,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiSecurityScheme : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSecurityScheme, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -1279,6 +1309,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiSecuritySchemeReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSecurityScheme, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable { @@ -1326,6 +1357,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public static class OpenApiServerExtensions { @@ -1347,12 +1379,14 @@ namespace Microsoft.OpenApi public void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public enum OpenApiSpecVersion { OpenApi2_0 = 0, OpenApi3_0 = 1, OpenApi3_1 = 2, + OpenApi3_2 = 3, } public class OpenApiTag : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiReadOnlyDescribedElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiTag, Microsoft.OpenApi.IShallowCopyable { @@ -1365,6 +1399,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiTagReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyDescribedElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiTag, Microsoft.OpenApi.IShallowCopyable { @@ -1643,6 +1678,7 @@ namespace Microsoft.OpenApi public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } + public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } public class OpenApiYamlWriter : Microsoft.OpenApi.OpenApiWriterBase { @@ -1986,6 +2022,7 @@ namespace Microsoft.OpenApi.Reader public static bool is2_0(this string version) { } public static bool is3_0(this string version) { } public static bool is3_1(this string version) { } + public static bool is3_2(this string version) { } } public class ParsingContext { diff --git a/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs b/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs index 26bd6d472..33d22e0f7 100644 --- a/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs +++ b/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs @@ -15,7 +15,7 @@ public async Task UsesSettingsBaseUrl() await File.WriteAllTextAsync(tempFilePathReferee, """ { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": { "title": "OData Service for namespace microsoft.graph", "description": "This OData service is located at https://graph.microsoft.com/v1.0", @@ -61,7 +61,7 @@ await File.WriteAllTextAsync(tempFilePathReferee, await File.WriteAllTextAsync(tempFilePathReferrer, $$$""" { - "openapi": "3.1.1", + "openapi": "3.1.2", "info": { "title": "OData Service for namespace microsoft.graph", "description": "This OData service is located at https://graph.microsoft.com/v1.0", From 8ad6bfaba1c051f3826b1083f59c680b2c1fe2b6 Mon Sep 17 00:00:00 2001 From: kilifu Date: Tue, 23 Sep 2025 23:00:02 -0400 Subject: [PATCH 005/146] Update default OpenApiVersion to 3_2 --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index d63d559f0..d6ec132d8 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -67,7 +67,7 @@ public static async Task TransformOpenApiDocumentAsync(HidiOptions options, ILog throw new IOException($"The file {options.Output} already exists. Please input a new file path."); } - // Default to yaml and OpenApiVersion 3_1 during csdl to OpenApi conversion + // Default to yaml and OpenApiVersion 3_2 during csdl to OpenApi conversion var openApiFormat = options.OpenApiFormat ?? (!string.IsNullOrEmpty(options.OpenApi) ? GetOpenApiFormat(options.OpenApi, logger) : OpenApiConstants.Yaml); var openApiVersion = options.Version != null ? TryParseOpenApiSpecVersion(options.Version) : OpenApiSpecVersion.OpenApi3_2; From 7406953f7299292f320181777a147cc150c6a8b5 Mon Sep 17 00:00:00 2001 From: kilifu Date: Wed, 24 Sep 2025 08:28:16 -0400 Subject: [PATCH 006/146] Update version service documentation to V3.2 --- src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs index 51f00b86d..38e7452d1 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32VersionService.cs @@ -7,7 +7,7 @@ namespace Microsoft.OpenApi.Reader.V32 { /// - /// The version service for the Open API V3.1. + /// The version service for the Open API V3.2. /// internal class OpenApiV32VersionService : BaseOpenApiVersionService { From ec3026292768908fe3cf40dc0a2d4dc9626cd83d Mon Sep 17 00:00:00 2001 From: costabello matthieu Date: Wed, 24 Sep 2025 14:20:55 -0400 Subject: [PATCH 007/146] duplicate V31 tests for V32 --- ...enApiCallbackReferenceDeserializerTests.cs | 37 ++ .../V32Tests/OpenApiComponentsTests.cs | 40 ++ .../OpenApiDocumentSerializationTests .cs | 88 +++ ...DocumentWith32PropertiesWorks.verified.txt | 116 ++++ .../V32Tests/OpenApiDocumentTests.cs | 616 ++++++++++++++++++ ...penApiExampleReferenceDeserializerTests.cs | 42 ++ ...OpenApiHeaderReferenceDeserializerTests.cs | 40 ++ .../V32Tests/OpenApiInfoTests.cs | 56 ++ .../V32Tests/OpenApiLicenseTests.cs | 45 ++ .../OpenApiLinkReferenceDeserializerTests.cs | 39 ++ ...nApiParameterReferenceDeserializerTests.cs | 41 ++ ...enApiPathItemReferenceDeserializerTests.cs | 42 ++ ...piRequestBodyReferenceDeserializerTests.cs | 39 ++ ...enApiResponseReferenceDeserializerTests.cs | 39 ++ ...OpenApiSchemaReferenceDeserializerTests.cs | 61 ++ .../V32Tests/OpenApiSchemaTests.cs | 614 +++++++++++++++++ ...ecuritySchemeReferenceDeserializerTests.cs | 42 ++ .../OpenApiTagReferenceDeserializerTests.cs | 48 ++ .../ReferenceSamples/OAS-schemas.yaml | 19 + .../V32Tests/ReferenceSamples/STJSchema.json | 67 ++ .../componentExternalReference.yaml | 13 + .../ReferenceSamples/customApiKey.yaml | 11 + .../V32Tests/ReferenceSamples/examples.yaml | 11 + .../externalComponentSubschemaReference.yaml | 14 + .../inlineExternalReference.yaml | 15 + .../inlineLocalReference.yaml | 14 + .../internalComponentReferenceUsingId.yaml | 29 + .../internalComponentsSubschemaReference.yaml | 55 ++ .../localReferenceToJsonSchemaResource.yaml | 23 + .../recursiveRelativeSubschemaReference.json | 96 +++ .../relativeSubschemaReference.json | 58 ++ .../rootComponentSchemaReference.yaml | 23 + .../rootInlineSchemaReference.yaml | 18 + .../subschemaComponentSchemaReference.yaml | 22 + .../subschemaInlineSchemaReference.yaml | 20 + .../V32Tests/RelativeReferenceTests.cs | 515 +++++++++++++++ .../OpenApiDocument/docWith32properties.json | 166 +++++ .../OpenApiDocument/docWithExample.yaml | 96 +++ .../docWithPatternPropertiesInSchema.yaml | 26 + .../OpenApiDocument/docWithReferenceById.yaml | 45 ++ ...docWithReferencedExampleInSchemaWorks.yaml | 30 + .../documentWith32Properties.yaml | 129 ++++ .../documentWithEmptyTags.json | 51 ++ .../documentWithReusablePaths.yaml | 95 +++ .../OpenApiDocument/documentWithSchema.json | 27 + ...tWithSummaryAndDescriptionInReference.yaml | 43 ++ .../OpenApiDocument/documentWithWebhooks.yaml | 91 +++ .../OpenApiDocument/externalRefById.yaml | 14 + .../externalRefByJsonPointer.yaml | 15 + .../OpenApiDocument/externalResource.yaml | 22 + .../Samples/OpenApiInfo/basicInfo.yaml | 17 + .../licenseWithSpdxIdentifier.yaml | 3 + .../Samples/OpenApiSchema/advancedSchema.yaml | 48 ++ .../Samples/OpenApiSchema/jsonSchema.json | 49 ++ .../Samples/OpenApiSchema/schema.yaml | 8 + .../OpenApiSchema/schemaWithConst.json | 22 + .../OpenApiSchema/schemaWithExamples.yaml | 4 + .../schemaWithJsonSchemaKeywords.yaml | 31 + .../OpenApiSchema/schemaWithNullable.yaml | 2 + .../schemaWithNullableExtension.yaml | 2 + .../OpenApiSchema/schemaWithTypeArray.yaml | 3 + 61 files changed, 4107 insertions(+) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiCallbackReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLinkReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiParameterReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiRequestBodyReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiResponseReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiTagReferenceDeserializerTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/OAS-schemas.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/STJSchema.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/componentExternalReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/customApiKey.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/examples.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/externalComponentSubschemaReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineExternalReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineLocalReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentReferenceUsingId.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentsSubschemaReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/localReferenceToJsonSchemaResource.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/recursiveRelativeSubschemaReference.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/relativeSubschemaReference.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootComponentSchemaReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootInlineSchemaReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaComponentSchemaReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaInlineSchemaReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWith32properties.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithExample.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferenceById.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWith32Properties.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithEmptyTags.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSchema.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefById.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalResource.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiInfo/basicInfo.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/advancedSchema.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/jsonSchema.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schema.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithConst.json create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithExamples.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullable.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullableExtension.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithTypeArray.yaml diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiCallbackReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiCallbackReferenceDeserializerTests.cs new file mode 100644 index 000000000..d73bfbd2c --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiCallbackReferenceDeserializerTests.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiCallbackReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeCallbackReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/callbacks/MyCallback" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyCallback", new OpenApiCallback + { + // Optionally add a PathItem or similar here if needed + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadCallback(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyCallback", resultReference.Reference.Id); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs new file mode 100644 index 000000000..da82e35e7 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Reader; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests +{ + public class OpenApiComponentsTests + { + [Theory] + [InlineData("./FirstLevel/SecondLevel/ThridLevel/File.json#/components/schemas/ExternalRelativePathModel", "ExternalRelativePathModel", "./FirstLevel/SecondLevel/ThridLevel/File.json")] + [InlineData("File.json#/components/schemas/ExternalSimpleRelativePathModel", "ExternalSimpleRelativePathModel", "File.json")] + [InlineData("A:\\Dir\\File.json#/components/schemas/ExternalAbsWindowsPathModel", "ExternalAbsWindowsPathModel", "A:\\Dir\\File.json")] + [InlineData("/Dir/File.json#/components/schemas/ExternalAbsUnixPathModel", "ExternalAbsUnixPathModel", "/Dir/File.json")] + [InlineData("https://host.lan:1234/path/to/file/resource.json#/components/schemas/ExternalHttpsModel", "ExternalHttpsModel", "https://host.lan:1234/path/to/file/resource.json")] + [InlineData("File.json", "File.json", null)] + public void ParseExternalSchemaReferenceShouldSucceed(string reference, string referenceId, string externalResource) + { + var input = $@"{{ + ""schemas"": {{ + ""Model"": {{ + ""$ref"": ""{reference.Replace("\\", "\\\\")}"" + }} + }} +}} +"; + var openApiDocument = new OpenApiDocument(); + + // Act + var components = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_2, openApiDocument, out _, "json"); + + // Assert + var schema = components.Schemas["Model"] as OpenApiSchemaReference; + var expected = new OpenApiSchemaReference(referenceId, openApiDocument, externalResource); + Assert.Equivalent(expected, schema); + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs new file mode 100644 index 000000000..c82bc37e2 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests +{ + public class OpenApiDocumentSerializationTests + { + private const string SampleFolderPath = "V32Tests/Samples/OpenApiDocument/"; + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_2)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + public async Task Serialize_DoesNotMutateDom(OpenApiSpecVersion version) + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "docWith31properties.json"); + var (doc, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + + // Act: Serialize using System.Text.Json + var options = new JsonSerializerOptions + { + Converters = + { + new HttpMethodOperationDictionaryConverter() + }, + }; + var originalSerialized = JsonSerializer.Serialize(doc, options); + Assert.NotNull(originalSerialized); // sanity check + + // Serialize using native OpenAPI writer + var jsonWriter = new StringWriter(); + var openApiWriter = new OpenApiJsonWriter(jsonWriter); + switch (version) + { + case OpenApiSpecVersion.OpenApi3_2: + doc.SerializeAsV32(openApiWriter); + break; + case OpenApiSpecVersion.OpenApi3_1: + doc.SerializeAsV31(openApiWriter); + break; + case OpenApiSpecVersion.OpenApi3_0: + doc.SerializeAsV3(openApiWriter); + break; + default: + doc.SerializeAsV2(openApiWriter); + break; + } + + // Serialize again with STJ after native writer serialization + var finalSerialized = JsonSerializer.Serialize(doc, options); + Assert.NotNull(finalSerialized); // sanity check + + // Assert: Ensure no mutation occurred in the DOM after native serialization + Assert.True(JsonNode.DeepEquals(originalSerialized, finalSerialized), "OpenAPI DOM was mutated by the native serializer."); + } + } + + public class HttpMethodOperationDictionaryConverter : JsonConverter> + { + public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (var kvp in value) + { + writer.WritePropertyName(kvp.Key.Method.ToLowerInvariant()); + JsonSerializer.Serialize(writer, kvp.Value, options); + } + + writer.WriteEndObject(); + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt new file mode 100644 index 000000000..a9a5d5e1b --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt @@ -0,0 +1,116 @@ +openapi: '3.2.0' +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +info: + title: Sample OpenAPI 3.2 API + description: A sample API demonstrating OpenAPI 3.2 features + license: + name: Apache 2.0 + identifier: Apache-2.0 + version: 2.0.0 + summary: Sample OpenAPI 3.2 API with the latest features +servers: + - url: https://api.example.com/v2 + description: Main production server +paths: + /pets: + get: + tags: + - pets + summary: List all pets + operationId: listPets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + schema: + exclusiveMaximum: 100 + exclusiveMinimum: 1 + type: integer + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + $ref: https://example.com/schemas/pet.json + /sample: + get: + summary: Sample endpoint + responses: + '200': + description: Sample response + content: + application/json: + schema: + $id: https://example.com/schemas/person.schema.yaml + $schema: https://json-schema.org/draft/2020-12/schema + $comment: A schema defining a pet object with optional references to dynamic components. + $vocabulary: + https://json-schema.org/draft/2020-12/vocab/core: true + https://json-schema.org/draft/2020-12/vocab/applicator: true + https://json-schema.org/draft/2020-12/vocab/validation: true + https://json-schema.org/draft/2020-12/vocab/meta-data: false + https://json-schema.org/draft/2020-12/vocab/format-annotation: false + $dynamicAnchor: addressDef + title: Pet + required: + - name + type: object + properties: + name: + $comment: The pet's full name + type: string + address: + $comment: Reference to an address definition which can change dynamically + $dynamicRef: '#addressDef' + description: Schema for a pet object +components: + schemas: + Pet: + $id: https://example.com/schemas/pet.json + $comment: This schema represents a pet in the system. + $defs: + ExtraInfo: + type: string + required: + - id + - weight + type: object + properties: + id: + type: string + format: uuid + weight: + exclusiveMinimum: 0 + type: number + description: Weight of the pet in kilograms + attributes: + patternProperties: + '^attr_[A-Za-z]+$': + type: string + type: + - 'null' + - object + description: Dynamic attributes for the pet + securitySchemes: + api_key: + type: apiKey + name: api_key + in: header +security: + - api_key: [ ] +tags: + - name: pets +webhooks: + newPetAlert: + post: + summary: Notify about a new pet being added + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + '200': + description: Webhook processed successfully diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs new file mode 100644 index 000000000..055511286 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs @@ -0,0 +1,616 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; +using Xunit; +using VerifyXunit; +using System; +using System.Net.Http; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests +{ + public class OpenApiDocumentTests + { + private const string SampleFolderPath = "V32Tests/Samples/OpenApiDocument/"; + + [Fact] + public async Task ParseDocumentWithWebhooksShouldSucceed() + { + // Arrange and Act + var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml"), SettingsFixture.ReaderSettings); + var petSchema = new OpenApiSchemaReference("petSchema", actual.Document); + + var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.Document); + + var components = new OpenApiComponents + { + Schemas = new Dictionary() + { + ["petSchema"] = new OpenApiSchema() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "id", + "name" + }, + DependentRequired = new Dictionary> + { + { "tag", new HashSet { "category" } } + }, + Properties = new Dictionary() + { + ["id"] = new OpenApiSchema() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["tag"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["category"] = new OpenApiSchema() + { + Type = JsonSchemaType.String, + }, + } + }, + ["newPetSchema"] = new OpenApiSchema() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "name" + }, + DependentRequired = new Dictionary> + { + { "tag", new HashSet { "category" } } + }, + Properties = new Dictionary() + { + ["id"] = new OpenApiSchema() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["tag"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["category"] = new OpenApiSchema() + { + Type = JsonSchemaType.String, + }, + } + } + } + }; + + var expected = new OpenApiDocument + { + Info = new OpenApiInfo + { + Version = "1.0.0", + Title = "Webhook Example" + }, + Webhooks = new Dictionary + { + ["pets"] = new OpenApiPathItem + { + Operations = new() + { + [HttpMethod.Get] = new OpenApiOperation + { + Description = "Returns all pets from the system that the user has access to", + OperationId = "findPets", + Parameters = + [ + new OpenApiParameter + { + Name = "tags", + In = ParameterLocation.Query, + Description = "tags to filter by", + Required = false, + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.Array, + Items = new OpenApiSchema() + { + Type = JsonSchemaType.String + } + } + }, + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.Integer, + Format = "int32" + } + } + ], + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.Array, + Items = petSchema + } + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.Array, + Items = petSchema + } + } + } + } + } + }, + [HttpMethod.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "Information about a new pet in the system", + Required = true, + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType + { + Schema = newPetSchema + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Return a 200 status to indicate that the data was received successfully", + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType + { + Schema = petSchema + } + } + } + } + } + } + } + }, + Components = components + }; + + // Assert + Assert.Equivalent(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_2, Format = OpenApiConstants.Yaml }, actual.Diagnostic); + actual.Document.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + } + + [Fact] + public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds() + { + // Arrange && Act + var actual = await OpenApiDocument.LoadAsync("V32Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml", SettingsFixture.ReaderSettings); + + var components = new OpenApiComponents + { + Schemas = new Dictionary() + { + ["petSchema"] = new OpenApiSchema() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "id", + "name" + }, + DependentRequired = new Dictionary> + { + { "tag", new HashSet { "category" } } + }, + Properties = new Dictionary() + { + ["id"] = new OpenApiSchema() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["tag"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["category"] = new OpenApiSchema() + { + Type = JsonSchemaType.String, + }, + } + }, + ["newPetSchema"] = new OpenApiSchema() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "name" + }, + DependentRequired = new Dictionary> + { + { "tag", new HashSet { "category" } } + }, + Properties = new Dictionary() + { + ["id"] = new OpenApiSchema() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["tag"] = new OpenApiSchema() + { + Type = JsonSchemaType.String + }, + ["category"] = new OpenApiSchema() + { + Type = JsonSchemaType.String, + }, + } + } + } + }; + + // Create a clone of the schema to avoid modifying things in components. + var petSchema = new OpenApiSchemaReference("petSchema", actual.Document); + + var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.Document); + + components.PathItems = new Dictionary + { + ["pets"] = new OpenApiPathItem + { + Operations = new() + { + [HttpMethod.Get] = new OpenApiOperation + { + Description = "Returns all pets from the system that the user has access to", + OperationId = "findPets", + Parameters = + [ + new OpenApiParameter + { + Name = "tags", + In = ParameterLocation.Query, + Description = "tags to filter by", + Required = false, + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.Array, + Items = new OpenApiSchema() + { + Type = JsonSchemaType.String + } + } + }, + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.Integer, + Format = "int32" + } + } + ], + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = JsonSchemaType.Array, + Items = petSchema + } + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = JsonSchemaType.Array, + Items = petSchema + } + } + } + } + } + }, + [HttpMethod.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "Information about a new pet in the system", + Required = true, + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType + { + Schema = newPetSchema + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Return a 200 status to indicate that the data was received successfully", + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType + { + Schema = petSchema + }, + } + } + } + } + } + } + }; + + var expected = new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "Webhook Example", + Version = "1.0.0" + }, + JsonSchemaDialect = new Uri("http://json-schema.org/draft-07/schema#"), + Webhooks = new Dictionary + { + ["pets"] = components.PathItems["pets"] + }, + Components = components + }; + + // Assert + actual.Document.Should().BeEquivalentTo(expected, options => options + .Excluding(x => x.Workspace) + .Excluding(y => y.BaseUri)); + Assert.Equivalent( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_2, Format = OpenApiConstants.Yaml }, actual.Diagnostic); + } + + [Fact] + public async Task ParseDocumentWithExampleInSchemaShouldSucceed() + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); + + // Act + var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithExample.yaml"), SettingsFixture.ReaderSettings); + actual.Document.SerializeAsV32(writer); + + // Assert + Assert.NotNull(actual); + } + + [Fact] + public async Task ParseDocumentWithPatternPropertiesInSchemaWorks() + { + // Arrange and Act + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithPatternPropertiesInSchema.yaml"), SettingsFixture.ReaderSettings); + var actualSchema = result.Document.Paths["/example"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + + var expectedSchema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary() + { + ["prop1"] = new OpenApiSchema + { + Type = JsonSchemaType.String + }, + ["prop2"] = new OpenApiSchema + { + Type = JsonSchemaType.String + }, + ["prop3"] = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }, + PatternProperties = new Dictionary() + { + ["^x-.*$"] = new OpenApiSchema + { + Type = JsonSchemaType.String + } + } + }; + + // Serialization + var mediaType = result.Document.Paths["/example"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"]; + + var expectedMediaType = @"schema: + patternProperties: + ^x-.*$: + type: string + type: object + properties: + prop1: + type: string + prop2: + type: string + prop3: + type: string"; + + var actualMediaType = await mediaType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + Assert.Equivalent(expectedSchema, actualSchema); + Assert.Equal(expectedMediaType.MakeLineBreaksEnvironmentNeutral(), actualMediaType.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public async Task ParseDocumentWithReferenceByIdGetsResolved() + { + // Arrange and Act + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithReferenceById.yaml"), SettingsFixture.ReaderSettings); + + var responseSchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + var requestBodySchema = result.Document.Paths["/resource"].Operations[HttpMethod.Post].RequestBody.Content["application/json"].Schema; + var parameterSchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Parameters[0].Schema; + + // Assert + Assert.Equal(JsonSchemaType.Object, responseSchema.Type); + Assert.Equal(JsonSchemaType.Object, requestBodySchema.Type); + Assert.Equal(JsonSchemaType.String, parameterSchema.Type); + } + + [Fact] + public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWorks() + { + // Arrange + var documentName = "externalRefByJsonPointer.yaml"; + var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath, documentName); + + var settings = new OpenApiReaderSettings + { + LoadExternalRefs = true, + BaseUrl = new(path), + }; + settings.AddYamlReader(); + + // Act + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, documentName), settings); + var responseSchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + + // Assert + var externalResourceUri = new Uri( + "file://" + + Path.Combine(Path.GetFullPath(SampleFolderPath), + "externalResource.yaml#/components/schemas/todo")).AbsoluteUri; + + Assert.True(result.Document.Workspace.Contains(externalResourceUri)); + Assert.Equal(2, responseSchema.Properties.Count); // reference has been resolved + } + + [Fact] + public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() + { + // Arrange + var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + + var settings = new OpenApiReaderSettings + { + LoadExternalRefs = true, + BaseUrl = new(path), + }; + settings.AddYamlReader(); + + // Act + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings); + var doc2 = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalResource.yaml"), SettingsFixture.ReaderSettings)).Document; + + var requestBodySchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Parameters[0].Schema; + result.Document.Workspace.RegisterComponents(doc2); + + // Assert + Assert.Equal(2, requestBodySchema.Properties.Count); // reference has been resolved + } + + [Fact] + public async Task ParseDocumentWith31PropertiesWorks() + { + var path = Path.Combine(SampleFolderPath, "documentWith31Properties.yaml"); + var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; + var outputStringWriter = new StringWriter(); + doc.SerializeAsV32(new OpenApiYamlWriter(outputStringWriter)); + await outputStringWriter.FlushAsync(); + var actual = outputStringWriter.GetStringBuilder().ToString(); + + // Assert + await Verifier.Verify(actual); + } + + [Fact] + public async Task ParseDocumentWithEmptyTagsWorks() + { + var path = Path.Combine(SampleFolderPath, "documentWithEmptyTags.json"); + var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; + + doc.Paths["/groups"].Operations[HttpMethod.Get].Tags.Should().BeNull("Empty tags are ignored, so we should not have any tags"); + } + [Fact] + public async Task DocumentWithSchemaResultsInWarning() + { + var path = Path.Combine(SampleFolderPath, "documentWithSchema.json"); + var (doc, diag) = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + Assert.NotNull(doc); + Assert.NotNull(diag); + Assert.Empty(diag.Errors); + Assert.Single(diag.Warnings); + Assert.StartsWith("$schema is not a valid property", diag.Warnings[0].Message); + } + + [Fact] + public void ParseEmptyMemoryStreamThrowsAnArgumentException() + { + Assert.Throws(() => OpenApiDocument.Load(new MemoryStream())); + } + + [Fact] + public async Task ValidateReferencedExampleInSchemaWorks() + { + // Arrange && Act + var path = Path.Combine(SampleFolderPath, "docWithReferencedExampleInSchemaWorks.yaml"); + var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var actualSchemaExample = result.Document.Components.Schemas["DiffCreatedEvent"].Properties["updatedAt"].Example; + var targetSchemaExample = result.Document.Components.Schemas["Timestamp"].Example; + + // Assert + Assert.Equal(targetSchemaExample, actualSchemaExample); + Assert.Empty(result.Diagnostic.Errors); + Assert.Empty(result.Diagnostic.Warnings); + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleReferenceDeserializerTests.cs new file mode 100644 index 000000000..c32460cba --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleReferenceDeserializerTests.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiExampleReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeExampleReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/examples/MyExample", + "description": "This is an example reference", + "summary": "Example Summary reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyExample", new OpenApiExample + { + Summary = "This is an example", + Description = "This is an example description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadExample(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyExample", resultReference.Reference.Id); + Assert.Equal("This is an example reference", resultReference.Description); + Assert.Equal("Example Summary reference", resultReference.Summary); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderReferenceDeserializerTests.cs new file mode 100644 index 000000000..abe5146c4 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderReferenceDeserializerTests.cs @@ -0,0 +1,40 @@ + +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiHeaderReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/headers/MyHeader", + "description": "This is a header reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyHeader", new OpenApiHeader + { + Description = "This is a header" + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadHeader(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyHeader", resultReference.Reference.Id); + Assert.Equal("This is a header reference", resultReference.Description); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs new file mode 100644 index 000000000..aa578e200 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Microsoft.OpenApi.YamlReader; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests +{ + public class OpenApiInfoTests + { + private const string SampleFolderPath = "V32Tests/Samples/OpenApiInfo/"; + + [Fact] + public void ParseBasicInfoShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicInfo.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents[0].RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); + + // Act + var openApiInfo = OpenApiV32Deserializer.LoadInfo(node, new()); + + // Assert + Assert.Equivalent( + new OpenApiInfo + { + Title = "Basic Info", + Summary = "Sample Summary", + Description = "Sample Description", + Version = "1.0.1", + TermsOfService = new Uri("http://swagger.io/terms/"), + Contact = new OpenApiContact + { + Email = "support@swagger.io", + Name = "API Support", + Url = new Uri("http://www.swagger.io/support") + }, + License = new OpenApiLicense + { + Name = "Apache 2.0", + Url = new Uri("http://www.apache.org/licenses/LICENSE-2.0.html") + } + }, openApiInfo); + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs new file mode 100644 index 000000000..94515b9e7 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.IO; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Microsoft.OpenApi.YamlReader; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests +{ + + public class OpenApiLicenseTests + { + private const string SampleFolderPath = "V32Tests/Samples/OpenApiLicense/"; + + [Fact] + public void ParseLicenseWithSpdxIdentifierShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "licenseWithSpdxIdentifier.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents[0].RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); + + // Act + var license = OpenApiV32Deserializer.LoadLicense(node, new()); + + // Assert + Assert.Equivalent( + new OpenApiLicense + { + Name = "Apache 2.0", + Identifier = "Apache-2.0" + }, license); + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLinkReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLinkReferenceDeserializerTests.cs new file mode 100644 index 000000000..4716d05fb --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLinkReferenceDeserializerTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiLinkReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeLinkReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/links/MyLink", + "description": "This is a link reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyLink", new OpenApiLink + { + Description = "This is a link description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadLink(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyLink", resultReference.Reference.Id); + Assert.Equal("This is a link reference", resultReference.Description); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiParameterReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiParameterReferenceDeserializerTests.cs new file mode 100644 index 000000000..6f4f8b4cc --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiParameterReferenceDeserializerTests.cs @@ -0,0 +1,41 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiParameterReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeParameterReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/parameters/MyParameter", + "description": "This is a parameter reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyParameter", new OpenApiParameter + { + Name = "myParam", + In = ParameterLocation.Query, + Description = "This is a parameter description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadParameter(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyParameter", resultReference.Reference.Id); + Assert.Equal("This is a parameter reference", resultReference.Description); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemReferenceDeserializerTests.cs new file mode 100644 index 000000000..76af3ce00 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemReferenceDeserializerTests.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiPathItemReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializePathItemReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/pathItems/MyPathItem", + "description": "This is a path item reference", + "summary": "PathItem Summary reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyPathItem", new OpenApiPathItem + { + Summary = "This is a path item", + Description = "This is a path item description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadPathItem(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyPathItem", resultReference.Reference.Id); + Assert.Equal("This is a path item reference", resultReference.Description); + Assert.Equal("PathItem Summary reference", resultReference.Summary); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiRequestBodyReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiRequestBodyReferenceDeserializerTests.cs new file mode 100644 index 000000000..cda54fc9e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiRequestBodyReferenceDeserializerTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiRequestBodyReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeRequestBodyReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/requestBodies/MyRequestBody", + "description": "This is a request body reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyRequestBody", new OpenApiRequestBody + { + Description = "This is a request body description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadRequestBody(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyRequestBody", resultReference.Reference.Id); + Assert.Equal("This is a request body reference", resultReference.Description); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiResponseReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiResponseReferenceDeserializerTests.cs new file mode 100644 index 000000000..eac03d63b --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiResponseReferenceDeserializerTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiResponseReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeResponseReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/responses/MyResponse", + "description": "This is a response reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyResponse", new OpenApiResponse + { + Description = "This is a response description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadResponse(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyResponse", resultReference.Reference.Id); + Assert.Equal("This is a response reference", resultReference.Description); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs new file mode 100644 index 000000000..bb7b15ab0 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiSchemaReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeSchemaReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/schemas/MySchema", + "description": "This is a schema reference", + "default": "foo", + "readOnly": true, + "writeOnly": true, + "deprecated": true, + "title": "This is a schema reference", + "examples": ["example reference value"] + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MySchema", new OpenApiSchema + { + Title = "This is a schema", + Description = "This is a schema description", + Default = "bar", + Type = JsonSchemaType.String, + ReadOnly = false, + WriteOnly = false, + Deprecated = false, + Examples = new List { "example value" }, + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadSchema(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MySchema", resultReference.Reference.Id); + Assert.Equal("This is a schema reference", resultReference.Description); + Assert.Equal("foo", resultReference.Default?.ToString()); + Assert.True(resultReference.ReadOnly); + Assert.True(resultReference.WriteOnly); + Assert.True(resultReference.Deprecated); + Assert.Equal("This is a schema reference", resultReference.Title); + Assert.NotNull(resultReference.Examples); + Assert.Single(resultReference.Examples); + Assert.Equal("example reference value", resultReference.Examples[0]?.ToString()); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs new file mode 100644 index 000000000..73be91f10 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs @@ -0,0 +1,614 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using FluentAssertions; +using FluentAssertions.Equivalency; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; +using Xunit; +using System; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests +{ + public class OpenApiSchemaTests + { + private const string SampleFolderPath = "V32Tests/Samples/OpenApiSchema/"; + + + public static MemoryStream GetMemoryStream(string fileName) + { + var filePath = Path.Combine(SampleFolderPath, fileName); + var fileBytes = File.ReadAllBytes(filePath); + return new MemoryStream(fileBytes); + } + + [Fact] + public async Task ParseBasicV32SchemaShouldSucceed() + { + var expectedObject = new OpenApiSchema() + { + Id = "https://example.com/arrays.schema.json", + Schema = new Uri("https://json-schema.org/draft/2020-12/schema"), + Description = "A representation of a person, company, organization, or place", + Type = JsonSchemaType.Object, + Properties = new Dictionary() + { + ["fruits"] = new OpenApiSchema + { + Type = JsonSchemaType.Array, + Items = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }, + ["vegetables"] = new OpenApiSchema + { + Type = JsonSchemaType.Array + } + }, + Definitions = new Dictionary + { + ["veggie"] = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "veggieName", + "veggieLike" + }, + DependentRequired = new Dictionary> + { + { "veggieType", new HashSet { "veggieColor", "veggieSize" } } + }, + Properties = new Dictionary() + { + ["veggieName"] = new OpenApiSchema + { + Type = JsonSchemaType.String, + Description = "The name of the vegetable." + }, + ["veggieLike"] = new OpenApiSchema + { + Type = JsonSchemaType.Boolean, + Description = "Do I like this vegetable?" + }, + ["veggieType"] = new OpenApiSchema + { + Type = JsonSchemaType.String, + Description = "The type of vegetable (e.g., root, leafy, etc.)." + }, + ["veggieColor"] = new OpenApiSchema + { + Type = JsonSchemaType.String, + Description = "The color of the vegetable." + }, + ["veggieSize"] = new OpenApiSchema + { + Type = JsonSchemaType.String, + Description = "The size of the vegetable." + } + } + } + } + }; + + // Act + var schema = await OpenApiModelFactory.LoadAsync( + Path.Combine(SampleFolderPath, "jsonSchema.json"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + + // Assert + Assert.Equivalent(expectedObject, schema); + } + + [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 = new Uri("https://json-schema.org/draft/2020-12/schema"), + Description = "A representation of a person, company, organization, or place", + Type = JsonSchemaType.Object | JsonSchemaType.Null + }; + + // Act + var actual = OpenApiModelFactory.Parse(schema, OpenApiSpecVersion.OpenApi3_2, new(), out _); + + // Assert + Assert.Equivalent(expected, actual); + } + + [Fact] + public void TestSchemaCopyConstructorWithTypeArrayWorks() + { + /* Arrange + * Test schema's copy constructor for deep-cloning type array + */ + var schemaWithTypeArray = new OpenApiSchema() + { + Type = JsonSchemaType.Array | JsonSchemaType.Null, + Items = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }; + + var simpleSchema = new OpenApiSchema() + { + Type = JsonSchemaType.String + }; + + // Act + var schemaWithArrayCopy = schemaWithTypeArray.CreateShallowCopy() as OpenApiSchema; + schemaWithArrayCopy.Type = JsonSchemaType.String; + + var simpleSchemaCopy = simpleSchema.CreateShallowCopy() as OpenApiSchema; + simpleSchemaCopy.Type = JsonSchemaType.String | JsonSchemaType.Null; + + // Assert + Assert.NotEqual(schemaWithTypeArray.Type, schemaWithArrayCopy.Type); + schemaWithTypeArray.Type = JsonSchemaType.String | JsonSchemaType.Null; + + Assert.NotEqual(simpleSchema.Type, simpleSchemaCopy.Type); + simpleSchema.Type = JsonSchemaType.String; + } + + [Fact] + public async Task ParseV32SchemaShouldSucceed() + { + var path = Path.Combine(SampleFolderPath, "schema.yaml"); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var expectedSchema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary() + { + ["one"] = new OpenApiSchema() + { + Description = "type array", + Type = JsonSchemaType.Integer | JsonSchemaType.String + } + } + }; + + // Assert + Assert.Equivalent(expectedSchema, schema); + } + + [Fact] + public async Task ParseAdvancedV32SchemaShouldSucceed() + { + // Arrange and Act + var path = Path.Combine(SampleFolderPath, "advancedSchema.yaml"); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + + var expectedSchema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary() + { + ["one"] = new OpenApiSchema() + { + Description = "type array", + Type = JsonSchemaType.Integer | JsonSchemaType.String + }, + ["two"] = new OpenApiSchema() + { + Description = "type 'null'", + Type = JsonSchemaType.Null + }, + ["three"] = new OpenApiSchema() + { + Description = "type array including 'null'", + Type = JsonSchemaType.String | JsonSchemaType.Null + }, + ["four"] = new OpenApiSchema() + { + Description = "array with no items", + Type = JsonSchemaType.Array + }, + ["five"] = new OpenApiSchema() + { + Description = "singular example", + Type = JsonSchemaType.String, + Examples = new List + { + "exampleValue" + } + }, + ["six"] = new OpenApiSchema() + { + Description = "exclusiveMinimum true", + ExclusiveMinimum = "10" + }, + ["seven"] = new OpenApiSchema() + { + Description = "exclusiveMinimum false", + Minimum = "10" + }, + ["eight"] = new OpenApiSchema() + { + Description = "exclusiveMaximum true", + ExclusiveMaximum = "20" + }, + ["nine"] = new OpenApiSchema() + { + Description = "exclusiveMaximum false", + Maximum = "20" + }, + ["ten"] = new OpenApiSchema() + { + Description = "nullable string", + Type = JsonSchemaType.String | JsonSchemaType.Null + }, + ["eleven"] = new OpenApiSchema() + { + Description = "x-nullable string", + Type = JsonSchemaType.String | JsonSchemaType.Null + }, + ["twelve"] = new OpenApiSchema() + { + Description = "file/binary" + } + } + }; + + // Assert + schema.Should().BeEquivalentTo(expectedSchema, options => options + .IgnoringCyclicReferences() + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Parent"))); + } + + [Fact] + public void ParseSchemaWithExamplesShouldSucceed() + { + // Arrange + var input = @" +type: string +examples: + - fedora + - ubuntu +"; + // Act + var schema = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_2, new(), out _, "yaml", SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(2, schema.Examples.Count); + } + + [Fact] + public void CloningSchemaWithExamplesAndEnumsShouldSucceed() + { + // Arrange + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Integer, + Default = 5, + Examples = [2, 3], + Enum = [1, 2, 3] + }; + + var clone = schema.CreateShallowCopy() as OpenApiSchema; + clone.Examples.Add(4); + clone.Enum.Add(4); + clone.Default = 6; + + // Assert + Assert.Equivalent(new int[] { 1, 2, 3, 4 }, clone.Enum.Select(static x => x.GetValue()).ToArray()); + Assert.Equivalent(new int[] { 2, 3, 4 }, clone.Examples.Select(static x => x.GetValue()).ToArray()); + Assert.Equivalent(6, clone.Default.GetValue()); + } + + [Fact] + public async Task SerializeV32SchemaWithMultipleTypesAsV3Works() + { + // Arrange + var expected = @"type: string +nullable: true"; + + var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + + var writer = new StringWriter(); + schema.SerializeAsV3(new OpenApiYamlWriter(writer)); + var schema1String = writer.ToString(); + + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), schema1String.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public async Task SerializeV32SchemaWithMultipleTypesAsV2Works() + { + // Arrange + var expected = @"type: string +x-nullable: true"; + + var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + + var writer = new StringWriter(); + schema.SerializeAsV2(new OpenApiYamlWriter(writer)); + var schema1String = writer.ToString(); + + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), schema1String.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public async Task SerializeV3SchemaWithNullableAsV32Works() + { + // Arrange + var expected = @"type: + - 'null' + - string"; + + var path = Path.Combine(SampleFolderPath, "schemaWithNullable.yaml"); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + + var writer = new StringWriter(); + schema.SerializeAsV32(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), schemaString.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public async Task SerializeV2SchemaWithNullableExtensionAsV32Works() + { + // Arrange + var expected = @"type: + - 'null' + - string"; + + var path = Path.Combine(SampleFolderPath, "schemaWithNullableExtension.yaml"); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings); + + var writer = new StringWriter(); + schema.SerializeAsV32(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), schemaString.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeSchemaWithTypeArrayAndNullableDoesntEmitType() + { + var input = @"type: +- ""string"" +- ""int"" +nullable: true"; + + var expected = @"x-nullable: true"; + + var schema = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_2, new(), out _, "yaml", SettingsFixture.ReaderSettings); + + var writer = new StringWriter(); + schema.SerializeAsV2(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), schemaString.MakeLineBreaksEnvironmentNeutral()); + } + + [Theory] + [InlineData("schemaWithNullable.yaml")] + [InlineData("schemaWithNullableExtension.yaml")] + public async Task LoadSchemaWithNullableExtensionAsV32Works(string filePath) + { + // Arrange + var path = Path.Combine(SampleFolderPath, filePath); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type); + } + + [Fact] + public async Task SerializeSchemaWithJsonSchemaKeywordsWorks() + { + // Arrange + var expected = @"$id: https://example.com/schemas/person.schema.yaml +$schema: https://json-schema.org/draft/2020-12/schema +$comment: A schema defining a person object with optional references to dynamic components. +$vocabulary: + https://json-schema.org/draft/2020-12/vocab/core: true + https://json-schema.org/draft/2020-12/vocab/applicator: true + https://json-schema.org/draft/2020-12/vocab/validation: true + https://json-schema.org/draft/2020-12/vocab/meta-data: false + https://json-schema.org/draft/2020-12/vocab/format-annotation: false +$dynamicAnchor: addressDef +title: Person +required: + - name +type: object +properties: + name: + $comment: The person's full name + type: string + age: + $comment: Age must be a non-negative integer + minimum: 0 + type: integer + address: + $comment: Reference to an address definition which can change dynamically + $dynamicRef: '#addressDef' +description: Schema for a person object +"; + var path = Path.Combine(SampleFolderPath, "schemaWithJsonSchemaKeywords.yaml"); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + + // serialization + var writer = new StringWriter(); + schema.SerializeAsV32(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + // Assert + Assert.Equal(5, schema.Vocabulary.Keys.Count); + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), schemaString.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public async Task ParseSchemaWithConstWorks() + { + var expected = @"{ + ""$schema"": ""https://json-schema.org/draft/2020-12/schema"", + ""required"": [ + ""status"" + ], + ""type"": ""object"", + ""properties"": { + ""status"": { + ""const"": ""active"", + ""type"": ""string"" + }, + ""user"": { + ""required"": [ + ""role"" + ], + ""type"": ""object"", + ""properties"": { + ""role"": { + ""const"": ""admin"", + ""type"": ""string"" + } + } + } + } +}"; + + var path = Path.Combine(SampleFolderPath, "schemaWithConst.json"); + + // Act + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + Assert.Equal("active", schema.Properties["status"].Const); + Assert.Equal("admin", schema.Properties["user"].Properties["role"].Const); + + // serialization + var writer = new StringWriter(); + schema.SerializeAsV32(new OpenApiJsonWriter(writer)); + var schemaString = writer.ToString(); + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), schemaString.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void ParseSchemaWithUnrecognizedKeywordsWorks() + { + var input = @"{ + ""type"": ""string"", + ""format"": ""date-time"", + ""customKeyword"": ""customValue"", + ""anotherKeyword"": 42, + ""x-test"": ""test"" +} +"; + var schema = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_2, new(), out _, "json"); + Assert.Equal(2, schema.UnrecognizedKeywords.Count); + } + + [Fact] + public void ParseSchemaExampleWithPrimitivesWorks() + { + var expected1 = @"{ + ""type"": ""string"", + ""example"": ""2024-01-02"" +}"; + + var expected2 = @"{ + ""type"": ""string"", + ""example"": ""3.24"" +}"; + var schema = new OpenApiSchema() + { + Type = JsonSchemaType.String, + Example = JsonValue.Create("2024-01-02") + }; + + var schema2 = new OpenApiSchema() + { + Type = JsonSchemaType.String, + Example = JsonValue.Create("3.24") + }; + + var textWriter = new StringWriter(); + var writer = new OpenApiJsonWriter(textWriter); + schema.SerializeAsV32(writer); + var actual1 = textWriter.ToString(); + Assert.Equal(expected1.MakeLineBreaksEnvironmentNeutral(), actual1.MakeLineBreaksEnvironmentNeutral()); + + textWriter = new StringWriter(); + writer = new OpenApiJsonWriter(textWriter); + schema2.SerializeAsV32(writer); + var actual2 = textWriter.ToString(); + Assert.Equal(expected2.MakeLineBreaksEnvironmentNeutral(), actual2.MakeLineBreaksEnvironmentNeutral()); + } + + [Theory] + [InlineData(JsonSchemaType.Integer | JsonSchemaType.String, new[] { "integer", "string" })] + [InlineData(JsonSchemaType.Integer | JsonSchemaType.Null, new[] { "integer", "null" })] + [InlineData(JsonSchemaType.Integer, new[] { "integer" })] + public void NormalizeFlaggableJsonSchemaTypeEnumWorks(JsonSchemaType type, string[] expected) + { + var schema = new OpenApiSchema + { + Type = type + }; + + var actual = schema.Type.ToIdentifiers(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(new[] { "integer", "string" }, JsonSchemaType.Integer | JsonSchemaType.String)] + [InlineData(new[] { "integer", "null" }, JsonSchemaType.Integer | JsonSchemaType.Null)] + [InlineData(new[] { "integer" }, JsonSchemaType.Integer)] + public void ArrayIdentifierToEnumConversionWorks(string[] type, JsonSchemaType expected) + { + var actual = type.ToJsonSchemaType(); + Assert.Equal(expected, actual); + } + + [Fact] + public void StringIdentifierToEnumConversionWorks() + { + var actual = "integer".ToJsonSchemaType(); + Assert.Equal(JsonSchemaType.Integer, actual); + } + + [Fact] + public void ReturnSingleIdentifierWorks() + { + var type = JsonSchemaType.Integer; + var types = JsonSchemaType.Integer | JsonSchemaType.Null; + + Assert.Equal("integer", type.ToSingleIdentifier()); + Assert.Throws(() => types.ToSingleIdentifier()); + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs new file mode 100644 index 000000000..82d85926d --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeReferenceDeserializerTests.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiSecuritySchemeReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeSecuritySchemeReferenceAnnotations() + { + var json = + """ + { + "$ref": "#/components/securitySchemes/MyScheme", + "description": "This is a security scheme reference" + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.AddComponent("MyScheme", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey, + Name = "api_key", + In = ParameterLocation.Header, + Description = "This is a security scheme description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadSecurityScheme(parseNode, hostDocument); + + Assert.NotNull(result); + var resultReference = Assert.IsType(result); + + Assert.Equal("MyScheme", resultReference.Reference.Id); + Assert.Equal("This is a security scheme reference", resultReference.Description); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiTagReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiTagReferenceDeserializerTests.cs new file mode 100644 index 000000000..6aa3b356a --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiTagReferenceDeserializerTests.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V32; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests; + +public class OpenApiTagReferenceDeserializerTests +{ + [Fact] + public void ShouldDeserializeTagReferenceAnnotations() + { + var json = + """ + { + "tags" : [ + "MyTag" + ] + } + """; + + var hostDocument = new OpenApiDocument(); + hostDocument.Tags ??= new HashSet(); + hostDocument.Tags.Add(new OpenApiTag + { + Name = "MyTag", + Description = "This is a tag description", + }); + var jsonNode = JsonNode.Parse(json); + var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode); + + var result = OpenApiV32Deserializer.LoadOperation(parseNode, hostDocument); + // this diverges from the other unit tests because Tag References are implemented + // through the reference infrastructure for convenience, but the behave quite differently + + Assert.NotNull(result); + Assert.NotNull(result.Tags); + Assert.Single(result.Tags); + var resultReference = Assert.IsType(result.Tags.First()); + + Assert.Equal("MyTag", resultReference.Reference.Id); + Assert.Equal("This is a tag description", resultReference.Description); + Assert.NotNull(resultReference.Target); + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/OAS-schemas.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/OAS-schemas.yaml new file mode 100644 index 000000000..13f9735e4 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/OAS-schemas.yaml @@ -0,0 +1,19 @@ +openapi: 3.2.0 +info: + title: OpenAPI document containing reusable components + version: 1.0.0 +components: + schemas: + person: + type: object + properties: + name: + type: string + address: + type: object + properties: + street: + type: string + city: + type: string + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/STJSchema.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/STJSchema.json new file mode 100644 index 000000000..a938b139d --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/STJSchema.json @@ -0,0 +1,67 @@ +{ + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + }, + "parent": { + "type": [ + "object", + "null" + ], + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + }, + "parent": { + "$ref": "#/properties/parent", + "x-schema-id": "Category" + }, + "tags": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + } + }, + "required": [ + "name" + ], + "x-schema-id": "Tag" + } + } + }, + "required": [ + "name" + ], + "x-schema-id": "Category" + }, + "tags": { + "$ref": "#/properties/parent/properties/tags" + } + }, + "required": [ + "name" + ], + "x-schema-id": "Category" +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/componentExternalReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/componentExternalReference.yaml new file mode 100644 index 000000000..7f3404257 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/componentExternalReference.yaml @@ -0,0 +1,13 @@ +openapi: 3.2.0 +info: + title: Example of reference object in a component object + version: 1.0.0 +paths: + /item: + get: + security: + - customapikey: [] +components: + securitySchemes: + customapikey: + $ref: ./customApiKey.yaml#/components/securityschemes/customapikey diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/customApiKey.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/customApiKey.yaml new file mode 100644 index 000000000..e011543c9 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/customApiKey.yaml @@ -0,0 +1,11 @@ +openapi: 3.2.0 +info: + title: Example of reference object pointing to a parameter + version: 1.0.0 +paths: {} +components: + securitySchemes: + customapikey: + type: apiKey + name: x-api-key + in: header diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/examples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/examples.yaml new file mode 100644 index 000000000..7285fc09b --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/examples.yaml @@ -0,0 +1,11 @@ +# file for examples (examples.yaml) +openapi: 3.2.0 +info: + title: OpenAPI document containing examples for reuse + version: 1.0.0 +components: + examples: + item-list: + value: + - name: thing + description: a thing diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/externalComponentSubschemaReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/externalComponentSubschemaReference.yaml new file mode 100644 index 000000000..46824675e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/externalComponentSubschemaReference.yaml @@ -0,0 +1,14 @@ +openapi: 3.2.0 +info: + title: Reference to an external OpenApi document component + version: 1.0.0 +paths: + /person/{id}: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + $ref: 'OAS-schemas.yaml#/components/schemas/person/properties/address' diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineExternalReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineExternalReference.yaml new file mode 100644 index 000000000..848be1e5f --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineExternalReference.yaml @@ -0,0 +1,15 @@ +openapi: 3.2.0 +info: + title: Example of reference object pointing to an example object in an OpenAPI document + version: 1.0.0 +paths: + /items: + get: + responses: + '200': + description: sample description + content: + application/json: + examples: + item-list: + $ref: './examples.yaml#/components/examples/item-list' diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineLocalReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineLocalReference.yaml new file mode 100644 index 000000000..e123c7e3b --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/inlineLocalReference.yaml @@ -0,0 +1,14 @@ +openapi: 3.2.0 +info: + title: Example of reference object pointing to a parameter + version: 1.0.0 +paths: + /item: + get: + parameters: + - $ref: '#/components/parameters/size' +components: + parameters: + size: + schema: + type: number diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentReferenceUsingId.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentReferenceUsingId.yaml new file mode 100644 index 000000000..817512702 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentReferenceUsingId.yaml @@ -0,0 +1,29 @@ +openapi: 3.2.0 +info: + title: Reference an internal component using id + version: 1.0.0 +paths: + /person/{id}: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + $ref: 'https://schemas.acme.org/person' +components: + schemas: + person: + $id: 'https://schemas.acme.org/person' + type: object + properties: + name: + type: string + address: + type: object + properties: + street: + type: string + city: + type: string diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentsSubschemaReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentsSubschemaReference.yaml new file mode 100644 index 000000000..9164dbefa --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/internalComponentsSubschemaReference.yaml @@ -0,0 +1,55 @@ +openapi: 3.2.0 +info: + title: Reference to an internal component + version: 1.0.0 +paths: + /person/{id}: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/person' + /person/{id}/address: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/person/properties/address' + /human: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/human/allOf/0' +components: + schemas: + human: + allOf: + - $ref: '#/components/schemas/person/items' + - type: object + properties: + name: + type: string + person: + type: object + properties: + name: + type: string + address: + type: object + properties: + street: + type: string + city: + type: string + items: + type: integer diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/localReferenceToJsonSchemaResource.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/localReferenceToJsonSchemaResource.yaml new file mode 100644 index 000000000..ce29f718f --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/localReferenceToJsonSchemaResource.yaml @@ -0,0 +1,23 @@ +openapi: 3.2.0 +info: + title: OpenAPI document containing examples for reuse + version: 1.0.0 +components: + schemas: + a: + type: + - object + - 'null' + properties: + b: + type: + - object + - 'null' + properties: + c: + type: + - object + - 'null' + properties: + b: + $ref: '#/properties/b' diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/recursiveRelativeSubschemaReference.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/recursiveRelativeSubschemaReference.json new file mode 100644 index 000000000..d58d97ebf --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/recursiveRelativeSubschemaReference.json @@ -0,0 +1,96 @@ +{ + "openapi": "3.2.0", + "info": { + "title": "Recursive relative reference in a subschema of an component schema", + "version": "1.0.0" + }, + "paths": { + "/items": { + "get": { + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Foo" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Foo": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + }, + "parent": { + "type": [ + "object", + "null" + ], + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + }, + "parent": { + "$ref": "#/properties/parent", + "x-schema-id": "Category" + }, + "tags": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "format": null, + "x-schema-id": null + } + }, + "required": [ + "name" + ], + "x-schema-id": "Tag" + } + } + }, + "required": [ + "name" + ], + "x-schema-id": "Category" + }, + "tags": { + "$ref": "#/properties/parent/properties/tags" + } + }, + "required": [ + "name" + ], + "x-schema-id": "Category" + } + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/relativeSubschemaReference.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/relativeSubschemaReference.json new file mode 100644 index 000000000..cfeace1be --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/relativeSubschemaReference.json @@ -0,0 +1,58 @@ +{ + "openapi": "3.2.0", + "info": { + "title": "Relative reference in a subschema of an component schema", + "version": "1.0.0" + }, + "paths": { + "/items": { + "get": { + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Foo" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Foo": { + "type": "object", + "properties": { + "seq1": { + "type": [ + "array", + "null" + ], + "items": { + "type": "array", + "items": { + "type": "string", + "format": null, + "x-schema-id": null + } + } + }, + "seq2": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/properties/seq1/items" + } + } + }, + "x-schema-id": "ContainerType" + } + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootComponentSchemaReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootComponentSchemaReference.yaml new file mode 100644 index 000000000..a893382e0 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootComponentSchemaReference.yaml @@ -0,0 +1,23 @@ +openapi: 3.2.0 +info: + title: Reference at the root of a component schema + version: 1.0.0 +paths: + /items: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/specialitem' +components: + schemas: + specialitem: + $ref: "#/components/schemas/item" + item: + title: Item + type: object diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootInlineSchemaReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootInlineSchemaReference.yaml new file mode 100644 index 000000000..5374b1851 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/rootInlineSchemaReference.yaml @@ -0,0 +1,18 @@ +openapi: 3.2.0 +info: + title: Reference in at the root of an inline schema + version: 1.0.0 +paths: + /item: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/item' +components: + schemas: + item: + type: object diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaComponentSchemaReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaComponentSchemaReference.yaml new file mode 100644 index 000000000..2d480704e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaComponentSchemaReference.yaml @@ -0,0 +1,22 @@ +openapi: 3.2.0 +info: + title: Reference in a subschema of an component schema + version: 1.0.0 +paths: + /items: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + $ref: '#/components/schemas/items' +components: + schemas: + items: + type: array + items: + $ref: '#/components/schemas/item' + item: + type: object diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaInlineSchemaReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaInlineSchemaReference.yaml new file mode 100644 index 000000000..ce48ac2a1 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/ReferenceSamples/subschemaInlineSchemaReference.yaml @@ -0,0 +1,20 @@ +openapi: 3.2.0 +info: + title: Reference in at the root of an inline schema + version: 1.0.0 +paths: + /items: + get: + responses: + 200: + description: ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/item' +components: + schemas: + item: + type: object diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs new file mode 100644 index 000000000..c2b335ec8 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs @@ -0,0 +1,515 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Microsoft.OpenApi.Reader; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V32Tests +{ + public class RelativeReferenceTests + { + private const string SampleFolderPath = "V32Tests/ReferenceSamples"; + + [Fact] + public async Task ParseInlineLocalReferenceWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "inlineLocalReference.yaml"); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var schemaType = actual.Paths["/item"].Operations[HttpMethod.Get].Parameters[0].Schema.Type; + + // Assert + Assert.Equal(JsonSchemaType.Number, schemaType); + } + + [Fact] + public async Task ParseInlineExternalReferenceWorks() + { + // Arrange + var expected = new JsonArray + { + new JsonObject + { + ["name"] = "thing", + ["description"] = "a thing" + } + }; + + var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + + var settings = new OpenApiReaderSettings + { + LoadExternalRefs = true, + BaseUrl = new(path), + }; + settings.AddYamlReader(); + + // Act + var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "inlineExternalReference.yaml"), settings)).Document; + var exampleValue = actual.Paths["/items"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Examples["item-list"].Value; + + // Assert + Assert.NotNull(exampleValue); + Assert.IsType(exampleValue); + Assert.Equal(expected.ToJsonString(), exampleValue.ToJsonString()); + } + + [Fact] + public async Task ParseComponentExternalReferenceWorks() + { + // Arrange + var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var settings = new OpenApiReaderSettings + { + LoadExternalRefs = true, + BaseUrl = new(path), + }; + settings.AddYamlReader(); + + // Act + var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "componentExternalReference.yaml"), settings)).Document; + var securitySchemeValue = actual.Components.SecuritySchemes["customapikey"]; + + // Assert + Assert.Equal("x-api-key", securitySchemeValue.Name); + } + + [Fact] + public async Task ParseRootInlineJsonSchemaReferenceWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "rootInlineSchemaReference.yaml"); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var schema = actual.Paths["/item"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + + // Assert + Assert.Equal(JsonSchemaType.Object, schema.Type); + } + + [Fact] + public async Task ParseSubschemaInlineJsonSchemaReferenceWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "subschemaInlineSchemaReference.yaml"); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var schema = actual.Paths["/items"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema.Items; + + // Assert + Assert.Equal(JsonSchemaType.Object, schema.Type); + } + + [Fact] + public async Task ParseRootComponentJsonSchemaReferenceWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "rootComponentSchemaReference.yaml"); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var schema = actual.Components.Schemas["specialitem"]; + + // Assert + Assert.Equal(JsonSchemaType.Object, schema.Type); + Assert.Equal("Item", schema.Title); + } + + [Fact] + public async Task ParseSubschemaComponentJsonSchemaReferenceWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "subschemaComponentSchemaReference.yaml"); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var schema = actual.Components.Schemas["items"].Items; + + // Assert + Assert.Equal(JsonSchemaType.Object, schema.Type); + } + + [Fact] + public async Task ParseInternalComponentSubschemaJsonSchemaReferenceWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "internalComponentsSubschemaReference.yaml"); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var addressSchema = actual.Paths["/person/{id}/address"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + var itemsSchema = actual.Paths["/human"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + + // Assert + Assert.Equal(JsonSchemaType.Object, addressSchema.Type); + Assert.Equal(JsonSchemaType.Integer, itemsSchema.Type); + } + + [Fact] + public async Task ParseExternalComponentSubschemaJsonSchemaReferenceWorks() + { + // Arrange + var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var settings = new OpenApiReaderSettings + { + LoadExternalRefs = true, + BaseUrl = new(path), + }; + settings.AddYamlReader(); + + // Act + var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalComponentSubschemaReference.yaml"), settings)).Document; + var schema = actual.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + + // Assert + Assert.Equal(JsonSchemaType.Object, schema.Type); + } + + [Fact] + public async Task ParseReferenceToInternalComponentUsingDollarIdWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "internalComponentReferenceUsingId.yaml"); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var schema = actual.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; + + // Assert + Assert.Equal(JsonSchemaType.Object, schema.Type); + } + + [Fact] + public async Task ParseLocalReferenceToJsonSchemaResourceWorks() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "localReferenceToJsonSchemaResource.yaml"); + var stringWriter = new StringWriter(); + var writer = new OpenApiYamlWriter(stringWriter); + + // Act + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var schema = actual.Components.Schemas["a"].Properties["b"].Properties["c"].Properties["b"]; + schema.SerializeAsV32(writer); + + // Assert + Assert.Equal(JsonSchemaType.Object | JsonSchemaType.Null, schema.Type); + } + + [Fact] + public void ResolveSubSchema_ShouldTraverseKnownKeywords() + { + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary + { + ["a"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["b"] = new OpenApiSchema { Type = JsonSchemaType.String } + } + } + } + }; + + var path = new[] { "properties", "a", "properties", "b" }; + + var result = OpenApiWorkspace.ResolveSubSchema(schema, path, []); + + Assert.NotNull(result); + Assert.Equal(JsonSchemaType.String, result!.Type); + } + + public static IEnumerable SubSchemaKeywordPropertyPaths => + [ + [new[] { "properties", "properties" }], + [new[] { "properties", "allOf" }] + ]; + + + [Theory] + [MemberData(nameof(SubSchemaKeywordPropertyPaths))] + public void ResolveSubSchema_ShouldHandleUserDefinedKeywordNamedProperty(string[] pathSegments) + { + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary + { + ["properties"] = new OpenApiSchema { Type = JsonSchemaType.String }, + ["allOf"] = new OpenApiSchema { Type = JsonSchemaType.String } + } + }; + + var result = OpenApiWorkspace.ResolveSubSchema(schema, pathSegments, []); + + Assert.NotNull(result); + Assert.Equal(JsonSchemaType.String, result!.Type); + } + + [Fact] + public void ResolveSubSchema_ShouldRecurseIntoAllOfComposition() + { + var schema = new OpenApiSchema + { + AllOf = + [ + new OpenApiSchema + { + Properties = new Dictionary + { + ["x"] = new OpenApiSchema { Type = JsonSchemaType.Integer } + } + } + ] + }; + + var path = new[] { "allOf", "0", "properties", "x" }; + + var result = OpenApiWorkspace.ResolveSubSchema(schema, path, []); + + Assert.NotNull(result); + Assert.Equal(JsonSchemaType.Integer, result!.Type); + } + [Fact] + public async Task ShouldResolveRelativeSubReference() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json"); + + // Act + var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + + var fooComponentSchema = actual.Components.Schemas["Foo"]; + var seq1Property = fooComponentSchema.Properties["seq1"]; + Assert.NotNull(seq1Property); + var seq2Property = fooComponentSchema.Properties["seq2"]; + Assert.NotNull(seq2Property); + Assert.Equal(JsonSchemaType.Array, seq2Property.Items.Type); + Assert.Equal(JsonSchemaType.String, seq2Property.Items.Items.Type); + } + [Fact] + public async Task ShouldResolveRelativeSubReferenceUsingParsingContext() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json"); + using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); + var jsonNode = await JsonNode.ParseAsync(fs); + var schemaJsonNode = jsonNode["components"]?["schemas"]?["Foo"]; + Assert.NotNull(schemaJsonNode); + var diagnostic = new OpenApiDiagnostic(); + var parsingContext = new ParsingContext(diagnostic); + parsingContext.StartObject("components"); + parsingContext.StartObject("schemas"); + parsingContext.StartObject("Foo"); + var document = new OpenApiDocument(); + + // Act + var fooComponentSchema = parsingContext.ParseFragment(schemaJsonNode, OpenApiSpecVersion.OpenApi3_2, document); + document.AddComponent("Foo", fooComponentSchema); + var seq1Property = fooComponentSchema.Properties["seq1"]; + Assert.NotNull(seq1Property); + var seq2Property = fooComponentSchema.Properties["seq2"]; + Assert.NotNull(seq2Property); + Assert.Equal(JsonSchemaType.Array, seq2Property.Items.Type); + Assert.Equal(JsonSchemaType.String, seq2Property.Items.Items.Type); + } + [Fact] + public void ShouldFailToResolveRelativeSubReferenceFromTheObjectModel() + { + var document = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test API", Version = "1.0.0" }, + }; + document.Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["Foo"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["seq1"] = new OpenApiSchema { Type = JsonSchemaType.Array | JsonSchemaType.Null, Items = new OpenApiSchema { Type = JsonSchemaType.Array, Items = new OpenApiSchema { Type = JsonSchemaType.String } } }, + ["seq2"] = new OpenApiSchema { Type = JsonSchemaType.Array | JsonSchemaType.Null, Items = new OpenApiSchemaReference("#/properties/seq1/items", document) } + } + } + } + }; + document.RegisterComponents(); + + var fooComponentSchema = document.Components.Schemas["Foo"]; + var seq1Property = fooComponentSchema.Properties["seq1"]; + Assert.NotNull(seq1Property); + var seq2Property = fooComponentSchema.Properties["seq2"]; + Assert.NotNull(seq2Property); + Assert.Throws(() => seq2Property.Items.Type); + // it's impossible to resolve relative references from the object model only because we don't have a way to get to + // the parent object to build the full path for the reference. + + + // #/properties/seq1/items + // #/components/schemas/Foo/properties/seq1/items + } + [Fact] + public void ShouldResolveAbsoluteSubReferenceFromTheObjectModel() + { + var document = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test API", Version = "1.0.0" }, + }; + document.Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["Foo"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["seq1"] = new OpenApiSchema { Type = JsonSchemaType.Array | JsonSchemaType.Null, Items = new OpenApiSchema { Type = JsonSchemaType.Array, Items = new OpenApiSchema { Type = JsonSchemaType.String } } }, + ["seq2"] = new OpenApiSchema { Type = JsonSchemaType.Array | JsonSchemaType.Null, Items = new OpenApiSchemaReference("#/components/schemas/Foo/properties/seq1/items", document) } + } + } + } + }; + document.RegisterComponents(); + + var fooComponentSchema = document.Components.Schemas["Foo"]; + var seq1Property = fooComponentSchema.Properties["seq1"]; + Assert.NotNull(seq1Property); + var seq2Property = fooComponentSchema.Properties["seq2"]; + Assert.NotNull(seq2Property); + Assert.Equal(JsonSchemaType.Array, seq2Property.Items.Type); + Assert.Equal(JsonSchemaType.String, seq2Property.Items.Items.Type); + } + [Fact] + public async Task ShouldResolveRecursiveRelativeSubReference() + { + // Arrange + var filePath = Path.Combine(SampleFolderPath, "recursiveRelativeSubschemaReference.json"); + + // Act + var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + + var fooComponentSchema = actual.Components.Schemas["Foo"]; + var fooSchemaParentProperty = fooComponentSchema.Properties["parent"]; + Assert.NotNull(fooSchemaParentProperty); + var fooSchemaParentPropertyTagsProperty = fooSchemaParentProperty.Properties["tags"]; + Assert.NotNull(fooSchemaParentPropertyTagsProperty); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, fooSchemaParentPropertyTagsProperty.Type); + Assert.Equal(JsonSchemaType.Object, fooSchemaParentPropertyTagsProperty.Items.Type); + + var fooSchemaTagsProperty = fooComponentSchema.Properties["tags"]; + Assert.NotNull(fooSchemaTagsProperty); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, fooSchemaTagsProperty.Type); + Assert.Equal(JsonSchemaType.Object, fooSchemaTagsProperty.Items.Type); + } + [Fact] + public async Task ShouldResolveReferencesInSchemasFromSystemTextJson() + { + var filePath = Path.Combine(SampleFolderPath, "STJSchema.json"); + using var fs = File.OpenRead(filePath); + var jsonNode = await JsonNode.ParseAsync(fs); + + var parsingContext = new ParsingContext(new OpenApiDiagnostic()); + var document = new OpenApiDocument(); + var schema = parsingContext.ParseFragment(jsonNode, OpenApiSpecVersion.OpenApi3_2, document); + Assert.NotNull(schema); + + document.AddComponent("Foo", schema); + var tagsProperty = Assert.IsType(schema.Properties["tags"]); + // this is the reference that is generated by STJ schema generator which does not have OAI in context. + Assert.Equal("#/properties/parent/properties/tags", tagsProperty.Reference.ReferenceV3); + // this is the reference that needs to be used in the document for components resolution. + var absoluteReferenceId = $"#/components/schemas/Foo{tagsProperty.Reference.ReferenceV3.Replace("#", string.Empty)}"; + schema.Properties["tags"] = new OpenApiSchemaReference(absoluteReferenceId, document); + var updatedTagsProperty = Assert.IsType(schema.Properties["tags"]); + Assert.Equal(absoluteReferenceId, updatedTagsProperty.Reference.ReferenceV3); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, updatedTagsProperty.Type); + Assert.Equal(JsonSchemaType.Object, updatedTagsProperty.Items.Type); + + + // doing the same for the parent property + + var parentProperty = Assert.IsType(schema.Properties["parent"]); + var parentSubProperty = Assert.IsType(parentProperty.Properties["parent"]); + Assert.Equal("#/properties/parent", parentSubProperty.Reference.ReferenceV3); + parentProperty.Properties["parent"] = new OpenApiSchemaReference($"#/components/schemas/Foo{parentSubProperty.Reference.ReferenceV3.Replace("#", string.Empty)}", document); + var updatedParentSubProperty = Assert.IsType(parentProperty.Properties["parent"]); + Assert.Equal(JsonSchemaType.Object | JsonSchemaType.Null, updatedParentSubProperty.Type); + + var pathItem = new OpenApiPathItem + { + Operations = new Dictionary + { + [HttpMethod.Post] = new OpenApiOperation + { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + } + }, + RequestBody = new OpenApiRequestBody + { + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchemaReference("#/components/schemas/Foo", document) + } + } + } + } + } + }; + document.Paths.Add("/", pathItem); + + var requestBodySchema = pathItem.Operations[HttpMethod.Post].RequestBody.Content["application/json"].Schema; + Assert.NotNull(requestBodySchema); + var requestBodyTagsProperty = Assert.IsType(requestBodySchema.Properties["tags"]); + Assert.Equal(JsonSchemaType.Object, requestBodyTagsProperty.Items.Type); + } + + [Fact] + public void ExitsEarlyOnCyclicalReferences() + { + var document = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test API", Version = "1.0.0" }, + }; + var categorySchema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary + { + ["name"] = new OpenApiSchema { Type = JsonSchemaType.String }, + ["parent"] = new OpenApiSchemaReference("#/components/schemas/Category", document), + // this is intentionally wrong and cyclical reference + // it tests whether we're going in an infinite resolution loop + ["tags"] = new OpenApiSchemaReference("#/components/schemas/Category/properties/parent/properties/tags", document) + } + }; + document.AddComponent("Category", categorySchema); + document.RegisterComponents(); + + var tagsSchemaRef = Assert.IsType(categorySchema.Properties["tags"]); + Assert.Throws(() => tagsSchemaRef.Items); + Assert.Equal("#/components/schemas/Category/properties/parent/properties/tags", tagsSchemaRef.Reference.ReferenceV3); + Assert.Throws(() => tagsSchemaRef.Target); + + var parentSchemaRef = Assert.IsType(categorySchema.Properties["parent"]); + Assert.Equal("#/components/schemas/Category", parentSchemaRef.Reference.ReferenceV3); + Assert.NotNull(parentSchemaRef.Target); + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWith32properties.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWith32properties.json new file mode 100644 index 000000000..e98c74362 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWith32properties.json @@ -0,0 +1,166 @@ +{ + "openapi": "3.2.0", + "info": { + "title": "Sample OpenAPI 3.2 API", + "description": "A sample API demonstrating OpenAPI 3.2 features", + "version": "2.0.0", + "summary": "Sample OpenAPI 3.2 API with the latest features", + "license": { + "name": "Apache 2.0", + "identifier": "Apache-2.0" + } + }, + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", + "servers": [ + { + "url": "https://api.example.com/v2", + "description": "Main production server" + } + ], + "webhooks": { + "newPetAlert": { + "post": { + "summary": "Notify about a new pet being added", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "responses": { + "200": { + "description": "Webhook processed successfully" + } + } + } + } + }, + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "exclusiveMinimum": 1, + "exclusiveMaximum": 100 + } + } + ], + "responses": { + "200": { + "description": "A paged array of pets", + "content": { + "application/json": { + "schema": { + "$ref": "https://example.com/schemas/pet.json" + } + } + } + } + } + } + }, + "/sample": { + "get": { + "summary": "Sample endpoint", + "responses": { + "200": { + "description": "Sample response", + "content": { + "application/json": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/person.schema.yaml", + "$comment": "A schema defining a pet object with optional references to dynamic components.", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": false, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": false + }, + "title": "Pet", + "description": "Schema for a pet object", + "type": "object", + "properties": { + "name": { + "type": "string", + "$comment": "The pet's full name" + }, + "address": { + "$dynamicRef": "#addressDef", + "$comment": "Reference to an address definition which can change dynamically" + } + }, + "required": [ + "name" + ], + "$dynamicAnchor": "addressDef" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "schemas": { + "Pet": { + "$id": "https://example.com/schemas/pet.json", + "type": "object", + "required": [ + "id", + "weight" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "weight": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Weight of the pet in kilograms" + }, + "attributes": { + "type": [ + "object", + "null" + ], + "description": "Dynamic attributes for the pet", + "patternProperties": { + "^attr_[A-Za-z]+$": { + "type": "string" + } + } + } + }, + "$comment": "This schema represents a pet in the system.", + "$defs": { + "ExtraInfo": { + "type": "string" + } + } + } + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithExample.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithExample.yaml new file mode 100644 index 000000000..85560384a --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithExample.yaml @@ -0,0 +1,96 @@ +openapi: 3.2.0 # The version of the OpenAPI Specification +info: # Metadata about the API + title: A simple OpenAPI 3.2 example + version: 1.0.0 + license: + name: Apache 2.0 + identifier: Apache-2.0 # The SPDX license identifier +paths: # The available paths and operations for the API + /echo: # A path for echoing messages using WebSockets + get: # An operation using the GET method + summary: Echo a message + description: Send a message to the server and receive the same message back + responses: + '101': + description: Switching Protocols + headers: + Upgrade: + schema: + type: string + enum: + - websocket + Connection: + schema: + type: string + enum: + - Upgrade + Sec-WebSocket-Accept: + schema: + type: string + content: {} # No content is returned for this response + servers: + - url: ws://example.com # The WebSocket server URL + /upload: # A path for uploading files using multipart/form-data + post: # An operation using the POST method + summary: Upload a file + description: Upload a file to the server and receive a confirmation message + requestBody: + required: true + content: + multipart/form-data: # The media type for sending multiple parts of data + schema: + type: object + properties: + file: # A property for the file data + type: string + format: binary + comment: # A property for the file comment + type: string + encoding: # The encoding for each part of data + file: + contentType: application/octet-stream # The media type for the file data + comment: + contentType: text/plain # The media type for the file comment + responses: + '200': + description: File uploaded successfully + content: + application/json: # The media type for the response body + schema: + type: object + properties: + message: # A property for the confirmation message + type: string + examples: + - The file was uploaded successfully +components: # Reusable components for the API + schemas: # JSON Schema definitions for the API + Pet: # A schema for a pet object + type: object + required: + - petType + properties: + petType: # A property for the pet type + type: string + discriminator: # The discriminator for resolving the concrete schema type + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + Cat: # A schema for a cat object + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + name: # A property for the cat name + type: string + default: "Fluffy" # The default value for the cat name + Dog: # A schema for a dog object + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + bark: # A property for the dog bark + type: string + default: "Woof" # The default value for the dog bark + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml new file mode 100644 index 000000000..b51e48523 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml @@ -0,0 +1,26 @@ +openapi: 3.2.0 +info: + title: Example API + version: 1.0.0 +paths: + /example: + get: + summary: Get example object + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + properties: + prop1: + type: string + prop2: + type: string + prop3: + type: string + patternProperties: + "^x-.*$": + type: string + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferenceById.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferenceById.yaml new file mode 100644 index 000000000..26c0ae066 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferenceById.yaml @@ -0,0 +1,45 @@ +openapi: 3.2.0 +info: + title: ReferenceById + version: 1.0.0 +paths: + /resource: + get: + parameters: + - name: id + in: query + required: true + schema: + $ref: 'https://example.com/schemas/id.json' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'https://example.com/schemas/resource.json' + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: 'https://example.com/schemas/resource.json' + responses: + '200': + description: OK +components: + schemas: + Resource: + $id: 'https://example.com/schemas/resource.json' + type: object + properties: + id: + type: string + name: + type: string + reference: + $ref: '#/components/schemas/Resource' + Id: + $id: 'https://example.com/schemas/id.json' + type: string diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml new file mode 100644 index 000000000..0d8163cd5 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/docWithReferencedExampleInSchemaWorks.yaml @@ -0,0 +1,30 @@ +openapi: 3.2.0 +info: + title: ReferenceById + version: 1.0.0 +paths: + /resource: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DiffCreatedEvent' +components: + schemas: + DiffCreatedEvent: + description: 'diff index created' + type: object + additionalProperties: false + properties: + updatedAt: + $ref: '#/components/schemas/Timestamp' + example: + "updatedAt": '2020-06-30T06:43:51.391Z' + Timestamp: + type: string + format: date-time + description: 'timestamp' + example: '2020-06-30T06:43:51.391Z' diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWith32Properties.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWith32Properties.yaml new file mode 100644 index 000000000..d7262b59d --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWith32Properties.yaml @@ -0,0 +1,129 @@ +openapi: 3.2.0 +info: + title: Sample OpenAPI 3.2 API + description: A sample API demonstrating OpenAPI 3.2 features + version: "2.0.0" + summary: Sample OpenAPI 3.2 API with the latest features # OpenAPI 3.2 feature + license: + name: Apache 2.0 + identifier: Apache-2.0 # SPDX license identifier, a new 3.2 feature to define an API's SPDX license expression + +# JSON Schema 2020-12 feature +jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema" + +servers: + - url: https://api.example.com/v2 + description: Main production server + +# Example Webhooks (OpenAPI 3.2 feature) +webhooks: + newPetAlert: + post: + summary: Notify about a new pet being added + requestBody: + required: true + content: + application/json: + schema: + type: string + responses: + '200': + description: Webhook processed successfully +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + #exclusiveMinimum and exclusiveMaximum now represent distinct values + exclusiveMinimum: 1 + exclusiveMaximum: 100 + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + # 3.2 feature where we can reference schemas using their identifier + $ref: 'https://example.com/schemas/pet.json' + /sample: + get: + summary: Sample endpoint + responses: + '200': + description: Sample response + content: + application/json: + schema: + #JSON schema keywords + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/person.schema.yaml" + $comment: "A schema defining a pet object with optional references to dynamic components." + $vocabulary: + "https://json-schema.org/draft/2020-12/vocab/core": true + "https://json-schema.org/draft/2020-12/vocab/applicator": true + "https://json-schema.org/draft/2020-12/vocab/validation": true + "https://json-schema.org/draft/2020-12/vocab/meta-data": false + "https://json-schema.org/draft/2020-12/vocab/format-annotation": false + + title: "Pet" + description: "Schema for a pet object" + type: "object" + properties: + name: + type: "string" + $comment: "The pet's full name" + address: + $dynamicRef: "#addressDef" + $comment: "Reference to an address definition which can change dynamically" + required: + - name + $dynamicAnchor: "addressDef" +components: + securitySchemes: + api_key: + type: apiKey + name: api_key + in: header + schemas: + Pet: + $id: 'https://example.com/schemas/pet.json' + type: object + required: + - id + - weight + properties: + id: + type: string + format: uuid + weight: + type: number + exclusiveMinimum: 0 + description: Weight of the pet in kilograms + # Pattern properties and Type array feature from JSON Schema + attributes: + type: + - "object" + - "null" + description: Dynamic attributes for the pet + patternProperties: + "^attr_[A-Za-z]+$": + type: string + $comment: "This schema represents a pet in the system." # JSON Schema 2020-12 feature + $defs: # JSON Schema 2020-12 feature + ExtraInfo: + type: string + +tags: + - name: pets + +security: + - api_key: [] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithEmptyTags.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithEmptyTags.json new file mode 100644 index 000000000..00834158e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithEmptyTags.json @@ -0,0 +1,51 @@ +{ + "openapi": "3.2.0", + "info": { + "description": "Groups API", + "title": "Groups", + "version": "1.0" + }, + "paths": { + "/groups": { + "get": { + "operationId": "getGroups", + "parameters": [ + { + "description": "Zero-based page index (0..N)", + "example": 0, + "in": "query", + "name": "page", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 0, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedGroup" } } } + } + }, + "tags": [ "" ] + } + } + }, + "components": { + "schemas": { + "PaginatedGroup": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "format": "int32", + "description": "The number of the current page." + } + } + } + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml new file mode 100644 index 000000000..cfa3872ee --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml @@ -0,0 +1,95 @@ +openapi : 3.2.0 +info: + title: Webhook Example + version: 1.0.0 +jsonSchemaDialect: "http://json-schema.org/draft-07/schema#" +webhooks: + pets: + "$ref": '#/components/pathItems/pets' +components: + schemas: + petSchema: + type: object + required: + - id + - name + dependentRequired: + tag: + - category + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + category: + type: string + newPetSchema: + type: object + required: + - name + dependentRequired: + tag: + - category + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + category: + type: string + pathItems: + pets: + get: + description: Returns all pets from the system that the user has access to + operationId: findPets + parameters: + - name: tags + in: query + description: tags to filter by + required: false + schema: + type: array + items: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + application/xml: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + post: + requestBody: + description: Information about a new pet in the system + required: true + content: + 'application/json': + schema: + "$ref": '#/components/schemas/newPetSchema' + responses: + "200": + description: Return a 200 status to indicate that the data was received successfully + content: + application/json: + schema: + $ref: '#/components/schemas/petSchema' diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSchema.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSchema.json new file mode 100644 index 000000000..51427b914 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSchema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://spec.openapis.org/oas/3.2/schema/2025-02-13", + "openapi": "3.2.0", + "info": { + "title": "Sample API", + "version": "1.0.0" + }, + "paths": { + "/example": { + "get": { + "summary": "Example operation", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml new file mode 100644 index 000000000..87d44d28a --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml @@ -0,0 +1,43 @@ +openapi: '3.2.0' +info: + version: '1.0.0' + title: Swagger Petstore (Simple) +paths: + /pets: + get: + description: Returns all pets from the system that the user has access to + responses: + '200': + description: pet response + content: + application/json: + schema: + "$ref": '#/components/schemas/pet' +components: + headers: + X-Test: + description: Test + summary: An X-Test header + schema: + type: string + responses: + Test: + description: Test Response + headers: + X-Test: + $ref: '#/components/headers/X-Test' + schemas: + pet: + description: A referenced pet in a petstore + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml new file mode 100644 index 000000000..08bbfff80 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml @@ -0,0 +1,91 @@ +openapi: 3.2.0 +info: + title: Webhook Example + version: 1.0.0 +webhooks: + pets: + get: + description: Returns all pets from the system that the user has access to + operationId: findPets + parameters: + - name: tags + in: query + description: tags to filter by + required: false + schema: + type: array + items: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + application/xml: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + post: + requestBody: + description: Information about a new pet in the system + required: true + content: + 'application/json': + schema: + "$ref": '#/components/schemas/newPetSchema' + responses: + "200": + description: Return a 200 status to indicate that the data was received successfully + content: + application/json: + schema: + $ref: '#/components/schemas/petSchema' +components: + schemas: + petSchema: + type: object + required: + - id + - name + dependentRequired: + tag: + - category + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + category: + type: string + newPetSchema: + type: object + required: + - name + dependentRequired: + tag: + - category + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + category: + type: string diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefById.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefById.yaml new file mode 100644 index 000000000..68f5ffdfd --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefById.yaml @@ -0,0 +1,14 @@ +openapi: 3.2.0 +info: + title: ReferenceById + version: 1.0.0 +paths: + /resource: + get: + parameters: + - name: id + in: query + required: true + schema: + $ref: 'https://example.com/schemas/user.json' +components: {} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml new file mode 100644 index 000000000..25c18f845 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml @@ -0,0 +1,15 @@ +openapi: 3.2.0 +info: + title: ReferenceById + version: 1.0.0 +paths: + /resource: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './externalResource.yaml#/components/schemas/todo' +components: {} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalResource.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalResource.yaml new file mode 100644 index 000000000..ce0efebd7 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiDocument/externalResource.yaml @@ -0,0 +1,22 @@ +openapi: 3.2.0 +info: + title: ReferencedById + version: 1.0.0 +paths: {} +components: + schemas: + todo: + type: object + properties: + id: + type: string + name: + type: string + user: + $id: 'https://example.com/schemas/user.json' + type: object + properties: + id: + type: string + name: + type: string diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiInfo/basicInfo.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiInfo/basicInfo.yaml new file mode 100644 index 000000000..941f6dbd3 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiInfo/basicInfo.yaml @@ -0,0 +1,17 @@ +{ + "title": "Basic Info", + "summary": "Sample Summary", + "description": "Sample Description", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.1" +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml new file mode 100644 index 000000000..7a0d3bd76 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml @@ -0,0 +1,3 @@ +name: Apache 2.0 +identifier: Apache-2.0 + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/advancedSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/advancedSchema.yaml new file mode 100644 index 000000000..a01d9f294 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/advancedSchema.yaml @@ -0,0 +1,48 @@ +type: object +properties: + one: + description: type array + type: + - integer + - string + two: + description: type 'null' + type: "null" + three: + description: type array including 'null' + type: + - string + - "null" + four: + description: array with no items + type: array + five: + description: singular example + type: string + examples: + - exampleValue + six: + description: exclusiveMinimum true + exclusiveMinimum: 10 + seven: + description: exclusiveMinimum false + minimum: 10 + eight: + description: exclusiveMaximum true + exclusiveMaximum: 20 + nine: + description: exclusiveMaximum false + maximum: 20 + ten: + description: nullable string + type: + - string + - "null" + eleven: + description: x-nullable string + type: + - string + - "null" + twelve: + description: file/binary + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/jsonSchema.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/jsonSchema.json new file mode 100644 index 000000000..dc55b72c2 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/jsonSchema.json @@ -0,0 +1,49 @@ +{ + "$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?" + }, + "veggieType": { + "type": "string", + "description": "The type of vegetable (e.g., root, leafy, etc.)." + }, + "veggieColor": { + "type": "string", + "description": "The color of the vegetable." + }, + "veggieSize": { + "type": "string", + "description": "The size of the vegetable." + } + }, + "dependentRequired": { + "veggieType": [ "veggieColor", "veggieSize" ] + } + } + } +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schema.yaml new file mode 100644 index 000000000..0b2740b28 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schema.yaml @@ -0,0 +1,8 @@ +type: object +properties: + one: + description: type array + type: + - integer + - string + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithConst.json b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithConst.json new file mode 100644 index 000000000..d394f142f --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithConst.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "active" + }, + "user": { + "type": "object", + "properties": { + "role": { + "type": "string", + "const": "admin" + } + }, + "required": [ "role" ] + } + }, + "required": [ "status" ] +} + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithExamples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithExamples.yaml new file mode 100644 index 000000000..98f19e3f0 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithExamples.yaml @@ -0,0 +1,4 @@ +type: string +examples: + - fedora + - ubuntu diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml new file mode 100644 index 000000000..0c20b0d3d --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml @@ -0,0 +1,31 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://example.com/schemas/person.schema.yaml" +$comment: "A schema defining a person object with optional references to dynamic components." +$vocabulary: + "https://json-schema.org/draft/2020-12/vocab/core": true + "https://json-schema.org/draft/2020-12/vocab/applicator": true + "https://json-schema.org/draft/2020-12/vocab/validation": true + "https://json-schema.org/draft/2020-12/vocab/meta-data": false + "https://json-schema.org/draft/2020-12/vocab/format-annotation": false + +title: "Person" +description: "Schema for a person object" +type: "object" + +properties: + name: + type: "string" + $comment: "The person's full name" + age: + type: "integer" + minimum: 0 + $comment: "Age must be a non-negative integer" + address: + $dynamicRef: "#addressDef" + $comment: "Reference to an address definition which can change dynamically" + +required: + - name + +$dynamicAnchor: "addressDef" + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullable.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullable.yaml new file mode 100644 index 000000000..5953df083 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullable.yaml @@ -0,0 +1,2 @@ +type: string +nullable: true diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullableExtension.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullableExtension.yaml new file mode 100644 index 000000000..271d5f4c9 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithNullableExtension.yaml @@ -0,0 +1,2 @@ +type: string +x-nullable: true diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithTypeArray.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithTypeArray.yaml new file mode 100644 index 000000000..2a916fb56 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSchema/schemaWithTypeArray.yaml @@ -0,0 +1,3 @@ +type: +- "string" +- "null" From c7658165835178357f37844f3c7ecb953c1ebfb5 Mon Sep 17 00:00:00 2001 From: costabello matthieu Date: Wed, 24 Sep 2025 15:52:35 -0400 Subject: [PATCH 008/146] missing 3.1 => 3.2 --- .../V32Tests/OpenApiDocumentSerializationTests .cs | 2 +- .../V32Tests/OpenApiDocumentTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs index c82bc37e2..2888e39e2 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests .cs @@ -22,7 +22,7 @@ public class OpenApiDocumentSerializationTests public async Task Serialize_DoesNotMutateDom(OpenApiSpecVersion version) { // Arrange - var filePath = Path.Combine(SampleFolderPath, "docWith31properties.json"); + var filePath = Path.Combine(SampleFolderPath, "docWith32properties.json"); var (doc, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); // Act: Serialize using System.Text.Json diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs index 055511286..aae922700 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Threading.Tasks; @@ -558,9 +558,9 @@ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() } [Fact] - public async Task ParseDocumentWith31PropertiesWorks() + public async Task ParseDocumentWith32PropertiesWorks() { - var path = Path.Combine(SampleFolderPath, "documentWith31Properties.yaml"); + var path = Path.Combine(SampleFolderPath, "documentWith32Properties.yaml"); var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; var outputStringWriter = new StringWriter(); doc.SerializeAsV32(new OpenApiYamlWriter(outputStringWriter)); From 263a8d9954bdcd242cacba4cd53b3de90adbc01c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 26 Sep 2025 16:52:16 -0400 Subject: [PATCH 009/146] fix: a bug where json schema keywords would be missing from serialized value in OAS 3.2 --- 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 0ad6a7274..c371e9786 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -389,7 +389,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version { writer.WriteStartObject(); - if (version == OpenApiSpecVersion.OpenApi3_1) + if (version >= OpenApiSpecVersion.OpenApi3_1) { WriteJsonSchemaKeywords(writer); } From 2e2c3cf06416daa5d1a35680638e5fca984920a9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:13:36 -0400 Subject: [PATCH 010/146] feat: Add support for response summary in OpenAPI 3.2.0 (#23) * Initial plan * Add Summary support to OpenApiResponse with serialization/deserialization Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> * Complete implementation with reference support and test fixes Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> * Update IOpenApiResponse to derive from IOpenApiSummarizedElement Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> * Apply suggestions from code review * Apply suggestion from @baywet * chore: copy reference implementation Signed-off-by: Vincent Biret * chore: adds missing using Signed-off-by: Vincent Biret * chore: linting Signed-off-by: Vincent Biret * chore: updates public API export Signed-off-by: Vincent Biret --------- Signed-off-by: Vincent Biret Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> Co-authored-by: Vincent Biret Co-authored-by: Vincent Biret --- .../Models/Interfaces/IOpenApiResponse.cs | 2 +- .../Models/OpenApiResponse.cs | 16 +++ .../References/OpenApiResponseReference.cs | 13 ++- .../Reader/V3/OpenApiResponseDeserializer.cs | 12 ++- .../Reader/V31/OpenApiResponseDeserializer.cs | 12 ++- .../Reader/V32/OpenApiResponseDeserializer.cs | 6 ++ .../TryLoadReferenceV2Tests.cs | 1 + .../V3Tests/OpenApiResponseTests.cs | 24 +++++ .../OpenApiResponse/responseWithSummary.yaml | 16 +++ .../responseWithSummaryExtension.yaml | 16 +++ .../Models/OpenApiResponseTests.cs | 97 +++++++++++++++++++ .../PublicApi/PublicApi.approved.txt | 10 +- 12 files changed, 215 insertions(+), 10 deletions(-) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummary.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummaryExtension.yaml diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiResponse.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiResponse.cs index dfddd7838..e8b8357fe 100644 --- a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiResponse.cs @@ -6,7 +6,7 @@ namespace Microsoft.OpenApi; /// Defines the base properties for the response object. /// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking. /// -public interface IOpenApiResponse : IOpenApiDescribedElement, IOpenApiReadOnlyExtensible, IShallowCopyable, IOpenApiReferenceable +public interface IOpenApiResponse : IOpenApiDescribedElement, IOpenApiReadOnlyExtensible, IShallowCopyable, IOpenApiReferenceable, IOpenApiSummarizedElement { /// /// Maps a header name to its definition. diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index e15ea5742..68ff27fc5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -12,6 +12,9 @@ namespace Microsoft.OpenApi /// public class OpenApiResponse : IOpenApiExtensible, IOpenApiResponse { + /// + public string? Summary { get; set; } + /// public string? Description { get; set; } @@ -38,6 +41,7 @@ public OpenApiResponse() { } internal OpenApiResponse(IOpenApiResponse response) { Utils.CheckArgumentNull(response); + Summary = response.Summary ?? Summary; Description = response.Description ?? Description; Headers = response.Headers != null ? new Dictionary(response.Headers) : null; Content = response.Content != null ? new Dictionary(response.Content) : null; @@ -76,6 +80,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteStartObject(); + // summary - only for v3.2+ + if (version >= OpenApiSpecVersion.OpenApi3_2) + { + writer.WriteProperty(OpenApiConstants.Summary, Summary); + } + // description writer.WriteRequiredProperty(OpenApiConstants.Description, Description); @@ -88,6 +98,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version // links writer.WriteOptionalMap(OpenApiConstants.Links, Links, callback); + // summary as extension for v3.1 and earlier + if (version < OpenApiSpecVersion.OpenApi3_2 && !string.IsNullOrEmpty(Summary)) + { + writer.WriteProperty(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.Summary, Summary); + } + // extension writer.WriteExtensions(Extensions, version); diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs index f76ddea15..3133e7054 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs @@ -8,7 +8,7 @@ namespace Microsoft.OpenApi /// /// Response Object Reference. /// - public class OpenApiResponseReference : BaseOpenApiReferenceHolder, IOpenApiResponse + public class OpenApiResponseReference : BaseOpenApiReferenceHolder, IOpenApiResponse { /// /// Constructor initializing the reference object. @@ -32,6 +32,13 @@ private OpenApiResponseReference(OpenApiResponseReference openApiResponseReferen } + /// + public string? Summary + { + get => string.IsNullOrEmpty(Reference.Summary) ? Target?.Summary : Reference.Summary; + set => Reference.Summary = value; + } + /// public string? Description { @@ -63,9 +70,9 @@ public IOpenApiResponse CreateShallowCopy() return new OpenApiResponseReference(this); } /// - protected override OpenApiReferenceWithDescription CopyReference(OpenApiReferenceWithDescription sourceReference) + protected override OpenApiReferenceWithDescriptionAndSummary CopyReference(OpenApiReferenceWithDescriptionAndSummary sourceReference) { - return new OpenApiReferenceWithDescription(sourceReference); + return new OpenApiReferenceWithDescriptionAndSummary(sourceReference); } } } diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs index a2f4318ba..d251a1992 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs @@ -34,7 +34,17 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _responsePatternFields = new() { - {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => + { + if (p.Equals("x-oai-summary", StringComparison.OrdinalIgnoreCase)) + { + o.Summary = n.GetScalarValue(); + } + else + { + o.AddExtension(p, LoadExtension(p,n)); + } + }} }; public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs index d25603126..d00b3eb80 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs @@ -39,7 +39,17 @@ internal static partial class OpenApiV31Deserializer private static readonly PatternFieldMap _responsePatternFields = new() { - {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => + { + if (p.Equals("x-oai-summary", StringComparison.OrdinalIgnoreCase)) + { + o.Summary = n.GetScalarValue(); + } + else + { + o.AddExtension(p, LoadExtension(p,n)); + } + }} }; public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument) diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs index eec9a03d1..a6020991b 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs @@ -10,6 +10,12 @@ internal static partial class OpenApiV32Deserializer { private static readonly FixedFieldMap _responseFixedFields = new() { + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, { "description", (o, n, _) => { diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index b20f72e9a..cb9571f42 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -67,6 +67,7 @@ public async Task LoadResponseReference() Assert.Equivalent( new OpenApiResponse { + Summary = null, Description = "Entity not found.", Content = new Dictionary() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs index 0f98b40ce..47d017b4b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs @@ -24,5 +24,29 @@ public async Task ResponseWithReferencedHeaderShouldReferenceComponent() Assert.Equal(expected.Description, actual.Description); } + + [Fact] + public async Task ResponseWithSummaryV32ShouldDeserializeCorrectly() + { + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummary.yaml"), SettingsFixture.ReaderSettings); + + var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse; + + Assert.NotNull(response); + Assert.Equal("Successful response", response.Summary); + Assert.Equal("A successful response with summary", response.Description); + } + + [Fact] + public async Task ResponseWithSummaryExtensionV31ShouldDeserializeCorrectly() + { + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummaryExtension.yaml"), SettingsFixture.ReaderSettings); + + var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse; + + Assert.NotNull(response); + Assert.Equal("Successful response", response.Summary); + Assert.Equal("A successful response with summary extension", response.Description); + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummary.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummary.yaml new file mode 100644 index 000000000..0de4941fe --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummary.yaml @@ -0,0 +1,16 @@ +openapi: 3.2.0 +info: + title: Test API + version: 1.0.0 +components: + responses: + SuccessResponse: + summary: Successful response + description: A successful response with summary + content: + application/json: + schema: + type: object + properties: + message: + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummaryExtension.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummaryExtension.yaml new file mode 100644 index 000000000..2b3660cec --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiResponse/responseWithSummaryExtension.yaml @@ -0,0 +1,16 @@ +openapi: 3.1.0 +info: + title: Test API + version: 1.0.0 +components: + responses: + SuccessResponse: + description: A successful response with summary extension + x-oai-summary: Successful response + content: + application/json: + schema: + type: object + properties: + message: + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs index 15c392466..cb9182577 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Text.Json.Nodes; using System.Threading.Tasks; using VerifyXunit; using Xunit; @@ -166,6 +167,22 @@ public class OpenApiResponseTests } }; + private static OpenApiResponse ResponseWithSummary => new OpenApiResponse + { + Summary = "Successful response", + Description = "A detailed description of a successful response", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.Object + } + } + } + }; + [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Json)] [InlineData(OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Json)] @@ -399,5 +416,85 @@ public async Task SerializeReferencedResponseAsV2JsonWithoutReferenceWorksAsync( // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); } + + [Fact] + public async Task SerializeResponseWithSummaryAsV32Works() + { + // Arrange + var expected = @"{ + ""summary"": ""Successful response"", + ""description"": ""A detailed description of a successful response"", + ""content"": { + ""application/json"": { + ""schema"": { + ""type"": ""object"" + } + } + } +}"; + + // Act + var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + + // Assert + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeResponseWithSummaryAsV31Works() + { + // Arrange + var expected = @"{ + ""description"": ""A detailed description of a successful response"", + ""content"": { + ""application/json"": { + ""schema"": { + ""type"": ""object"" + } + } + }, + ""x-oai-summary"": ""Successful response"" +}"; + + // Act + var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + + // Assert + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeResponseWithSummaryAsV3Works() + { + // Arrange + var expected = @"{ + ""description"": ""A detailed description of a successful response"", + ""content"": { + ""application/json"": { + ""schema"": { + ""type"": ""object"" + } + } + }, + ""x-oai-summary"": ""Successful response"" +}"; + + // Act + var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + // Assert + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public void ResponseWithSummaryShouldImplementIOpenApiSummarizedElement() + { + // Arrange + var response = new OpenApiResponse { Summary = "Test summary" }; + + // Act & Assert + Assert.IsType(response, exactMatch: false); + Assert.Equal("Test summary", response.Summary); + } } } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index a00be2a9b..5ad03ed5f 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -209,7 +209,7 @@ namespace Microsoft.OpenApi Microsoft.OpenApi.IOpenApiParameter? ConvertToBodyParameter(Microsoft.OpenApi.IOpenApiWriter writer); System.Collections.Generic.IEnumerable? ConvertToFormDataParameters(Microsoft.OpenApi.IOpenApiWriter writer); } - public interface IOpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable + public interface IOpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable { System.Collections.Generic.IDictionary? Content { get; } System.Collections.Generic.IDictionary? Headers { get; } @@ -1112,7 +1112,7 @@ namespace Microsoft.OpenApi public Microsoft.OpenApi.IOpenApiRequestBody CreateShallowCopy() { } public override void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } } - public class OpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable + public class OpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable { public OpenApiResponse() { } public System.Collections.Generic.IDictionary? Content { get; set; } @@ -1120,13 +1120,14 @@ namespace Microsoft.OpenApi public System.Collections.Generic.IDictionary? Extensions { get; set; } public System.Collections.Generic.IDictionary? Headers { get; set; } public System.Collections.Generic.IDictionary? Links { get; set; } + public string? Summary { get; set; } public Microsoft.OpenApi.IOpenApiResponse CreateShallowCopy() { } public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { } public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { } } - public class OpenApiResponseReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable + public class OpenApiResponseReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable { public OpenApiResponseReference(string referenceId, Microsoft.OpenApi.OpenApiDocument? hostDocument = null, string? externalResource = null) { } public System.Collections.Generic.IDictionary? Content { get; } @@ -1134,7 +1135,8 @@ namespace Microsoft.OpenApi public System.Collections.Generic.IDictionary? Extensions { get; } public System.Collections.Generic.IDictionary? Headers { get; } public System.Collections.Generic.IDictionary? Links { get; } - protected override Microsoft.OpenApi.OpenApiReferenceWithDescription CopyReference(Microsoft.OpenApi.OpenApiReferenceWithDescription sourceReference) { } + public string? Summary { get; set; } + protected override Microsoft.OpenApi.OpenApiReferenceWithDescriptionAndSummary CopyReference(Microsoft.OpenApi.OpenApiReferenceWithDescriptionAndSummary sourceReference) { } public override Microsoft.OpenApi.IOpenApiResponse CopyReferenceAsTargetElementWithOverrides(Microsoft.OpenApi.IOpenApiResponse source) { } public Microsoft.OpenApi.IOpenApiResponse CreateShallowCopy() { } } From 9f0b3223e621bbc7bb145b74ed39358332783cb0 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 29 Sep 2025 12:22:40 -0400 Subject: [PATCH 011/146] chore: updates benchmark results --- .../performance.Descriptions-report-github.md | 22 +++--- .../performance.Descriptions-report.csv | 8 +-- .../performance.Descriptions-report.html | 22 +++--- .../performance.Descriptions-report.json | 2 +- .../performance.EmptyModels-report-github.md | 70 +++++++++---------- .../performance.EmptyModels-report.csv | 56 +++++++-------- .../performance.EmptyModels-report.html | 70 +++++++++---------- .../performance.EmptyModels-report.json | 2 +- 8 files changed, 126 insertions(+), 126 deletions(-) diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md index 5c8d571e6..d271111c0 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md @@ -1,18 +1,18 @@ ``` -BenchmarkDotNet v0.15.2, Linux Ubuntu 24.04.2 LTS (Noble Numbat) -AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores -.NET SDK 8.0.413 - [Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2 - ShortRun : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2 +BenchmarkDotNet v0.15.4, Windows 11 (10.0.26200.6584) +11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores +.NET SDK 8.0.414 + [Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4 + ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4 Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 ``` -| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|------------- |---------------:|--------------:|------------:|-----------:|-----------:|----------:|-------------:| -| PetStoreYaml | 529.5 μs | 62.50 μs | 3.43 μs | 23.4375 | 3.9063 | - | 387.26 KB | -| PetStoreJson | 240.8 μs | 15.69 μs | 0.86 μs | 13.6719 | 1.9531 | - | 249.1 KB | -| GHESYaml | 1,097,576.6 μs | 100,584.42 μs | 5,513.37 μs | 26000.0000 | 20000.0000 | 3000.0000 | 384492.38 KB | -| GHESJson | 516,328.2 μs | 87,964.22 μs | 4,821.62 μs | 16000.0000 | 9000.0000 | 2000.0000 | 245957.5 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------- |---------------:|---------------:|-------------:|-----------:|-----------:|----------:|-------------:| +| PetStoreYaml | 979.3 μs | 375.3 μs | 20.57 μs | 62.5000 | 7.8125 | - | 387.37 KB | +| PetStoreJson | 508.5 μs | 427.5 μs | 23.43 μs | 39.0625 | 7.8125 | - | 249.51 KB | +| GHESYaml | 1,587,861.1 μs | 1,076,577.3 μs | 59,010.84 μs | 66000.0000 | 22000.0000 | 4000.0000 | 384511.51 KB | +| GHESJson | 599,442.5 μs | 843,317.6 μs | 46,225.08 μs | 40000.0000 | 16000.0000 | 3000.0000 | 245982.08 KB | diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv index 1a21885c7..f68d5f850 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv @@ -1,5 +1,5 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Gen0,Gen1,Gen2,Allocated -PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,529.5 μs,62.50 μs,3.43 μs,23.4375,3.9063,0.0000,387.26 KB -PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,240.8 μs,15.69 μs,0.86 μs,13.6719,1.9531,0.0000,249.1 KB -GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"1,097,576.6 μs","100,584.42 μs","5,513.37 μs",26000.0000,20000.0000,3000.0000,384492.38 KB -GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"516,328.2 μs","87,964.22 μs","4,821.62 μs",16000.0000,9000.0000,2000.0000,245957.5 KB +PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,11111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,979.3 μs,375.3 μs,20.57 μs,62.5000,7.8125,0.0000,387.37 KB +PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,11111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,508.5 μs,427.5 μs,23.43 μs,39.0625,7.8125,0.0000,249.51 KB +GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,11111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"1,587,861.1 μs","1,076,577.3 μs","59,010.84 μs",66000.0000,22000.0000,4000.0000,384511.51 KB +GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,11111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"599,442.5 μs","843,317.6 μs","46,225.08 μs",40000.0000,16000.0000,3000.0000,245982.08 KB diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html index 5661bcdf8..b8752c100 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html @@ -2,7 +2,7 @@ -performance.Descriptions-20250820-142630 +performance.Descriptions-20250929-121242