From 88badd429a8f1e29949399d5628087e614eb7d07 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Jan 2025 13:39:57 -0500 Subject: [PATCH 1/2] chore: aligns test definition with other tests Signed-off-by: Vincent Biret --- ...nWorks_produceTerseOutput=False.verified.txt | 17 ++++++++++++++++- ...onWorks_produceTerseOutput=True.verified.txt | 2 +- .../OpenApiRequestBodyReferenceTests.cs | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index a9be81418..716f480fd 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -1,3 +1,18 @@ { - "$ref": "#/components/requestBodies/UserRequest" + "description": "User request body", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + } + } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index 04f67afdd..161f80087 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"$ref":"#/components/requestBodies/UserRequest"} \ No newline at end of file +{"description":"User request body","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index 0f1d8f634..9bcf15a03 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -145,7 +145,7 @@ public async Task SerializeRequestBodyReferenceAsV31JsonWorks(bool produceTerseO { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); // Act _localRequestBodyReference.SerializeAsV31(writer); From 425335eb46d4a48104046af62265ba0ca6a1ec7b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Jan 2025 13:40:17 -0500 Subject: [PATCH 2/2] fix: proxy design pattern implementation for request body Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi.Hidi/StatsVisitor.cs | 2 +- .../StatsVisitor.cs | 2 +- .../Models/Interfaces/IOpenApiRequestBody.cs | 35 ++++++ .../Models/OpenApiComponents.cs | 8 +- .../Models/OpenApiDocument.cs | 2 +- .../Models/OpenApiOperation.cs | 20 +-- .../Models/OpenApiRequestBody.cs | 61 ++++------ .../References/OpenApiRequestBodyReference.cs | 114 ++++++------------ .../Reader/V2/OpenApiDocumentDeserializer.cs | 15 +-- .../Reader/V2/OpenApiOperationDeserializer.cs | 2 +- .../V3/OpenApiRequestBodyDeserializer.cs | 3 +- .../V31/OpenApiRequestBodyDeserializer.cs | 3 +- .../Services/CopyReferences.cs | 6 +- .../Services/OpenApiVisitorBase.cs | 4 +- .../Services/OpenApiWalker.cs | 4 +- .../Validations/OpenApiValidator.cs | 2 +- .../V2Tests/OpenApiPathItemTests.cs | 4 +- .../Models/OpenApiCallbackTests.cs | 4 +- .../Models/OpenApiDocumentTests.cs | 2 +- .../Models/OpenApiOperationTests.cs | 6 +- .../PublicApi/PublicApi.approved.txt | 52 ++++---- .../Visitors/InheritanceTests.cs | 4 +- 22 files changed, 161 insertions(+), 194 deletions(-) create mode 100644 src/Microsoft.OpenApi/Models/Interfaces/IOpenApiRequestBody.cs diff --git a/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs b/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs index 53f52ab3d..645f94319 100644 --- a/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs +++ b/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs @@ -41,7 +41,7 @@ public override void Visit(IOpenApiPathItem pathItem) public int RequestBodyCount { get; set; } - public override void Visit(OpenApiRequestBody requestBody) + public override void Visit(IOpenApiRequestBody requestBody) { RequestBodyCount++; } diff --git a/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs b/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs index 6097f1f4e..fbf9f3c9a 100644 --- a/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs +++ b/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs @@ -41,7 +41,7 @@ public override void Visit(IOpenApiPathItem pathItem) public int RequestBodyCount { get; set; } - public override void Visit(OpenApiRequestBody requestBody) + public override void Visit(IOpenApiRequestBody requestBody) { RequestBodyCount++; } diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiRequestBody.cs new file mode 100644 index 000000000..f014d2b4d --- /dev/null +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiRequestBody.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.Interfaces; + +/// +/// Defines the base properties for the request body object. +/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking. +/// +public interface IOpenApiRequestBody : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible +{ + /// + /// Determines if the request body is required in the request. Defaults to false. + /// + public bool Required { get; } + + /// + /// REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it. + /// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* + /// + public IDictionary Content { get; } + /// + /// Converts the request body to a body parameter in preparation for a v2 serialization. + /// + /// The writer to use to read settings from. + /// The converted OpenAPI parameter + IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer); + /// + /// Converts the request body to a set of form data parameters in preparation for a v2 serialization. + /// + /// The writer to use to read settings from + /// The converted OpenAPI parameters + IEnumerable ConvertToFormDataParameters(IOpenApiWriter writer); +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index 7cd577397..f58d2644a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -39,10 +39,10 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible public IDictionary? Examples { get; set; } = new Dictionary(); /// - /// An object to hold reusable Objects. + /// An object to hold reusable Objects. /// - public IDictionary? RequestBodies { get; set; } = - new Dictionary(); + public IDictionary? RequestBodies { get; set; } = + new Dictionary(); /// /// An object to hold reusable Objects. @@ -89,7 +89,7 @@ public OpenApiComponents(OpenApiComponents? components) Responses = components?.Responses != null ? new Dictionary(components.Responses) : null; Parameters = components?.Parameters != null ? new Dictionary(components.Parameters) : null; Examples = components?.Examples != null ? new Dictionary(components.Examples) : null; - RequestBodies = components?.RequestBodies != null ? new Dictionary(components.RequestBodies) : null; + RequestBodies = components?.RequestBodies != null ? new Dictionary(components.RequestBodies) : null; Headers = components?.Headers != null ? new Dictionary(components.Headers) : null; SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary(components.SecuritySchemes) : null; Links = components?.Links != null ? new Dictionary(components.Links) : null; diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 6e733d0fa..52e2243b7 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -600,7 +600,7 @@ public bool AddComponent(string id, T componentToRegister) Components.Responses.Add(id, openApiResponse); break; case OpenApiRequestBody openApiRequestBody: - Components.RequestBodies ??= new Dictionary(); + Components.RequestBodies ??= new Dictionary(); Components.RequestBodies.Add(id, openApiRequestBody); break; case OpenApiLink openApiLink: diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 0a2f4259b..927bf839a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -66,7 +66,7 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA /// has explicitly defined semantics for request bodies. /// In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers. /// - public OpenApiRequestBody? RequestBody { get; set; } + public IOpenApiRequestBody? RequestBody { get; set; } /// /// REQUIRED. The list of possible responses as they are returned from executing this operation. @@ -128,7 +128,7 @@ public OpenApiOperation(OpenApiOperation? operation) ExternalDocs = operation?.ExternalDocs != null ? new(operation?.ExternalDocs) : null; OperationId = operation?.OperationId ?? OperationId; Parameters = operation?.Parameters != null ? new List(operation.Parameters) : null; - RequestBody = operation?.RequestBody != null ? new(operation?.RequestBody) : null; + RequestBody = operation?.RequestBody != null ? new OpenApiRequestBody(operation?.RequestBody) : null; Responses = operation?.Responses != null ? new(operation?.Responses) : null; Callbacks = operation?.Callbacks != null ? new Dictionary(operation.Callbacks) : null; Deprecated = operation?.Deprecated ?? Deprecated; @@ -235,15 +235,7 @@ public void SerializeAsV2(IOpenApiWriter writer) // operationId writer.WriteProperty(OpenApiConstants.OperationId, OperationId); - List parameters; - if (Parameters == null) - { - parameters = []; - } - else - { - parameters = [.. Parameters]; - } + List parameters = Parameters is null ? new() : new(Parameters); if (RequestBody != null) { @@ -255,17 +247,17 @@ public void SerializeAsV2(IOpenApiWriter writer) if (consumes.Contains("application/x-www-form-urlencoded") || consumes.Contains("multipart/form-data")) { - parameters.AddRange(RequestBody.ConvertToFormDataParameters()); + parameters.AddRange(RequestBody.ConvertToFormDataParameters(writer)); } else { parameters.Add(RequestBody.ConvertToBodyParameter(writer)); } } - else if (RequestBody.Reference != null && RequestBody.Reference.HostDocument is {} hostDocument) + else if (RequestBody is OpenApiRequestBodyReference requestBodyReference) { parameters.Add( - new OpenApiParameterReference(RequestBody.Reference.Id, hostDocument)); + new OpenApiParameterReference(requestBodyReference.Reference.Id, requestBodyReference.Reference.HostDocument)); } if (consumes.Count > 0) diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 2524c41d6..b5fd3f605 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -15,39 +15,19 @@ namespace Microsoft.OpenApi.Models /// /// Request Body Object /// - public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible, IOpenApiRequestBody { - /// - /// Indicates if object is populated with data or is just a reference to the data - /// - public bool UnresolvedReference { get; set; } - - /// - /// Reference object. - /// - public OpenApiReference Reference { get; set; } - - /// - /// A brief description of the request body. This could contain examples of use. - /// CommonMark syntax MAY be used for rich text representation. - /// - public virtual string Description { get; set; } + /// + public string Description { get; set; } - /// - /// Determines if the request body is required in the request. Defaults to false. - /// - public virtual bool Required { get; set; } + /// + public bool Required { get; set; } - /// - /// REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it. - /// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* - /// - public virtual IDictionary Content { get; set; } = new Dictionary(); + /// + public IDictionary Content { get; set; } = new Dictionary(); - /// - /// This object MAY be extended with Specification Extensions. - /// - public virtual IDictionary Extensions { get; set; } = new Dictionary(); + /// + public IDictionary Extensions { get; set; } = new Dictionary(); /// /// Parameter-less constructor @@ -55,12 +35,11 @@ public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible public OpenApiRequestBody() { } /// - /// Initializes a copy instance of an object + /// Initializes a copy instance of an object /// - public OpenApiRequestBody(OpenApiRequestBody requestBody) + public OpenApiRequestBody(IOpenApiRequestBody requestBody) { - UnresolvedReference = requestBody?.UnresolvedReference ?? UnresolvedReference; - Reference = requestBody?.Reference != null ? new(requestBody?.Reference) : null; + Utils.CheckArgumentNull(requestBody); Description = requestBody?.Description ?? Description; Required = requestBody?.Required ?? Required; Content = requestBody?.Content != null ? new Dictionary(requestBody.Content) : null; @@ -70,7 +49,7 @@ public OpenApiRequestBody(OpenApiRequestBody requestBody) /// /// Serialize to Open Api v3.1 /// - public virtual void SerializeAsV31(IOpenApiWriter writer) + public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } @@ -78,12 +57,12 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) /// /// Serialize to Open Api v3.0 /// - public virtual void SerializeAsV3(IOpenApiWriter writer) + public void SerializeAsV3(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { Utils.CheckArgumentNull(writer); @@ -113,7 +92,8 @@ public void SerializeAsV2(IOpenApiWriter writer) // RequestBody object does not exist in V2. } - internal virtual IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer) + /// + public IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer) { var bodyParameter = new OpenApiBodyParameter { @@ -135,7 +115,8 @@ internal virtual IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer) return bodyParameter; } - internal IEnumerable ConvertToFormDataParameters() + /// + public IEnumerable ConvertToFormDataParameters(IOpenApiWriter writer) { if (Content == null || !Content.Any()) yield break; @@ -143,14 +124,14 @@ internal IEnumerable ConvertToFormDataParameters() foreach (var property in Content.First().Value.Schema.Properties) { var paramSchema = property.Value; - if ("string".Equals(paramSchema.Type.ToIdentifier(), StringComparison.OrdinalIgnoreCase) + if ((paramSchema.Type & JsonSchemaType.String) == JsonSchemaType.String && ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase) || "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase))) { paramSchema.Type = "file".ToJsonSchemaType(); paramSchema.Format = null; } - yield return new() + yield return new OpenApiFormDataParameter() { Description = property.Value.Description, Name = property.Key, diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs index 5aa466415..d698dd092 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Writers; @@ -12,29 +13,8 @@ namespace Microsoft.OpenApi.Models.References /// /// Request Body Object Reference. /// - public class OpenApiRequestBodyReference : OpenApiRequestBody, IOpenApiReferenceHolder + public class OpenApiRequestBodyReference : BaseOpenApiReferenceHolder, IOpenApiRequestBody { - internal OpenApiRequestBody _target; - private readonly OpenApiReference _reference; - private string _description; - - /// - /// Gets the target request body. - /// - /// - /// If the reference is not resolved, this will return null. - /// - public OpenApiRequestBody Target - { - get - { - _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - OpenApiRequestBody resolved = new OpenApiRequestBody(_target); - if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; - return resolved; - } - } - /// /// Constructor initializing the reference object. /// @@ -45,93 +25,69 @@ public OpenApiRequestBody Target /// 1. a absolute/relative file path, for example: ../commons/pet.json /// 2. a Url, for example: http://localhost/pet.json /// - public OpenApiRequestBodyReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + public OpenApiRequestBodyReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.RequestBody, externalResource) { - Utils.CheckArgumentNullOrEmpty(referenceId); - - _reference = new OpenApiReference() - { - Id = referenceId, - HostDocument = hostDocument, - Type = ReferenceType.RequestBody, - ExternalResource = externalResource - }; - - Reference = _reference; } - - internal OpenApiRequestBodyReference(OpenApiRequestBody target, string referenceId) + internal OpenApiRequestBodyReference(OpenApiRequestBody target, string referenceId):base(target, referenceId, ReferenceType.RequestBody) { - _target = target; - - _reference = new OpenApiReference() - { - Id = referenceId, - Type = ReferenceType.RequestBody, - }; } /// - public override string Description + public string Description { - get => string.IsNullOrEmpty(_description) ? Target.Description : _description; - set => _description = value; + get => string.IsNullOrEmpty(Reference?.Description) ? Target?.Description : Reference.Description; + set + { + if (Reference is not null) + { + Reference.Description = value; + } + } } /// - public override IDictionary Content { get => Target.Content; set => Target.Content = value; } + public IDictionary Content { get => Target?.Content; } /// - public override bool Required { get => Target.Required; set => Target.Required = value; } + public bool Required { get => Target?.Required ?? false; } /// - public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + public IDictionary Extensions { get => Target?.Extensions; } /// - public override void SerializeAsV3(IOpenApiWriter writer) + public override IOpenApiRequestBody CopyReferenceAsTargetElementWithOverrides(IOpenApiRequestBody source) { - if (!writer.GetSettings().ShouldInlineReference(_reference)) - { - _reference.SerializeAsV3(writer); - } - else - { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); - } + return source is OpenApiRequestBody ? new OpenApiRequestBody(this) : source; } - /// - public override void SerializeAsV31(IOpenApiWriter writer) + public override void SerializeAsV2(IOpenApiWriter writer) { - if (!writer.GetSettings().ShouldInlineReference(_reference)) + // doesn't exist in v2 + } + /// + public IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer) + { + if (writer.GetSettings().ShouldInlineReference(Reference)) { - _reference.SerializeAsV31(writer); + return Target.ConvertToBodyParameter(writer); } else { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + return new OpenApiParameterReference(Reference.Id, Reference.HostDocument); } } - - /// - private void SerializeInternal(IOpenApiWriter writer, - Action action) - { - Utils.CheckArgumentNull(writer); - action(writer, Target); - } - /// - internal override IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer) + public IEnumerable ConvertToFormDataParameters(IOpenApiWriter writer) { - if (writer.GetSettings().ShouldInlineReference(_reference)) + if (writer.GetSettings().ShouldInlineReference(Reference)) { - return Target.ConvertToBodyParameter(writer); - } - else - { - return new OpenApiParameterReference(_reference.Id, _reference.HostDocument); + return Target.ConvertToFormDataParameters(writer); } + + if (Content == null || !Content.Any()) + return []; + + return Content.First().Value.Schema.Properties.Select(x => new OpenApiParameterReference(x.Key, Reference.HostDocument)); } } } diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs index e24c10227..ee0bf0c8f 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Reader.ParseNodes; using Microsoft.OpenApi.Services; @@ -303,8 +304,8 @@ private static bool IsHostValid(string host) internal class RequestBodyReferenceFixer : OpenApiVisitorBase { - private readonly IDictionary _requestBodies; - public RequestBodyReferenceFixer(IDictionary requestBodies) + private readonly IDictionary _requestBodies; + public RequestBodyReferenceFixer(IDictionary requestBodies) { _requestBodies = requestBodies; } @@ -318,15 +319,7 @@ public override void Visit(OpenApiOperation operation) if (body != null) { operation.Parameters.Remove(body); - operation.RequestBody = new() - { - UnresolvedReference = true, - Reference = new() - { - Id = body.Reference.Id, - Type = ReferenceType.RequestBody - } - }; + operation.RequestBody = new OpenApiRequestBodyReference(body.Reference.Id, body.Reference.HostDocument); } } } diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs index 3fd5743c9..957a02ab7 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs @@ -179,7 +179,7 @@ private static OpenApiRequestBody CreateFormBody(ParsingContext context, List s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiRequestBody LoadRequestBody(ParseNode node, OpenApiDocument hostDocument) + public static IOpenApiRequestBody LoadRequestBody(ParseNode node, OpenApiDocument hostDocument) { var mapNode = node.CheckMapNode("requestBody"); diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs index e26ec20f9..db6792a5f 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs @@ -1,5 +1,6 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Reader.ParseNodes; @@ -40,7 +41,7 @@ internal static partial class OpenApiV31Deserializer {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiRequestBody LoadRequestBody(ParseNode node, OpenApiDocument hostDocument) + public static IOpenApiRequestBody LoadRequestBody(ParseNode node, OpenApiDocument hostDocument) { var mapNode = node.CheckMapNode("requestBody"); diff --git a/src/Microsoft.OpenApi/Services/CopyReferences.cs b/src/Microsoft.OpenApi/Services/CopyReferences.cs index 982162442..eadabbaaf 100644 --- a/src/Microsoft.OpenApi/Services/CopyReferences.cs +++ b/src/Microsoft.OpenApi/Services/CopyReferences.cs @@ -118,9 +118,9 @@ private void AddRequestBodyToComponents(OpenApiRequestBody requestBody, string r { EnsureComponentsExist(); EnsureRequestBodiesExist(); - if (!Components.RequestBodies.ContainsKey(referenceId ?? requestBody.Reference.Id)) + if (!Components.RequestBodies.ContainsKey(referenceId)) { - Components.RequestBodies.Add(referenceId ?? requestBody.Reference.Id, requestBody); + Components.RequestBodies.Add(referenceId, requestBody); } } private void AddLinkToComponents(OpenApiLink link, string referenceId = null) @@ -215,7 +215,7 @@ private void EnsureResponsesExist() private void EnsureRequestBodiesExist() { - _target.Components.RequestBodies ??= new Dictionary(); + _target.Components.RequestBodies ??= new Dictionary(); } private void EnsureExamplesExist() diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs index 63b065bbd..f118a3f06 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -153,9 +153,9 @@ public virtual void Visit(IOpenApiParameter parameter) } /// - /// Visits + /// Visits /// - public virtual void Visit(OpenApiRequestBody requestBody) + public virtual void Visit(IOpenApiRequestBody requestBody) { } diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index 66ca7e6fe..b76f33ad9 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -713,9 +713,9 @@ internal void Walk(OpenApiResponse response, bool isComponent = false) } /// - /// Visits and child objects + /// Visits and child objects /// - internal void Walk(OpenApiRequestBody requestBody, bool isComponent = false) + internal void Walk(IOpenApiRequestBody requestBody, bool isComponent = false) { if (requestBody == null) { diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs index 63076ed9d..a5a4885de 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs @@ -141,7 +141,7 @@ public void AddWarning(OpenApiValidatorWarning warning) public override void Visit(OpenApiSecurityRequirement securityRequirement) => Validate(securityRequirement); /// - public override void Visit(OpenApiRequestBody requestBody) => Validate(requestBody); + public override void Visit(IOpenApiRequestBody requestBody) => Validate(requestBody); /// public override void Visit(OpenApiPaths paths) => Validate(paths); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs index be19365d5..35ffd15d5 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs @@ -60,7 +60,7 @@ public class OpenApiPathItemTests } } ], - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Content = { @@ -166,7 +166,7 @@ public class OpenApiPathItemTests } } ], - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Content = { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs index a2ef27222..267e29ede 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs @@ -28,7 +28,7 @@ public class OpenApiCallbackTests [OperationType.Post] = new() { - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Content = { @@ -68,7 +68,7 @@ public class OpenApiCallbackTests [OperationType.Post] = new() { - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Content = { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 19ebca7fb..c333fcdb7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -1171,7 +1171,7 @@ public OpenApiDocumentTests() { Description = "Creates a new pet in the store. Duplicates are allowed", OperationId = "addPet", - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Description = "Pet to add to the store", Required = true, diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index 81da044bf..6b3c61417 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -39,7 +39,7 @@ public class OpenApiOperationTests Name = "parameter2" } ], - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Description = "description2", Required = true, @@ -113,7 +113,7 @@ public class OpenApiOperationTests Name = "parameter2" } ], - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Description = "description2", Required = true, @@ -191,7 +191,7 @@ public class OpenApiOperationTests } } ], - RequestBody = new() + RequestBody = new OpenApiRequestBody() { Content = { diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index cf54ef45a..93aef1c4b 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -392,6 +392,13 @@ namespace Microsoft.OpenApi.Models.Interfaces System.Collections.Generic.IList Parameters { get; } System.Collections.Generic.IList Servers { get; } } + public interface IOpenApiRequestBody : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable, Microsoft.OpenApi.Models.Interfaces.IOpenApiDescribedElement + { + System.Collections.Generic.IDictionary Content { get; } + bool Required { get; } + Microsoft.OpenApi.Models.Interfaces.IOpenApiParameter ConvertToBodyParameter(Microsoft.OpenApi.Writers.IOpenApiWriter writer); + System.Collections.Generic.IEnumerable ConvertToFormDataParameters(Microsoft.OpenApi.Writers.IOpenApiWriter writer); + } public interface IOpenApiSummarizedElement : Microsoft.OpenApi.Interfaces.IOpenApiElement { string Summary { get; set; } @@ -432,7 +439,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IDictionary? Links { get; set; } public System.Collections.Generic.IDictionary? Parameters { get; set; } public System.Collections.Generic.IDictionary? PathItems { get; set; } - public System.Collections.Generic.IDictionary? RequestBodies { get; set; } + public System.Collections.Generic.IDictionary? RequestBodies { get; set; } public System.Collections.Generic.IDictionary? Responses { get; set; } public System.Collections.Generic.IDictionary? Schemas { get; set; } public System.Collections.Generic.IDictionary? SecuritySchemes { get; set; } @@ -820,7 +827,7 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.OpenApiExternalDocs? ExternalDocs { get; set; } public string? OperationId { get; set; } public System.Collections.Generic.IList? Parameters { get; set; } - public Microsoft.OpenApi.Models.OpenApiRequestBody? RequestBody { get; set; } + public Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody? RequestBody { get; set; } public Microsoft.OpenApi.Models.OpenApiResponses? Responses { get; set; } public System.Collections.Generic.IList? Security { get; set; } public System.Collections.Generic.IList? Servers { get; set; } @@ -891,19 +898,19 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiRequestBody : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiRequestBody : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable, Microsoft.OpenApi.Models.Interfaces.IOpenApiDescribedElement, Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody { public OpenApiRequestBody() { } - public OpenApiRequestBody(Microsoft.OpenApi.Models.OpenApiRequestBody requestBody) { } - public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public bool UnresolvedReference { get; set; } - public virtual System.Collections.Generic.IDictionary Content { get; set; } - public virtual string Description { get; set; } - public virtual System.Collections.Generic.IDictionary Extensions { get; set; } - public virtual bool Required { get; set; } + public OpenApiRequestBody(Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody requestBody) { } + public System.Collections.Generic.IDictionary Content { get; set; } + public string Description { get; set; } + public System.Collections.Generic.IDictionary Extensions { get; set; } + public bool Required { get; set; } + public Microsoft.OpenApi.Models.Interfaces.IOpenApiParameter ConvertToBodyParameter(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public System.Collections.Generic.IEnumerable ConvertToFormDataParameters(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiResponse : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -1258,16 +1265,17 @@ namespace Microsoft.OpenApi.Models.References public override Microsoft.OpenApi.Models.Interfaces.IOpenApiPathItem CopyReferenceAsTargetElementWithOverrides(Microsoft.OpenApi.Models.Interfaces.IOpenApiPathItem source) { } public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiRequestBodyReference : Microsoft.OpenApi.Models.OpenApiRequestBody, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReferenceHolder, Microsoft.OpenApi.Interfaces.IOpenApiReferenceHolder, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiRequestBodyReference : Microsoft.OpenApi.Models.References.BaseOpenApiReferenceHolder, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable, Microsoft.OpenApi.Models.Interfaces.IOpenApiDescribedElement, Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody { public OpenApiRequestBodyReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } - public Microsoft.OpenApi.Models.OpenApiRequestBody Target { get; } - public override System.Collections.Generic.IDictionary Content { get; set; } - public override string Description { get; set; } - public override System.Collections.Generic.IDictionary Extensions { get; set; } - public override bool Required { get; set; } - public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public System.Collections.Generic.IDictionary Content { get; } + public string Description { get; set; } + public System.Collections.Generic.IDictionary Extensions { get; } + public bool Required { get; } + public Microsoft.OpenApi.Models.Interfaces.IOpenApiParameter ConvertToBodyParameter(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public System.Collections.Generic.IEnumerable ConvertToFormDataParameters(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody CopyReferenceAsTargetElementWithOverrides(Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody source) { } + public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiResponseReference : Microsoft.OpenApi.Models.OpenApiResponse, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReferenceHolder, Microsoft.OpenApi.Interfaces.IOpenApiReferenceHolder, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -1556,6 +1564,7 @@ namespace Microsoft.OpenApi.Services public virtual void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiLink link) { } public virtual void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiParameter parameter) { } public virtual void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiPathItem pathItem) { } + public virtual void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody requestBody) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiComponents components) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiContact contact) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiDocument doc) { } @@ -1567,7 +1576,6 @@ namespace Microsoft.OpenApi.Services public virtual void Visit(Microsoft.OpenApi.Models.OpenApiOAuthFlow openApiOAuthFlow) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiOperation operation) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiPaths paths) { } - public virtual void Visit(Microsoft.OpenApi.Models.OpenApiRequestBody requestBody) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiResponse response) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiResponses response) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiSchema schema) { } @@ -1655,6 +1663,7 @@ namespace Microsoft.OpenApi.Validations public override void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiLink link) { } public override void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiParameter parameter) { } public override void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiPathItem pathItem) { } + public override void Visit(Microsoft.OpenApi.Models.Interfaces.IOpenApiRequestBody requestBody) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiComponents components) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiContact contact) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiDocument doc) { } @@ -1666,7 +1675,6 @@ namespace Microsoft.OpenApi.Validations public override void Visit(Microsoft.OpenApi.Models.OpenApiOAuthFlow openApiOAuthFlow) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiOperation operation) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiPaths paths) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiRequestBody requestBody) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiResponse response) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiResponses response) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiSchema schema) { } diff --git a/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs b/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs index fd65e7dd4..49595a1be 100644 --- a/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs @@ -32,7 +32,7 @@ public void ExpectedVirtualsInvolved() visitor.Visit(default(OpenApiOperation)); visitor.Visit(default(IList)); visitor.Visit(default(IOpenApiParameter)); - visitor.Visit(default(OpenApiRequestBody)); + visitor.Visit(default(IOpenApiRequestBody)); visitor.Visit(default(IDictionary)); visitor.Visit(default(IDictionary)); visitor.Visit(default(OpenApiResponse)); @@ -166,7 +166,7 @@ public override void Visit(IOpenApiParameter parameter) base.Visit(parameter); } - public override void Visit(OpenApiRequestBody requestBody) + public override void Visit(IOpenApiRequestBody requestBody) { EncodeCall(); base.Visit(requestBody);