diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs index c838a67..01c8043 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using System.Collections.Generic; +using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Common; @@ -72,13 +73,15 @@ namespace Microsoft.OpenApi.OData.Generator /// /// The OData context. /// The Edm operation import. + /// The OData path. /// The created . - public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperationImport operationImport) + public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperationImport operationImport, ODataPath path) { Utils.CheckArgumentNull(context, nameof(context)); Utils.CheckArgumentNull(operationImport, nameof(operationImport)); + Utils.CheckArgumentNull(path, nameof(path)); - return context.CreateResponses(operationImport.Operation); + return context.CreateResponses(operationImport.Operation, path); } /// @@ -86,13 +89,15 @@ namespace Microsoft.OpenApi.OData.Generator /// /// The OData context. /// The Edm operation. + /// The OData path. /// The created . - public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperation operation) + public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperation operation, ODataPath path) { Utils.CheckArgumentNull(context, nameof(context)); Utils.CheckArgumentNull(operation, nameof(operation)); + Utils.CheckArgumentNull(path, nameof(path)); - OpenApiResponses responses = new OpenApiResponses(); + OpenApiResponses responses = new(); if (operation.IsAction() && operation.ReturnType == null) { @@ -100,7 +105,44 @@ namespace Microsoft.OpenApi.OData.Generator } else { - OpenApiResponse response = new OpenApiResponse + OpenApiSchema schema; + if (operation.ReturnType.IsCollection()) + { + // Get the entity type of the previous segment + IEdmEntityType entityType = path.Segments.Reverse().Skip(1)?.Take(1)?.FirstOrDefault()?.EntityType; + schema = new OpenApiSchema + { + Title = entityType == null ? null : $"Collection of {entityType.Name}", + Type = "object", + Properties = new Dictionary + { + { + "value", context.CreateEdmTypeSchema(operation.ReturnType) + } + } + }; + } + else if (operation.ReturnType.IsPrimitive()) + { + // A property or operation response that is of a primitive type is represented as an object with a single name/value pair, + // whose name is value and whose value is a primitive value. + schema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary + { + { + "value", context.CreateEdmTypeSchema(operation.ReturnType) + } + } + }; + } + else + { + schema = context.CreateEdmTypeSchema(operation.ReturnType); + } + + OpenApiResponse response = new() { Description = "Success", Content = new Dictionary @@ -109,7 +151,7 @@ namespace Microsoft.OpenApi.OData.Generator Constants.ApplicationJsonMediaType, new OpenApiMediaType { - Schema = context.CreateEdmTypeSchema(operation.ReturnType) + Schema = schema } } } @@ -117,7 +159,7 @@ namespace Microsoft.OpenApi.OData.Generator responses.Add(Constants.StatusCode200, response); } - // both action & function has the default response. + // Both action & function have the default response. responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); return responses; diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs index c26317f..8125f8a 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs @@ -79,7 +79,7 @@ namespace Microsoft.OpenApi.OData.Operation // describing the structure of the success response by referencing an appropriate schema // in the global schemas. In addition, it contains a default name/value pair for // the OData error response referencing the global responses. - operation.Responses = Context.CreateResponses(EdmOperationImport); + operation.Responses = Context.CreateResponses(EdmOperationImport, Path); base.SetResponses(operation); } diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs index 1e98109..4b2c64d 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs @@ -166,56 +166,7 @@ namespace Microsoft.OpenApi.OData.Operation /// protected override void SetResponses(OpenApiOperation operation) { - if (EdmOperation.IsAction() && EdmOperation.ReturnType == null) - { - operation.Responses.Add(Constants.StatusCode204, Constants.StatusCode204.GetResponse()); - } - else - { - OpenApiSchema schema; - if (EdmOperation.ReturnType.TypeKind() == EdmTypeKind.Collection) - { - // Get the entity type of the previous segment - IEdmEntityType entityType = Path.Segments.Reverse().Skip(1).Take(1).FirstOrDefault().EntityType; - schema = new OpenApiSchema - { - Title = $"Collection of {entityType.Name}", - Type = "object", - Properties = new Dictionary - { - { - "value", Context.CreateEdmTypeSchema(EdmOperation.ReturnType) - } - } - }; - } - else - { - schema = Context.CreateEdmTypeSchema(EdmOperation.ReturnType); - } - - // function should have a return type. - OpenApiResponse response = new OpenApiResponse - { - Description = "Success", - Content = new Dictionary - { - { - Constants.ApplicationJsonMediaType, - new OpenApiMediaType - { - Schema = schema - } - } - } - }; - - operation.Responses.Add(Constants.StatusCode200, response); - } - - // both action & function has the default response. - operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); - + operation.Responses = Context.CreateResponses(EdmOperation, Path); base.SetResponses(operation); } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs index c1ed87e..40f33dc 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs @@ -104,7 +104,7 @@ namespace Microsoft.OpenApi.OData.Generator.Tests ODataContext context = null; // Act & Assert - Assert.Throws("context", () => context.CreateResponses(operationImport: null)); + Assert.Throws("context", () => context.CreateResponses(operationImport: null, path: null)); } [Fact] @@ -114,7 +114,19 @@ namespace Microsoft.OpenApi.OData.Generator.Tests ODataContext context = new ODataContext(EdmCoreModel.Instance); // Act & Assert - Assert.Throws("operationImport", () => context.CreateResponses(operationImport: null)); + Assert.Throws("operationImport", () => context.CreateResponses(operationImport: null, path: null)); + } + + [Fact] + public void CreateResponseForoperationImportThrowArgumentNullPath() + { + // Arrange + ODataContext context = new ODataContext(EdmCoreModel.Instance); + EdmFunction function = new EdmFunction("NS", "MyFunction", EdmCoreModel.Instance.GetString(false)); + EdmFunctionImport functionImport = new EdmFunctionImport(new EdmEntityContainer("NS", "Default"), "MyFunctionImport", function); + + // Act & Assert + Assert.Throws("path", () => context.CreateResponses(operationImport: functionImport, path: null)); } [Fact] @@ -124,7 +136,7 @@ namespace Microsoft.OpenApi.OData.Generator.Tests ODataContext context = null; // Act & Assert - Assert.Throws("context", () => context.CreateResponses(operation: null)); + Assert.Throws("context", () => context.CreateResponses(operation: null, path: null)); } [Fact] @@ -134,7 +146,19 @@ namespace Microsoft.OpenApi.OData.Generator.Tests ODataContext context = new ODataContext(EdmCoreModel.Instance); // Act & Assert - Assert.Throws("operation", () => context.CreateResponses(operation: null)); + Assert.Throws("operation", () => context.CreateResponses(operation: null, path: null)); + } + + [Fact] + public void CreateResponseForOperationThrowArgumentNullPath() + { + // Arrange + ODataContext context = new ODataContext(EdmCoreModel.Instance); + EdmFunction function = new EdmFunction("NS", "MyFunction", EdmCoreModel.Instance.GetString(false)); + EdmFunctionImport functionImport = new EdmFunctionImport(new EdmEntityContainer("NS", "Default"), "MyFunctionImport", function); + + // Act & Assert + Assert.Throws("path", () => context.CreateResponses(operation: function, path: null)); } [Theory] @@ -157,13 +181,15 @@ namespace Microsoft.OpenApi.OData.Generator.Tests { IEdmOperationImport operationImport = model.EntityContainer.OperationImports().First(o => o.Name == operationName); Assert.NotNull(operationImport); // guard - responses = context.CreateResponses(operationImport); + ODataPath path = new ODataPath(new ODataOperationImportSegment(operationImport)); + responses = context.CreateResponses(operationImport, path); } else { IEdmOperation operation = model.SchemaElements.OfType().First(o => o.Name == operationName); Assert.NotNull(operation); // guard - responses = context.CreateResponses(operation); + ODataPath path = new ODataPath(new ODataOperationSegment(operation)); + responses = context.CreateResponses(operation, path); } // Assert @@ -195,7 +221,7 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.NotNull(mediaType.Schema.AnyOf); var anyOf = Assert.Single(mediaType.Schema.AnyOf); Assert.Equal("Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person", anyOf.Reference.Id); - } + } } [Theory] @@ -214,13 +240,15 @@ namespace Microsoft.OpenApi.OData.Generator.Tests { IEdmOperationImport operationImport = model.EntityContainer.OperationImports().First(o => o.Name == actionName); Assert.NotNull(operationImport); // guard - responses = context.CreateResponses(operationImport); + ODataPath path = new ODataPath(new ODataOperationImportSegment(operationImport)); + responses = context.CreateResponses(operationImport, path); } else { IEdmOperation operation = model.SchemaElements.OfType().First(o => o.Name == actionName); Assert.NotNull(operation); // guard - responses = context.CreateResponses(operation); + ODataPath path = new ODataPath(new ODataOperationSegment(operation)); + responses = context.CreateResponses(operation, path); } // Assert diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json index 8ad1c25..7949025 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.json @@ -1313,8 +1313,13 @@ "200": { "description": "Success", "schema": { - "default": false, - "type": "boolean" + "type": "object", + "properties": { + "value": { + "default": false, + "type": "boolean" + } + } } }, "default": { @@ -3000,8 +3005,13 @@ "200": { "description": "Success", "schema": { - "default": false, - "type": "boolean" + "type": "object", + "properties": { + "value": { + "default": false, + "type": "boolean" + } + } } }, "default": { @@ -4767,8 +4777,13 @@ "200": { "description": "Success", "schema": { - "default": false, - "type": "boolean" + "type": "object", + "properties": { + "value": { + "default": false, + "type": "boolean" + } + } } }, "default": { diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml index dbe4998..5a5533a 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.V2.yaml @@ -898,8 +898,11 @@ paths: '200': description: Success schema: - default: false - type: boolean + type: object + properties: + value: + default: false + type: boolean default: $ref: '#/responses/error' x-ms-docs-operation-type: function @@ -2079,8 +2082,11 @@ paths: '200': description: Success schema: - default: false - type: boolean + type: object + properties: + value: + default: false + type: boolean default: $ref: '#/responses/error' x-ms-docs-operation-type: function @@ -3320,8 +3326,11 @@ paths: '200': description: Success schema: - default: false - type: boolean + type: object + properties: + value: + default: false + type: boolean default: $ref: '#/responses/error' x-ms-docs-operation-type: function diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json index 5d09291..4c06fd0 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json @@ -1483,8 +1483,13 @@ "content": { "application/json": { "schema": { - "type": "boolean", - "default": false + "type": "object", + "properties": { + "value": { + "type": "boolean", + "default": false + } + } } } } @@ -3366,8 +3371,13 @@ "content": { "application/json": { "schema": { - "type": "boolean", - "default": false + "type": "object", + "properties": { + "value": { + "type": "boolean", + "default": false + } + } } } } @@ -5351,8 +5361,13 @@ "content": { "application/json": { "schema": { - "type": "boolean", - "default": false + "type": "object", + "properties": { + "value": { + "type": "boolean", + "default": false + } + } } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml index 5c441d1..7b9f87f 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml @@ -992,8 +992,11 @@ paths: content: application/json: schema: - type: boolean - default: false + type: object + properties: + value: + type: boolean + default: false default: $ref: '#/components/responses/error' x-ms-docs-operation-type: function @@ -2285,8 +2288,11 @@ paths: content: application/json: schema: - type: boolean - default: false + type: object + properties: + value: + type: boolean + default: false default: $ref: '#/components/responses/error' x-ms-docs-operation-type: function @@ -3649,8 +3655,11 @@ paths: content: application/json: schema: - type: boolean - default: false + type: object + properties: + value: + type: boolean + default: false default: $ref: '#/components/responses/error' x-ms-docs-operation-type: function