diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 93d88b310..5177e4f45 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -160,7 +160,7 @@ public void SerializeAsV2(IOpenApiWriter writer) // paths writer.WriteRequiredObject(OpenApiConstants.Paths, Paths, (w, p) => p.SerializeAsV2(w)); - // If references have been inlined we don't need the to render the components section + // If references have been inlined we don't need to render the components section // however if they have cycles, then we will need a component rendered if (writer.GetSettings().InlineLocalReferences) { @@ -208,9 +208,20 @@ public void SerializeAsV2(IOpenApiWriter writer) }); } // parameters + var parameters = Components?.Parameters != null + ? new Dictionary(Components.Parameters) + : new Dictionary(); + + if (Components?.RequestBodies != null) + { + foreach (var requestBody in Components.RequestBodies.Where(b => !parameters.ContainsKey(b.Key))) + { + parameters.Add(requestBody.Key, requestBody.Value.ConvertToBodyParameter()); + } + } writer.WriteOptionalMap( OpenApiConstants.Parameters, - Components?.Parameters, + parameters, (w, key, component) => { if (component.Reference != null && diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 7983a243e..d047b9cb6 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -224,7 +224,7 @@ public void SerializeAsV2(IOpenApiWriter writer) // operationId writer.WriteProperty(OpenApiConstants.OperationId, OperationId); - IList parameters; + List parameters; if (Parameters == null) { parameters = new List(); @@ -237,70 +237,58 @@ public void SerializeAsV2(IOpenApiWriter writer) if (RequestBody != null) { // consumes - writer.WritePropertyName(OpenApiConstants.Consumes); - writer.WriteStartArray(); var consumes = RequestBody.Content.Keys.Distinct().ToList(); - foreach (var mediaType in consumes) + if (consumes.Any()) { - writer.WriteValue(mediaType); - } - - writer.WriteEndArray(); - - // This is form data. We need to split the request body into multiple parameters. - if (consumes.Contains("application/x-www-form-urlencoded") || - consumes.Contains("multipart/form-data")) - { - foreach (var property in RequestBody.Content.First().Value.Schema.Properties) + // This is form data. We need to split the request body into multiple parameters. + if (consumes.Contains("application/x-www-form-urlencoded") || + consumes.Contains("multipart/form-data")) { - var paramSchema = property.Value; - if ("string".Equals(paramSchema.Type, StringComparison.OrdinalIgnoreCase) - && ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase) - || "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase))) - { - paramSchema.Type = "file"; - paramSchema.Format = null; - } - parameters.Add( - new OpenApiFormDataParameter - { - Description = property.Value.Description, - Name = property.Key, - Schema = property.Value, - Required = RequestBody.Content.First().Value.Schema.Required.Contains(property.Key) - - }); + parameters.AddRange(RequestBody.ConvertToFormDataParameters()); + } + else + { + parameters.Add(RequestBody.ConvertToBodyParameter()); } } - else + else if (RequestBody.Reference != null) { - var content = RequestBody.Content.Values.FirstOrDefault(); + parameters.Add( + new OpenApiParameter + { + UnresolvedReference = true, + Reference = RequestBody.Reference + }); - var bodyParameter = new OpenApiBodyParameter + if (RequestBody.Reference.HostDocument != null) { - Description = RequestBody.Description, - // V2 spec actually allows the body to have custom name. - // To allow round-tripping we use an extension to hold the name - Name = "body", - Schema = content?.Schema ?? new OpenApiSchema(), - Required = RequestBody.Required, - Extensions = RequestBody.Extensions.ToDictionary(k => k.Key, v => v.Value) // Clone extensions so we can remove the x-bodyName extensions from the output V2 model. - }; - - if (bodyParameter.Extensions.ContainsKey(OpenApiConstants.BodyName)) + var effectiveRequestBody = RequestBody.GetEffective(RequestBody.Reference.HostDocument); + if (effectiveRequestBody != null) + consumes = effectiveRequestBody.Content.Keys.Distinct().ToList(); + } + } + + if (consumes.Any()) + { + writer.WritePropertyName(OpenApiConstants.Consumes); + writer.WriteStartArray(); + foreach (var mediaType in consumes) { - bodyParameter.Name = (RequestBody.Extensions[OpenApiConstants.BodyName] as OpenApiString)?.Value ?? "body"; - bodyParameter.Extensions.Remove(OpenApiConstants.BodyName); + writer.WriteValue(mediaType); } - - parameters.Add(bodyParameter); + writer.WriteEndArray(); } } if (Responses != null) { - var produces = Responses.Where(r => r.Value.Content != null) - .SelectMany(r => r.Value.Content?.Keys) + var produces = Responses + .Where(static r => r.Value.Content != null) + .SelectMany(static r => r.Value.Content?.Keys) + .Concat( + Responses + .Where(static r => r.Value.Reference != null && r.Value.Reference.HostDocument != null) + .SelectMany(static r => r.Value.GetEffective(r.Value.Reference.HostDocument)?.Content?.Keys)) .Distinct() .ToList(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index 31cc5a6e8..ecc643dc3 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -214,31 +214,17 @@ private string GetExternalReferenceV2() private string GetReferenceTypeNameAsV2(ReferenceType type) { - switch (type) + return type switch { - case ReferenceType.Schema: - return OpenApiConstants.Definitions; - - case ReferenceType.Parameter: - return OpenApiConstants.Parameters; - - case ReferenceType.Response: - return OpenApiConstants.Responses; - - case ReferenceType.Header: - return OpenApiConstants.Headers; - - case ReferenceType.Tag: - return OpenApiConstants.Tags; - - case ReferenceType.SecurityScheme: - return OpenApiConstants.SecurityDefinitions; - - default: - // If the reference type is not supported in V2, simply return null - // to indicate that the reference is not pointing to any object. - return null; - } + ReferenceType.Schema => OpenApiConstants.Definitions, + ReferenceType.Parameter or ReferenceType.RequestBody => OpenApiConstants.Parameters, + ReferenceType.Response => OpenApiConstants.Responses, + ReferenceType.Header => OpenApiConstants.Headers, + ReferenceType.Tag => OpenApiConstants.Tags, + ReferenceType.SecurityScheme => OpenApiConstants.SecurityDefinitions, + _ => null,// If the reference type is not supported in V2, simply return null + // to indicate that the reference is not pointing to any object. + }; } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 9016fd7a3..70f1f742a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -144,5 +146,50 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) { // RequestBody object does not exist in V2. } + + internal OpenApiBodyParameter ConvertToBodyParameter() + { + var bodyParameter = new OpenApiBodyParameter + { + Description = Description, + // V2 spec actually allows the body to have custom name. + // To allow round-tripping we use an extension to hold the name + Name = "body", + Schema = Content.Values.FirstOrDefault()?.Schema ?? new OpenApiSchema(), + Required = Required, + Extensions = Extensions.ToDictionary(static k => k.Key, static v => v.Value) // Clone extensions so we can remove the x-bodyName extensions from the output V2 model. + }; + if (bodyParameter.Extensions.ContainsKey(OpenApiConstants.BodyName)) + { + bodyParameter.Name = (Extensions[OpenApiConstants.BodyName] as OpenApiString)?.Value ?? "body"; + bodyParameter.Extensions.Remove(OpenApiConstants.BodyName); + } + return bodyParameter; + } + + internal IEnumerable ConvertToFormDataParameters() + { + if (Content == null || !Content.Any()) + yield break; + + foreach (var property in Content.First().Value.Schema.Properties) + { + var paramSchema = property.Value; + if ("string".Equals(paramSchema.Type, StringComparison.OrdinalIgnoreCase) + && ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase) + || "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase))) + { + paramSchema.Type = "file"; + paramSchema.Format = null; + } + yield return new OpenApiFormDataParameter + { + Description = property.Value.Description, + Name = property.Key, + Schema = property.Value, + Required = Content.First().Value.Schema.Required.Contains(property.Key) + }; + } + } } }