From c8d87d73470e74026cbe28ab2c828731282a4cd2 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 23 Sep 2025 21:05:41 -0400 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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 {