diff --git a/src/NSwag.CodeGeneration.CSharp/Models/CSharpFileTemplateModel.cs b/src/NSwag.CodeGeneration.CSharp/Models/CSharpFileTemplateModel.cs index 89458e284f..6002400490 100644 --- a/src/NSwag.CodeGeneration.CSharp/Models/CSharpFileTemplateModel.cs +++ b/src/NSwag.CodeGeneration.CSharp/Models/CSharpFileTemplateModel.cs @@ -96,7 +96,7 @@ public CSharpFileTemplateModel( public bool RequiresFileParameterType => _settings.CSharpGeneratorSettings.ExcludedTypeNames?.Contains("FileParameter") != true && (_document.Operations.Any(o => o.Operation.ActualParameters.Any(p => p.ActualTypeSchema.IsBinary)) || - _document.Operations.Any(o => o.Operation?.RequestBody?.Content?.Any(c => c.Value.Schema?.IsBinary == true || + _document.Operations.Any(o => o.Operation?.ActualRequestBody?.Content?.Any(c => c.Value.Schema?.IsBinary == true || c.Value.Schema?.ActualSchema.ActualProperties.Any(p => p.Value.IsBinary || p.Value.Item?.IsBinary == true || p.Value.Items.Any(i => i.IsBinary) diff --git a/src/NSwag.CodeGeneration.TypeScript/Models/TypeScriptFileTemplateModel.cs b/src/NSwag.CodeGeneration.TypeScript/Models/TypeScriptFileTemplateModel.cs index eac318d868..97798b8976 100644 --- a/src/NSwag.CodeGeneration.TypeScript/Models/TypeScriptFileTemplateModel.cs +++ b/src/NSwag.CodeGeneration.TypeScript/Models/TypeScriptFileTemplateModel.cs @@ -130,7 +130,7 @@ public IEnumerable ResponseClassNames public bool RequiresFileParameterInterface => !_settings.TypeScriptGeneratorSettings.ExcludedTypeNames.Contains("FileParameter") && (_document.Operations.Any(o => o.Operation.ActualParameters.Any(p => p.ActualTypeSchema.IsBinary)) || - _document.Operations.Any(o => o.Operation?.RequestBody?.Content?.Any(c => c.Value.Schema?.IsBinary == true || + _document.Operations.Any(o => o.Operation?.ActualRequestBody?.Content?.Any(c => c.Value.Schema?.IsBinary == true || c.Value.Schema?.ActualProperties.Any(p => p.Value.IsBinary || p.Value.Item?.IsBinary == true || p.Value.Items.Any(i => i.IsBinary) diff --git a/src/NSwag.CodeGeneration/Models/OperationModelBase.cs b/src/NSwag.CodeGeneration/Models/OperationModelBase.cs index c3149853e3..9af99efa68 100644 --- a/src/NSwag.CodeGeneration/Models/OperationModelBase.cs +++ b/src/NSwag.CodeGeneration/Models/OperationModelBase.cs @@ -216,7 +216,7 @@ public TParameterModel ContentParameter /// Gets a value indicating whether the operation consumes 'application/x-www-form-urlencoded'. public bool ConsumesFormUrlEncoded => _operation.ActualConsumes?.Any(c => c == "application/x-www-form-urlencoded") == true || - _operation.RequestBody?.Content.Any(mt => mt.Key == "application/x-www-form-urlencoded") == true; + _operation.ActualRequestBody?.Content.Any(mt => mt.Key == "application/x-www-form-urlencoded") == true; /// Gets the form parameters. public IEnumerable FormParameters => Parameters.Where(p => p.Kind == OpenApiParameterKind.FormData); @@ -259,7 +259,7 @@ public string Consumes } return _operation.ActualConsumes?.FirstOrDefault() ?? - _operation.RequestBody?.Content.Keys.FirstOrDefault() ?? + _operation.ActualRequestBody?.Content.Keys.FirstOrDefault() ?? "application/json"; } } @@ -350,8 +350,8 @@ protected IList GetActualParameters() .ToList(); var formDataSchema = - _operation?.RequestBody?.Content?.ContainsKey("multipart/form-data") == true ? - _operation.RequestBody.Content["multipart/form-data"]?.Schema.ActualSchema: null; + _operation?.ActualRequestBody?.Content?.ContainsKey("multipart/form-data") == true ? + _operation.ActualRequestBody.Content["multipart/form-data"]?.Schema.ActualSchema: null; if (formDataSchema != null && formDataSchema.ActualProperties.Count > 0) { diff --git a/src/NSwag.Core.Tests/Serialization/ExternalReferenceTests.cs b/src/NSwag.Core.Tests/Serialization/ExternalReferenceTests.cs index 319bde82e9..03c0eabf3f 100644 --- a/src/NSwag.Core.Tests/Serialization/ExternalReferenceTests.cs +++ b/src/NSwag.Core.Tests/Serialization/ExternalReferenceTests.cs @@ -24,6 +24,15 @@ public async Task When_file_contains_path_reference_to_another_file_it_is_loaded Assert.Equal("External path", document.Paths.First().Value.ActualPathItem.Values.First().Description); } + [Fact] + public async Task When_file_contains_requestBody_reference_to_another_file_it_is_loaded() + { + var document = await OpenApiDocument.FromFileAsync("TestFiles/requestBody-reference.json"); + + Assert.NotNull(document); + Assert.Equal("External request body", document.Paths.First().Value.Values.First().RequestBody.ActualRequestBody.Description); + } + [Fact] public async Task When_file_contains_response_reference_to_another_file_it_is_loaded() { diff --git a/src/NSwag.Core.Tests/TestFiles/common.json b/src/NSwag.Core.Tests/TestFiles/common.json index c2dc0990e0..c3ddbead67 100644 --- a/src/NSwag.Core.Tests/TestFiles/common.json +++ b/src/NSwag.Core.Tests/TestFiles/common.json @@ -55,6 +55,19 @@ "additionalProperties": true } }, + "requestBodies": { + "TestRequest": { + "description": "External request body", + "required": true, + "content": { + "text/json": { + "schema": { + "$ref": "#/components/schemas/TestObject" + } + } + } + } + }, "responses": { "TestResponse": { "description": "External response", diff --git a/src/NSwag.Core.Tests/TestFiles/requestBody-reference.json b/src/NSwag.Core.Tests/TestFiles/requestBody-reference.json new file mode 100644 index 0000000000..841c8e384d --- /dev/null +++ b/src/NSwag.Core.Tests/TestFiles/requestBody-reference.json @@ -0,0 +1,14 @@ +{ + "openapi": "3.0.2", + "paths": { + "/test": { + "get": { + "description": "Test path", + "requestBody": { + "$ref": "./common.json#/components/requestBodies/TestRequest" + }, + "responses": {} + } + } + } +} \ No newline at end of file diff --git a/src/NSwag.Core/OpenApiComponents.cs b/src/NSwag.Core/OpenApiComponents.cs index b1e416d111..985c04f454 100644 --- a/src/NSwag.Core/OpenApiComponents.cs +++ b/src/NSwag.Core/OpenApiComponents.cs @@ -38,6 +38,16 @@ public OpenApiComponents(OpenApiDocument document) }; Schemas = schemas; + var requestBodies = new ObservableDictionary(); + requestBodies.CollectionChanged += (sender, args) => + { + foreach (var path in RequestBodies.Values) + { + path.Parent = document; + } + }; + RequestBodies = requestBodies; + var responses = new ObservableDictionary(); responses.CollectionChanged += (sender, args) => { @@ -86,6 +96,10 @@ public OpenApiComponents(OpenApiDocument document) [JsonProperty(PropertyName = "schemas", DefaultValueHandling = DefaultValueHandling.Ignore)] public IDictionary Schemas { get; } + /// Gets or sets the responses which can be used for all operations. + [JsonProperty(PropertyName = "requestBodies", DefaultValueHandling = DefaultValueHandling.Ignore)] + public IDictionary RequestBodies { get; } + /// Gets or sets the responses which can be used for all operations. [JsonProperty(PropertyName = "responses", DefaultValueHandling = DefaultValueHandling.Ignore)] public IDictionary Responses { get; } diff --git a/src/NSwag.Core/OpenApiMediaType.cs b/src/NSwag.Core/OpenApiMediaType.cs index bf16abfb08..3ed4e81735 100644 --- a/src/NSwag.Core/OpenApiMediaType.cs +++ b/src/NSwag.Core/OpenApiMediaType.cs @@ -29,7 +29,7 @@ public JsonSchema Schema set { _schema = value; - Parent?.Parent?.UpdateBodyParameter(); + Parent?.ParentOperation?.UpdateBodyParameter(); } } @@ -41,7 +41,7 @@ public object Example set { _example = value; - Parent?.Parent?.UpdateBodyParameter(); + Parent?.ParentOperation?.UpdateBodyParameter(); } } diff --git a/src/NSwag.Core/OpenApiOperation.cs b/src/NSwag.Core/OpenApiOperation.cs index 98230d700d..a02a600433 100644 --- a/src/NSwag.Core/OpenApiOperation.cs +++ b/src/NSwag.Core/OpenApiOperation.cs @@ -168,6 +168,10 @@ public IReadOnlyList ActualParameters [JsonIgnore] public ICollection ActualSchemes => Schemes ?? Parent.Parent.Schemes; + /// Gets the response body and dereferences it if necessary. + [JsonIgnore] + public OpenApiRequestBody ActualRequestBody => RequestBody?.ActualRequestBody; + /// Gets the responses from the operation and from the and dereferences them if necessary. [JsonIgnore] public IReadOnlyDictionary ActualResponses => Responses.ToDictionary(t => t.Key, t => t.Value.ActualResponse); diff --git a/src/NSwag.Core/OpenApiParameter.cs b/src/NSwag.Core/OpenApiParameter.cs index 814bb87242..316f4a5c30 100644 --- a/src/NSwag.Core/OpenApiParameter.cs +++ b/src/NSwag.Core/OpenApiParameter.cs @@ -225,7 +225,7 @@ public bool IsXmlBodyParameter var parent = Parent as OpenApiOperation; var consumes = parent?.ActualConsumes?.Any() == true ? parent.ActualConsumes : - parent?.RequestBody?.Content.Keys; + parent?.ActualRequestBody?.Content.Keys; return consumes?.Any() == true && consumes.Any(p => p.Contains("application/xml")) && @@ -256,7 +256,7 @@ public bool IsBinaryBodyParameter } else { - var consumes = parent?.RequestBody?.Content; + var consumes = parent?.ActualRequestBody?.Content; return (consumes?.Any(p => p.Key == "multipart/form-data") == true || consumes?.Any(p => p.Value.Schema?.IsBinary != false) == true) && consumes.Any(p => p.Key.Contains("*/*") && p.Value.Schema?.IsBinary != true) == false && @@ -286,7 +286,7 @@ public bool HasBinaryBodyWithMultipleMimeTypes } else { - var consumes = parent?.RequestBody?.Content; + var consumes = parent?.ActualRequestBody?.Content; return consumes?.Any() == true && (consumes.Count() > 1 || consumes.Any(p => p.Key.Contains("*"))); diff --git a/src/NSwag.Core/OpenApiRequestBody.cs b/src/NSwag.Core/OpenApiRequestBody.cs index 27cf0d34a3..5663eb8f15 100644 --- a/src/NSwag.Core/OpenApiRequestBody.cs +++ b/src/NSwag.Core/OpenApiRequestBody.cs @@ -8,12 +8,13 @@ using System.Collections.Generic; using Newtonsoft.Json; +using NJsonSchema.References; using NSwag.Collections; namespace NSwag { /// The OpenApi request body (OpenAPI only). - public class OpenApiRequestBody + public class OpenApiRequestBody : JsonReferenceBase, IJsonReference { private string _name; private bool _isRequired; @@ -31,13 +32,20 @@ public OpenApiRequestBody() mediaType.Parent = this; } - Parent?.UpdateBodyParameter(); + ParentOperation?.UpdateBodyParameter(); }; Content = content; } [JsonIgnore] - internal OpenApiOperation Parent { get; set; } + internal object Parent { get; set; } + + [JsonIgnore] + internal OpenApiOperation ParentOperation => Parent as OpenApiOperation; + + /// Gets the actual request body, either this or the referenced request body. + [JsonIgnore] + public OpenApiRequestBody ActualRequestBody => Reference ?? this; /// Gets or sets the name. [JsonProperty(PropertyName = "x-name", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] @@ -47,7 +55,7 @@ public string Name set { _name = value; - Parent?.UpdateBodyParameter(); + ParentOperation?.UpdateBodyParameter(); } } @@ -59,7 +67,7 @@ public string Description set { _description = value; - Parent?.UpdateBodyParameter(); + ParentOperation?.UpdateBodyParameter(); } } @@ -75,7 +83,7 @@ public bool IsRequired set { _isRequired = value; - Parent?.UpdateBodyParameter(); + ParentOperation?.UpdateBodyParameter(); } } @@ -87,12 +95,22 @@ public int? Position set { _position = value; - Parent?.UpdateBodyParameter(); + ParentOperation?.UpdateBodyParameter(); } } /// Gets the actual name of the request body parameter. [JsonIgnore] public string ActualName => string.IsNullOrEmpty(Name) ? "body" : Name; + + #region Implementation of IJsonReference + + [JsonIgnore] + IJsonReference IJsonReference.ActualObject => ActualRequestBody; + + [JsonIgnore] + object IJsonReference.PossibleRoot => ParentOperation?.Parent?.Parent; + + #endregion } } \ No newline at end of file