From 1b8d685dc79c0697dabeb5095517ab9e0999dda5 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Wed, 8 Aug 2018 10:18:01 -0700 Subject: [PATCH] Merge Graph changes into Master branch --- .../Abstractions/IAuthorizationProvider.cs | 23 + .../Annotations/CustomParameter.cs | 76 ++ .../Annotations/Example.cs | 76 ++ .../Annotations/ExternalExample.cs | 33 + .../Annotations/HttpRequest.cs | 84 +++ .../Annotations/HttpRequestsAnnotation.cs | 100 +++ .../Annotations/HttpRequestsHelper.cs | 154 ++++ .../Annotations/HttpResponse.cs | 57 ++ .../Annotations/InlineExample.cs | 33 + .../Authorizations/ApiKey.cs | 69 ++ .../Authorizations/Authorization.cs | 114 +++ .../Authorizations/AuthorizationConstants.cs | 68 ++ .../Authorizations/AuthorizationProvider.cs | 61 ++ .../Authorizations/AuthorizationScope.cs | 42 ++ .../Authorizations/Http.cs | 48 ++ .../Authorizations/OAuth2AuthCode.cs | 47 ++ .../Authorizations/OAuth2ClientCredentials.cs | 39 + .../Authorizations/OAuth2Implicit.cs | 39 + .../Authorizations/OAuth2Password.cs | 39 + .../Authorizations/OAuthAuthorization.cs | 80 ++ .../Authorizations/OpenIDConnect.cs | 41 + .../Authorizations/SecurityScheme.cs | 43 ++ .../{Generator => Common}/Constants.cs | 24 +- .../{ => Common}/Error.cs | 25 +- .../Common/Utils.cs | 32 + .../Edm/EdmModelExtensions.cs | 150 ++++ .../Edm/ODataContext.cs | 236 ++++++ .../Edm/ODataKeySegment.cs | 53 ++ .../Edm/ODataNavigationPropertySegment.cs | 42 ++ .../Edm/ODataNavigationSourceSegment.cs | 42 ++ .../Edm/ODataOperationImportSegment.cs | 42 ++ .../Edm/ODataOperationSegment.cs | 110 +++ .../Edm/ODataPath.cs | 217 ++++++ .../Edm/ODataPathHandler.cs | 269 +++++++ .../Edm/ODataPathType.cs | 43 ++ .../Edm/ODataSegment.cs | 25 + .../Edm/ODataTypeCastSegment.cs | 37 + .../Edm/RecordExpressionExtensions.cs | 255 +++++++ .../EdmModelOpenApiExtensions.cs | 2 + .../Generator/ODataContext.cs | 120 --- .../Generator/OpenApiComponentsGenerator.cs | 9 +- .../Generator/OpenApiDocumentGenerator.cs | 15 +- .../OpenApiEdmTypeSchemaGenerator.cs | 37 +- .../Generator/OpenApiErrorSchemaGenerator.cs | 13 +- .../Generator/OpenApiExampleGenerator.cs | 145 ++++ .../Generator/OpenApiInfoGenerator.cs | 7 +- .../Generator/OpenApiLinkGenerator.cs | 53 ++ .../Generator/OpenApiOperationGenerator.cs | 1 + .../Generator/OpenApiParameterGenerator.cs | 36 +- .../Generator/OpenApiPathItemGenerator.cs | 344 +-------- .../Generator/OpenApiPathsGenerator.cs | 7 +- .../Generator/OpenApiRequestBodyGenerator.cs | 24 +- .../Generator/OpenApiResponseGenerator.cs | 29 +- .../Generator/OpenApiSchemaGenerator.cs | 134 +++- .../OpenApiSecurityRequirementGenerator.cs | 51 ++ .../OpenApiSecuritySchemeGenerator.cs | 169 +++++ .../Generator/OpenApiServerGenerator.cs | 7 +- .../OpenApiSpatialTypeSchemaGenerator.cs | 21 +- .../Generator/OpenApiTagGenerator.cs | 13 +- .../Generator/PathItemNameExtensions.cs | 91 +-- .../Microsoft.OpenAPI.OData.Reader.csproj | 4 +- .../OpenApiConvertSettings.cs | 30 +- .../EdmActionImportOperationHandler.cs | 37 + .../Operation/EdmActionOperationHandler.cs | 43 ++ .../EdmFunctionImportOperationHandler.cs | 39 + .../Operation/EdmFunctionOperationHandler.cs | 32 + .../EdmOperationImportOperationHandler.cs | 95 +++ .../Operation/EdmOperationOperationHandler.cs | 121 +++ .../Operation/EntityDeleteOperationHandler.cs | 70 ++ .../Operation/EntityGetOperationHandler.cs | 113 +++ .../Operation/EntityPatchOperationHandler.cs | 80 ++ .../Operation/EntitySetGetOperationHandler.cs | 166 ++++ .../Operation/EntitySetOperationHandler.cs | 46 ++ .../EntitySetPostOperationHandler.cs | 114 +++ .../Operation/IOperationHandler.cs | 29 + .../Operation/IOperationHandlerProvider.cs | 24 + .../NavigationPropertyGetOperationHandler.cs | 159 ++++ .../NavigationPropertyOperationHandler.cs | 136 ++++ ...NavigationPropertyPatchOperationHandler.cs | 78 ++ .../NavigationPropertyPostOperationHandler.cs | 102 +++ .../Operation/OperationHandler.cs | 212 ++++++ .../Operation/OperationHandlerProvider.cs | 77 ++ .../Operation/SingletonGetOperationHandler.cs | 102 +++ .../Operation/SingletonOperationHandler.cs | 47 ++ .../SingletonPatchOperationHandler.cs | 79 ++ .../PathItem/EntityPathItemHandler.cs | 38 + .../PathItem/EntitySetPathItemHandler.cs | 44 ++ .../PathItem/IPathItemHandler.cs | 24 + .../PathItem/IPathItemHandlerProvider.cs | 22 + .../NavigationPropertyPathItemHandler.cs | 136 ++++ .../OperationImportPathItemHandler.cs | 52 ++ .../PathItem/OperationPathItemHandler.cs | 87 +++ .../PathItem/PathItemHandler.cs | 79 ++ .../PathItem/PathItemHandlerProvider.cs | 43 ++ .../PathItem/SingletonPathItemHandler.cs | 45 ++ .../Properties/SRResource.Designer.cs | 9 + .../Properties/SRResource.resx | 3 + src/OoasGui/MainForm.cs | 2 +- src/OoasGui/OoasGui.csproj | 8 +- src/OoasGui/packages.config | 4 +- .../AuthorizationProviderTest.cs | 131 ++++ .../{Generator => Edm}/ODataContextTests.cs | 24 +- .../Edm/ODataKeySegmentTests.cs | 66 ++ .../ODataNavigationPropertySegmentTests.cs | 14 + .../Edm/ODataNavigationSourceSegmentTests.cs | 14 + .../Edm/ODataOperationImportSegmentTests.cs | 14 + .../Edm/ODataOperationSegmentTests.cs | 14 + .../Edm/ODataPathTests.cs | 14 + .../Edm/ODataTypeCastSegmentTests.cs | 49 ++ .../OpenApiComponentsGeneratorTests.cs | 1 + .../OpenApiDocumentGeneratorTests.cs | 1 + .../OpenApiEdmTypeSchemaGeneratorTest.cs | 1 + .../Generator/OpenApiInfoGeneratorTests.cs | 3 +- .../OpenApiOperationGeneratorTests.cs | 1 + .../OpenApiParameterGeneratorTests.cs | 1 + .../OpenApiPathItemGeneratorTests.cs | 121 +-- .../Generator/OpenApiPathsGeneratorTests.cs | 7 +- .../OpenApiRequestBodyGeneratorTests.cs | 1 + .../OpenApiResponseGeneratorTests.cs | 1 + .../Generator/OpenApiSchemaGeneratorTests.cs | 42 +- .../OpenApiSecuritySchemeGeneratorTests.cs | 116 +++ .../Generator/OpenApiServerGeneratorTests.cs | 1 + .../OpenApiSpatialTypeSchemaGeneratorTest.cs | 1 + .../Generator/OpenApiTagGeneratorTests.cs | 1 + .../Generator/PathItemNameExtensionsTests.cs | 1 + ...icrosoft.OpenAPI.OData.Reader.Tests.csproj | 2 +- .../EdmActionImportOperationHandlerTests.cs | 46 ++ .../EdmActionOperationHandlerTests.cs | 52 ++ .../EdmFunctionImportOperationHandlerTests.cs | 47 ++ .../EdmFunctionOperationHandlerTests.cs | 52 ++ .../EntityDeleteOperationHandlerTests.cs | 47 ++ .../EntityGetOperationHandlerTests.cs | 46 ++ .../EntityPatchOperationHandlerTests.cs | 47 ++ .../EntitySetGetOperationHandlerTests.cs | 43 ++ .../EntitySetPostOperationHandlerTests.cs | 43 ++ ...igationPropertyGetOperationHandlerTests.cs | 50 ++ ...ationPropertyPatchOperationHandlerTests.cs | 51 ++ ...gationPropertyPostOperationHandlerTests.cs | 51 ++ .../OperationHandlerProviderTests.cs | 42 ++ .../SingletonGetOperationHandlerTests.cs | 47 ++ .../SingletonPatchOperationHandlerTests.cs | 45 ++ .../PathItem/EntityPathItemHandlerTests.cs | 42 ++ .../PathItem/EntitySetPathItemHandlerTests.cs | 42 ++ .../NavigationPropertyPathItemHandlerTests.cs | 88 +++ .../OperationImportPathItemHandlerTests.cs | 48 ++ .../PathItem/OperationPathItemHandlerTests.cs | 50 ++ .../PathItem/PathItemHandlerProviderTests.cs | 33 + .../PathItem/SingletonPathItemHandlerTests.cs | 42 ++ .../Resources.cs | 1 + .../Resources/Basic.OpenApi.json | 202 +++-- .../Resources/Basic.OpenApi.yaml | 135 ++-- .../Resources/TripService.OpenApi.json | 711 ++++++++++++++---- .../Resources/TripService.OpenApi.yaml | 460 ++++++++--- test/UpdateDocs/Program.cs | 2 +- test/UpdateDocs/UpdateDocs.csproj | 8 +- test/UpdateDocs/packages.config | 4 +- 156 files changed, 8948 insertions(+), 1198 deletions(-) create mode 100644 src/Microsoft.OpenApi.OData.Reader/Abstractions/IAuthorizationProvider.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/CustomParameter.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/Example.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/ExternalExample.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequest.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsAnnotation.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsHelper.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/HttpResponse.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Annotations/InlineExample.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/ApiKey.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/Authorization.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationConstants.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationProvider.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationScope.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/Http.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2AuthCode.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2ClientCredentials.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Implicit.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Password.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuthAuthorization.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/OpenIDConnect.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Authorizations/SecurityScheme.cs rename src/Microsoft.OpenApi.OData.Reader/{Generator => Common}/Constants.cs (59%) rename src/Microsoft.OpenApi.OData.Reader/{ => Common}/Error.cs (77%) create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataContext.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataKeySegment.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationPropertySegment.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationSourceSegment.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationImportSegment.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathType.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataSegment.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/ODataTypeCastSegment.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs delete mode 100644 src/Microsoft.OpenApi.OData.Reader/Generator/ODataContext.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiLinkGenerator.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecurityRequirementGenerator.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecuritySchemeGenerator.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionImportOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionImportOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EntityDeleteOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EntityGetOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EntityPatchOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetGetOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandlerProvider.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyGetOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPatchOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPostOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandlerProvider.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/SingletonGetOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/SingletonOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/SingletonPatchOperationHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandlerProvider.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandlerProvider.cs create mode 100644 src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Authorizations/AuthorizationProviderTest.cs rename test/Microsoft.OpenAPI.OData.Reader.Tests/{Generator => Edm}/ODataContextTests.cs (54%) create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataKeySegmentTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationPropertySegmentTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationSourceSegmentTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationImportSegmentTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataTypeCastSegmentTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSecuritySchemeGeneratorTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionImportOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionImportOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityDeleteOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityGetOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityPatchOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPatchOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPostOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/OperationHandlerProviderTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonGetOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonPatchOperationHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/PathItemHandlerProviderTests.cs create mode 100644 test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs diff --git a/src/Microsoft.OpenApi.OData.Reader/Abstractions/IAuthorizationProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Abstractions/IAuthorizationProvider.cs new file mode 100644 index 0000000..d068e1a --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Abstractions/IAuthorizationProvider.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OpenApi.OData.Abstractions +{ + /// + /// The provider interface. + /// + public interface IAuthorizationProvider + { + /// + /// Provide the . + /// + /// The . + //IEnumerable GetAuthorizations(IEdmModel model, IEdmVocabularyAnnotatable target); + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/CustomParameter.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/CustomParameter.cs new file mode 100644 index 0000000..e7994da --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/CustomParameter.cs @@ -0,0 +1,76 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// Complex type Org.OData.Core.V1.CustomParameter + /// + internal class CustomParameter + { + /// + /// The Name. + /// + public string Name { get; set; } + + /// + /// The Description. + /// + public string Description { get; set; } + + /// + /// The DocumentationURL. + /// + public string DocumentationURL { get; set; } + + /// + /// The Required. + /// + public bool? Required { get; set; } + + /// + /// The ExampleValues. + /// + public IEnumerable ExampleValues { get; set; } + + /// + /// Init the + /// + /// The input record. + public virtual void Init(IEdmRecordExpression record) + { + Utils.CheckArgumentNull(record, nameof(record)); + + // Name + Name = record.GetString("Name"); + + // Description + Description = record.GetString("Description"); + + // DocumentationURL + DocumentationURL = record.GetString("DocumentationURL"); + + // Required + Required = record.GetBoolean("Required"); + + // ExampleValues + ExampleValues = record.GetCollection("ExampleValues", r => + { + IEdmRecordExpression itemRecord = r as IEdmRecordExpression; + if (itemRecord != null) + { + return Example.CreateExample(itemRecord); + } + + return null; + }); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/Example.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/Example.cs new file mode 100644 index 0000000..7b9468b --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/Example.cs @@ -0,0 +1,76 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// Org.OData.Core.V1.Example + /// + internal abstract class Example + { + /// + /// Description. + /// + public string Description { get; set; } + + /// + /// Init the . + /// + /// The input record. + public virtual void Init(IEdmRecordExpression record) + { + Utils.CheckArgumentNull(record, nameof(record)); + + // Description + Description = record.GetString("Description"); + } + + /// + /// Creat the corresponding example object. + /// + /// The input record. + /// The created example object. + public static Example CreateExample(IEdmRecordExpression record) + { + if (record == null || record.DeclaredType == null) + { + return null; + } + + IEdmComplexType complexType = record.DeclaredType.Definition as IEdmComplexType; + if (complexType == null) + { + return null; + } + + Example example = null; + switch (complexType.FullTypeName()) + { + case "Org.OData.Core.V1.ExternalExample": + example = new ExternalExample(); + break; + + case "Org.OData.Core.V1.InlineExample": + example = new InlineExample(); + break; + + default: + break; + } + + if (example != null) + { + example.Init(record); + } + + return example; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/ExternalExample.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/ExternalExample.cs new file mode 100644 index 0000000..b9bde4b --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/ExternalExample.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// Complex type Org.OData.Core.V1.ExternalExample + /// + internal class ExternalExample : Example + { + /// + /// ExternalValue + /// + public string ExternalValue { get; set; } + + /// + /// Init the . + /// + /// The record. + public override void Init(IEdmRecordExpression record) + { + base.Init(record); + + // ExternalValue + ExternalValue = record.GetString("ExternalValue"); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequest.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequest.cs new file mode 100644 index 0000000..5d9552d --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequest.cs @@ -0,0 +1,84 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Authorizations; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// Org.OData.Core.V1.HttpRequest + /// + internal class HttpRequest + { + /// + /// The description. + /// + public string Description { get; set; } + + /// + /// The MethodDescription. + /// + public string MethodDescription { get; set; } + + /// + /// The MethodType. + /// + public string MethodType { get; set; } + + /// + /// The Custom Query Options. + /// + public IList CustomQueryOptions { get; set; } + + /// + /// The custom Headers. + /// + public IList CustomHeaders { get; set; } + + /// + /// The http responses. + /// + public IList HttpResponses { get; set; } + + /// + /// The security sechems. + /// + public IList SecuritySchemes { get; set; } + + /// + /// Init the . + /// + /// The input record. + public virtual void Init(IEdmRecordExpression record) + { + Utils.CheckArgumentNull(record, nameof(record)); + + // Description. + Description = record.GetString("Description"); + + // MethodDescription. + MethodDescription = record.GetString("MethodDescription"); + + // MethodType. + MethodType = record.GetString("MethodType"); + + // CustomQueryOptions + CustomQueryOptions = record.GetCollection("CustomQueryOptions", (s, r) => s.Init(r as IEdmRecordExpression)); + + // CustomHeaders + CustomHeaders = record.GetCollection("CustomHeaders", (s, r) => s.Init(r as IEdmRecordExpression)); + + // HttpResponses + HttpResponses = record.GetCollection("HttpResponses", (s, r) => s.Init(r as IEdmRecordExpression)); + + // SecuritySchemes + SecuritySchemes = record.GetCollection("SecuritySchemes", (s, r) => s.Init(r as IEdmRecordExpression)); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsAnnotation.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsAnnotation.cs new file mode 100644 index 0000000..31a3dce --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsAnnotation.cs @@ -0,0 +1,100 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// Org.OData.Core.V1.HttpRequests + /// + internal class HttpRequestsAnnotation + { + /// + /// The Term type name. + /// + public virtual string QualifiedName => "Org.OData.Core.V1.HttpRequests"; + + /// + /// Gets the http request array. + /// + public IList Requests { get; private set; } + + /// + /// Gets the Edm mode. + /// + public IEdmModel Model { get; } + + /// + /// Gets the vocabulary annotatble. + /// + public IEdmVocabularyAnnotatable Target { get; } + + /// + /// Initializes a new instance of class. + /// + /// The Edm model. + /// The Edm annotation target. + public HttpRequestsAnnotation(IEdmModel model, IEdmVocabularyAnnotatable target) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(target, nameof(target)); + + Model = model; + Target = target; + + Initialize(); + } + + public HttpRequest GetRequest(string method) + { + if (Requests == null) + { + return null; + } + + return Requests.FirstOrDefault(e => string.Equals(e.MethodType, method, System.StringComparison.OrdinalIgnoreCase)); + } + + protected virtual void Initialize() + { + IEdmVocabularyAnnotation annotation = Model.GetVocabularyAnnotation(Target, QualifiedName); + if (annotation == null) + { + IEdmNavigationSource navigationSource = Target as IEdmNavigationSource; + + // if not, search the entity type. + if (navigationSource != null) + { + IEdmEntityType entityType = navigationSource.EntityType(); + annotation = Model.GetVocabularyAnnotation(entityType, QualifiedName); + } + } + + if (annotation == null || + annotation.Value == null || + annotation.Value.ExpressionKind != EdmExpressionKind.Collection) + { + return; + } + + IEdmCollectionExpression collection = (IEdmCollectionExpression)annotation.Value; + + Requests = new List(); + foreach (var item in collection.Elements) + { + IEdmRecordExpression record = (IEdmRecordExpression)item; + HttpRequest request = new HttpRequest(); + request.Init(record); + Requests.Add(request); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsHelper.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsHelper.cs new file mode 100644 index 0000000..39c6f53 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpRequestsHelper.cs @@ -0,0 +1,154 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +#if false +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// Org.OData.Core.V1.HttpRequests + /// + internal class HttpRequestsHelper + { + /// + /// The Term type name. + /// + public virtual string QualifiedName => "Org.OData.Core.V1.HttpRequests"; + + public IEdmTerm Term { get; private set; } + + /// + /// Gets the http request array. + /// + public IDictionary > Requests { get; private set; } + + /// + /// Gets the Edm mode. + /// + public IEdmModel Model { get; } + + /// + /// Initializes a new instance of class. + /// + /// The Edm model. + public HttpRequestsHelper(IEdmModel model) + { + Utils.CheckArgumentNull(model, nameof(model)); + Term = model.FindTerm(QualifiedName); + Model = model; + } + + public HttpRequest GetRequest(string method) + { + if (Requests == null) + { + return null; + } + + return Requests.FirstOrDefault(e => string.Equals(e.MethodType, method, System.StringComparison.OrdinalIgnoreCase)); + } + + protected virtual HttpRequest FindRequest(IEdmVocabularyAnnotatable target, string method) + { + if (Requests == null) + { + Requests = new Dictionary>(); + } + + IEdmVocabularyAnnotatable newTarget = target; + IEdmNavigationSource navigationSource = target as IEdmNavigationSource; + if (navigationSource != null) + { + newTarget = navigationSource.EntityType(); + } + + if (Requests.TryGetValue(newTarget, out IList values)) + { + return values.FirstOrDefault(e => string.Equals(e.MethodType, method, System.StringComparison.OrdinalIgnoreCase)); + } + + var annotations = Model.FindVocabularyAnnotations(target, Term); + if (annotations != null && annotations.Any()) + { + + } + } + + protected virtual void Initialize() + { + IEdmVocabularyAnnotation annotation = Model.GetVocabularyAnnotation(Target, QualifiedName); + if (annotation == null) + { + IEdmNavigationSource navigationSource = Target as IEdmNavigationSource; + + // if not, search the entity type. + if (navigationSource != null) + { + IEdmEntityType entityType = navigationSource.EntityType(); + annotation = Model.GetVocabularyAnnotation(entityType, QualifiedName); + } + } + + if (annotation == null || + annotation.Value == null || + annotation.Value.ExpressionKind != EdmExpressionKind.Collection) + { + return; + } + + IEdmCollectionExpression collection = (IEdmCollectionExpression)annotation.Value; + + Requests = new List(); + foreach (var item in collection.Elements) + { + IEdmRecordExpression record = (IEdmRecordExpression)item; + HttpRequest request = new HttpRequest(); + request.Init(record); + Requests.Add(request); + } + } + + protected virtual void Initialize() + { + IEdmVocabularyAnnotation annotation = Model.GetVocabularyAnnotation(Target, QualifiedName); + if (annotation == null) + { + IEdmNavigationSource navigationSource = Target as IEdmNavigationSource; + + // if not, search the entity type. + if (navigationSource != null) + { + IEdmEntityType entityType = navigationSource.EntityType(); + annotation = Model.GetVocabularyAnnotation(entityType, QualifiedName); + } + } + + if (annotation == null || + annotation.Value == null || + annotation.Value.ExpressionKind != EdmExpressionKind.Collection) + { + return; + } + + IEdmCollectionExpression collection = (IEdmCollectionExpression)annotation.Value; + + Requests = new List(); + foreach (var item in collection.Elements) + { + IEdmRecordExpression record = (IEdmRecordExpression)item; + HttpRequest request = new HttpRequest(); + request.Init(record); + Requests.Add(request); + } + } + } +} +#endif diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpResponse.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpResponse.cs new file mode 100644 index 0000000..ccd5658 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/HttpResponse.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// The Org.OData.Core.v1.HttpResponse + /// + internal class HttpResponse + { + /// + /// Description. + /// + public string Description { get; set; } + + /// + /// ResponseCode + /// + public string ResponseCode { get; set; } + + /// + /// Examples + /// + public IEnumerable Examples { get; set; } + + /// + /// Int the . + /// + /// The input record. + public void Init(IEdmRecordExpression record) + { + // ResponseCode + ResponseCode = record.GetString("ResponseCode"); + + // Description + Description = record.GetString("Description"); + + // Examples + Examples = record.GetCollection("Examples", r => + { + IEdmRecordExpression itemRecord = r as IEdmRecordExpression; + if (itemRecord != null) + { + return Example.CreateExample(itemRecord); + } + + return null; + }); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Annotations/InlineExample.cs b/src/Microsoft.OpenApi.OData.Reader/Annotations/InlineExample.cs new file mode 100644 index 0000000..bb03ddd --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Annotations/InlineExample.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Annotations +{ + /// + /// Complex type Org.OData.Core.V1.InlineExample + /// + internal class InlineExample : Example + { + /// + /// InlineValue + /// + public string InlineValue { get; set; } + + /// + /// Init the . + /// + /// The record. + public override void Init(IEdmRecordExpression record) + { + base.Init(record); + + // InlineValue + InlineValue = record.GetString("InlineValue"); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/ApiKey.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/ApiKey.cs new file mode 100644 index 0000000..017e7f2 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/ApiKey.cs @@ -0,0 +1,69 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Enum type KeyLocation + /// + internal enum KeyLocation + { + /// + /// API Key is passed in the header. + /// + Header, + + /// + /// API Key is passed as a query option. + /// + QueryOption, + + /// + /// API Key is passed as a cookie. + /// + Cookie + } + + /// + /// Complex type 'Org.OData.Core.V1.ApiKey' + /// + internal class ApiKey : Authorization + { + /// + /// The name of the header or query parameter. + /// + public string KeyName { get; set; } + + /// + /// Whether the API Key is passed in the header or as a query option. + /// + public KeyLocation? Location { get; set; } + + /// + /// Gets the security scheme type. + /// + public override SecuritySchemeType SchemeType => SecuritySchemeType.ApiKey; + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // KeyName. + KeyName = record.GetString("KeyName"); + + // Location. + Location = record.GetEnum("Location"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/Authorization.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/Authorization.cs new file mode 100644 index 0000000..9825739 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/Authorization.cs @@ -0,0 +1,114 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Properties; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Abstract complex type 'Org.OData.Core.V1.Authorization' + /// + internal abstract class Authorization + { + /// + /// Name that can be used to reference the authorization flow. + /// + public string Name { get; set; } + + /// + /// Description of the authorization method. + /// + public string Description { get; set; } + + /// + /// Gets the security scheme type. + /// + public abstract SecuritySchemeType SchemeType { get; } + + /// + /// Init the . + /// + /// The corresponding record. + public virtual void Init(IEdmRecordExpression record) + { + Utils.CheckArgumentNull(record, nameof(record)); + + // Name. + Name = record.GetString("Name"); + + // Description. + Description = record.GetString("Description"); + } + + /// + /// Create the corresponding Authorization object. + /// + /// The input record. + /// The created object. + public static Authorization CreateAuthorization(IEdmRecordExpression record) + { + if (record == null || record.DeclaredType == null) + { + return null; + } + + IEdmComplexType complexType = record.DeclaredType.Definition as IEdmComplexType; + if (complexType == null) + { + return null; + } + + Authorization auth = null; + switch (complexType.FullTypeName()) + { + case AuthorizationConstants.OpenIDConnect: // OpenIDConnect + auth = new OpenIDConnect(); + break; + + case AuthorizationConstants.Http: // Http + auth = new Http(); + break; + + case AuthorizationConstants.ApiKey: // ApiKey + auth = new ApiKey(); + break; + + case AuthorizationConstants.OAuth2ClientCredentials: // OAuth2ClientCredentials + auth = new OAuth2ClientCredentials(); + break; + + case AuthorizationConstants.OAuth2Implicit: // OAuth2Implicit + auth = new OAuth2Implicit(); + break; + + case AuthorizationConstants.OAuth2Password: // OAuth2Password + auth = new OAuth2Password(); + break; + + case AuthorizationConstants.OAuth2AuthCode: // OAuth2AuthCode + auth = new OAuth2AuthCode(); + break; + + case AuthorizationConstants.OAuthAuthorization: // OAuthAuthorization + default: + throw new OpenApiException(String.Format(SRResource.AuthorizationRecordTypeNameNotCorrect, complexType.FullTypeName())); + } + + if (auth != null) + { + auth.Init(record); + } + + return auth; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationConstants.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationConstants.cs new file mode 100644 index 0000000..191b75a --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationConstants.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Constant values for Authorization Vocabulary + /// + internal class AuthorizationConstants + { + /// + /// The namespace of Authorization annotation. + /// + public const string Namespace = "Org.OData.Authorization.V1"; + + /// + /// Term Org.OData.Authorization.V1.Authorizations + /// + public const string Authorizations = Namespace + ".Authorizations"; + + /// + /// Term Org.OData.Authorization.V1.SecuritySchemes + /// + public const string SecuritySchemes = Namespace + ".SecuritySchemes"; + + /// + /// Complex type: Org.OData.Authorization.V1.OpenIDConnect + /// + public const string OpenIDConnect = Namespace + ".OpenIDConnect"; + + /// + /// Complex type: Org.OData.Authorization.V1.Http + /// + public const string Http = Namespace + ".Http"; + + /// + /// Complex type: Org.OData.Authorization.V1.ApiKey + /// + public const string ApiKey = Namespace + ".ApiKey"; + + /// + /// Complex type: Org.OData.Authorization.V1.OAuth2ClientCredentials + /// + public const string OAuth2ClientCredentials = Namespace + ".OAuth2ClientCredentials"; + + /// + /// Complex type: Org.OData.Authorization.V1.OAuth2Implicit + /// + public const string OAuth2Implicit = Namespace + ".OAuth2Implicit"; + + /// + /// Complex type: Org.OData.Authorization.V1.OAuth2Password + /// + public const string OAuth2Password = Namespace + ".OAuth2Password"; + + /// + /// Complex type: Org.OData.Authorization.V1.OAuth2AuthCode + /// + public const string OAuth2AuthCode = Namespace + ".OAuth2AuthCode"; + + /// + /// Complex type: Org.OData.Authorization.V1.OAuthAuthorization + /// + public const string OAuthAuthorization = Namespace + ".OAuthAuthorization"; + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationProvider.cs new file mode 100644 index 0000000..d237893 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationProvider.cs @@ -0,0 +1,61 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Abstractions; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// The default 'Org.OData.Core.V1.Authorization' provider. + /// + internal class AuthorizationProvider + { + /// + /// Gets the collections for a given target in the given Edm model. + /// + /// The collections. + public virtual IEnumerable GetAuthorizations(IEdmModel model, IEdmVocabularyAnnotatable target) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(target, nameof(target)); + + // Retrieve it every time when it needed. Don't want to cache the result. + return RetrieveAuthorizations(model, target); + } + + /// + /// Create the corresponding Authorization object. + /// + /// The input record. + /// The created object. + private IEnumerable RetrieveAuthorizations(IEdmModel model, IEdmVocabularyAnnotatable target) + { + IEdmVocabularyAnnotation annotation = model.GetVocabularyAnnotation(target, AuthorizationConstants.Authorizations); + if (annotation != null && annotation.Value != null && annotation.Value.ExpressionKind == EdmExpressionKind.Collection) + { + IEdmCollectionExpression collection = (IEdmCollectionExpression)annotation.Value; + foreach (var item in collection.Elements) + { + IEdmRecordExpression record = item as IEdmRecordExpression; + if (record == null || record.DeclaredType == null) + { + continue; + } + + Authorization auth = Authorization.CreateAuthorization(record); + if (auth != null) + { + yield return auth; + } + } + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationScope.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationScope.cs new file mode 100644 index 0000000..5c659ef --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/AuthorizationScope.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type 'Org.OData.Core.V1.AuthorizationScope' + /// + internal class AuthorizationScope + { + /// + /// Scope name. + /// + public string Scope { get; set; } + + /// + /// Description of the scope. + /// + public string Description { get; set; } + + /// + /// Init the . + /// + /// The corresponding record. + public virtual void Init(IEdmRecordExpression record) + { + Utils.CheckArgumentNull(record, nameof(record)); + + // Scope. + Scope = record.GetString("Scope"); + + // Description. + Description = record.GetString("Description"); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/Http.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/Http.cs new file mode 100644 index 0000000..9619cae --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/Http.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type 'Org.OData.Core.V1.Http' + /// + internal class Http : Authorization + { + /// + /// HTTP Authorization scheme to be used in the Authorization header, as per RFC7235. + /// + public string Scheme { get; set; } + + /// + /// Format of the bearer token. + /// + public string BearerFormat { get; set; } + + /// + /// Gets the security scheme type. + /// + public override SecuritySchemeType SchemeType => SecuritySchemeType.Http; + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // Scheme + Scheme = record.GetString("Scheme"); + + // BearerFormat + BearerFormat = record.GetString("BearerFormat"); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2AuthCode.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2AuthCode.cs new file mode 100644 index 0000000..d473fcf --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2AuthCode.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type 'Org.OData.Core.V1.OAuth2AuthCode'. + /// + internal class OAuth2AuthCode : OAuthAuthorization + { + /// + /// Authorization URL. + /// + public string AuthorizationUrl { get; set; } + + /// + /// Token Url. + /// + public string TokenUrl { get; set; } + + /// + /// Gets the OAuth2 type. + /// + public override OAuth2Type OAuth2Type => OAuth2Type.AuthCode; + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // AuthorizationUrl + AuthorizationUrl = record.GetString("AuthorizationUrl"); + + // TokenUrl + TokenUrl = record.GetString("TokenUrl"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2ClientCredentials.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2ClientCredentials.cs new file mode 100644 index 0000000..f53be44 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2ClientCredentials.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type 'Org.OData.Core.V1.OAuth2ClientCredentials' + /// + internal class OAuth2ClientCredentials : OAuthAuthorization + { + /// + /// Token Url. + /// + public string TokenUrl { get; set; } + + /// + /// Gets the OAuth2 type. + /// + public override OAuth2Type OAuth2Type => OAuth2Type.ClientCredentials; + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // TokenUrl + TokenUrl = record.GetString("TokenUrl"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Implicit.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Implicit.cs new file mode 100644 index 0000000..d926d54 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Implicit.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type 'Org.OData.Core.V1.OAuth2Implicit' + /// + internal class OAuth2Implicit : OAuthAuthorization + { + /// + /// Authorization URL. + /// + public string AuthorizationUrl { get; set; } + + /// + /// Gets the OAuth2 type. + /// + public override OAuth2Type OAuth2Type => OAuth2Type.Implicit; + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // AuthorizationUrl + AuthorizationUrl = record.GetString("AuthorizationUrl"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Password.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Password.cs new file mode 100644 index 0000000..c5a28cb --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuth2Password.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type 'Org.OData.Core.V1.OAuth2Password' + /// + internal class OAuth2Password : OAuthAuthorization + { + /// + /// Token Url. + /// + public string TokenUrl { get; set; } + + /// + /// Gets the OAuth2 type. + /// + public override OAuth2Type OAuth2Type => OAuth2Type.Pasword; + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // TokenUrl + TokenUrl = record.GetString("TokenUrl"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuthAuthorization.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuthAuthorization.cs new file mode 100644 index 0000000..3c32aee --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OAuthAuthorization.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// OAuth2 type kind. + /// + internal enum OAuth2Type + { + /// + /// ClientCredentials + /// + ClientCredentials, + + /// + /// Implicit + /// + Implicit, + + /// + /// Pasword + /// + Pasword, + + /// + /// AuthCode + /// + AuthCode + } + + /// + /// Abstract complex type 'Org.OData.Core.V1.OAuthAuthorization' + /// + internal abstract class OAuthAuthorization : Authorization + { + /// + /// Available scopes. + /// + public IList Scopes { get; set; } + + /// + /// Refresh Url + /// + public string RefreshUrl { get; set; } + + /// + /// Gets the security scheme type. + /// + public override SecuritySchemeType SchemeType => SecuritySchemeType.OAuth2; + + /// + /// Gets the OAuth2 type. + /// + public abstract OAuth2Type OAuth2Type { get; } + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // Scopes + Scopes = record.GetCollection("Scopes", (s, item) => s.Init(item as IEdmRecordExpression)); + + // RefreshUrl + RefreshUrl = record.GetString("RefreshUrl"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/OpenIDConnect.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OpenIDConnect.cs new file mode 100644 index 0000000..73f064e --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/OpenIDConnect.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type 'Org.OData.Core.V1.OpenIDConnect' + /// + internal class OpenIDConnect : Authorization + { + /// + /// Issuer location for the OpenID Provider. + /// Configuration information can be obtained by appending `/.well-known/openid-configuration` to this Url. + /// + public string IssuerUrl { get; set; } + + /// + /// Gets the security scheme type. + /// + public override SecuritySchemeType SchemeType => SecuritySchemeType.OpenIdConnect; + + /// + /// Init . + /// + /// the input record. + public override void Init(IEdmRecordExpression record) + { + // base checked. + base.Init(record); + + // IssuerUrl + IssuerUrl = record.GetString("IssuerUrl"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Authorizations/SecurityScheme.cs b/src/Microsoft.OpenApi.OData.Reader/Authorizations/SecurityScheme.cs new file mode 100644 index 0000000..8390822 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Authorizations/SecurityScheme.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Authorizations +{ + /// + /// Complex type Org.OData.Core.V1.SecurityScheme + /// + internal class SecurityScheme + { + /// + /// The name of a required authorization scheme. + /// + public string AuthorizationSchemeName { get; set; } + + /// + /// The names of scopes required from this authorization scheme. + /// + public IList RequiredScopes { get; set; } + + /// + /// Init the . + /// + /// The input record. + public void Init(IEdmRecordExpression record) + { + Utils.CheckArgumentNull(record, nameof(record)); + + // AuthorizationSchemeName + AuthorizationSchemeName = record.GetString("AuthorizationSchemeName"); + + // RequiredScopes + RequiredScopes = record.GetCollection("RequiredScopes"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/Constants.cs b/src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs similarity index 59% rename from src/Microsoft.OpenApi.OData.Reader/Generator/Constants.cs rename to src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs index 06a223e..a0e4813 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/Constants.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs @@ -3,7 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ -namespace Microsoft.OpenApi.OData.Generator +namespace Microsoft.OpenApi.OData.Common { /// /// Constant strings @@ -31,8 +31,28 @@ namespace Microsoft.OpenApi.OData.Generator public static string StatusCode204 = "204"; /// - /// Status code: 204 + /// Status code: default /// public static string StatusCodeDefault = "default"; + + /// + /// extension for toc (table of content) type + /// + public static string xMsTocType = "x-ms-docs-toc-type"; + + /// + /// extension for key type + /// + public static string xMsKeyType = "x-ms-docs-key-type"; + + /// + /// extension for operation type + /// + public static string xMsDosOperationType = "x-ms-docs-operation-type"; + + /// + /// extension for group type + /// + public static string xMsDosGroupPath = "x-ms-docs-grouped-path"; } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Error.cs b/src/Microsoft.OpenApi.OData.Reader/Common/Error.cs similarity index 77% rename from src/Microsoft.OpenApi.OData.Reader/Error.cs rename to src/Microsoft.OpenApi.OData.Reader/Common/Error.cs index 1169be0..3fb033a 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Error.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Common/Error.cs @@ -7,7 +7,7 @@ using System; using System.Globalization; using Microsoft.OpenApi.OData.Properties; -namespace Microsoft.OpenApi.OData +namespace Microsoft.OpenApi.OData.Common { /// /// Utility class for creating and unwrapping instances. @@ -80,6 +80,29 @@ namespace Microsoft.OpenApi.OData return Error.Argument(parameterName, SRResource.ArgumentNullOrEmpty, parameterName); } + /// + /// Creates an . + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static InvalidOperationException InvalidOperation(string messageFormat, params object[] messageArgs) + { + return new InvalidOperationException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an . + /// + /// Inner exception + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static InvalidOperationException InvalidOperation(Exception innerException, string messageFormat, params object[] messageArgs) + { + return new InvalidOperationException(Error.Format(messageFormat, messageArgs), innerException); + } + /// /// Creates an . /// diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs index 1a90f31..5e1d049 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs @@ -12,6 +12,22 @@ namespace Microsoft.OpenApi.OData.Common /// public static class Utils { + /// + /// Upper the first character of the string. + /// + /// The input string. + /// The changed string. + public static string UpperFirstChar(string input) + { + if (input == null) + { + return input; + } + + char first = Char.ToUpper(input[0]); + return first + input.Substring(1); + } + /// /// Check the input argument whether its value is null or not. /// @@ -28,5 +44,21 @@ namespace Microsoft.OpenApi.OData.Common return value; } + + /// + /// Check the input string null or empty. + /// + /// The input string + /// The input parameter name. + /// The input value. + internal static string CheckArgumentNullOrEmpty(string value, string parameterName) + { + if (String.IsNullOrEmpty(value)) + { + throw Error.ArgumentNullOrEmpty(parameterName); + } + + return value; + } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs new file mode 100644 index 0000000..2821ad7 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs @@ -0,0 +1,150 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Extension methods for + /// + public static class EdmModelExtensions + { + /// + /// Gets the vocabulary annotation from a target annotatable. + /// + /// The model referenced to. + /// The target Annotatable to find annotation + /// The annotation or null. + public static IEdmVocabularyAnnotation GetVocabularyAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(target, nameof(target)); + Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName)); + + IEdmTerm term = model.FindTerm(qualifiedName); + if (term != null) + { + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(target, term).FirstOrDefault(); + if (annotation != null) + { + return annotation; + } + } + + return null; + } + + /// + /// Gets the vocabulary annotation from a target annotatable. + /// + /// The model referenced to. + /// The target Annotatable to find annotation + /// The annotation or null. + public static IEdmVocabularyAnnotation GetVocabularyAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(target, nameof(target)); + Utils.CheckArgumentNull(term, nameof(term)); + + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(target, term).FirstOrDefault(); + if (annotation != null) + { + return annotation; + } + + return null; + } + + /// + /// Checks if the is assignable to . + /// In other words, if is a subtype of or not. + /// + /// Type of the base type. + /// Type of the sub type. + /// true, if the is assignable to . Otherwise returns false. + public static bool IsAssignableFrom(this IEdmEntityType baseType, IEdmEntityType subtype) + { + Utils.CheckArgumentNull(baseType, nameof(baseType)); + Utils.CheckArgumentNull(subtype, nameof(subtype)); + + if (baseType.TypeKind != subtype.TypeKind) + { + return false; + } + + if (subtype.IsEquivalentTo(baseType)) + { + return true; + } + + IEdmStructuredType structuredSubType = subtype; + while (structuredSubType != null) + { + if (structuredSubType.IsEquivalentTo(baseType)) + { + return true; + } + + structuredSubType = structuredSubType.BaseType; + } + + return false; + } + + /// + /// Check whether the operaiton is overload in the model. + /// + /// The Edm model. + /// The test operations. + /// True/false. + public static bool IsOperationOverload(this IEdmModel model, IEdmOperation operation) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(operation, nameof(operation)); + + return model.SchemaElements.OfType() + .Where(o => o.IsBound == operation.IsBound && o.FullName() == operation.FullName() && + o.Parameters.First().Type.Definition == operation.Parameters.First().Type.Definition + ).Count() > 1; + } + + public static bool IsOperationOverload(this IEdmModel model, IEdmOperationImport operationImport) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(operationImport, nameof(operationImport)); + + if (model.EntityContainer == null) + { + return false; + } + + return model.EntityContainer.OperationImports() + .Where(o => o.Operation.IsBound == operationImport.Operation.IsBound && o.Name == operationImport.Name).Count() > 1; + } + + public static bool IsNavigationTypeOverload(this IEdmModel model, IEdmEntityType entityType, IEdmNavigationProperty navigationProperty) + { + Utils.CheckArgumentNull(model, nameof(model)); + Utils.CheckArgumentNull(entityType, nameof(entityType)); + Utils.CheckArgumentNull(navigationProperty, nameof(navigationProperty)); + + int count = 0; + IEdmEntityType nvaEntityType = navigationProperty.ToEntityType(); + foreach(var np in entityType.DeclaredNavigationProperties()) + { + if (np.ToEntityType() == nvaEntityType) + { + count++; + } + } + + return count == 0; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataContext.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataContext.cs new file mode 100644 index 0000000..c15f218 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataContext.cs @@ -0,0 +1,236 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Abstractions; +using Microsoft.OpenApi.OData.Annotations; +using Microsoft.OpenApi.OData.Authorizations; +using Microsoft.OpenApi.OData.Capabilities; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; +using Microsoft.OpenApi.OData.Operation; +using Microsoft.OpenApi.OData.PathItem; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Context information for the , configuration, etc. + /// + internal class ODataContext + { + private IDictionary _boundOperations; + private bool _keyAsSegmentSupported = false; + private IList _tags = new List(); + private ODataPathHandler _pathHandler; + + /// + /// Initializes a new instance of class. + /// + /// The Edm model. + public ODataContext(IEdmModel model) + : this(model, new OpenApiConvertSettings()) + { + } + + /// + /// Initializes a new instance of class. + /// + /// The Edm model. + /// The convert setting. + public ODataContext(IEdmModel model, OpenApiConvertSettings settings) + { + Model = model ?? throw Error.ArgumentNull(nameof(model)); + Settings = settings ?? throw Error.ArgumentNull(nameof(settings)); + + EdmModelVisitor visitor = new EdmModelVisitor(); + visitor.Visit(model); + IsSpatialTypeUsed = visitor.IsSpatialTypeUsed; + + _keyAsSegmentSupported = settings.KeyAsSegment ?? model.GetKeyAsSegmentSupported(); + + _pathHandler = new ODataPathHandler(this); + + OperationHanderProvider = new OperationHandlerProvider(); + PathItemHanderProvider = new PathItemHandlerProvider(); + + AuthorizationProvider = new AuthorizationProvider(); + } + + public IPathItemHandlerProvider PathItemHanderProvider { get; } + + public IOperationHandlerProvider OperationHanderProvider { get; } + + /// + /// Gets the to provider the authorization. + /// + public AuthorizationProvider AuthorizationProvider { get; } + + /// + /// Gets the Edm model. + /// + public IEdmModel Model { get; } + + /// + /// Gets the Entity Container. + /// + public IEdmEntityContainer EntityContainer + { + get + { + return Model.EntityContainer; + } + } + + /// + /// Gets the s. + /// + public IList Paths => _pathHandler.Paths; + + /// + /// Gets the boolean value indicating to support key as segment. + /// + public bool KeyAsSegment => _keyAsSegmentSupported; + + /// + /// Gets the value indicating the Edm spatial type used. + /// + public bool IsSpatialTypeUsed { get; private set; } + + /// + /// Gets the convert settings. + /// + public OpenApiConvertSettings Settings { get; } + + /// + /// Gets the bound operations (functions & actions). + /// + public IDictionary BoundOperations + { + get + { + if (_boundOperations == null) + { + GenerateBoundOperations(); + } + + return _boundOperations; + } + } + + /// + /// Finds the operations using the + /// + /// The Edm model. + /// The entity type. + /// The collection flag. + /// The found operations. + public IEnumerable> FindOperations(IEdmEntityType entityType, bool collection) + { + Utils.CheckArgumentNull(entityType, nameof(entityType)); + + string fullTypeName = collection ? + "Collection(" + entityType.FullName() + ")" : + entityType.FullName(); + + foreach (var item in BoundOperations) + { + IEdmEntityType operationBindingType; + if (collection) + { + if (!item.Key.IsCollection()) + { + continue; + } + + operationBindingType = item.Key.AsCollection().ElementType().AsEntity().EntityDefinition(); + } + else + { + if (item.Key.IsCollection()) + { + continue; + } + + operationBindingType = item.Key.AsEntity().EntityDefinition(); + } + + if (entityType.IsAssignableFrom(operationBindingType)) + { + yield return Tuple.Create(operationBindingType, item.Value); + } + } + } + + private IDictionary _requests; + + public HttpRequest FindRequest(IEdmVocabularyAnnotatable target, string method) + { + if (_requests == null) + { + _requests = new Dictionary(); + } + + if (!_requests.TryGetValue(target, out HttpRequestsAnnotation value)) + { + value = new HttpRequestsAnnotation(Model, target); + _requests.Add(target, value); + } + + return value.GetRequest(method); + } + + private void GenerateBoundOperations() + { + if (_boundOperations != null) + { + return; + } + + _boundOperations = new Dictionary(); + foreach (var edmOperation in Model.SchemaElements.OfType().Where(e => e.IsBound)) + { + IEdmOperationParameter bindingParameter = edmOperation.Parameters.First(); + _boundOperations.Add(bindingParameter.Type, edmOperation); + } + } + + public IList Tags + { + get + { + return _tags; + } + } + + public void AppendTag(OpenApiTag tagItem) + { + if (_tags.Any(c => c.Name == tagItem.Name)) + { + return; + } + _tags.Add(tagItem); + } + + private IDictionary _cached1 = new Dictionary(); + public int GetIndex(string source) + { + if (_cached1.TryGetValue(source, out int value)) + { + _cached1[source]++; + return _cached1[source]; + } + else + { + _cached1[source] = 0; + return 0; + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataKeySegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataKeySegment.cs new file mode 100644 index 0000000..4bd6451 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataKeySegment.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// The key segment. + /// + public class ODataKeySegment : ODataSegment + { + /// + /// Initializes a new instance of class. + /// + /// The entity type contains the keys. + public ODataKeySegment(IEdmEntityType entityType) + { + EntityType = entityType ?? throw Error.ArgumentNull(nameof(entityType)); + } + + /// + public override IEdmEntityType EntityType { get; } + + /// + public override string Name => throw new NotImplementedException(); + + /// + public override string ToString() + { + IList keys = EntityType.Key().ToList(); + if (keys.Count() == 1) + { + return "{" + keys.First().Name + "}"; + } + else + { + IList keyStrings = new List(); + foreach (var keyProperty in keys) + { + keyStrings.Add(keyProperty.Name + "={" + keyProperty.Name + "}"); + } + return "{" + String.Join(",", keyStrings) + "}"; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationPropertySegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationPropertySegment.cs new file mode 100644 index 0000000..adbf8f8 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationPropertySegment.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Navigation property segment. + /// + public class ODataNavigationPropertySegment : ODataSegment + { + /// + /// Initializes a new instance of class. + /// + /// + public ODataNavigationPropertySegment(IEdmNavigationProperty navigationProperty) + { + NavigationProperty = navigationProperty ?? throw Error.ArgumentNull(nameof(navigationProperty)); + } + + /// + /// Gets the navigation property. + /// + public IEdmNavigationProperty NavigationProperty { get; } + + /// + public override IEdmEntityType EntityType => NavigationProperty.ToEntityType(); + + /// + public override string Name => NavigationProperty.Name; + + /// + public override string ToString() + { + return NavigationProperty.Name; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationSourceSegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationSourceSegment.cs new file mode 100644 index 0000000..ba7329b --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataNavigationSourceSegment.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Navigation source (entity set or singleton) segment. + /// + public class ODataNavigationSourceSegment : ODataSegment + { + /// + /// Initializes a new instance of class. + /// + /// The navigation source. + public ODataNavigationSourceSegment(IEdmNavigationSource navigaitonSource) + { + NavigationSource = navigaitonSource ?? throw Error.ArgumentNull(nameof(navigaitonSource)); + } + + /// + /// Gets the navigation source. + /// + public IEdmNavigationSource NavigationSource { get; } + + /// + public override IEdmEntityType EntityType => NavigationSource.EntityType(); + + /// + public override string Name => NavigationSource.Name; + + /// + public override string ToString() + { + return NavigationSource.Name; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationImportSegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationImportSegment.cs new file mode 100644 index 0000000..2ba6844 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationImportSegment.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Operation import segment. + /// + public class ODataOperationImportSegment : ODataSegment + { + /// + /// Initializes a new instance of class. + /// + /// The operation import. + public ODataOperationImportSegment(IEdmOperationImport operationImport) + { + OperationImport = operationImport ?? throw Error.ArgumentNull(nameof(operationImport)); + } + + /// + /// Gets the operation import. + /// + public IEdmOperationImport OperationImport { get; } + + /// + public override IEdmEntityType EntityType => throw new System.NotImplementedException(); + + /// + public override string Name => OperationImport.Name; + + /// + public override string ToString() + { + return OperationImport.Name; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs new file mode 100644 index 0000000..411d699 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs @@ -0,0 +1,110 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Linq; +using System.Text; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Operation segment + /// + public class ODataOperationSegment : ODataSegment + { + /// + /// Initializes a new instance of class. + /// + /// The operation. + public ODataOperationSegment(IEdmOperation operation) + : this(operation, true) + { } + + /// + /// Initializes a new instance of class. + /// + /// The operation. + /// The unqualified call. + public ODataOperationSegment(IEdmOperation operation, bool unqualifiedCall) + { + Operation = operation ?? throw Error.ArgumentNull(nameof(operation)); + UnqualifiedCall = unqualifiedCall; + } + + /// + /// Gets the operation. + /// + public IEdmOperation Operation { get; } + + /// + /// Gets the unqualified call. + /// + public bool UnqualifiedCall { get; } + + /// + public override string Name => Operation.Name; + + /// + public override IEdmEntityType EntityType => throw new NotImplementedException(); + + /// + public override string ToString() + { + if (Operation.IsFunction()) + { + return FunctionName(Operation as IEdmFunction); + } + + return ActionName(Operation as IEdmAction); + } + + private string FunctionName(IEdmFunction function) + { + StringBuilder functionName = new StringBuilder(); + if (UnqualifiedCall) + { + functionName.Append(function.Name); + } + else + { + functionName.Append(function.FullName()); + } + functionName.Append("("); + + // Structured or collection-valued parameters are represented as a parameter alias in the path template + // and the parameters array contains a Parameter Object for the parameter alias as a query option of type string. + int skip = function.IsBound ? 1 : 0; + functionName.Append(String.Join(",", function.Parameters.Skip(skip).Select(p => + { + if (p.Type.IsStructured() || p.Type.IsCollection()) + { + return p.Name + "=@" + p.Name; + } + else + { + return p.Name + "={" + p.Name + "}"; + } + }))); + + functionName.Append(")"); + + return functionName.ToString(); + } + + private string ActionName(IEdmAction action) + { + if (UnqualifiedCall) + { + return action.Name; + } + else + { + return action.FullName(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs new file mode 100644 index 0000000..e828e02 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs @@ -0,0 +1,217 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Describes an OData path. + /// + public class ODataPath : IEnumerable + { + private ODataPathType? _pathType; + + /// + /// Initializes a new instance of class. + /// + /// The segments. + public ODataPath(IEnumerable segments) + { + Segments = segments.ToList(); + if (Segments.Any(s => s == null)) + { + throw Error.ArgumentNull("segments"); + } + } + + /// + /// Creates a new instance of containing the given segments. + /// + /// The segments that make up the path. + /// Throws if input segments is null. + public ODataPath(params ODataSegment[] segments) + : this((IEnumerable)segments) + { + } + + /// + /// Gets the segments. + /// + public IList Segments { get; private set; } + + /// + /// Pop the last segment. + /// + /// The pop last segment. + public ODataSegment Pop() + { + if (!Segments.Any()) + { + throw Error.InvalidOperation("Pop a segment is invalid. The segments in the path is empty."); + } + + ODataSegment segment = Segments.Last(); + Segments.RemoveAt(Segments.Count - 1); + return segment; + } + + /// + /// Push a segment to the last. + /// + /// The pushed segment. + /// The whole path object. + public ODataPath Push(ODataSegment segment) + { + if (Segments == null) + { + Segments = new List(); + } + + Segments.Add(segment); + return this; + } + + /// + /// Gets the first segment in the path. Returns null if the path is empty. + /// + public ODataSegment FirstSegment + { + get + { + return this.Segments.Count == 0 ? null : this.Segments[0]; + } + } + + /// + /// Get the last segment in the path. Returns null if the path is empty. + /// + public ODataSegment LastSegment + { + get + { + return this.Segments.Count == 0 ? null : this.Segments[this.Segments.Count - 1]; + } + } + + /// + /// Get the number of segments in this path. + /// + public int Count + { + get { return this.Segments.Count; } + } + + /// + /// Get the segments enumerator + /// + /// The segments enumerator + public IEnumerator GetEnumerator() + { + return this.Segments.GetEnumerator(); + } + + /// + /// Get the segments enumerator + /// + /// The segments enumerator. + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + /// Clone a new ODataPath object. + /// + /// The new ODataPath. + public ODataPath Clone() + { + return new ODataPath(Segments); + } + + /// + /// Get the segment count. + /// + /// A bool value indicating whether to count key segment or not. + /// The count. + public int GetCount(bool keySegmentAsDepth) + { + return Segments.Count(c => keySegmentAsDepth ? true : !(c is ODataKeySegment)); + } + + /// + /// Output the path string. + /// + /// The string. + public override string ToString() + { + return "/" + String.Join("/", Segments); + } + + internal ODataPathType PathType + { + get + { + if (_pathType == null) + { + CalcPathType(); + } + + return _pathType.Value; + } + } + + private void CalcPathType() + { + if (Segments.Any(c => c is ODataNavigationPropertySegment)) + { + _pathType = ODataPathType.NavigationProperty; + return; + } + else if (Segments.Any(c => c is ODataOperationImportSegment)) + { + _pathType = ODataPathType.OperationImport; + return; + } + else if (Segments.Any(c => c is ODataOperationSegment)) + { + _pathType = ODataPathType.Operation; + return; + } + + if (Segments.Count == 1) + { + ODataNavigationSourceSegment segment = Segments[0] as ODataNavigationSourceSegment; + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + if (segment.NavigationSource is IEdmSingleton) + { + _pathType = ODataPathType.Singleton; + } + else + { + _pathType = ODataPathType.EntitySet; + } + } + else + { + if (Segments.Count != 2) + { + throw Error.InvalidOperation("Calc the path type wrong!"); + } + + _pathType = ODataPathType.Entity; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathHandler.cs new file mode 100644 index 0000000..7b0a6a6 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathHandler.cs @@ -0,0 +1,269 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Helper class for generating. + /// + internal class ODataPathHandler + { + private IList _paths = null; + + /// + /// Gets the OData Context + /// + public ODataContext Context { get; } + + /// + /// Gets the s. + /// + public IList Paths => GeneratePaths(); + + /// + /// Initializes a new instance of class. + /// + /// The OData context. + public ODataPathHandler(ODataContext context) + { + Context = context ?? throw Error.ArgumentNull(nameof(context)); + } + + /// + /// Generate the from and . + /// + /// The generated paths. + private IList GeneratePaths() + { + if (_paths != null) + { + return _paths; + } + + _paths = new List(); + if (Context.Model.EntityContainer == null) + { + return _paths; + } + + // entity set + foreach (IEdmEntitySet entitySet in Context.Model.EntityContainer.EntitySets()) + { + RetrieveNavigationSourcePaths(entitySet); + + if (Context.Settings.EnableOperationPath) + { + RetrieveOperationPaths(entitySet); + } + } + + // singleton + foreach (IEdmSingleton singleton in Context.Model.EntityContainer.Singletons()) + { + RetrieveNavigationSourcePaths(singleton); + + if (Context.Settings.EnableOperationPath) + { + RetrieveOperationPaths(singleton); + } + } + + // operation import + if (Context.Settings.EnableOperationImportPath) + { + foreach (IEdmOperationImport import in Context.Model.EntityContainer.OperationImports()) + { + _paths.Add(new ODataPath(new ODataOperationImportSegment(import))); + } + } + + return _paths; + } + + /// + /// Retrieve the path for . + /// + /// The navigation source. + private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource) + { + Debug.Assert(navigationSource != null); + + // navigation source itself + ODataPath path = new ODataPath(); + path.Push(new ODataNavigationSourceSegment(navigationSource)); + _paths.Add(path.Clone()); + + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + IEdmEntityType entityType = navigationSource.EntityType(); + + // for entity set, create a path with key + if (entitySet != null) + { + path.Push(new ODataKeySegment(entityType)); + _paths.Add(path.Clone()); + } + + // navigation property + if (Context.Settings.EnableNavigationPropertyPath) + { + foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties()) + { + RetrieveNavigationPropertyPaths(np, path); + } + } + + if (entitySet != null) + { + path.Pop(); // end of entity + } + path.Pop(); // end of navigation source. + Debug.Assert(path.Any() == false); + } + + /// + /// Retrieve the path for . + /// + /// The navigation property. + /// The current OData path. + private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationProperty, ODataPath currentPath) + { + Debug.Assert(navigationProperty != null); + Debug.Assert(currentPath != null); + + int count = currentPath.GetCount(Context.Settings.CountKeySegmentAsDepth); + if (count > Context.Settings.NavigationPropertyDepth) + { + return; + } + + bool shouldExpand = ShouldExpandNavigationProperty(navigationProperty, currentPath); + + // append a navigation property. + currentPath.Push(new ODataNavigationPropertySegment(navigationProperty)); + _paths.Add(currentPath.Clone()); + + // append a navigation property key. + IEdmEntityType navEntityType = navigationProperty.ToEntityType(); + if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + currentPath.Push(new ODataKeySegment(navEntityType)); + _paths.Add(currentPath.Clone()); + } + + if (shouldExpand) + { + foreach (IEdmNavigationProperty subNavProperty in navEntityType.DeclaredNavigationProperties()) + { + RetrieveNavigationPropertyPaths(subNavProperty, currentPath); + } + } + + if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + currentPath.Pop(); + } + + currentPath.Pop(); + } + + /// + /// Retrieve the path for . + /// + /// The navigation source. + private void RetrieveOperationPaths(IEdmNavigationSource navigationSource) + { + Debug.Assert(navigationSource != null); + + IEnumerable> operations; + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(navigationSource)); + + if (entitySet != null) + { + operations = Context.FindOperations(navigationSource.EntityType(), collection: true); + foreach (var operation in operations) + { + // Append the type cast + if (!operation.Item1.IsEquivalentTo(navigationSource.EntityType())) + { + path.Push(new ODataTypeCastSegment(operation.Item1)); + path.Push(new ODataOperationSegment(operation.Item2, Context.Settings.UnqualifiedCall)); + _paths.Add(path.Clone()); + path.Pop(); + path.Pop(); + } + else + { + path.Push(new ODataOperationSegment(operation.Item2, Context.Settings.UnqualifiedCall)); + _paths.Add(path.Clone()); + path.Pop(); + } + } + } + + // for single + if (entitySet != null) + { + path.Push(new ODataKeySegment(navigationSource.EntityType())); + } + + operations = Context.FindOperations(navigationSource.EntityType(), collection: false); + foreach (var operation in operations) + { + // Append the type cast + if (!operation.Item1.IsEquivalentTo(navigationSource.EntityType())) + { + path.Push(new ODataTypeCastSegment(operation.Item1)); + path.Push(new ODataOperationSegment(operation.Item2, Context.Settings.UnqualifiedCall)); + _paths.Add(path.Clone()); + path.Pop(); + path.Pop(); + } + else + { + path.Push(new ODataOperationSegment(operation.Item2, Context.Settings.UnqualifiedCall)); + _paths.Add(path.Clone()); + path.Pop(); + } + } + + if (entitySet != null) + { + path.Pop(); + } + + path.Pop(); + + Debug.Assert(path.Any() == false); + } + + private static bool ShouldExpandNavigationProperty(IEdmNavigationProperty navigationProperty, ODataPath currentPath) + { + if (!navigationProperty.ContainsTarget) + { + return false; + } + + IEdmEntityType navEntityType = navigationProperty.ToEntityType(); + foreach (ODataSegment segment in currentPath) + { + if (navEntityType.IsAssignableFrom(segment.EntityType)) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathType.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathType.cs new file mode 100644 index 0000000..45c848c --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathType.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Enum types for Edm path. + /// + public enum ODataPathType + { + /// + /// Represents an entity set path. for example: ~/users + /// + EntitySet, + + /// + /// Represents an entity path, for example: ~/users/{id} + /// + Entity, + + /// + /// Represents a singleton path, for example: ~/me + /// + Singleton, + + /// + /// Represents an operation (function or action) path, for example: ~/users/NS.findRooms(roomId={roomId}) + /// + Operation, + + /// + /// Represents an operation import (function import or action import path), for example: ~/ResetData + /// + OperationImport, + + /// + /// Represents an navigation propert path, for example: ~/users/{id}/onedrive + /// + NavigationProperty + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataSegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataSegment.cs new file mode 100644 index 0000000..d461065 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataSegment.cs @@ -0,0 +1,25 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Represents an OData segment. For example, an entity set segment. + /// + public abstract class ODataSegment + { + /// + /// Gets the entity type of current segment. + /// + public abstract IEdmEntityType EntityType { get; } + + /// + /// Ges the name of this segment. + /// + public abstract string Name { get; } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataTypeCastSegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataTypeCastSegment.cs new file mode 100644 index 0000000..dca80f9 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataTypeCastSegment.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Type cast segment. + /// + public class ODataTypeCastSegment : ODataSegment + { + /// + /// Initializes a new instance of class. + /// + /// The type cast type. + public ODataTypeCastSegment(IEdmEntityType entityType) + { + EntityType = entityType ?? throw Error.ArgumentNull(nameof(entityType)); + } + + /// + public override IEdmEntityType EntityType { get; } + + /// + public override string ToString() + { + return EntityType.FullTypeName(); + } + + /// + public override string Name => EntityType.FullTypeName(); + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs new file mode 100644 index 0000000..ee1e079 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs @@ -0,0 +1,255 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Edm +{ + /// + /// Extension methods for + /// + internal static class RecordExpressionExtensions + { + /// + /// Gets the string value of a property in the given record expression. + /// + /// The given record. + /// The property name. + /// The property string value. + public static string GetString(this IEdmRecordExpression record, string propertyName) + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + if (record.Properties != null) + { + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmStringConstantExpression value = property.Value as IEdmStringConstantExpression; + if (value != null) + { + return value.Value; + } + } + } + + return null; + } + + /// + /// Get the boolean value from the record using the given property name. + /// + /// The record expression. + /// The property name. + /// The boolean value or null. + public static bool? GetBoolean(this IEdmRecordExpression record, string propertyName) + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + if (record.Properties != null) + { + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmBooleanConstantExpression value = property.Value as IEdmBooleanConstantExpression; + if (value != null) + { + return value.Value; + } + } + } + + return null; + } + + /// + /// Get the Enum value from the record using the given property name. + /// + /// The output enum type. + /// The record expression. + /// The property name. + /// The Enum value or null. + public static T? GetEnum(this IEdmRecordExpression record, string propertyName) + where T : struct + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + if (record.Properties != null) + { + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmEnumMemberExpression value = property.Value as IEdmEnumMemberExpression; + if (value != null && value.EnumMembers != null && value.EnumMembers.Any()) + { + IEdmEnumMember member = value.EnumMembers.First(); + T result; + if (Enum.TryParse(member.Name, out result)) + { + return result; + } + } + } + } + + return null; + } + + /// + /// Get the property path from the record using the given property name. + /// + /// The record expression. + /// The property name. + /// The property path or null. + public static string GetPropertyPath(this IEdmRecordExpression record, string propertyName) + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + if (record.Properties != null) + { + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmPathExpression value = property.Value as IEdmPathExpression; + if (value != null) + { + return value.Path; + } + } + } + + return null; + } + + /// + /// Get the collection of property path from the record using the given property name. + /// + /// The record expression. + /// The property name. + /// The collection of property path or null. + public static IList GetCollectionPropertyPath(this IEdmRecordExpression record, string propertyName) + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + if (record.Properties != null) + { + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmCollectionExpression value = property.Value as IEdmCollectionExpression; + if (value != null && value.Elements != null) + { + IList properties = new List(); + foreach (var a in value.Elements.Select(e => e as IEdmPathExpression)) + { + properties.Add(a.Path); + } + + if (properties.Any()) + { + return properties; + } + } + } + } + + return null; + } + + /// + /// Get the collection of from the record using the given property name. + /// + /// The element type. + /// The record expression. + /// The property name. + /// The element action. + /// The collection or null. + public static IList GetCollection(this IEdmRecordExpression record, string propertyName, Action elementAction) + where T: new() + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmCollectionExpression collection = property.Value as IEdmCollectionExpression; + if (collection != null && collection.Elements != null) + { + IList items = new List(); + foreach (var item in collection.Elements) + { + T a = new T(); + elementAction(a, item); + items.Add(a); + } + + return items; + } + } + + return null; + } + + public static IEnumerable GetCollection(this IEdmRecordExpression record, string propertyName, Func elementFunc) + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmCollectionExpression collection = property.Value as IEdmCollectionExpression; + if (collection != null && collection.Elements != null) + { + return collection.Elements.Select(e => elementFunc(e)); + } + } + + return null; + } + + /// + /// Get the collection of string from the record using the given property name. + /// + /// The record expression. + /// The property name. + /// The collection of string or null. + public static IList GetCollection(this IEdmRecordExpression record, string propertyName) + { + Utils.CheckArgumentNull(record, nameof(record)); + Utils.CheckArgumentNull(propertyName, nameof(propertyName)); + + IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == propertyName); + if (property != null) + { + IEdmCollectionExpression collection = property.Value as IEdmCollectionExpression; + if (collection != null && collection.Elements != null) + { + IList items = new List(); + foreach (var item in collection.Elements) + { + IEdmStringConstantExpression itemRecord = item as IEdmStringConstantExpression; + items.Add(itemRecord.Value); + } + + return items; + } + } + + return null; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/EdmModelOpenApiExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/EdmModelOpenApiExtensions.cs index cb64f21..da80cc7 100644 --- a/src/Microsoft.OpenApi.OData.Reader/EdmModelOpenApiExtensions.cs +++ b/src/Microsoft.OpenApi.OData.Reader/EdmModelOpenApiExtensions.cs @@ -8,6 +8,8 @@ using Microsoft.OData.Edm; using Microsoft.OData.Edm.Validation; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Generator; namespace Microsoft.OpenApi.OData diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/ODataContext.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/ODataContext.cs deleted file mode 100644 index 003fa97..0000000 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/ODataContext.cs +++ /dev/null @@ -1,120 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------ - -using System.Collections.Generic; -using System.Linq; -using Microsoft.OData.Edm; -using Microsoft.OpenApi.OData.Capabilities; - -namespace Microsoft.OpenApi.OData.Generator -{ - /// - /// Context information for the , configuration, etc. - /// - internal class ODataContext - { - private IDictionary _boundOperations; - private bool _keyAsSegmentSupported = false; - - /// - /// Initializes a new instance of class. - /// - /// The Edm model. - public ODataContext(IEdmModel model) - : this(model, new OpenApiConvertSettings()) - { - } - - /// - /// Initializes a new instance of class. - /// - /// The Edm model. - /// The convert setting. - public ODataContext(IEdmModel model, OpenApiConvertSettings settings) - { - Model = model ?? throw Error.ArgumentNull(nameof(model)); - Settings = settings ?? throw Error.ArgumentNull(nameof(settings)); - - EdmModelVisitor visitor = new EdmModelVisitor(); - visitor.Visit(model); - IsSpatialTypeUsed = visitor.IsSpatialTypeUsed; - - _keyAsSegmentSupported = settings.KeyAsSegment ?? model.GetKeyAsSegmentSupported(); - } - - /// - /// Gets the Edm model. - /// - public IEdmModel Model { get; } - - /// - /// Gets the Entity Container. - /// - public IEdmEntityContainer EntityContainer - { - get - { - return Model.EntityContainer; - } - } - - /// - /// Gets the boolean value indicating to support key as segment. - /// - public bool KeyAsSegment => _keyAsSegmentSupported; - - /// - /// Gets the value indicating the Edm spatial type used. - /// - public bool IsSpatialTypeUsed { get; private set; } - - /// - /// Gets the convert settings. - /// - public OpenApiConvertSettings Settings { get; } - - public IDictionary BoundOperations - { - get - { - if (_boundOperations == null) - { - GenerateBoundOperations(); - } - - return _boundOperations; - } - } - - public IEnumerable FindOperations(IEdmEntityType entityType, bool collection) - { - string fullTypeName = collection ? "Collection(" + entityType.FullName() + ")" : - entityType.FullName(); - - foreach (var item in BoundOperations) - { - if (item.Key.FullName() == fullTypeName) - { - yield return item.Value; - } - } - } - - private void GenerateBoundOperations() - { - if (_boundOperations != null) - { - return; - } - - _boundOperations = new Dictionary(); - foreach (var edmOperation in Model.SchemaElements.OfType().Where(e => e.IsBound)) - { - IEdmOperationParameter bindingParameter = edmOperation.Parameters.First(); - _boundOperations.Add(bindingParameter.Type, edmOperation); - } - } - } -} diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiComponentsGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiComponentsGenerator.cs index 70cee72..2ae59d8 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiComponentsGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiComponentsGenerator.cs @@ -4,6 +4,8 @@ // ------------------------------------------------------------ using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -21,10 +23,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The created object. public static OpenApiComponents CreateComponents(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); // "components": { // "schemas": …, @@ -51,7 +50,7 @@ namespace Microsoft.OpenApi.OData.Generator Examples = null, - SecuritySchemes = null, + SecuritySchemes = context.CreateSecuritySchemes(), Links = null, diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiDocumentGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiDocumentGenerator.cs index f2052d8..8ed0a91 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiDocumentGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiDocumentGenerator.cs @@ -3,8 +3,9 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ -using System; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -20,10 +21,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The created object. public static OpenApiDocument CreateDocument(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); // An OAS document consists of a single OpenAPI Object represented as OpenApiDocument object. // { @@ -34,13 +32,13 @@ namespace Microsoft.OpenApi.OData.Generator // "paths": …, // "components": … // } - return new OpenApiDocument + OpenApiDocument doc = new OpenApiDocument { Info = context.CreateInfo(), Servers = context.CreateServers(), - Tags = context.CreateTags(), + // Tags = context.CreateTags(), Paths = context.CreatePaths(), @@ -50,6 +48,9 @@ namespace Microsoft.OpenApi.OData.Generator ExternalDocs = null }; + + doc.Tags = context.CreateTags_FromTagItems(); + return doc; } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiEdmTypeSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiEdmTypeSchemaGenerator.cs index 12ff4fb..a44eff3 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiEdmTypeSchemaGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiEdmTypeSchemaGenerator.cs @@ -5,12 +5,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Microsoft.OData.Edm; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; -using System.Diagnostics; +using Microsoft.OpenApi.OData.Common; namespace Microsoft.OpenApi.OData.Generator { @@ -27,15 +29,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiSchema CreateEdmTypeSchema(this ODataContext context, IEdmTypeReference edmTypeReference) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (edmTypeReference == null) - { - throw Error.ArgumentNull(nameof(edmTypeReference)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(edmTypeReference, nameof(edmTypeReference)); switch (edmTypeReference.TypeKind()) { @@ -84,15 +79,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiveTypeReference primitiveType) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (primitiveType == null) - { - throw Error.ArgumentNull(nameof(primitiveType)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(primitiveType, nameof(primitiveType)); OpenApiSchema schema = context.CreateSchema(primitiveType.PrimitiveDefinition()); if (schema != null) @@ -149,15 +137,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiveType primitiveType) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (primitiveType == null) - { - throw Error.ArgumentNull(nameof(primitiveType)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(primitiveType, nameof(primitiveType)); // Spec has different configure for double, AnyOf or OneOf? OpenApiSchema schema = new OpenApiSchema diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiErrorSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiErrorSchemaGenerator.cs index c4ea147..e8d93a6 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiErrorSchemaGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiErrorSchemaGenerator.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -22,10 +24,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The string/schema dictionary. public static IDictionary CreateODataErrorSchemas(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); IDictionary schemas = new Dictionary(); @@ -50,7 +49,7 @@ namespace Microsoft.OpenApi.OData.Generator return new OpenApiSchema { Type = "object", - Required = new List + Required = new HashSet { "error" }, @@ -80,7 +79,7 @@ namespace Microsoft.OpenApi.OData.Generator return new OpenApiSchema { Type = "object", - Required = new List + Required = new HashSet { "code", "message" }, @@ -131,7 +130,7 @@ namespace Microsoft.OpenApi.OData.Generator return new OpenApiSchema { Type = "object", - Required = new List + Required = new HashSet { "code", "message" }, diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs new file mode 100644 index 0000000..f8c30e2 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs @@ -0,0 +1,145 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Generator +{ + /// + /// Extension methods to create by . + /// + internal static class OpenApiExampleGenerator + { + /// + /// Create the dictionary of object. + /// + /// The OData to Open API context. + /// The securitySchemes. + /// The created dictionary. + public static IDictionary CreateExamples(this ODataContext context) + { + Utils.CheckArgumentNull(context, nameof(context)); + + IDictionary examples = new Dictionary(); + + // Each entity type, complex type, enumeration type, and type definition directly + // or indirectly used in the paths field is represented as a name / value pair of the schemas map. + foreach (var element in context.Model.SchemaElements.Where(c => !c.Namespace.StartsWith("Org.OData."))) + { + switch (element.SchemaElementKind) + { + case EdmSchemaElementKind.TypeDefinition: // Type definition + { + IEdmType reference = (IEdmType)element; + OpenApiExample example = context.CreateExample(reference); + if (example != null) + { + examples.Add(reference.FullTypeName(), example); + } + } + break; + } + } + + return examples; + } + + private static OpenApiExample CreateExample(this ODataContext context, IEdmType edmType) + { + Debug.Assert(context != null); + Debug.Assert(edmType != null); + + switch (edmType.TypeKind) + { + case EdmTypeKind.Complex: // complex type + case EdmTypeKind.Entity: // entity type + return CreateStructuredTypeExample((IEdmStructuredType)edmType); + } + + return null; + } + + private static OpenApiExample CreateStructuredTypeExample(IEdmStructuredType structuredType) + { + OpenApiExample example = new OpenApiExample(); + + OpenApiObject value = new OpenApiObject(); + + IEdmEntityType entityType = structuredType as IEdmEntityType; + + // properties + foreach (var property in structuredType.DeclaredProperties.OrderBy(p => p.Name)) + { + // IOpenApiAny item; + IEdmTypeReference propertyType = property.Type; + + IOpenApiAny item = GetTypeNameForExample(propertyType); + + EdmTypeKind typeKind = propertyType.TypeKind(); + if (typeKind == EdmTypeKind.Primitive && item is OpenApiString) + { + OpenApiString stringAny = item as OpenApiString; + string propertyValue = stringAny.Value; + if (entityType != null && entityType.Key().Any(k => k.Name == property.Name)) + { + propertyValue += " (identifier)"; + } + if (propertyType.IsDateTimeOffset() || propertyType.IsDate() || propertyType.IsTimeOfDay()) + { + propertyValue += " (timestamp)"; + } + item = new OpenApiString(propertyValue); + } + + value.Add(property.Name, item); + } + example.Value = value; + return example; + } + + private static IOpenApiAny GetTypeNameForExample(IEdmTypeReference edmTypeReference) + { + switch (edmTypeReference.TypeKind()) + { + case EdmTypeKind.Primitive: + if (edmTypeReference.IsBoolean()) + { + return new OpenApiBoolean(true); + } + else + { + return new OpenApiString(edmTypeReference.AsPrimitive().PrimitiveDefinition().Name); + } + + case EdmTypeKind.Entity: + case EdmTypeKind.Complex: + case EdmTypeKind.Enum: + OpenApiObject obj = new OpenApiObject(); + obj["@odata.type"] = new OpenApiString(edmTypeReference.FullName()); + return obj; + + case EdmTypeKind.Collection: + OpenApiArray array = new OpenApiArray(); + IEdmTypeReference elementType = edmTypeReference.AsCollection().ElementType(); + array.Add(GetTypeNameForExample(elementType)); + return array; + + case EdmTypeKind.Untyped: + case EdmTypeKind.TypeDefinition: + case EdmTypeKind.EntityReference: + default: + throw new OpenApiException("Not support for the type kind " + edmTypeReference.TypeKind()); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiInfoGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiInfoGenerator.cs index dff50f1..9fec2b2 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiInfoGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiInfoGenerator.cs @@ -7,6 +7,8 @@ using System.Diagnostics; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -22,10 +24,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The created object. public static OpenApiInfo CreateInfo(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); // The value of info is an Info Object, // It contains the fields title and version, and optionally the field description. diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiLinkGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiLinkGenerator.cs new file mode 100644 index 0000000..3b599c1 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiLinkGenerator.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Generator +{ + /// + /// Extension methods to create by . + /// + internal static class OpenApiLinkGenerator + { + /// + /// Create the collection of object. + /// + /// The OData context. + /// The Entity Set. + /// The created dictionary of object. + public static IDictionary CreateLinks(this ODataContext context, IEdmEntitySet entitySet) + { + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(entitySet, nameof(entitySet)); + + IDictionary links = new Dictionary(); + IEdmEntityType entityType = entitySet.EntityType(); + foreach (var np in entityType.DeclaredNavigationProperties()) + { + OpenApiLink link = new OpenApiLink(); + string typeName = entitySet.EntityType().Name; + link.OperationId = entitySet.Name + "." + typeName + ".Get" + Utils.UpperFirstChar(typeName); + link.Parameters = new Dictionary(); + foreach (var key in entityType.Key()) + { + link.Parameters[key.Name] = new RuntimeExpressionAnyWrapper + { + Any = new OpenApiString("$request.path." + key.Name) + }; + } + + links[np.Name] = link; + } + + return links; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiOperationGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiOperationGenerator.cs index 9e03046..c8443d9 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiOperationGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiOperationGenerator.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using Microsoft.OData.Edm; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; using Microsoft.OpenApi.OData.Common; diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs index d1244b6..b83115e 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs @@ -11,6 +11,7 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Capabilities; using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -27,10 +28,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The created map of object. public static IDictionary CreateParameters(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); // It allows defining query options and headers that can be reused across operations of the service. // The value of parameters is a map of Parameter Objects. @@ -52,15 +50,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created list of . public static IList CreateParameters(this ODataContext context, IEdmFunctionImport functionImport) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (functionImport == null) - { - throw Error.ArgumentNull(nameof(functionImport)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(functionImport, nameof(functionImport)); return context.CreateParameters(functionImport.Function); } @@ -73,15 +64,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created list of . public static IList CreateParameters(this ODataContext context, IEdmFunction function) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (function == null) - { - throw Error.ArgumentNull(nameof(function)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(function, nameof(function)); IList parameters = new List(); int skip = function.IsBound ? 1 : 0; @@ -134,10 +118,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created the list of . public static IList CreateKeyParameters(this ODataContext context, IEdmEntityType entityType) { - if (entityType == null) - { - throw Error.ArgumentNull(nameof(entityType)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(entityType, nameof(entityType)); IList parameters = new List(); @@ -153,6 +135,8 @@ namespace Microsoft.OpenApi.OData.Generator Schema = context.CreateEdmTypeSchema(keyProperty.Type) }; + parameter.Extensions.Add(Constants.xMsKeyType, new OpenApiString(entityType.Name)); + parameters.Add(parameter); } diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathItemGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathItemGenerator.cs index a87f1d0..4ee968c 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathItemGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathItemGenerator.cs @@ -4,12 +4,10 @@ // ------------------------------------------------------------ using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Common; -using Microsoft.OpenApi.OData.Capabilities; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.PathItem; namespace Microsoft.OpenApi.OData.Generator { @@ -33,345 +31,13 @@ namespace Microsoft.OpenApi.OData.Generator return pathItems; } - // visit all elements in the container - foreach (var element in context.EntityContainer.Elements) + foreach (ODataPath path in context.Paths) { - switch (element.ContainerElementKind) - { - case EdmContainerElementKind.EntitySet: // entity set - IEdmEntitySet entitySet = (IEdmEntitySet)element; - // entity set - string entitySetPathName = "/" + entitySet.Name; - var entitySetPathItem = context.CreateEntitySetPathItem(entitySet); - pathItems.Add(entitySetPathName, entitySetPathItem); - - // entity - string entityPathName = context.CreateEntityPathName(entitySet); - var entityPathItem = context.CreateEntityPathItem(entitySet); - pathItems.Add(entityPathName, entityPathItem); - - // navigation property - foreach (var item in context.CreateNavigationPathItems(entitySet)) - { - pathItems.Add(item.Key, item.Value); - } - - // bound operations to entity set or entity - foreach (var item in context.CreateOperationPathItems(entitySet)) - { - pathItems.Add(item.Key, item.Value); - } - - break; - - case EdmContainerElementKind.Singleton: // singleton - IEdmSingleton singleton = (IEdmSingleton)element; - string singletonPathName = "/" + singleton.Name; - var singletonPathItem = context.CreateSingletonPathItem(singleton); - pathItems.Add(singletonPathName, singletonPathItem); - - // navigation property - foreach (var item in context.CreateNavigationPathItems(singleton)) - { - pathItems.Add(item.Key, item.Value); - } - - // bound operations to singleton - foreach (var item in context.CreateOperationPathItems(singleton)) - { - pathItems.Add(item.Key, item.Value); - } - break; - - case EdmContainerElementKind.FunctionImport: // function import - IEdmFunctionImport functionImport = (IEdmFunctionImport)element; - string functionImportName = context.CreatePathItemName(functionImport); - var functionImportPathItem = context.CreatePathItem(functionImport); - pathItems.Add(functionImportName, functionImportPathItem); - break; - - case EdmContainerElementKind.ActionImport: // action import - IEdmActionImport actionImport = (IEdmActionImport)element; - string actionImportName = context.CreatePathItemName(actionImport); - var actionImportPathItem = context.CreatePathItem(actionImport); - pathItems.Add(actionImportName, actionImportPathItem); - break; - } + IPathItemHandler handler = context.PathItemHanderProvider.GetHandler(path.PathType); + pathItems.Add(path.ToString(), handler.CreatePathItem(context, path)); } return pathItems; } - - /// - /// Create a for . - /// - /// The OData context. - /// The Edm entity set. - /// The created . - public static OpenApiPathItem CreateEntitySetPathItem(this ODataContext context, IEdmEntitySet entitySet) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(entitySet, nameof(entitySet)); - - OpenApiPathItem pathItem = new OpenApiPathItem(); - - pathItem.AddOperation(OperationType.Get, context.CreateEntitySetGetOperation(entitySet)); - - InsertRestrictions insert = new InsertRestrictions(context.Model, entitySet); - if (insert.IsInsertable()) - { - pathItem.AddOperation(OperationType.Post, context.CreateEntitySetPostOperation(entitySet)); - } - - return pathItem; - } - - /// - /// Create a for a single entity in . - /// - /// The OData context. - /// The Edm entity set. - /// The created . - public static OpenApiPathItem CreateEntityPathItem(this ODataContext context, IEdmEntitySet entitySet) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(entitySet, nameof(entitySet)); - - OpenApiPathItem pathItem = new OpenApiPathItem(); - - IndexableByKey index = new IndexableByKey(context.Model, entitySet); - if (index.IsSupported()) - { - pathItem.AddOperation(OperationType.Get, context.CreateEntityGetOperation(entitySet)); - } - - UpdateRestrictions update = new UpdateRestrictions(context.Model, entitySet); - if (update.IsUpdatable()) - { - pathItem.AddOperation(OperationType.Patch, context.CreateEntityPatchOperation(entitySet)); - } - - DeleteRestrictions delete = new DeleteRestrictions(context.Model, entitySet); - if (delete.IsDeletable()) - { - pathItem.AddOperation(OperationType.Delete, context.CreateEntityDeleteOperation(entitySet)); - } - - return pathItem; - } - - /// - /// Create a for . - /// - /// The OData context. - /// The singleton. - /// The created on this singleton. - public static OpenApiPathItem CreateSingletonPathItem(this ODataContext context, IEdmSingleton singleton) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(singleton, nameof(singleton)); - - OpenApiPathItem pathItem = new OpenApiPathItem(); - - // Retrieve a singleton. - pathItem.AddOperation(OperationType.Get, context.CreateSingletonGetOperation(singleton)); - - // Update a singleton - pathItem.AddOperation(OperationType.Patch, context.CreateSingletonPatchOperation(singleton)); - - return pathItem; - } - - /// - /// Create the bound operations for the navigation source. - /// - /// The OData context. - /// The navigation source. - /// The name/value pairs describing the allowed operations on this navigation source. - public static IDictionary CreateOperationPathItems(this ODataContext context, - IEdmNavigationSource navigationSource) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(navigationSource, nameof(navigationSource)); - - IDictionary operationPathItems = new Dictionary(); - - IEnumerable operations; - IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; - - // collection bound - if (entitySet != null) - { - operations = context.FindOperations(navigationSource.EntityType(), collection: true); - foreach (var operation in operations) - { - OpenApiPathItem pathItem = context.CreatePathItem(navigationSource, operation); - string pathName = context.CreatePathItemName(operation); - operationPathItems.Add("/" + navigationSource.Name + pathName, pathItem); - } - } - - // non-collection bound - operations = context.FindOperations(navigationSource.EntityType(), collection: false); - foreach (var operation in operations) - { - OpenApiPathItem pathItem = context.CreatePathItem(navigationSource, operation); - string pathName = context.CreatePathItemName(operation); - - string entityPathName; - if (entitySet != null) - { - entityPathName = context.CreateEntityPathName(entitySet); - - OpenApiOperation openApiOperation = pathItem.Operations.First().Value; - Debug.Assert(openApiOperation != null); - openApiOperation.Parameters = context.CreateKeyParameters(entitySet.EntityType()); - } - else - { - entityPathName = "/" + navigationSource.Name; - } - - operationPathItems.Add(entityPathName + pathName, pathItem); - } - - return operationPathItems; - } - - /// - /// Create the navigation property path item for the navigation source. - /// - /// The OData context. - /// The navigation source. - /// The name/value pairs describing the allowed operations on this navigation source. - public static IDictionary CreateNavigationPathItems(this ODataContext context, - IEdmNavigationSource navigationSource) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(navigationSource, nameof(navigationSource)); - - IDictionary navPathItems = new Dictionary(); - if (!context.Settings.NavigationPropertyPathItem) - { - return navPathItems; - } - - IEdmEntityType entityType = navigationSource.EntityType(); - foreach (var navProperty in entityType.DeclaredNavigationProperties()) - { - string pathItemName = context.CreateNavigationPathItemName(navigationSource, navProperty); - OpenApiPathItem pathItem = context.CreatePathItem(navigationSource, navProperty); - - navPathItems.Add(pathItemName, pathItem); - } - - return navPathItems; - } - - /// - /// Create a for a single . - /// - /// The OData context. - /// The binding navigation source. - /// The Edm navigation property. - /// The created . - public static OpenApiPathItem CreatePathItem(this ODataContext context, IEdmNavigationSource navigationSource, - IEdmNavigationProperty navigationProperty) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(navigationSource, nameof(navigationSource)); - Utils.CheckArgumentNull(navigationProperty, nameof(navigationProperty)); - - OpenApiPathItem pathItem = new OpenApiPathItem(); - - pathItem.AddOperation(OperationType.Get, context.CreateNavigationGetOperation(navigationSource, navigationProperty)); - - if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) - { - InsertRestrictions insert = new InsertRestrictions(context.Model, navigationProperty); - if (insert.IsInsertable()) - { - pathItem.AddOperation(OperationType.Post, context.CreateNavigationPostOperation(navigationSource, navigationProperty)); - } - } - else - { - UpdateRestrictions update = new UpdateRestrictions(context.Model, navigationProperty); - if (update.IsUpdatable()) - { - pathItem.AddOperation(OperationType.Patch, context.CreateNavigationPatchOperation(navigationSource, navigationProperty)); - } - } - - return pathItem; - } - - /// - /// Create a for a single . - /// - /// The OData context. - /// The binding navigation source. - /// The Edm opeation. - /// The created . - public static OpenApiPathItem CreatePathItem(this ODataContext context, IEdmNavigationSource navigationSource, IEdmOperation edmOperation) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(navigationSource, nameof(navigationSource)); - Utils.CheckArgumentNull(edmOperation, nameof(edmOperation)); - - OpenApiPathItem pathItem = new OpenApiPathItem(); - - OpenApiOperation operation = context.CreateOperation(navigationSource, edmOperation); - - if (edmOperation.IsAction()) - { - // The Path Item Object for a bound action contains the keyword post, - // The value of the operation keyword is an Operation Object that describes how to invoke the action. - pathItem.AddOperation(OperationType.Post, operation); - } - else - { - // The Path Item Object for a bound function contains the keyword get, - // The value of the operation keyword is an Operation Object that describes how to invoke the function. - pathItem.AddOperation(OperationType.Get, operation); - } - - return pathItem; - } - - /// - /// Create a for a single . - /// - /// The OData context. - /// The Edm operation import. - /// The created . - public static OpenApiPathItem CreatePathItem(this ODataContext context, IEdmOperationImport operationImport) - { - Utils.CheckArgumentNull(context, nameof(context)); - Utils.CheckArgumentNull(operationImport, nameof(operationImport)); - - OpenApiPathItem pathItem = new OpenApiPathItem(); - - OpenApiOperation operation = context.CreateOperation(operationImport); - - if (operationImport.IsActionImport()) - { - // Each action import is represented as a name/value pair whose name is the service-relative - // resource path of the action import prepended with a forward slash, and whose value is a Path - // Item Object containing the keyword post with an Operation Object as value that describes - // how to invoke the action import. - pathItem.AddOperation(OperationType.Post, operation); - } - else - { - // Each function import is represented as a name/value pair whose name is the service-relative - // resource path of the function import prepended with a forward slash, and whose value is a Path - // Item Object containing the keyword get with an Operation Object as value that describes - // how to invoke the function import. - pathItem.AddOperation(OperationType.Get, operation); - } - - return pathItem; - } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathsGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathsGenerator.cs index e662fd4..6e9ce49 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathsGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiPathsGenerator.cs @@ -4,6 +4,8 @@ // ------------------------------------------------------------ using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -23,10 +25,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The created object. public static OpenApiPaths CreatePaths(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); // Due to the power and flexibility of OData a full representation of all service capabilities // in the Paths Object is typically not feasible, so this mapping only describes the minimum diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiRequestBodyGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiRequestBodyGenerator.cs index 4d64873..989f8e9 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiRequestBodyGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiRequestBodyGenerator.cs @@ -7,6 +7,8 @@ using System.Linq; using System.Collections.Generic; using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Common; namespace Microsoft.OpenApi.OData.Generator { @@ -23,15 +25,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created or null. public static OpenApiRequestBody CreateRequestBody(this ODataContext context, IEdmActionImport actionImport) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (actionImport == null) - { - throw Error.ArgumentNull(nameof(actionImport)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(actionImport, nameof(actionImport)); return context.CreateRequestBody(actionImport.Action); } @@ -44,15 +39,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created or null. public static OpenApiRequestBody CreateRequestBody(this ODataContext context, IEdmAction action) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (action == null) - { - throw Error.ArgumentNull(nameof(action)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(action, nameof(action)); // return null for empty action parameters int skip = 0; diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs index bbdb239..125b8d9 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -57,10 +59,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The name/value pairs for the standard OData error response. public static IDictionary CreateResponses(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); return new Dictionary { @@ -76,15 +75,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperationImport operationImport) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (operationImport == null) - { - throw Error.ArgumentNull(nameof(operationImport)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(operationImport, nameof(operationImport)); return context.CreateResponses(operationImport.Operation); } @@ -97,15 +89,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperation operation) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (operation == null) - { - throw Error.ArgumentNull(nameof(operation)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(operation, nameof(operation)); OpenApiResponses responses = new OpenApiResponses(); diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs index 00a7af0..e083667 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs @@ -10,6 +10,10 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.OData.Properties; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.Exceptions; +using System.Linq; namespace Microsoft.OpenApi.OData.Generator { @@ -27,16 +31,13 @@ namespace Microsoft.OpenApi.OData.Generator /// The string/schema dictionary. public static IDictionary CreateSchemas(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); IDictionary schemas = new Dictionary(); // Each entity type, complex type, enumeration type, and type definition directly // or indirectly used in the paths field is represented as a name / value pair of the schemas map. - foreach (var element in context.Model.SchemaElements) + foreach (var element in context.Model.SchemaElements.Where(c => !c.Namespace.StartsWith("Org.OData."))) { switch (element.SchemaElementKind) { @@ -74,15 +75,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEdmEnumType enumType) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (enumType == null) - { - throw Error.ArgumentNull(nameof(enumType)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(enumType, nameof(enumType)); OpenApiSchema schema = new OpenApiSchema { @@ -115,17 +109,10 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiSchema CreateStructuredTypeSchema(this ODataContext context, IEdmStructuredType structuredType) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(structuredType, nameof(structuredType)); - if (structuredType == null) - { - throw Error.ArgumentNull(nameof(structuredType)); - } - - return context.CreateStructuredTypeSchema(structuredType, true); + return context.CreateStructuredTypeSchema(structuredType, true, true); } /// @@ -139,15 +126,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created . public static OpenApiSchema CreatePropertySchema(this ODataContext context, IEdmProperty property) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (property == null) - { - throw Error.ArgumentNull(nameof(property)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(property, nameof(property)); OpenApiSchema schema = context.CreateEdmTypeSchema(property.Type); @@ -174,6 +154,9 @@ namespace Microsoft.OpenApi.OData.Generator /// The created map of . public static IDictionary CreateStructuredTypePropertiesSchema(this ODataContext context, IEdmStructuredType structuredType) { + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(structuredType, nameof(structuredType)); + // The name is the property name, the value is a Schema Object describing the allowed values of the property. IDictionary properties = new Dictionary(); @@ -209,7 +192,7 @@ namespace Microsoft.OpenApi.OData.Generator { case EdmTypeKind.Complex: // complex type case EdmTypeKind.Entity: // entity type - return context.CreateStructuredTypeSchema((IEdmStructuredType)edmType, true); + return context.CreateStructuredTypeSchema((IEdmStructuredType)edmType, true, true); case EdmTypeKind.Enum: // enum type return context.CreateEnumTypeSchema((IEdmEnumType)edmType); @@ -223,7 +206,7 @@ namespace Microsoft.OpenApi.OData.Generator } } - private static OpenApiSchema CreateStructuredTypeSchema(this ODataContext context, IEdmStructuredType structuredType, bool processBase) + private static OpenApiSchema CreateStructuredTypeSchema(this ODataContext context, IEdmStructuredType structuredType, bool processBase, bool processExample) { Debug.Assert(context != null); Debug.Assert(structuredType != null); @@ -247,12 +230,13 @@ namespace Microsoft.OpenApi.OData.Generator }, // 2. a Schema Object describing the derived type - context.CreateStructuredTypeSchema(structuredType, false) + context.CreateStructuredTypeSchema(structuredType, false, false) }, AnyOf = null, OneOf = null, - Properties = null + Properties = null, + Example = CreateStructuredTypePropertiesExample(structuredType) }; } else @@ -287,10 +271,86 @@ namespace Microsoft.OpenApi.OData.Generator schema.Description = context.Model.GetDescriptionAnnotation(entity); } + if (processExample) + { + schema.Example = CreateStructuredTypePropertiesExample(structuredType); + } + return schema; } } + private static IOpenApiAny CreateStructuredTypePropertiesExample(IEdmStructuredType structuredType) + { + OpenApiObject example = new OpenApiObject(); + + IEdmEntityType entityType = structuredType as IEdmEntityType; + + // properties + foreach (var property in structuredType.Properties()) + { + // IOpenApiAny item; + IEdmTypeReference propertyType = property.Type; + + IOpenApiAny item = GetTypeNameForExample(propertyType); + + EdmTypeKind typeKind = propertyType.TypeKind(); + if (typeKind == EdmTypeKind.Primitive && item is OpenApiString) + { + OpenApiString stringAny = item as OpenApiString; + string value = stringAny.Value; + if (entityType != null && entityType.Key().Any(k => k.Name == property.Name)) + { + value += " (identifier)"; + } + if (propertyType.IsDateTimeOffset() || propertyType.IsDate() || propertyType.IsTimeOfDay()) + { + value += " (timestamp)"; + } + item = new OpenApiString(value); + } + + example.Add(property.Name, item); + } + + return example; + } + + private static IOpenApiAny GetTypeNameForExample(IEdmTypeReference edmTypeReference) + { + switch (edmTypeReference.TypeKind()) + { + case EdmTypeKind.Primitive: + if (edmTypeReference.IsBoolean()) + { + return new OpenApiBoolean(true); + } + else + { + return new OpenApiString(edmTypeReference.AsPrimitive().PrimitiveDefinition().Name); + } + + case EdmTypeKind.Entity: + case EdmTypeKind.Complex: + case EdmTypeKind.Enum: + OpenApiObject obj = new OpenApiObject(); + obj["@odata.type"] = new OpenApiString(edmTypeReference.FullName()); + return obj; + + case EdmTypeKind.Collection: + OpenApiArray array = new OpenApiArray(); + IEdmTypeReference elementType = edmTypeReference.AsCollection().ElementType(); + array.Add(GetTypeNameForExample(elementType)); + return array; + + case EdmTypeKind.Untyped: + case EdmTypeKind.TypeDefinition: + case EdmTypeKind.EntityReference: + default: + throw new OpenApiException("Not support for the type kind " + edmTypeReference.TypeKind()); + } + } + private static IOpenApiAny CreateDefault(this IEdmStructuralProperty property) { if (property == null || diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecurityRequirementGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecurityRequirementGenerator.cs new file mode 100644 index 0000000..42768bf --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecurityRequirementGenerator.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Authorizations; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Generator +{ + /// + /// Extension methods to create by . + /// + internal static class OpenApiSecurityRequirementGenerator + { + /// + /// Create the list of object. + /// + /// The OData to Open API context. + /// The securitySchemes. + /// The created collection. + public static IEnumerable CreateSecurityRequirements(this ODataContext context, + IList securitySchemes) + { + Utils.CheckArgumentNull(context, nameof(context)); + + if (securitySchemes != null) + { + foreach (var securityScheme in securitySchemes) + { + yield return new OpenApiSecurityRequirement + { + [ + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = securityScheme.AuthorizationSchemeName + } + } + ] = new List(securityScheme.RequiredScopes ?? new List()) + }; + } + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecuritySchemeGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecuritySchemeGenerator.cs new file mode 100644 index 0000000..6e100e1 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSecuritySchemeGenerator.cs @@ -0,0 +1,169 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Authorizations; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Generator +{ + /// + /// Extension methods to create by . + /// + internal static class OpenApiSecuritySchemeGenerator + { + /// + /// Create the dictionary of object. + /// The name of each pair is the name of authorization. The value of each pair is a . + /// + /// The OData to Open API context. + /// The string/security scheme dictionary. + public static IDictionary CreateSecuritySchemes(this ODataContext context) + { + Utils.CheckArgumentNull(context, nameof(context)); + + if (context.Model == null || context.Model.EntityContainer == null) + { + return null; + } + + IDictionary securitySchemes = new Dictionary(); + var authorizations = context.AuthorizationProvider.GetAuthorizations(context.Model, context.EntityContainer); + foreach (var authorization in authorizations) + { + OpenApiSecurityScheme scheme = new OpenApiSecurityScheme + { + Type = authorization.SchemeType, + Description = authorization.Description + }; + + switch (authorization.SchemeType) + { + case SecuritySchemeType.ApiKey: // ApiKey + AppendApiKey(scheme, (ApiKey)authorization); + break; + + case SecuritySchemeType.Http: // Http + AppendHttp(scheme, (Http)authorization); + break; + + case SecuritySchemeType.OpenIdConnect: // OpenIdConnect + AppendOpenIdConnect(scheme, (OpenIDConnect)authorization); + break; + + case SecuritySchemeType.OAuth2: // OAuth2 + AppendOAuth2(scheme, (OAuthAuthorization)authorization); + break; + } + + securitySchemes[authorization.Name] = scheme; + } + + return securitySchemes; + } + + private static void AppendApiKey(OpenApiSecurityScheme scheme, ApiKey apiKey) + { + Debug.Assert(scheme != null); + Debug.Assert(apiKey != null); + + scheme.Name = apiKey.KeyName; + + Debug.Assert(apiKey.Location != null); + switch(apiKey.Location.Value) + { + case KeyLocation.Cookie: + scheme.In = ParameterLocation.Cookie; + break; + + case KeyLocation.Header: + scheme.In = ParameterLocation.Header; + break; + + case KeyLocation.QueryOption: + scheme.In = ParameterLocation.Query; + break; + } + } + + private static void AppendHttp(OpenApiSecurityScheme scheme, Http http) + { + Debug.Assert(scheme != null); + Debug.Assert(http != null); + + scheme.Scheme = http.Scheme; + scheme.BearerFormat = http.BearerFormat; + } + + private static void AppendOpenIdConnect(OpenApiSecurityScheme scheme, OpenIDConnect openIdConnect) + { + Debug.Assert(scheme != null); + Debug.Assert(openIdConnect != null); + + scheme.OpenIdConnectUrl = new Uri(openIdConnect.IssuerUrl); + } + + private static void AppendOAuth2(OpenApiSecurityScheme scheme, OAuthAuthorization oAuth2) + { + Debug.Assert(scheme != null); + Debug.Assert(oAuth2 != null); + + scheme.Flows = new OpenApiOAuthFlows(); + OpenApiOAuthFlow flow = null; + switch (oAuth2.OAuth2Type) + { + case OAuth2Type.AuthCode: // AuthCode + OAuth2AuthCode authCode = (OAuth2AuthCode)oAuth2; + flow = new OpenApiOAuthFlow + { + AuthorizationUrl = new Uri(authCode.AuthorizationUrl), + TokenUrl = new Uri(authCode.TokenUrl) + }; + scheme.Flows.AuthorizationCode = flow; + break; + + case OAuth2Type.Pasword: // Password + OAuth2Password password = (OAuth2Password)oAuth2; + flow = new OpenApiOAuthFlow + { + TokenUrl = new Uri(password.TokenUrl) + }; + scheme.Flows.Password = flow; + break; + + case OAuth2Type.Implicit: // Implicit + OAuth2Implicit @implicit = (OAuth2Implicit)oAuth2; + flow = new OpenApiOAuthFlow + { + AuthorizationUrl = new Uri(@implicit.AuthorizationUrl) + }; + scheme.Flows.Implicit = flow; + break; + + case OAuth2Type.ClientCredentials: // ClientCredentials + OAuth2ClientCredentials credentials = (OAuth2ClientCredentials)oAuth2; + flow = new OpenApiOAuthFlow + { + TokenUrl = new Uri(credentials.TokenUrl) + }; + scheme.Flows.ClientCredentials = flow; + break; + } + + Debug.Assert(flow != null); + flow.RefreshUrl = new Uri(oAuth2.RefreshUrl); + + if (oAuth2.Scopes != null) + { + flow.Scopes = oAuth2.Scopes.ToDictionary(s => s.Scope, s => s.Description); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiServerGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiServerGenerator.cs index 6a31fa5..c1ada2a 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiServerGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiServerGenerator.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -20,10 +22,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The created collection of object. public static IList CreateServers(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); // The value of servers is an array of Server Objects. // It contains one object with a field url. diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSpatialTypeSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSpatialTypeSchemaGenerator.cs index c6c6851..2c9cb75 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSpatialTypeSchemaGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSpatialTypeSchemaGenerator.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -23,10 +25,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The string/schema dictionary. public static IDictionary CreateSpatialSchemas(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); IDictionary schemas = new Dictionary(); @@ -243,7 +242,7 @@ namespace Microsoft.OpenApi.OData.Generator }, { "coordinates", new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "GeoJSON.position" } } } }, - Required = new List + Required = new HashSet { "type", "coordinates" @@ -278,7 +277,7 @@ namespace Microsoft.OpenApi.OData.Generator } } }, - Required = new List + Required = new HashSet { "type", "coordinates" @@ -317,7 +316,7 @@ namespace Microsoft.OpenApi.OData.Generator } } }, - Required = new List + Required = new HashSet { "type", "coordinates" @@ -351,7 +350,7 @@ namespace Microsoft.OpenApi.OData.Generator } } }, - Required = new List + Required = new HashSet { "type", "coordinates" @@ -390,7 +389,7 @@ namespace Microsoft.OpenApi.OData.Generator } } }, - Required = new List + Required = new HashSet { "type", "coordinates" @@ -433,7 +432,7 @@ namespace Microsoft.OpenApi.OData.Generator } } }, - Required = new List + Required = new HashSet { "type", "coordinates" @@ -467,7 +466,7 @@ namespace Microsoft.OpenApi.OData.Generator } } }, - Required = new List + Required = new HashSet { "type", "coordinates" diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiTagGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiTagGenerator.cs index 6b494f4..6ce0710 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiTagGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiTagGenerator.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Diagnostics; using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -15,6 +17,12 @@ namespace Microsoft.OpenApi.OData.Generator /// internal static class OpenApiTagGenerator { + public static IList CreateTags_FromTagItems(this ODataContext context) + { + Utils.CheckArgumentNull(context, nameof(context)); + return context.Tags; + } + /// /// Create the collection of object. /// @@ -22,10 +30,7 @@ namespace Microsoft.OpenApi.OData.Generator /// The created collection of object. public static IList CreateTags(this ODataContext context) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } + Utils.CheckArgumentNull(context, nameof(context)); // The value of tags is an array of Tag Objects. // For an OData service the natural groups are entity sets and singletons, diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/PathItemNameExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/PathItemNameExtensions.cs index 66e60bc..171f002 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/PathItemNameExtensions.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/PathItemNameExtensions.cs @@ -9,6 +9,8 @@ using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -25,15 +27,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created path item name. public static string CreateEntityPathName(this ODataContext context, IEdmEntitySet entitySet) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (entitySet == null) - { - throw Error.ArgumentNull(nameof(entitySet)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(entitySet, nameof(entitySet)); StringBuilder sb = new StringBuilder("/" + entitySet.Name); IList keys = entitySet.EntityType().Key().ToList(); @@ -69,15 +64,8 @@ namespace Microsoft.OpenApi.OData.Generator /// The created path item name public static string CreatePathItemName(this ODataContext context, IEdmOperationImport operationImport) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (operationImport == null) - { - throw Error.ArgumentNull(nameof(operationImport)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(operationImport, nameof(operationImport)); if (operationImport.IsActionImport()) { @@ -86,9 +74,19 @@ namespace Microsoft.OpenApi.OData.Generator else { StringBuilder functionName = new StringBuilder("/" + operationImport.Name + "("); - functionName.Append(String.Join(",", - operationImport.Operation.Parameters.Select(p => p.Name + "=" + "{" + p.Name + "}"))); + operationImport.Operation.Parameters.Select(p => + { + if (p.Type.IsStructured() || p.Type.IsCollection()) + { + return p.Name + "=@" + p.Name; + } + else + { + return p.Name + "={" + p.Name + "}"; + } + }))); + functionName.Append(")"); return functionName.ToString(); @@ -99,19 +97,12 @@ namespace Microsoft.OpenApi.OData.Generator /// Create the path item name for a . /// /// The OData context. - /// The Edm function. + /// The Edm operation. /// The created path item name public static string CreatePathItemName(this ODataContext context, IEdmOperation operation) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (operation == null) - { - throw Error.ArgumentNull(nameof(operation)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(operation, nameof(operation)); if (operation.IsAction()) { @@ -131,20 +122,9 @@ namespace Microsoft.OpenApi.OData.Generator public static string CreateNavigationPathItemName(this ODataContext context, IEdmNavigationSource navigationSource, IEdmNavigationProperty navigationProperty) { - if (context == null) - { - throw Error.ArgumentNull(nameof(context)); - } - - if (navigationSource == null) - { - throw Error.ArgumentNull(nameof(navigationSource)); - } - - if (navigationProperty == null) - { - throw Error.ArgumentNull(nameof(navigationProperty)); - } + Utils.CheckArgumentNull(context, nameof(context)); + Utils.CheckArgumentNull(navigationSource, nameof(navigationSource)); + Utils.CheckArgumentNull(navigationProperty, nameof(navigationProperty)); string pathItemName; IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; @@ -179,30 +159,17 @@ namespace Microsoft.OpenApi.OData.Generator // Structured or collection-valued parameters are represented as a parameter alias in the path template // and the parameters array contains a Parameter Object for the parameter alias as a query option of type string. int skip = function.IsBound ? 1 : 0; - int index = 0; - int parameterAliasIndex = 1; - foreach (IEdmOperationParameter edmParameter in function.Parameters.Skip(skip)) + functionName.Append(String.Join(",", function.Parameters.Skip(skip).Select(p => { - if (index == 0) + if (p.Type.IsStructured() || p.Type.IsCollection()) { - index++; + return p.Name + "=@" + p.Name; } else { - functionName.Append(","); + return p.Name + "={" + p.Name + "}"; } - - if (edmParameter.Type.IsStructured() || - edmParameter.Type.IsCollection()) - { - functionName.Append(edmParameter.Name).Append("=") - .Append(context.Settings.ParameterAlias).Append(parameterAliasIndex++); - } - else - { - functionName.Append(edmParameter.Name).Append("={").Append(edmParameter.Name).Append("}"); - } - } + }))); functionName.Append(")"); diff --git a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj index b10c838..abb6d21 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj +++ b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs index 339a5a8..fbc86ff 100644 --- a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs +++ b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using System; +using Microsoft.OpenApi.OData.Abstractions; namespace Microsoft.OpenApi.OData { @@ -42,10 +43,35 @@ namespace Microsoft.OpenApi.OData /// public bool EnumPrefixFree { get; set; } + /// + /// Gets/set a value indicating whether to output the path for Edm operation. + /// + public bool EnableOperationPath { get; set; } = true; + + /// + /// Gets/set a value indicating whether to output the path for Edm operation import. + /// + public bool EnableOperationImportPath { get; set; } = true; + + /// + /// Gets/set a value indicating whether to output the path for Edm navigation property. + /// + public bool EnableNavigationPropertyPath { get; set; } + /// /// Gets/set a value indicating the navigation property depth. /// - public bool NavigationPropertyPathItem { get; set; } + public int NavigationPropertyDepth { get; set; } = 0; + + /// + /// Gets/set a value indicating the tags name depth. + /// + public int TagDepth { get; set; } = 4; + + /// + /// Gets/set a value indicating whether we count key segment as a depth. + /// + public bool CountKeySegmentAsDepth { get; set; } = true; /// /// Gets/set a value indicating the prefix for the parameter alias. @@ -68,5 +94,7 @@ namespace Microsoft.OpenApi.OData /// otherwise keep number without quotes. /// public bool IEEE754Compatible { get; set; } + + public IAuthorizationProvider AuthorizationProvider { get; set; } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionImportOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionImportOperationHandler.cs new file mode 100644 index 0000000..4ba303a --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionImportOperationHandler.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// The Open Api operation for . + /// + internal class EdmActionImportOperationHandler : EdmOperationImportOperationHandler + { + /// + public override OperationType OperationType => OperationType.Post; + + protected override void SetRequestBody(OpenApiOperation operation) + { + IEdmActionImport actionImport = EdmOperationImport as IEdmActionImport; + + // The requestBody field contains a Request Body Object describing the structure of the request body. + // Its schema value follows the rules for Schema Objects for complex types, with one property per action parameter. + operation.RequestBody = Context.CreateRequestBody(actionImport); + } + + /// + protected override void SetExtensions(OpenApiOperation operation) + { + operation.Extensions.Add(Constants.xMsDosOperationType, new OpenApiString("actionImport")); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionOperationHandler.cs new file mode 100644 index 0000000..7f7b595 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmActionOperationHandler.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// The Open Api operation for . + /// + internal class EdmActionOperationHandler : EdmOperationOperationHandler + { + /// + public override OperationType OperationType => OperationType.Post; + + /// + /// Gets the Edm Action. + /// + public IEdmAction Action => EdmOperation as IEdmAction; + + /// + protected override void SetRequestBody(OpenApiOperation operation) + { + IEdmAction action = EdmOperation as IEdmAction; + if (action != null) + { + operation.RequestBody = Context.CreateRequestBody(action); + } + } + + /// + protected override void SetExtensions(OpenApiOperation operation) + { + operation.Extensions.Add(Constants.xMsDosOperationType, new OpenApiString("action")); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionImportOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionImportOperationHandler.cs new file mode 100644 index 0000000..754b71f --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionImportOperationHandler.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// The Open Api operation for . + /// + internal class EdmFunctionImportOperationHandler : EdmOperationImportOperationHandler + { + /// + public override OperationType OperationType => OperationType.Get; + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + + IEdmFunctionImport functionImport = EdmOperationImport as IEdmFunctionImport; + //The parameters array contains a Parameter Object for each parameter of the function overload, + // and it contains specific Parameter Objects for the allowed system query options. + operation.Parameters = Context.CreateParameters(functionImport); + } + + /// + protected override void SetExtensions(OpenApiOperation operation) + { + operation.Extensions.Add(Constants.xMsDosOperationType, new OpenApiString("functionImport")); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionOperationHandler.cs new file mode 100644 index 0000000..d42b690 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmFunctionOperationHandler.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// The Open Api operation for . + /// + internal class EdmFunctionOperationHandler : EdmOperationOperationHandler + { + /// + public override OperationType OperationType => OperationType.Get; + + /// + /// Gets the Edm Function. + /// + public IEdmFunction Function => EdmOperation as IEdmFunction; + + /// + protected override void SetExtensions(OpenApiOperation operation) + { + operation.Extensions.Add(Constants.xMsDosOperationType, new OpenApiString("function")); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs new file mode 100644 index 0000000..b4a9593 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationImportOperationHandler.cs @@ -0,0 +1,95 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for . + /// + internal abstract class EdmOperationImportOperationHandler : OperationHandler + { + /// + /// Gets the . + /// + protected IEdmOperationImport EdmOperationImport { get; private set; } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataOperationImportSegment operationImportSegment = path.LastSegment as ODataOperationImportSegment; + EdmOperationImport = operationImportSegment.OperationImport; + base.Initialize(context, path); + } + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + operation.Summary = "Invoke " + (EdmOperationImport.IsActionImport() ? "action " : "function ") + EdmOperationImport.Name; + + if (Context.Settings.OperationId) + { + string key = "OperationImport." + EdmOperationImport.Name; + operation.OperationId += "OperationImport." + Context.GetIndex(key) + "-" + Utils.UpperFirstChar(EdmOperationImport.Name); + } + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + // The responses object contains a name/value pair for the success case (HTTP response code 200) + // 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); + } + + /// + protected override void SetTags(OpenApiOperation operation) + { + operation.Tags = CreateTags(EdmOperationImport); + operation.Tags[0].Extensions.Add(Constants.xMsTocType, new OpenApiString("container")); + Context.AppendTag(operation.Tags[0]); + } + + private static IList CreateTags(IEdmOperationImport operationImport) + { + if (operationImport.EntitySet != null) + { + var pathExpression = operationImport.EntitySet as IEdmPathExpression; + if (pathExpression != null) + { + return new List + { + new OpenApiTag + { + Name = PathAsString(pathExpression.PathSegments) + } + }; + } + } + + return new List{ + new OpenApiTag + { + Name = operationImport.Name + } + }; + } + + internal static string PathAsString(IEnumerable path) + { + return String.Join("/", path); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs new file mode 100644 index 0000000..d593c65 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs @@ -0,0 +1,121 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for operation of . + /// + internal abstract class EdmOperationOperationHandler : OperationHandler + { + /// + /// Gets the navigation source. + /// + protected IEdmNavigationSource NavigationSource { get; private set; } + + /// + /// Gets the Edm operation. + /// + protected IEdmOperation EdmOperation { get; private set; } + + /// + /// Gets a value indicating whether the path has type cast segment or not. + /// + protected bool HasTypeCast { get; private set; } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + NavigationSource = navigationSourceSegment.NavigationSource; + + ODataOperationSegment operationSegment = path.LastSegment as ODataOperationSegment; + EdmOperation = operationSegment.Operation; + + HasTypeCast = path.Segments.Any(s => s is ODataTypeCastSegment); + + base.Initialize(context, path); + } + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Invoke " + (EdmOperation.IsAction() ? "action " : "function ") + EdmOperation.Name; + + // OperationId + if (Context.Settings.OperationId) + { + string key = NavigationSource.Name + "-" + Utils.UpperFirstChar(EdmOperation.Name); + int index = Context.GetIndex(key); + operation.OperationId = NavigationSource.Name + "." + index + "-" + Utils.UpperFirstChar(EdmOperation.Name); + } + } + + /// + protected override void SetTags(OpenApiOperation operation) + { + string value = EdmOperation.IsAction() ? "Actions" : "Functions"; + OpenApiTag tag = new OpenApiTag + { + Name = NavigationSource.Name + "." + value, + }; + tag.Extensions.Add(Constants.xMsTocType, new OpenApiString("container")); + operation.Tags.Add(tag); + + Context.AppendTag(tag); + } + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + /* + IEdmSingleton singleton = NavigationSource as IEdmSingleton; + if (singleton == null && EdmOperation.IsBound) + { + IEdmOperationParameter bindingParameter = EdmOperation.Parameters.FirstOrDefault(); + if (bindingParameter != null && + !bindingParameter.Type.IsCollection() && // bound to a single entity + bindingParameter.Type.IsEntity()) + { + operation.Parameters = Context.CreateKeyParameters(bindingParameter + .Type.AsEntity().EntityDefinition()); + } + }*/ + + if (EdmOperation.IsFunction()) + { + IEdmFunction function = (IEdmFunction)EdmOperation; + IList parameters = Context.CreateParameters(function); + if (operation.Parameters == null) + { + operation.Parameters = parameters; + } + else + { + foreach (var parameter in parameters) + { + operation.Parameters.Add(parameter); + } + } + } + } + + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = Context.CreateResponses(EdmOperation); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntityDeleteOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntityDeleteOperationHandler.cs new file mode 100644 index 0000000..44ad1d0 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntityDeleteOperationHandler.cs @@ -0,0 +1,70 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Delete an Entity + /// The Path Item Object for the entity set contains the keyword delete with an Operation Object as value + /// that describes the capabilities for deleting the entity. + /// + internal class EntityDeleteOperationHandler : EntitySetOperationHandler + { + /// + public override OperationType OperationType => OperationType.Delete; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Delete entity from " + EntitySet.Name; + // override the summary using the request.Description. + var request = Context.FindRequest(EntitySet.EntityType(), OperationType.ToString()); + if (request != null && request.Description != null) + { + operation.Summary = request.Description; + } + + // OperationId + if (Context.Settings.OperationId) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = EntitySet.Name + "." + typeName + ".Delete" + Utils.UpperFirstChar(typeName); + } + } + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + + operation.Parameters.Add(new OpenApiParameter + { + Name = "If-Match", + In = ParameterLocation.Header, + Description = "ETag", + Schema = new OpenApiSchema + { + Type = "string" + } + }); + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { Constants.StatusCode204, Constants.StatusCode204.GetResponse() }, + { Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse() } + }; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntityGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntityGetOperationHandler.cs new file mode 100644 index 0000000..018fe3c --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntityGetOperationHandler.cs @@ -0,0 +1,113 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Retrieve an Entity: + /// The Path Item Object for the entity contains the keyword get with an Operation Object as value + /// that describes the capabilities for retrieving a single entity. + /// + internal class EntityGetOperationHandler : EntitySetOperationHandler + { + /// + public override OperationType OperationType => OperationType.Get; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Get entity from " + EntitySet.Name + " by key"; + + // override the summary using the request.Description. + var request = Context.FindRequest(EntitySet.EntityType(), OperationType.ToString()); + if (request != null && request.Description != null) + { + operation.Summary = request.Description; + } + + // OperationId + if (Context.Settings.OperationId) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = EntitySet.Name + "." + typeName + ".Get" + Utils.UpperFirstChar(typeName); + } + } + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + + // $select + OpenApiParameter parameter = Context.CreateSelect(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $expand + parameter = Context.CreateExpand(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + var request = Context.FindRequest(EntitySet.EntityType(), OperationType.ToString()); + AppendCustomParameters(operation, request); + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { + Constants.StatusCode200, + new OpenApiResponse + { + Description = "Retrieved entity", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet.EntityType().FullName() + } + } + } + } + }, + Links = Context.CreateLinks(EntitySet) + } + } + }; + operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); + } + + /// + protected override void SetSecurity(OpenApiOperation operation) + { + var request = Context.FindRequest(EntitySet.EntityType(), OperationType.ToString()); + if (request != null) + { + operation.Security = Context.CreateSecurityRequirements(request.SecuritySchemes).ToList(); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntityPatchOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntityPatchOperationHandler.cs new file mode 100644 index 0000000..99e5284 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntityPatchOperationHandler.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Update an Entity + /// The Path Item Object for the entity set contains the keyword patch with an Operation Object as value + /// that describes the capabilities for updating the entity. + /// + internal class EntityPatchOperationHandler : EntitySetOperationHandler + { + /// + public override OperationType OperationType => OperationType.Patch; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Update entity in " + EntitySet.Name; + // override the summary using the request.Description. + var request = Context.FindRequest(EntitySet.EntityType(), OperationType.ToString()); + if (request != null && request.Description != null) + { + operation.Summary = request.Description; + } + + // OperationId + if (Context.Settings.OperationId) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = EntitySet.Name + "." + typeName + ".Update" + Utils.UpperFirstChar(typeName); + } + } + + /// + protected override void SetRequestBody(OpenApiOperation operation) + { + operation.RequestBody = new OpenApiRequestBody + { + Required = true, + Description = "New property values", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet.EntityType().FullName() + } + } + } + } + } + }; + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { Constants.StatusCode204, Constants.StatusCode204.GetResponse() }, + { Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse() } + }; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetGetOperationHandler.cs new file mode 100644 index 0000000..3fbb474 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetGetOperationHandler.cs @@ -0,0 +1,166 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Query a Collection of Entities, it's a "Get" operation for + /// The Path Item Object for the entity set contains the keyword get with an Operation Object as value + /// that describes the capabilities for querying the entity set. + /// For example: "/users" + /// + internal class EntitySetGetOperationHandler : EntitySetOperationHandler + { + /// + public override OperationType OperationType => OperationType.Get; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Get entities from " + EntitySet.Name; + var request = Context.FindRequest(EntitySet, OperationType.ToString()); + if (request != null && request.Description != null) + { + operation.Summary = request.Description; + } + + // OperationId + if (Context.Settings.OperationId) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = EntitySet.Name + "." + typeName + ".List" + Utils.UpperFirstChar(typeName); + } + } + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + + // The parameters array contains Parameter Objects for all system query options allowed for this collection, + // and it does not list system query options not allowed for this collection, see terms + // Capabilities.TopSupported, Capabilities.SkipSupported, Capabilities.SearchRestrictions, + // Capabilities.FilterRestrictions, and Capabilities.CountRestrictions + // $top + OpenApiParameter parameter = Context.CreateTop(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $skip + parameter = Context.CreateSkip(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $search + parameter = Context.CreateSearch(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $filter + parameter = Context.CreateFilter(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $count + parameter = Context.CreateCount(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // the syntax of the system query options $expand, $select, and $orderby is too flexible + // to be formally described with OpenAPI Specification means, yet the typical use cases + // of just providing a comma-separated list of properties can be expressed via an array-valued + // parameter with an enum constraint + // $order + parameter = Context.CreateOrderBy(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $select + parameter = Context.CreateSelect(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $expand + parameter = Context.CreateExpand(EntitySet); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + var request = Context.FindRequest(EntitySet, OperationType.ToString()); + AppendCustomParameters(operation, request); + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { + Constants.StatusCode200, + new OpenApiResponse + { + Description = "Retrieved entities", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Title = "Collection of " + EntitySet.EntityType().Name, + Type = "object", + Properties = new Dictionary + { + { + "value", + new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet.EntityType().FullName() + } + } + } + } + } + } + } + } + } + } + } + }; + + operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetOperationHandler.cs new file mode 100644 index 0000000..0832607 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetOperationHandler.cs @@ -0,0 +1,46 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for entity set operation. + /// + internal abstract class EntitySetOperationHandler : OperationHandler + { + /// + /// Gets/sets the . + /// + protected IEdmEntitySet EntitySet { get; private set; } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + // get the entity set. + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + EntitySet = navigationSourceSegment.NavigationSource as IEdmEntitySet; + base.Initialize(context, path); + } + + /// + protected override void SetTags(OpenApiOperation operation) + { + OpenApiTag tag = new OpenApiTag + { + Name = EntitySet.Name + "." + EntitySet.EntityType().Name, + }; + tag.Extensions.Add(Constants.xMsTocType, new OpenApiString("page")); + operation.Tags.Add(tag); + + Context.AppendTag(tag); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs new file mode 100644 index 0000000..7e2da34 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs @@ -0,0 +1,114 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Create an Entity: + /// The Path Item Object for the entity set contains the keyword "post" with an Operation Object as value + /// that describes the capabilities for creating new entities. + /// + internal class EntitySetPostOperationHandler : EntitySetOperationHandler + { + /// + public override OperationType OperationType => OperationType.Post; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Add new entity to " + EntitySet.Name; + var request = Context.FindRequest(EntitySet, OperationType.ToString()); + if (request != null && request.Description != null) + { + operation.Summary = request.Description; + } + + // OperationId + if (Context.Settings.OperationId) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = EntitySet.Name + "." + typeName + ".Create" + Utils.UpperFirstChar(typeName); + } + } + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + + var request = Context.FindRequest(EntitySet, OperationType.ToString()); + AppendCustomParameters(operation, request); + } + + /// + protected override void SetRequestBody(OpenApiOperation operation) + { + // The requestBody field contains a Request Body Object for the request body + // that references the schema of the entity set’s entity type in the global schemas. + operation.RequestBody = new OpenApiRequestBody + { + Required = true, + Description = "New entity", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet.EntityType().FullName() + } + } + } + } + } + }; + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { + Constants.StatusCode201, + new OpenApiResponse + { + Description = "Created entity", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet.EntityType().FullName() + } + } + } + } + } + } + } + }; + + operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandler.cs new file mode 100644 index 0000000..cd9d807 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandler.cs @@ -0,0 +1,29 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// An interface to create a based on . + /// + internal interface IOperationHandler + { + /// + /// The operation type. + /// + OperationType OperationType { get; } + + /// + /// Create the . + /// + /// The context. + /// The OData path. + /// The created . + OpenApiOperation CreateOperation(ODataContext context, ODataPath path); + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandlerProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandlerProvider.cs new file mode 100644 index 0000000..e2e3a63 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/IOperationHandlerProvider.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// An interface to provider . + /// + internal interface IOperationHandlerProvider + { + /// + /// Get the . + /// + /// The path type. + /// The operation type. + /// The corresponding . + IOperationHandler GetHandler(ODataPathType pathType, OperationType operationType); + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyGetOperationHandler.cs new file mode 100644 index 0000000..ca3f487 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyGetOperationHandler.cs @@ -0,0 +1,159 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Retrieve a navigation property from a navigation source. + /// The Path Item Object for the entity contains the keyword get with an Operation Object as value + /// that describes the capabilities for retrieving a navigation property form a navigation source. + /// + internal class NavigationPropertyGetOperationHandler : NavigationPropertyOperationHandler + { + /// + public override OperationType OperationType => OperationType.Get; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Get " + NavigationProperty.Name + " from " + NavigationSource.Name; + + // OperationId + if (Context.Settings.OperationId) + { + string prefix = "Get"; + if (!LastSegmentIsKeySegment && NavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + prefix = "List"; + } + + /* + string key = NavigationSource.Name + "." + NavigationProperty.Name + "-" + prefix + Utils.UpperFirstChar(NavigationProperty.ToEntityType().Name); + int index = Context.GetIndex(key); + operation.OperationId = NavigationSource.Name + "." + NavigationProperty.Name + index + "-" + prefix + Utils.UpperFirstChar(NavigationProperty.ToEntityType().Name); + */ + operation.OperationId = GetOperationId(prefix); + } + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { + Constants.StatusCode200, + new OpenApiResponse + { + Description = "Retrieved navigation property", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = NavigationProperty.ToEntityType().FullName() + } + } + } + } + } + } + } + }; + operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); + } + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + + if (operation.Parameters == null) + { + operation.Parameters = new List(); + } + + if (NavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + // The parameters array contains Parameter Objects for system query options allowed for this entity set, + // and it does not list system query options not allowed for this entity set. + OpenApiParameter parameter = Context.CreateTop(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateSkip(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateSearch(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateFilter(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateCount(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateOrderBy(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateSelect(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateExpand(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + } + else + { + OpenApiParameter parameter = Context.CreateSelect(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + parameter = Context.CreateExpand(NavigationProperty); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs new file mode 100644 index 0000000..9d63b77 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs @@ -0,0 +1,136 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for operation of . + /// + internal abstract class NavigationPropertyOperationHandler : OperationHandler + { + /// + /// Gets the navigation property. + /// + protected IEdmNavigationProperty NavigationProperty { get; private set; } + + /// + /// Gets the navigation source. + /// + protected IEdmNavigationSource NavigationSource { get; private set; } + + /// + /// Gets a bool value indicating whether the last segment is a key segment. + /// + protected bool LastSegmentIsKeySegment { get; private set; } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + NavigationSource = navigationSourceSegment.NavigationSource; + + LastSegmentIsKeySegment = path.LastSegment is ODataKeySegment; + + ODataNavigationPropertySegment npSegment = path.LastSegment as ODataNavigationPropertySegment; + if (npSegment == null) + { + npSegment = path.Segments[path.Count - 2] as ODataNavigationPropertySegment; + } + NavigationProperty = npSegment.NavigationProperty; + + base.Initialize(context, path); + } + + /// + protected override void SetTags(OpenApiOperation operation) + { + IList items = new List + { + NavigationSource.Name + }; + + foreach (var segment in Path.Segments.Skip(1)) + { + if (segment is ODataNavigationPropertySegment) + { + ODataNavigationPropertySegment npSegment = (ODataNavigationPropertySegment)segment; + if (npSegment.NavigationProperty == NavigationProperty) + { + items.Add(NavigationProperty.ToEntityType().Name); + break; + } + else + { + if (items.Count >= Context.Settings.TagDepth - 1) + { + items.Add(npSegment.NavigationProperty.ToEntityType().Name); + break; + } + else + { + items.Add(segment.Name); + } + } + } + } + + string name = string.Join(".", items); + OpenApiTag tag = new OpenApiTag + { + // Name = NavigationSource.Name + "." + NavigationProperty.ToEntityType().Name, + Name = name + }; + tag.Extensions.Add(Constants.xMsTocType, new OpenApiString("page")); + operation.Tags.Add(tag); + + Context.AppendTag(tag); + } + + protected string GetOperationId(string prefix) + { + IList items = new List + { + NavigationSource.Name + }; + + var lastpath = Path.Segments.Last(c => c is ODataNavigationPropertySegment); + foreach (var segment in Path.Segments.Skip(1)) + { + if (segment is ODataNavigationPropertySegment) + { + ODataNavigationPropertySegment npSegment = (ODataNavigationPropertySegment)segment;/* + if (npSegment.NavigationProperty == NavigationProperty) + { + items.Add(prefix + Utils.UpperFirstChar(NavigationProperty.ToEntityType().Name)); + break; + } + else + { + items.Add(segment.Name); + }*/ + if (segment == lastpath) + { + items.Add(prefix + Utils.UpperFirstChar(segment.Name)); + break; + } + else + { + items.Add(segment.Name); + } + } + } + + return string.Join(".", items); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPatchOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPatchOperationHandler.cs new file mode 100644 index 0000000..ccb83bc --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPatchOperationHandler.cs @@ -0,0 +1,78 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Update a navigation property for a navigation source. + /// The Path Item Object for the entity set contains the keyword patch with an Operation Object as value + /// that describes the capabilities for updating the navigation property for a navigation source. + /// + internal class NavigationPropertyPatchOperationHandler : NavigationPropertyOperationHandler + { + /// + public override OperationType OperationType => OperationType.Patch; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Update the navigation property " + NavigationProperty.Name + " in " + NavigationSource.Name; + + // OperationId + if (Context.Settings.OperationId) + { + string prefix = "Update"; + //string key = NavigationSource.Name + "." + NavigationProperty.Name + "-" + prefix + Utils.UpperFirstChar(NavigationProperty.ToEntityType().Name); + // int index = Context.GetIndex(key); + // operation.OperationId = NavigationSource.Name + "." + NavigationProperty.Name + index + "-" + prefix + Utils.UpperFirstChar(NavigationProperty.ToEntityType().Name); + + operation.OperationId = GetOperationId(prefix); + } + } + + /// + protected override void SetRequestBody(OpenApiOperation operation) + { + operation.RequestBody = new OpenApiRequestBody + { + Required = true, + Description = "New navigation property values", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = NavigationProperty.ToEntityType().FullName() + } + } + } + } + } + }; + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { Constants.StatusCode204, Constants.StatusCode204.GetResponse() }, + { Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse() } + }; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPostOperationHandler.cs new file mode 100644 index 0000000..e1a4d72 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyPostOperationHandler.cs @@ -0,0 +1,102 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Create a navigation for a navigation source. + /// The Path Item Object for the entity set contains the keyword delete with an Operation Object as value + /// that describes the capabilities for create a navigation for a navigation source. + /// + internal class NavigationPropertyPostOperationHandler : NavigationPropertyOperationHandler + { + /// + public override OperationType OperationType => OperationType.Post; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Create new navigation property to " + NavigationProperty.Name + " for " + NavigationSource.Name; + + // OperationId + if (Context.Settings.OperationId) + { + string prefix = "Create"; + /* + string key = NavigationSource.Name + "." + NavigationProperty.Name + "-" + prefix + Utils.UpperFirstChar(NavigationProperty.ToEntityType().Name); + int index = Context.GetIndex(key); + operation.OperationId = NavigationSource.Name + "." + NavigationProperty.Name + index + "-" + prefix + Utils.UpperFirstChar(NavigationProperty.ToEntityType().Name); + */ + operation.OperationId = GetOperationId(prefix); + } + } + + /// + protected override void SetRequestBody(OpenApiOperation operation) + { + operation.RequestBody = new OpenApiRequestBody + { + Required = true, + Description = "New navigation property", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = NavigationProperty.ToEntityType().FullName() + } + } + } + } + } + }; + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { + Constants.StatusCode201, + new OpenApiResponse + { + Description = "Created navigation property.", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = NavigationProperty.ToEntityType().FullName() + } + } + } + } + } + } + } + }; + operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandler.cs new file mode 100644 index 0000000..71a943d --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandler.cs @@ -0,0 +1,212 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Annotations; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for handler. + /// + internal abstract class OperationHandler : IOperationHandler + { + /// + public abstract OperationType OperationType { get; } + + /// + public virtual OpenApiOperation CreateOperation(ODataContext context, ODataPath path) + { + Context = context ?? throw Error.ArgumentNull(nameof(context)); + + Path = path ?? throw Error.ArgumentNull(nameof(path)); + + // Initialize the object ahead. + Initialize(context, path); + + OpenApiOperation operation = new OpenApiOperation(); + + // Description / Summary / OperationId + SetBasicInfo(operation); + + // Security + SetSecurity(operation); + + // Responses + SetResponses(operation); + + // RequestBody + SetRequestBody(operation); + + // Parameters + SetParameters(operation); + + // Tags + SetTags(operation); + + // Extensions + SetExtensions(operation); + + return operation; + } + + /// + /// Gets the OData context. + /// + protected ODataContext Context { get; private set; } + + /// + /// Gets the OData path. + /// + protected ODataPath Path { get; private set; } + + /// + /// Initialize the handler. + /// + /// The context. + /// The path. + protected virtual void Initialize(ODataContext context, ODataPath path) + { } + + /// + /// Set the basic information for . + /// + /// The . + protected virtual void SetBasicInfo(OpenApiOperation operation) + { } + + /// + /// Set the security information for . + /// + /// The . + protected virtual void SetSecurity(OpenApiOperation operation) + { } + + /// + /// Set the responses information for . + /// + /// The . + protected virtual void SetResponses(OpenApiOperation operation) + { } + + /// + /// Set the request body information for . + /// + /// The . + protected virtual void SetRequestBody(OpenApiOperation operation) + { } + + /// + /// Set the parameters information for . + /// + /// The . + protected virtual void SetParameters(OpenApiOperation operation) + { + foreach (ODataKeySegment keySegment in Path.OfType()) + { + foreach (var p in Context.CreateKeyParameters(keySegment.EntityType)) + { + operation.Parameters.Add(p); + } + } + } + + /// + /// Set the Tags information for . + /// + /// The . + protected virtual void SetTags(OpenApiOperation operation) + { } + + /// + /// Set the Extensions information for . + /// + /// The . + protected virtual void SetExtensions(OpenApiOperation operation) + { + operation.Extensions.Add(Constants.xMsDosOperationType, new OpenApiString("operation")); + } + + protected static void AppendCustomParameters(OpenApiOperation operation, HttpRequest request) + { + if (request == null) + { + return; + } + + if (operation.Parameters == null) + { + operation.Parameters = new List(); + } + + if (request.CustomQueryOptions != null) + { + AppendCustomParameters(operation.Parameters, request.CustomQueryOptions, ParameterLocation.Query); + } + + if (request.CustomQueryOptions != null) + { + AppendCustomParameters(operation.Parameters, request.CustomHeaders, ParameterLocation.Header); + } + } + + private static void AppendCustomParameters(IList parameters, IList headers, ParameterLocation location) + { + foreach (var param in headers) + { + OpenApiParameter parameter = new OpenApiParameter + { + In = location, + Name = param.Name, + Description = param.Description, + Schema = new OpenApiSchema + { + // Type = param.Type + }, + Required = param.Required ?? false + }; + + if (param.DocumentationURL != null) + { + parameter.Example = new OpenApiString(param.DocumentationURL?? "N/A"); + } + + if (param.ExampleValues != null) + { + parameter.Examples = new Dictionary(); + int index = 1; + foreach (var example in param.ExampleValues) + { + OpenApiExample ex = new OpenApiExample + { + Description = example.Description + }; + + if (example is ExternalExample) + { + var externalExample = (ExternalExample)example; + ex.Value = new OpenApiString(externalExample.ExternalValue ?? "N/A"); + } + else + { + var inlineExample = (InlineExample)example; + ex.Value = new OpenApiString(inlineExample.InlineValue ?? "N/A"); + } + + parameter.Examples.Add("example-" + index++, ex); + } + } + + parameters.Add(parameter); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandlerProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandlerProvider.cs new file mode 100644 index 0000000..c412506 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandlerProvider.cs @@ -0,0 +1,77 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using System.Collections.Generic; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// A class to provide the . + /// + internal class OperationHandlerProvider : IOperationHandlerProvider + { + private IDictionary> _handlers; + + /// + /// Initializes a new instance of class. + /// + public OperationHandlerProvider() + { + _handlers = new Dictionary>(); + + // entity set (Get/Post) + _handlers[ODataPathType.EntitySet] = new Dictionary + { + {OperationType.Get, new EntitySetGetOperationHandler() }, + {OperationType.Post, new EntitySetPostOperationHandler() } + }; + + // entity (Get/Patch/Delete) + _handlers[ODataPathType.Entity] = new Dictionary + { + {OperationType.Get, new EntityGetOperationHandler() }, + {OperationType.Patch, new EntityPatchOperationHandler() }, + {OperationType.Delete, new EntityDeleteOperationHandler() } + }; + + // singleton (Get/Patch) + _handlers[ODataPathType.Singleton] = new Dictionary + { + {OperationType.Get, new SingletonGetOperationHandler() }, + {OperationType.Patch, new SingletonPatchOperationHandler() } + }; + + // edm operation (Get|Post) + _handlers[ODataPathType.Operation] = new Dictionary + { + {OperationType.Get, new EdmFunctionOperationHandler() }, + {OperationType.Post, new EdmActionOperationHandler() } + }; + + // edm operation import (Get|Post) + _handlers[ODataPathType.OperationImport] = new Dictionary + { + {OperationType.Get, new EdmFunctionImportOperationHandler() }, + {OperationType.Post, new EdmActionImportOperationHandler() } + }; + + // navigation property (Get/Patch/Post) + _handlers[ODataPathType.NavigationProperty] = new Dictionary + { + {OperationType.Get, new NavigationPropertyGetOperationHandler() }, + {OperationType.Patch, new NavigationPropertyPatchOperationHandler() }, + {OperationType.Post, new NavigationPropertyPostOperationHandler() } + }; + } + + /// + public IOperationHandler GetHandler(ODataPathType pathType, OperationType operationType) + { + return _handlers[pathType][operationType]; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonGetOperationHandler.cs new file mode 100644 index 0000000..bcedf66 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonGetOperationHandler.cs @@ -0,0 +1,102 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Retrieve a Singleton + /// The Path Item Object for the entity set contains the keyword get with an Operation Object as value + /// that describes the capabilities for retrieving the singleton. + /// + internal class SingletonGetOperationHandler : SingletonOperationHandler + { + /// + public override OperationType OperationType => OperationType.Get; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Get " + Singleton.Name; + var request = Context.FindRequest(Singleton, OperationType.ToString()); + if (request != null && request.Description != null) + { + operation.Summary = request.Description; + } + + // OperationId + if (Context.Settings.OperationId) + { + string typeName = Singleton.EntityType().Name; + operation.OperationId = Singleton.Name + "." + typeName + ".Get" + Utils.UpperFirstChar(typeName); + } + } + + /// + protected override void SetParameters(OpenApiOperation operation) + { + base.SetParameters(operation); + + operation.Parameters = new List(); + IEdmEntityType entityType = Singleton.EntityType(); + + // $select + OpenApiParameter parameter = Context.CreateSelect(Singleton); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + // $expand + parameter = Context.CreateExpand(Singleton); + if (parameter != null) + { + operation.Parameters.Add(parameter); + } + + var request = Context.FindRequest(Singleton, OperationType.ToString()); + AppendCustomParameters(operation, request); + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { + Constants.StatusCode200, + new OpenApiResponse + { + Description = "Retrieved entity", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = Singleton.EntityType().FullName() + } + } + } + } + } + } + }, + }; + operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse()); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonOperationHandler.cs new file mode 100644 index 0000000..bd5e0aa --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonOperationHandler.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for operation of . + /// + internal abstract class SingletonOperationHandler : OperationHandler + { + /// + /// Gets the . + /// + protected IEdmSingleton Singleton { get; private set; } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + + Singleton = navigationSourceSegment.NavigationSource as IEdmSingleton; + + base.Initialize(context, path); + } + + /// + protected override void SetTags(OpenApiOperation operation) + { + OpenApiTag tag = new OpenApiTag + { + Name = Singleton.Name + "." + Singleton.EntityType().Name, + }; + tag.Extensions.Add(Constants.xMsTocType, new OpenApiString("page")); + operation.Tags.Add(tag); + + Context.AppendTag(tag); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonPatchOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonPatchOperationHandler.cs new file mode 100644 index 0000000..b28b2c1 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/SingletonPatchOperationHandler.cs @@ -0,0 +1,79 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Generator; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Update a Singleton + /// The Path Item Object for the entity set contains the keyword patch with an Operation Object as value + /// that describes the capabilities for updating the singleton, unless the singleton is read-only. + /// + internal class SingletonPatchOperationHandler : SingletonOperationHandler + { + /// + public override OperationType OperationType => OperationType.Patch; + + /// + protected override void SetBasicInfo(OpenApiOperation operation) + { + // Summary + operation.Summary = "Update " + Singleton.Name; + var request = Context.FindRequest(Singleton, OperationType.ToString()); + if (request != null && request.Description != null) + { + operation.Summary = request.Description; + } + + // OperationId + if (Context.Settings.OperationId) + { + string typeName = Singleton.EntityType().Name; + operation.OperationId = Singleton.Name + "." + typeName + ".Update" + Utils.UpperFirstChar(typeName); + } + } + + /// + protected override void SetRequestBody(OpenApiOperation operation) + { + operation.RequestBody = new OpenApiRequestBody + { + Required = true, + Description = "New property values", + Content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = Singleton.EntityType().FullName() + } + } + } + } + } + }; + } + + /// + protected override void SetResponses(OpenApiOperation operation) + { + operation.Responses = new OpenApiResponses + { + { Constants.StatusCode204, Constants.StatusCode204.GetResponse() }, + { Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse() } + }; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs new file mode 100644 index 0000000..8de3c61 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Capabilities; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Path item handler for Entity Set. + /// + internal class EntityPathItemHandler : EntitySetPathItemHandler + { + /// + protected override void SetOperations(OpenApiPathItem item) + { + IndexableByKey index = new IndexableByKey(Context.Model, EntitySet); + if (index.IsSupported()) + { + AddOperation(item, OperationType.Get); + } + + UpdateRestrictions update = new UpdateRestrictions(Context.Model, EntitySet); + if (update.IsUpdatable()) + { + AddOperation(item, OperationType.Patch); + } + + DeleteRestrictions delete = new DeleteRestrictions(Context.Model, EntitySet); + if (delete.IsDeletable()) + { + AddOperation(item, OperationType.Delete); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs new file mode 100644 index 0000000..1f45076 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Capabilities; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Create a for . + /// + internal class EntitySetPathItemHandler : PathItemHandler + { + /// + /// Gets the entity set. + /// + protected IEdmEntitySet EntitySet { get; private set; } + + /// + protected override void SetOperations(OpenApiPathItem item) + { + AddOperation(item, OperationType.Get); + + InsertRestrictions insert = new InsertRestrictions(Context.Model, EntitySet); + if (insert.IsInsertable()) + { + AddOperation(item, OperationType.Post); + } + } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + EntitySet = navigationSourceSegment.NavigationSource as IEdmEntitySet; + + base.Initialize(context, path); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandler.cs new file mode 100644 index 0000000..2b5029f --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandler.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Interface for . + /// + internal interface IPathItemHandler + { + /// + /// Create based on and . + /// + /// The context. + /// The path. + /// The created . + OpenApiPathItem CreatePathItem(ODataContext context, ODataPath path); + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandlerProvider.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandlerProvider.cs new file mode 100644 index 0000000..80e9c81 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/IPathItemHandlerProvider.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Interface for . + /// + internal interface IPathItemHandlerProvider + { + /// + /// Get the based on the path type. + /// + /// The path type. + /// The . + IPathItemHandler GetHandler(ODataPathType pathType); + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs new file mode 100644 index 0000000..47b19b6 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs @@ -0,0 +1,136 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Capabilities; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.Any; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Create a for a single . + /// + internal class NavigationPropertyPathItemHandler : PathItemHandler + { + /// + /// Gets the navigation property. + /// + public IEdmNavigationProperty NavigationProperty { get; private set; } + + /// + /// Gets the navigation source. + /// + public IEdmNavigationSource NavigationSource { get; private set; } + + /// + /// Gets a bool value indicating whether the last segment is a key segment. + /// + protected bool LastSegmentIsKeySegment { get; private set; } + + /// + protected override void SetOperations(OpenApiPathItem item) + { + // contaiment: Get / (Post - Collection | Patch - Single) + // non-containment: only Get + AddOperation(item, OperationType.Get); + + if (NavigationProperty.ContainsTarget) + { + if (NavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + if (LastSegmentIsKeySegment) + { + UpdateRestrictions update = new UpdateRestrictions(Context.Model, NavigationProperty); + if (update.IsUpdatable()) + { + AddOperation(item, OperationType.Patch); + } + } + else + { + InsertRestrictions insert = new InsertRestrictions(Context.Model, NavigationProperty); + if (insert.IsInsertable()) + { + AddOperation(item, OperationType.Post); + } + } + } + else + { + UpdateRestrictions update = new UpdateRestrictions(Context.Model, NavigationProperty); + if (update.IsUpdatable()) + { + AddOperation(item, OperationType.Patch); + } + } + } + } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + NavigationSource = navigationSourceSegment.NavigationSource; + + LastSegmentIsKeySegment = path.LastSegment is ODataKeySegment; + ODataNavigationPropertySegment npSegment = path.LastSegment as ODataNavigationPropertySegment; + if (npSegment == null) + { + npSegment = path.Segments[path.Count - 2] as ODataNavigationPropertySegment; + } + NavigationProperty = npSegment.NavigationProperty; + + base.Initialize(context, path); + } + + /// + protected override void SetExtensions(OpenApiPathItem item) + { + IList samePaths = new List(); + foreach (var path in Context.Paths.Where(p => p.PathType == ODataPathType.NavigationProperty && p != Path)) + { + bool lastIsKeySegment = path.LastSegment is ODataKeySegment; + if (LastSegmentIsKeySegment != lastIsKeySegment) + { + continue; + } + + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + if (NavigationSource != navigationSourceSegment.NavigationSource) + { + continue; + } + + ODataNavigationPropertySegment npSegment = path.LastSegment as ODataNavigationPropertySegment; + if (npSegment == null) + { + npSegment = path.Segments[path.Count - 2] as ODataNavigationPropertySegment; + } + if (NavigationProperty != npSegment.NavigationProperty) + { + continue; + } + + samePaths.Add(path); + } + + if (samePaths.Any()) + { + OpenApiArray array = new OpenApiArray(); + foreach(var p in samePaths) + { + array.Add(new OpenApiString(p.ToString())); + } + + item.Extensions.Add(Constants.xMsDosGroupPath, array); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs new file mode 100644 index 0000000..9c4bed6 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Create a for a single . + /// + internal class OperationImportPathItemHandler : PathItemHandler + { + /// + /// Gets the operation import. + /// + public IEdmOperationImport EdmOperationImport { get; private set; } + + /// + protected override void SetOperations(OpenApiPathItem item) + { + if (EdmOperationImport.IsActionImport()) + { + // Each action import is represented as a name/value pair whose name is the service-relative + // resource path of the action import prepended with a forward slash, and whose value is a Path + // Item Object containing the keyword post with an Operation Object as value that describes + // how to invoke the action import. + AddOperation(item, OperationType.Post); + } + else + { + // Each function import is represented as a name/value pair whose name is the service-relative + // resource path of the function import prepended with a forward slash, and whose value is a Path + // Item Object containing the keyword get with an Operation Object as value that describes + // how to invoke the function import. + AddOperation(item, OperationType.Get); + } + } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataOperationImportSegment operationImportSegment = path.FirstSegment as ODataOperationImportSegment; + EdmOperationImport = operationImportSegment.OperationImport; + + base.Initialize(context, path); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs new file mode 100644 index 0000000..2cd5a3d --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs @@ -0,0 +1,87 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Create the bound operations for the navigation source. + /// + internal class OperationPathItemHandler : PathItemHandler + { + /// + /// Gets the Edm operation. + /// + public IEdmOperation EdmOperation { get; private set; } + + /// + protected override void SetOperations(OpenApiPathItem item) + { + if (EdmOperation.IsAction()) + { + // The Path Item Object for a bound action contains the keyword post, + // The value of the operation keyword is an Operation Object that describes how to invoke the action. + AddOperation(item, OperationType.Post); + } + else + { + // The Path Item Object for a bound function contains the keyword get, + // The value of the operation keyword is an Operation Object that describes how to invoke the function. + AddOperation(item, OperationType.Get); + } + } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataOperationSegment operationSegment = path.LastSegment as ODataOperationSegment; + EdmOperation = operationSegment.Operation; + base.Initialize(context, path); + } + + /// + protected override void SetExtensions(OpenApiPathItem item) + { + ODataNavigationSourceSegment navigationSourceSegment = Path.FirstSegment as ODataNavigationSourceSegment; + IEdmNavigationSource currentNavSource = navigationSourceSegment.NavigationSource; + + IList samePaths = new List(); + foreach (var path in Context.Paths.Where(p => p.PathType == ODataPathType.Operation && p != Path)) + { + navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + if (currentNavSource != navigationSourceSegment.NavigationSource) + { + continue; + } + + ODataOperationSegment operationSegment = path.LastSegment as ODataOperationSegment; + if (EdmOperation.FullName() != operationSegment.Operation.FullName()) + { + continue; + } + + samePaths.Add(path); + } + + if (samePaths.Any()) + { + OpenApiArray array = new OpenApiArray(); + foreach (var p in samePaths) + { + array.Add(new OpenApiString(p.ToString())); + } + + item.Extensions.Add(Constants.xMsDosGroupPath, array); + } + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs new file mode 100644 index 0000000..35ecfe2 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs @@ -0,0 +1,79 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Operation; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Base class for . + /// + internal abstract class PathItemHandler : IPathItemHandler + { + /// + /// Gets the OData Context. + /// + protected ODataContext Context { get; private set; } + + /// + /// Gets the OData Path. + /// + protected ODataPath Path { get; private set; } + + /// + public virtual OpenApiPathItem CreatePathItem(ODataContext context, ODataPath path) + { + Context = context ?? throw Error.ArgumentNull(nameof(context)); + + Path = path ?? throw Error.ArgumentNull(nameof(path)); + + Initialize(context, path); + + OpenApiPathItem item = new OpenApiPathItem(); + + SetOperations(item); + + SetExtensions(item); + + return item; + } + + /// + /// Set the operation for the path item. + /// + /// The path item. + protected abstract void SetOperations(OpenApiPathItem item); + + /// + /// Initialize the Handler. + /// + /// The context. + /// The path. + protected virtual void Initialize(ODataContext context, ODataPath path) + { } + + /// + /// Set the extensions for the path item. + /// + /// The path item. + protected virtual void SetExtensions(OpenApiPathItem item) + { } + + /// + /// Add one operation into path item. + /// + /// The path item. + /// The operatin type. + protected void AddOperation(OpenApiPathItem item, OperationType operationType) + { + IOperationHandlerProvider provider = Context.OperationHanderProvider; + IOperationHandler operationHander = provider.GetHandler(Path.PathType, operationType); + item.AddOperation(operationType, operationHander.CreateOperation(Context, Path)); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandlerProvider.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandlerProvider.cs new file mode 100644 index 0000000..00351be --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandlerProvider.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Default path item handler provider. + /// + internal class PathItemHandlerProvider : IPathItemHandlerProvider + { + private IDictionary _handlers = new Dictionary + { + // Entity + { ODataPathType.EntitySet, new EntitySetPathItemHandler() }, + + // Entity + { ODataPathType.Entity, new EntityPathItemHandler() }, + + // Singleton + { ODataPathType.Singleton, new SingletonPathItemHandler() }, + + // Navigation property + { ODataPathType.NavigationProperty, new NavigationPropertyPathItemHandler() }, + + // Edm Operation + { ODataPathType.Operation, new OperationPathItemHandler() }, + + // Edm OperationImport + { ODataPathType.OperationImport, new OperationImportPathItemHandler() }, + }; + + /// + public IPathItemHandler GetHandler(ODataPathType pathType) + { + return _handlers[pathType]; + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs new file mode 100644 index 0000000..6280053 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Capabilities; +using Microsoft.OpenApi.OData.Edm; + +namespace Microsoft.OpenApi.OData.PathItem +{ + /// + /// Create a for . + /// + internal class SingletonPathItemHandler : PathItemHandler + { + /// + /// Gets the entity set. + /// + protected IEdmSingleton Singleton { get; private set; } + + /// + protected override void SetOperations(OpenApiPathItem item) + { + // Retrieve a singleton. + AddOperation(item, OperationType.Get); + + // Update a singleton + UpdateRestrictions update = new UpdateRestrictions(Context.Model, Singleton); + if (update.IsUpdatable()) + { + AddOperation(item, OperationType.Patch); + } + } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + Singleton = navigationSourceSegment.NavigationSource as IEdmSingleton; + base.Initialize(context, path); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.Designer.cs index 5808feb..2f545d8 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.Designer.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.Designer.cs @@ -69,6 +69,15 @@ namespace Microsoft.OpenApi.OData.Properties { } } + /// + /// Looks up a localized string similar to The authorization record type name '{0}' is not correct.. + /// + internal static string AuthorizationRecordTypeNameNotCorrect { + get { + return ResourceManager.GetString("AuthorizationRecordTypeNameNotCorrect", resourceCulture); + } + } + /// /// Looks up a localized string similar to The filed name of extension doesn't begin with x-.. /// diff --git a/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.resx b/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.resx index 4b69236..a1cae0e 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.resx +++ b/src/Microsoft.OpenApi.OData.Reader/Properties/SRResource.resx @@ -120,6 +120,9 @@ The argument '{0}' is null or empty. + + The authorization record type name '{0}' is not correct. + The filed name of extension doesn't begin with x-. diff --git a/src/OoasGui/MainForm.cs b/src/OoasGui/MainForm.cs index 47cf2a6..e3d2732 100644 --- a/src/OoasGui/MainForm.cs +++ b/src/OoasGui/MainForm.cs @@ -254,7 +254,7 @@ namespace OoasGui private void NavPathcheckBox_CheckedChanged(object sender, EventArgs e) { - Settings.NavigationPropertyPathItem = !Settings.NavigationPropertyPathItem; + Settings.EnableNavigationPropertyPath = !Settings.EnableNavigationPropertyPath; Convert(); } } diff --git a/src/OoasGui/OoasGui.csproj b/src/OoasGui/OoasGui.csproj index ab08b0c..4f923d2 100644 --- a/src/OoasGui/OoasGui.csproj +++ b/src/OoasGui/OoasGui.csproj @@ -32,11 +32,11 @@ 4 - - ..\..\packages\Microsoft.OData.Edm.7.3.1\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + ..\..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - ..\..\packages\Microsoft.OpenApi.1.0.0-beta012\lib\net46\Microsoft.OpenApi.dll + + ..\..\packages\Microsoft.OpenApi.1.0.1\lib\net46\Microsoft.OpenApi.dll diff --git a/src/OoasGui/packages.config b/src/OoasGui/packages.config index 7d4d870..aea1696 100644 --- a/src/OoasGui/packages.config +++ b/src/OoasGui/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Authorizations/AuthorizationProviderTest.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Authorizations/AuthorizationProviderTest.cs new file mode 100644 index 0000000..d2bad5d --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Authorizations/AuthorizationProviderTest.cs @@ -0,0 +1,131 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.OData.Authorizations; +using Xunit; + +namespace Microsoft.OpenApi.OData.Reader.Authorizations.Tests +{ + public class AuthorizationProviderTest + { + [Fact] + public void GetAuthorizationsThrowArgumentNullModel() + { + // Arrange & Act & Assert + Assert.Throws("model", + () => new AuthorizationProvider().GetAuthorizations(model: null, target: null)); + } + + [Fact] + public void GetAuthorizationsThrowArgumentNullTarget() + { + // Arrange & Act & Assert + Assert.Throws("target", + () => new AuthorizationProvider().GetAuthorizations(model: new EdmModel(), target: null)); + } + + [Fact] + public void GetAuthorizationsReturnsNullForTargetWithoutAuthorization() + { + // Arrange + EdmModel model = new EdmModel(); + EdmEntityContainer container = new EdmEntityContainer("NS", "Container"); + model.AddElement(container); + AuthorizationProvider provider = new AuthorizationProvider(); + + // Act & Assert + var authorizations = provider.GetAuthorizations(model, container); + + // Assert + Assert.Empty(authorizations); + } + + [Theory] + [InlineData("Entities")] + [InlineData("Me")] + public void GetAuthorizationsReturnsForEdmModelNavigationSourceWithAuthroizations(string name) + { + // Arrange + IEdmModel model = GetEdmModel(); + IEdmNavigationSource navigationSource = model.FindDeclaredNavigationSource(name); + Assert.NotNull(navigationSource); + AuthorizationProvider provider = new AuthorizationProvider(); + + // Act + var authorizations = provider.GetAuthorizations(model, navigationSource as IEdmVocabularyAnnotatable); + + // Assert + Assert.NotEmpty(authorizations); + Assert.Equal(2, authorizations.Count()); + Assert.IsType(authorizations.First()); + Assert.IsType(authorizations.Last()); + } + + private static IEdmModel GetEdmModel() + { + const string schema = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + IEdmModel parsedModel; + IEnumerable errors; + bool parsed = SchemaReader.TryParse(new XmlReader[] { XmlReader.Create(new StringReader(schema)) }, out parsedModel, out errors); + Assert.True(parsed); + return parsedModel; + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/ODataContextTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataContextTests.cs similarity index 54% rename from test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/ODataContextTests.cs rename to test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataContextTests.cs index 262ea85..6f9a03f 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/ODataContextTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataContextTests.cs @@ -4,10 +4,13 @@ // ------------------------------------------------------------ using System; +using System.Collections.Generic; +using System.IO; +using System.Text; using Microsoft.OpenApi.OData.Tests; using Xunit; -namespace Microsoft.OpenApi.OData.Generator.Tests +namespace Microsoft.OpenApi.OData.Edm.Tests { public class ODataContextTest { @@ -24,5 +27,24 @@ namespace Microsoft.OpenApi.OData.Generator.Tests // Arrange & Act & Assert Assert.Throws("settings", () => new ODataContext(EdmModelHelper.EmptyModel, settings: null)); } + + [Fact] + public void Test() + { + OpenApiConvertSettings settings = new OpenApiConvertSettings(); + settings.NavigationPropertyDepth = 7; + ODataContext context = new ODataContext(EdmModelHelper.GraphBetaModel, settings); + + IList paths = context.Paths; + + StringBuilder sb = new StringBuilder(); + foreach (var path in paths) + { + sb.Append(path.ToString()).Append("\n"); + } + File.WriteAllText("c:\\c.xml", sb.ToString()); + + Assert.Equal(321, paths.Count); + } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataKeySegmentTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataKeySegmentTests.cs new file mode 100644 index 0000000..361a6fe --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataKeySegmentTests.cs @@ -0,0 +1,66 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.OData.Edm; +using Xunit; + +namespace Microsoft.OpenApi.OData.Edm.Tests +{ + public class ODataKeySegmentTests + { + public IEdmEntityType Person { get; } + + public IEdmEntityType Customer { get; } + + public ODataKeySegmentTests() + { + var person = new EdmEntityType("NS", "Person"); + person.AddKeys(person.AddStructuralProperty("Id", EdmCoreModel.Instance.GetString(false))); + Person = person; + + var customer = new EdmEntityType("NS", "Customer"); + customer.AddKeys(customer.AddStructuralProperty("firstName", EdmCoreModel.Instance.GetString(false))); + customer.AddKeys(customer.AddStructuralProperty("lastName", EdmCoreModel.Instance.GetString(false))); + Customer = customer; + } + + [Fact] + public void KeySegmentConstructorThrowsArgumentNull() + { + Assert.Throws("entityType", () => new ODataKeySegment(null)); + } + + [Fact] + public void KeySegmentEntityTypePropertyReturnsSameEntityType() + { + // Arrange & Act + ODataKeySegment segment = new ODataKeySegment(Person); + + // Assert + Assert.Same(Person, segment.EntityType); + } + + [Fact] + public void KeySegmentToStringReturnsCorrectKeyString() + { + // Arrange & Act + ODataKeySegment segment = new ODataKeySegment(Person); + + // Assert + Assert.Equal("{Id}", segment.ToString()); + } + + [Fact] + public void KeySegmentToStringReturnsCorrectKeyStringForCompositeKeys() + { + // Arrange & Act + ODataKeySegment segment = new ODataKeySegment(Customer); + + // Assert + Assert.Equal("{firstName={firstName},lastName={lastName}}", segment.ToString()); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationPropertySegmentTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationPropertySegmentTests.cs new file mode 100644 index 0000000..932ab67 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationPropertySegmentTests.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Xunit; + +namespace Microsoft.OpenApi.OData.Edm.Tests +{ + public class ODataNavigationPropertySegmentTests + { + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationSourceSegmentTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationSourceSegmentTests.cs new file mode 100644 index 0000000..8ad7046 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataNavigationSourceSegmentTests.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Xunit; + +namespace Microsoft.OpenApi.OData.Edm.Tests +{ + public class ODataNavigationSourceSegmentTests + { + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationImportSegmentTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationImportSegmentTests.cs new file mode 100644 index 0000000..9d77c57 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationImportSegmentTests.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Xunit; + +namespace Microsoft.OpenApi.OData.Edm.Tests +{ + public class ODataOperationImportSegmentTests + { + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs new file mode 100644 index 0000000..bde1690 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Xunit; + +namespace Microsoft.OpenApi.OData.Edm.Tests +{ + public class ODataOperationSegmentTests + { + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathTests.cs new file mode 100644 index 0000000..65b8e76 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathTests.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Xunit; + +namespace Microsoft.OpenApi.OData.Edm.Tests +{ + public class ODataPathTests + { + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataTypeCastSegmentTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataTypeCastSegmentTests.cs new file mode 100644 index 0000000..e2a5fea --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataTypeCastSegmentTests.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.OData.Edm; +using Xunit; + +namespace Microsoft.OpenApi.OData.Edm.Tests +{ + public class ODataTypeCastSegmentTests + { + public IEdmEntityType Person { get; } + + public ODataTypeCastSegmentTests() + { + var person = new EdmEntityType("NS", "Person"); + person.AddKeys(person.AddStructuralProperty("Id", EdmCoreModel.Instance.GetString(false))); + Person = person; + } + + [Fact] + public void TypeCastSegmentConstructorThrowsArgumentNull() + { + Assert.Throws("entityType", () => new ODataTypeCastSegment(null)); + } + + [Fact] + public void TypeCastSegmentEntityTypePropertyReturnsSameEntityType() + { + // Arrange & Act + ODataTypeCastSegment segment = new ODataTypeCastSegment(Person); + + // Assert + Assert.Same(Person, segment.EntityType); + } + + [Fact] + public void TypeCastSegmentToStringReturnsCorrectKeyString() + { + // Arrange & Act + ODataTypeCastSegment segment = new ODataTypeCastSegment(Person); + + // Assert + Assert.Equal("NS.Person", segment.ToString()); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiComponentsGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiComponentsGeneratorTests.cs index b097394..49752a2 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiComponentsGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiComponentsGeneratorTests.cs @@ -5,6 +5,7 @@ using System; using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiDocumentGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiDocumentGeneratorTests.cs index 3788713..251ef78 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiDocumentGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiDocumentGeneratorTests.cs @@ -5,6 +5,7 @@ using System; using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiEdmTypeSchemaGeneratorTest.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiEdmTypeSchemaGeneratorTest.cs index e316c8e..aa39dee 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiEdmTypeSchemaGeneratorTest.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiEdmTypeSchemaGeneratorTest.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Generator; using Xunit; using Xunit.Abstractions; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiInfoGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiInfoGeneratorTests.cs index 41030da..c2c9ab7 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiInfoGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiInfoGeneratorTests.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using System; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; @@ -22,7 +23,7 @@ namespace Microsoft.OpenApi.OData.Generator.Tests } [Fact] - public void CreateInfoReturnsNullForEmptyModel() + public void CreateInfoReturnsNotNullForEmptyModel() { // Arrange ODataContext context = new ODataContext(EdmModelHelper.EmptyModel); diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiOperationGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiOperationGeneratorTests.cs index ef1810d..2046aab 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiOperationGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiOperationGeneratorTests.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs index 30de795..dd32395 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs index 8e112bc..a6b171e 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; @@ -54,35 +55,15 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.Equal(7, pathItems.Count); Assert.Contains("/People", pathItems.Keys); - Assert.Contains("/People('{UserName}')", pathItems.Keys); + Assert.Contains("/People/{UserName}", pathItems.Keys); Assert.Contains("/City", pathItems.Keys); - Assert.Contains("/City('{Name}')", pathItems.Keys); + Assert.Contains("/City/{Name}", pathItems.Keys); Assert.Contains("/CountryOrRegion", pathItems.Keys); - Assert.Contains("/CountryOrRegion('{Name}')", pathItems.Keys); + Assert.Contains("/CountryOrRegion/{Name}", pathItems.Keys); Assert.Contains("/Me", pathItems.Keys); } - #region EntitySet PathItem - [Fact] - public void CreateEntitySetPathItemThrowArgumentNullContext() - { - // Arrange - ODataContext context = null; - - // Act & Assert - Assert.Throws("context", () => context.CreateEntitySetPathItem(entitySet: null)); - } - - [Fact] - public void CreateEntitySetPathItemThrowArgumentNullEntitySet() - { - // Arrange - ODataContext context = new ODataContext(EdmModelHelper.BasicEdmModel); - - // Act & Assert - Assert.Throws("entitySet", () => context.CreateEntitySetPathItem(entitySet: null)); - } - + /* [Fact] public void CreateEntitySetPathItemReturnsCorrectPathItem() { @@ -104,28 +85,6 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Post }, pathItem.Operations.Select(o => o.Key)); } - #endregion - - #region Entity PathItem - [Fact] - public void CreateEntityPathItemThrowArgumentNullContext() - { - // Arrange - ODataContext context = null; - - // Act & Assert - Assert.Throws("context", () => context.CreateEntityPathItem(entitySet: null)); - } - - [Fact] - public void CreateEntityPathItemThrowArgumentNullEntitySet() - { - // Arrange - ODataContext context = new ODataContext(EdmModelHelper.BasicEdmModel); - - // Act & Assert - Assert.Throws("entitySet", () => context.CreateEntityPathItem(entitySet: null)); - } [Fact] public void CreateEntityPathItemReturnsCorrectPathItem() @@ -148,28 +107,6 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Patch, OperationType.Delete }, pathItem.Operations.Select(o => o.Key)); } - #endregion - - #region Singleton PathItem - [Fact] - public void CreateSingletonPathItemThrowArgumentNullContext() - { - // Arrange - ODataContext context = null; - - // Act & Assert - Assert.Throws("context", () => context.CreateSingletonPathItem(singleton: null)); - } - - [Fact] - public void CreateSingletonPathItemThrowArgumentNullSingleton() - { - // Arrange - ODataContext context = new ODataContext(EdmModelHelper.BasicEdmModel); - - // Act & Assert - Assert.Throws("singleton", () => context.CreateSingletonPathItem(singleton: null)); - } [Fact] public void CreateSingletonPathItemReturnsCorrectPathItem() @@ -192,30 +129,6 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Patch }, pathItem.Operations.Select(o => o.Key)); } - #endregion - - #region Bound Edm Operation PathItem - [Fact] - public void CreatePathItemForOperationThrowArgumentNullContext() - { - // Arrange - ODataContext context = null; - - // Act & Assert - Assert.Throws("context", - () => context.CreatePathItem(navigationSource: null, edmOperation: null)); - } - - [Fact] - public void CreatePathItemForOperationThrowArgumentNullNavigationSource() - { - // Arrange - ODataContext context = new ODataContext(EdmModelHelper.EmptyModel); - - // Act & Assert - Assert.Throws("navigationSource", - () => context.CreatePathItem(navigationSource: null, edmOperation: null)); - } [Fact] public void CreatePathItemForOperationThrowArgumentNulledmOperation() @@ -259,28 +172,6 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.Equal(expectSummary, operationKeyValue.Value.Summary); } - #endregion - - #region Edm OperationImport PathItem - [Fact] - public void CreatePathItemForOperationImportThrowArgumentNullContext() - { - // Arrange - ODataContext context = null; - - // Act & Assert - Assert.Throws("context", () => context.CreatePathItem(operationImport: null)); - } - - [Fact] - public void CreatePathItemForOperationImportThrowArgumentNullOperationImport() - { - // Arrange - ODataContext context = new ODataContext(EdmModelHelper.EmptyModel); - - // Act & Assert - Assert.Throws("operationImport", () => context.CreatePathItem(operationImport: null)); - } [Theory] [InlineData("GetNearestAirport", OperationType.Get)] @@ -309,6 +200,6 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.Equal(expectSummary, operationKeyValue.Value.Summary); } - #endregion + */ } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathsGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathsGeneratorTests.cs index 3700c9a..d41c1db 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathsGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathsGeneratorTests.cs @@ -5,6 +5,7 @@ using System; using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; @@ -54,11 +55,11 @@ namespace Microsoft.OpenApi.OData.Generator.Tests Assert.Equal(7, paths.Count); Assert.Contains("/People", paths.Keys); - Assert.Contains("/People('{UserName}')", paths.Keys); + Assert.Contains("/People/{UserName}", paths.Keys); Assert.Contains("/City", paths.Keys); - Assert.Contains("/City('{Name}')", paths.Keys); + Assert.Contains("/City/{Name}", paths.Keys); Assert.Contains("/CountryOrRegion", paths.Keys); - Assert.Contains("/CountryOrRegion('{Name}')", paths.Keys); + Assert.Contains("/CountryOrRegion/{Name}", paths.Keys); Assert.Contains("/Me", paths.Keys); } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiRequestBodyGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiRequestBodyGeneratorTests.cs index bea8482..2284b81 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiRequestBodyGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiRequestBodyGeneratorTests.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs index 49d5ced..cca88ce 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs @@ -10,6 +10,7 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.OData.Tests; using Microsoft.OpenApi.Models; using Xunit; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator.Tests { diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs index d730440..3f82517 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs @@ -9,6 +9,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Generator; using Xunit; using Xunit.Abstractions; @@ -95,9 +96,12 @@ namespace Microsoft.OpenApi.OData.Tests ""nullable"": true } }, - ""description"": ""Complex type 'Address' description."" -}" -.ChangeLineBreaks(), json); + ""description"": ""Complex type 'Address' description."", + ""example"": { + ""Street"": ""String"", + ""City"": ""String"" + } +}".ChangeLineBreaks(), json); } [Fact] @@ -176,7 +180,17 @@ namespace Microsoft.OpenApi.OData.Tests }, ""description"": ""Complex type 'Tree' description."" } - ] + ], + ""example"": { + ""Color"": { + ""@odata.type"": ""NS.Color"" + }, + ""Continent"": { + ""@odata.type"": ""NS.Continent"" + }, + ""Name"": ""String"", + ""Price"": ""Decimal"" + } }" .ChangeLineBreaks(), json); } @@ -226,9 +240,16 @@ namespace Microsoft.OpenApi.OData.Tests } } }, - ""description"": ""Entity type 'Zoo' description."" -}" -.ChangeLineBreaks(), json); + ""description"": ""Entity type 'Zoo' description."", + ""example"": { + ""Id"": ""Int32 (identifier)"", + ""Creatures"": [ + { + ""@odata.type"": ""NS.Creature"" + } + ] + } +}".ChangeLineBreaks(), json); } [Fact] @@ -294,7 +315,12 @@ namespace Microsoft.OpenApi.OData.Tests }, ""description"": ""Entity type 'Human' description."" } - ] + ], + ""example"": { + ""Id"": ""Int32 (identifier)"", + ""Age"": ""Int32"", + ""Name"": ""String"" + } }" .ChangeLineBreaks(), json); } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSecuritySchemeGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSecuritySchemeGeneratorTests.cs new file mode 100644 index 0000000..c4dac1d --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSecuritySchemeGeneratorTests.cs @@ -0,0 +1,116 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Validation; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Generator; +using Xunit; + +namespace Microsoft.OpenApi.OData.Tests +{ + public class OpenApiSecuritySchemeGeneratorTest + { + [Fact] + public void CreateSecuritySchemesWorksForAuthorizationsOnEntitySetContainer() + { + // Arrange + ODataContext context = new ODataContext(GetEdmModel()); + + // Act + var schemes = context.CreateSecuritySchemes(); + + // Assert + Assert.NotNull(schemes); + Assert.NotEmpty(schemes); + Assert.Equal(new[] { "OAuth2ClientCredentials Name", "Http Name" }, schemes.Keys); + + var scheme = schemes["OAuth2ClientCredentials Name"]; + Assert.Equal(SecuritySchemeType.OAuth2, scheme.Type); + Assert.NotNull(scheme.Flows.ClientCredentials); + Assert.Equal("http://TokenUrl", scheme.Flows.ClientCredentials.TokenUrl.OriginalString); + Assert.Equal("http://RefreshUrl", scheme.Flows.ClientCredentials.RefreshUrl.OriginalString); + Assert.Equal("OAuth2ClientCredentials Description", scheme.Description); + string json = scheme.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + Assert.Equal(@"{ + ""type"": ""oauth2"", + ""description"": ""OAuth2ClientCredentials Description"", + ""flows"": { + ""clientCredentials"": { + ""tokenUrl"": ""http://tokenurl/"", + ""refreshUrl"": ""http://refreshurl/"", + ""scopes"": { + ""Scope1"": ""Description 1"" + } + } + } +}".ChangeLineBreaks(), json); + + scheme = schemes["Http Name"]; + Assert.Equal(SecuritySchemeType.Http, scheme.Type); + json = scheme.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + Assert.Equal(@"{ + ""type"": ""http"", + ""description"": ""Http Description"", + ""scheme"": ""Http Scheme"", + ""bearerFormat"": ""Http BearerFormat"" +}".ChangeLineBreaks(), json); + } + + private static IEdmModel GetEdmModel() + { + const string schema = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + IEdmModel parsedModel; + IEnumerable errors; + bool parsed = SchemaReader.TryParse(new XmlReader[] { XmlReader.Create(new StringReader(schema)) }, out parsedModel, out errors); + Assert.True(parsed); + return parsedModel; + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiServerGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiServerGeneratorTests.cs index 2c819dd..8c92e6e 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiServerGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiServerGeneratorTests.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using System; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSpatialTypeSchemaGeneratorTest.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSpatialTypeSchemaGeneratorTest.cs index 97f3957..aa3339e 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSpatialTypeSchemaGeneratorTest.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSpatialTypeSchemaGeneratorTest.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Generator; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiTagGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiTagGeneratorTests.cs index de62cab..fad4441 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiTagGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiTagGeneratorTests.cs @@ -5,6 +5,7 @@ using System; using System.Linq; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/PathItemNameExtensionsTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/PathItemNameExtensionsTests.cs index c70d091..5fb92ca 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/PathItemNameExtensionsTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/PathItemNameExtensionsTests.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj b/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj index a6b4499..982f201 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj @@ -36,7 +36,7 @@ - + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionImportOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionImportOperationHandlerTests.cs new file mode 100644 index 0000000..b1d0d26 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionImportOperationHandlerTests.cs @@ -0,0 +1,46 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EdmActionImportOperationHandlerTests + { + private EdmActionImportOperationHandler _operationHandler = new EdmActionImportOperationHandler(); + + [Fact] + public void CreateOperationForEdmActionImportReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + + var actionImport = model.EntityContainer.FindOperationImports("ResetDataSource").FirstOrDefault(); + Assert.NotNull(actionImport); + ODataPath path = new ODataPath(new ODataOperationImportSegment(actionImport)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("Invoke action ResetDataSource", operation.Summary); + Assert.NotNull(operation.Tags); + + Assert.NotNull(operation.Parameters); + Assert.Empty(operation.Parameters); + + Assert.Null(operation.RequestBody); + + Assert.Equal(2, operation.Responses.Count); + Assert.Equal(new string[] { "204", "default" }, operation.Responses.Select(e => e.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs new file mode 100644 index 0000000..eca1149 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EdmActionOperationHandlerTests + { + private EdmActionOperationHandler _operationHandler = new EdmActionOperationHandler(); + + [Fact] + public void CreateOperationForEdmActionReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmAction action = model.SchemaElements.OfType().First(f => f.Name == "ShareTrip"); + Assert.NotNull(action); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType()), new ODataOperationSegment(action)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("Invoke action ShareTrip", operation.Summary); + Assert.NotNull(operation.Tags); + var tag = Assert.Single(operation.Tags); + Assert.Equal("People.Actions", tag.Name); + + Assert.NotNull(operation.Parameters); + Assert.Equal(1, operation.Parameters.Count); + Assert.Equal(new string[] { "UserName" }, operation.Parameters.Select(p => p.Name)); + + Assert.NotNull(operation.RequestBody); + Assert.Equal("Action parameters", operation.RequestBody.Description); + + Assert.Equal(2, operation.Responses.Count); + Assert.Equal(new string[] { "204", "default" }, operation.Responses.Select(e => e.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionImportOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionImportOperationHandlerTests.cs new file mode 100644 index 0000000..d011e10 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionImportOperationHandlerTests.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EdmFunctionImportOperationHandlerTests + { + private EdmFunctionImportOperationHandler _operationHandler = new EdmFunctionImportOperationHandler(); + + [Fact] + public void CreateOperationForEdmFunctionImportReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + var functionImport = model.EntityContainer.FindOperationImports("GetPersonWithMostFriends").FirstOrDefault(); + Assert.NotNull(functionImport); + ODataPath path = new ODataPath(new ODataOperationImportSegment(functionImport)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("Invoke function GetPersonWithMostFriends", operation.Summary); + Assert.NotNull(operation.Tags); + var tag = Assert.Single(operation.Tags); + Assert.Equal("People", tag.Name); + + Assert.NotNull(operation.Parameters); + Assert.Empty(operation.Parameters); + + Assert.Null(operation.RequestBody); + + Assert.Equal(2, operation.Responses.Count); + Assert.Equal(new string[] { "200", "default" }, operation.Responses.Select(e => e.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs new file mode 100644 index 0000000..930caf8 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EdmFunctionOperationHandlerTests + { + private EdmFunctionOperationHandler _operationHandler = new EdmFunctionOperationHandler(); + + [Fact] + public void CreateOperationForEdmFunctionReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmFunction function = model.SchemaElements.OfType().First(f => f.Name == "GetFavoriteAirline"); + Assert.NotNull(function); + + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType()), new ODataOperationSegment(function)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("Invoke function GetFavoriteAirline", operation.Summary); + Assert.NotNull(operation.Tags); + var tag = Assert.Single(operation.Tags); + Assert.Equal("People.Functions", tag.Name); + + Assert.NotNull(operation.Parameters); + Assert.Equal(1, operation.Parameters.Count); + Assert.Equal(new string[] { "UserName" }, operation.Parameters.Select(p => p.Name)); + + Assert.Null(operation.RequestBody); + + Assert.Equal(2, operation.Responses.Count); + Assert.Equal(new string[] { "200", "default" }, operation.Responses.Select(e => e.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityDeleteOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityDeleteOperationHandlerTests.cs new file mode 100644 index 0000000..74dba54 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityDeleteOperationHandlerTests.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EntityDeleteOperationHandlerTests + { + private EntityDeleteOperationHandler _operationHandler = new EntityDeleteOperationHandler(); + + [Fact] + public void CreateEntityDeleteOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("People"); + ODataContext context = new ODataContext(model); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType())); + + // Act + var delete = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(delete); + Assert.Equal("Delete entity from People", delete.Summary); + Assert.NotNull(delete.Tags); + var tag = Assert.Single(delete.Tags); + Assert.Equal("People.Person", tag.Name); + + Assert.NotNull(delete.Parameters); + Assert.Equal(2, delete.Parameters.Count); + + Assert.Null(delete.RequestBody); + + Assert.NotNull(delete.Responses); + Assert.Equal(2, delete.Responses.Count); + Assert.Equal(new[] { "204", "default" }, delete.Responses.Select(r => r.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityGetOperationHandlerTests.cs new file mode 100644 index 0000000..c3fd2b0 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityGetOperationHandlerTests.cs @@ -0,0 +1,46 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EntityGetOperationHandlerTests + { + private EntityGetOperationHandler _operationHandler = new EntityGetOperationHandler(); + + [Fact] + public void CreateEntityGetOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("People"); + ODataContext context = new ODataContext(model); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType())); + + // Act + var get = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(get); + Assert.Equal("Get entity from People by key", get.Summary); + Assert.NotNull(get.Tags); + var tag = Assert.Single(get.Tags); + Assert.Equal("People.Person", tag.Name); + + Assert.NotNull(get.Parameters); + Assert.Equal(3, get.Parameters.Count); + Assert.Null(get.RequestBody); + + Assert.NotNull(get.Responses); + Assert.Equal(2, get.Responses.Count); + Assert.Equal(new[] { "200", "default" }, get.Responses.Select(r => r.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityPatchOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityPatchOperationHandlerTests.cs new file mode 100644 index 0000000..69a0f03 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntityPatchOperationHandlerTests.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EntityPatchOperationHandlerTests + { + private EntityPatchOperationHandler _operationHandler = new EntityPatchOperationHandler(); + + [Fact] + public void CreateEntityPatchOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("People"); + ODataContext context = new ODataContext(model); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType())); + + // Act + var patch = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(patch); + Assert.Equal("Update entity in People", patch.Summary); + Assert.NotNull(patch.Tags); + var tag = Assert.Single(patch.Tags); + Assert.Equal("People.Person", tag.Name); + + Assert.NotNull(patch.Parameters); + Assert.Equal(1, patch.Parameters.Count); + + Assert.NotNull(patch.RequestBody); + + Assert.NotNull(patch.Responses); + Assert.Equal(2, patch.Responses.Count); + Assert.Equal(new[] { "204", "default" }, patch.Responses.Select(r => r.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs new file mode 100644 index 0000000..760eef1 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EntitySetGetOperationHandlerTests + { + private EntitySetGetOperationHandler _operationHandler = new EntitySetGetOperationHandler(); + + [Fact] + public void CreateEntitySetGetOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("People"); + ODataContext context = new ODataContext(model); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet)); + + // Act + var get = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(get); + Assert.Equal("Get entities from " + entitySet.Name, get.Summary); + Assert.NotNull(get.Tags); + var tag = Assert.Single(get.Tags); + Assert.Equal("People.Person", tag.Name); + + Assert.NotNull(get.Parameters); + Assert.Equal(8, get.Parameters.Count); + + Assert.NotNull(get.Responses); + Assert.Equal(2, get.Responses.Count); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs new file mode 100644 index 0000000..a3d8b36 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class EntitySetPostOperationHandlerTests + { + private EntitySetPostOperationHandler _operationHandler = new EntitySetPostOperationHandler(); + + [Fact] + public void CreateEntitySetPostOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("People"); + ODataContext context = new ODataContext(model); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet)); + + // Act + var post = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(post); + Assert.Equal("Add new entity to " + entitySet.Name, post.Summary); + Assert.NotNull(post.Tags); + var tag = Assert.Single(post.Tags); + Assert.Equal("People.Person", tag.Name); + + Assert.Empty(post.Parameters); + Assert.NotNull(post.RequestBody); + + Assert.NotNull(post.Responses); + Assert.Equal(2, post.Responses.Count); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs new file mode 100644 index 0000000..e973b30 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class NavigationPropertyGetOperationHandlerTests + { + private NavigationPropertyGetOperationHandler _operationHandler = new NavigationPropertyGetOperationHandler(); + + [Fact] + public void CreateNavigationGetOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmEntityType person = model.SchemaElements.OfType().First(c => c.Name == "Person"); + IEdmNavigationProperty navProperty = person.DeclaredNavigationProperties().First(c => c.Name == "Trips"); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType()), new ODataNavigationPropertySegment(navProperty)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("Get Trips from People", operation.Summary); + Assert.NotNull(operation.Tags); + var tag = Assert.Single(operation.Tags); + Assert.Equal("People.Trip", tag.Name); + + Assert.NotNull(operation.Parameters); + Assert.Equal(9, operation.Parameters.Count); + + Assert.Null(operation.RequestBody); + + Assert.Equal(2, operation.Responses.Count); + Assert.Equal(new string[] { "200", "default" }, operation.Responses.Select(e => e.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPatchOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPatchOperationHandlerTests.cs new file mode 100644 index 0000000..17ce504 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPatchOperationHandlerTests.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using System.Linq; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class NavigationPropertyPatchOperationHandlerTests + { + private NavigationPropertyPatchOperationHandler _operationHandler = new NavigationPropertyPatchOperationHandler(); + + [Fact] + public void CreateNavigationPatchOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmEntityType person = model.SchemaElements.OfType().First(c => c.Name == "Person"); + IEdmNavigationProperty navProperty = person.DeclaredNavigationProperties().First(c => c.Name == "BestFriend"); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType()), new ODataNavigationPropertySegment(navProperty)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("Update the navigation property BestFriend in People", operation.Summary); + Assert.NotNull(operation.Tags); + var tag = Assert.Single(operation.Tags); + Assert.Equal("People.Person", tag.Name); + + Assert.NotNull(operation.Parameters); + Assert.Equal(1, operation.Parameters.Count); + + Assert.NotNull(operation.RequestBody); + Assert.Equal("New navigation property values", operation.RequestBody.Description); + + Assert.Equal(2, operation.Responses.Count); + Assert.Equal(new string[] { "204", "default" }, operation.Responses.Select(e => e.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPostOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPostOperationHandlerTests.cs new file mode 100644 index 0000000..1be6484 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyPostOperationHandlerTests.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using System.Linq; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class NavigationPropertyPostOperationHandlerTests + { + private NavigationPropertyPostOperationHandler _operationHandler = new NavigationPropertyPostOperationHandler(); + + [Fact] + public void CreateNavigationPostOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmEntityType person = model.SchemaElements.OfType().First(c => c.Name == "Person"); + IEdmNavigationProperty navProperty = person.DeclaredNavigationProperties().First(c => c.Name == "Trips"); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType()), new ODataNavigationPropertySegment(navProperty)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("Create new navigation property to Trips for People", operation.Summary); + Assert.NotNull(operation.Tags); + var tag = Assert.Single(operation.Tags); + Assert.Equal("People.Trip", tag.Name); + + Assert.NotNull(operation.Parameters); + Assert.NotEmpty(operation.Parameters); + + Assert.NotNull(operation.RequestBody); + Assert.Equal("New navigation property", operation.RequestBody.Description); + + Assert.Equal(2, operation.Responses.Count); + Assert.Equal(new string[] { "201", "default" }, operation.Responses.Select(e => e.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/OperationHandlerProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/OperationHandlerProviderTests.cs new file mode 100644 index 0000000..f09e570 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/OperationHandlerProviderTests.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class OperationHandlerProviderTests + { + [Theory] + [InlineData(ODataPathType.EntitySet, OperationType.Get, typeof(EntitySetGetOperationHandler))] + [InlineData(ODataPathType.EntitySet, OperationType.Post, typeof(EntitySetPostOperationHandler))] + [InlineData(ODataPathType.Entity, OperationType.Get, typeof(EntityGetOperationHandler))] + [InlineData(ODataPathType.Entity, OperationType.Patch, typeof(EntityPatchOperationHandler))] + [InlineData(ODataPathType.Entity, OperationType.Delete, typeof(EntityDeleteOperationHandler))] + [InlineData(ODataPathType.Singleton, OperationType.Get, typeof(SingletonGetOperationHandler))] + [InlineData(ODataPathType.Singleton, OperationType.Patch, typeof(SingletonPatchOperationHandler))] + [InlineData(ODataPathType.NavigationProperty, OperationType.Get, typeof(NavigationPropertyGetOperationHandler))] + [InlineData(ODataPathType.NavigationProperty, OperationType.Post, typeof(NavigationPropertyPostOperationHandler))] + [InlineData(ODataPathType.NavigationProperty, OperationType.Patch, typeof(NavigationPropertyPatchOperationHandler))] + [InlineData(ODataPathType.Operation, OperationType.Get, typeof(EdmFunctionOperationHandler))] + [InlineData(ODataPathType.Operation, OperationType.Post, typeof(EdmActionOperationHandler))] + [InlineData(ODataPathType.OperationImport, OperationType.Get, typeof(EdmFunctionImportOperationHandler))] + [InlineData(ODataPathType.OperationImport, OperationType.Post, typeof(EdmActionImportOperationHandler))] + public void GetHandlerReturnsCorrectOperationHandlerType(ODataPathType pathType, OperationType operationType, Type handlerType) + { + // Arrange + OperationHandlerProvider provider = new OperationHandlerProvider(); + + // Act + IOperationHandler hander = provider.GetHandler(pathType, operationType); + + // Assert + Assert.Same(handlerType, hander.GetType()); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonGetOperationHandlerTests.cs new file mode 100644 index 0000000..b889641 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonGetOperationHandlerTests.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class SingletonGetOperationHandlerTests + { + private SingletonGetOperationHandler _operationHandler = new SingletonGetOperationHandler(); + + [Fact] + public void CreateSingletonGetOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + IEdmSingleton singleton = model.EntityContainer.FindSingleton("Me"); + ODataContext context = new ODataContext(model); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(singleton)); + + // Act + var get = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(get); + Assert.Equal("Get Me", get.Summary); + Assert.NotNull(get.Tags); + var tag = Assert.Single(get.Tags); + Assert.Equal("Me.Person", tag.Name); + + Assert.NotNull(get.Parameters); + Assert.Equal(2, get.Parameters.Count); + + Assert.Null(get.RequestBody); + + Assert.NotNull(get.Responses); + Assert.Equal(2, get.Responses.Count); + Assert.Equal(new[] { "200", "default" }, get.Responses.Select(r => r.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonPatchOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonPatchOperationHandlerTests.cs new file mode 100644 index 0000000..d44db58 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/SingletonPatchOperationHandlerTests.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.Operation.Tests +{ + public class SingletonPatchOperationHandlerTests + { + private SingletonPatchOperationHandler _operationHandler = new SingletonPatchOperationHandler(); + + [Fact] + public void CreateSingletonPatchOperationReturnsCorrectOperation() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + IEdmSingleton singleton = model.EntityContainer.FindSingleton("Me"); + ODataContext context = new ODataContext(model); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(singleton)); + + // Act + var patch = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(patch); + Assert.Equal("Update Me", patch.Summary); + Assert.NotNull(patch.Tags); + var tag = Assert.Single(patch.Tags); + Assert.Equal("Me.Person", tag.Name); + + Assert.Empty(patch.Parameters); + Assert.NotNull(patch.RequestBody); + + Assert.NotNull(patch.Responses); + Assert.Equal(2, patch.Responses.Count); + Assert.Equal(new[] { "204", "default" }, patch.Responses.Select(r => r.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs new file mode 100644 index 0000000..fc1666e --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class EntityPathItemGeneratorTests + { + private EntityPathItemHandler _pathItemHandler = new EntityPathItemHandler(); + + [Fact] + public void CreateEntityPathItemReturnsCorrectPathItem() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(entitySet); // guard + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType())); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + + Assert.NotNull(pathItem.Operations); + Assert.NotEmpty(pathItem.Operations); + Assert.Equal(3, pathItem.Operations.Count); + Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Patch, OperationType.Delete }, + pathItem.Operations.Select(o => o.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs new file mode 100644 index 0000000..46acf57 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class EntitySetPathItemHandlerTests + { + private EntitySetPathItemHandler _pathItemHandler = new EntitySetPathItemHandler(); + + [Fact] + public void CreateEntitySetPathItemReturnsCorrectPathItem() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(entitySet); // guard + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet)); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + + Assert.NotNull(pathItem.Operations); + Assert.NotEmpty(pathItem.Operations); + Assert.Equal(2, pathItem.Operations.Count); + Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Post }, + pathItem.Operations.Select(o => o.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs new file mode 100644 index 0000000..59f9217 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class NavigationPropertyPathItemGeneratorTest + { + private NavigationPropertyPathItemHandler _pathItemHandler = new NavigationPropertyPathItemHandler(); + + [Theory] + [InlineData(true, true, new OperationType[] { OperationType.Get, OperationType.Patch })] + [InlineData(true, false, new OperationType[] { OperationType.Get, OperationType.Post })] + [InlineData(false, true, new OperationType[] { OperationType.Get })] + [InlineData(false, false, new OperationType[] { OperationType.Get})] + public void CreateCollectionNavigationPropertyPathItemReturnsCorrectPathItem(bool containment, bool keySegment, OperationType[] expected) + { + // Arrange + IEdmModel model = EdmModelHelper.GraphBetaModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("users"); + Assert.NotNull(entitySet); // guard + IEdmEntityType entityType = entitySet.EntityType(); + + IEdmNavigationProperty property = entityType.DeclaredNavigationProperties() + .FirstOrDefault(c => c.ContainsTarget == containment && c.TargetMultiplicity() == EdmMultiplicity.Many); + Assert.NotNull(property); + + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), + new ODataKeySegment(entityType), + new ODataNavigationPropertySegment(property)); + + if (keySegment) + { + path.Push(new ODataKeySegment(property.ToEntityType())); + } + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + + Assert.NotNull(pathItem.Operations); + Assert.NotEmpty(pathItem.Operations); + Assert.Equal(expected, pathItem.Operations.Select(o => o.Key)); + } + + [Theory] + [InlineData(true, new OperationType[] { OperationType.Get, OperationType.Patch })] + [InlineData(false, new OperationType[] { OperationType.Get })] + public void CreateSingleNavigationPropertyPathItemReturnsCorrectPathItem(bool containment, OperationType[] expected) + { + // Arrange + IEdmModel model = EdmModelHelper.GraphBetaModel; + ODataContext context = new ODataContext(model); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("users"); + Assert.NotNull(entitySet); // guard + IEdmEntityType entityType = entitySet.EntityType(); + + IEdmNavigationProperty property = entityType.DeclaredNavigationProperties() + .FirstOrDefault(c => c.ContainsTarget == containment && c.TargetMultiplicity() != EdmMultiplicity.Many); + Assert.NotNull(property); + + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), + new ODataKeySegment(entityType), + new ODataNavigationPropertySegment(property)); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + + Assert.NotNull(pathItem.Operations); + Assert.NotEmpty(pathItem.Operations); + Assert.Equal(expected, pathItem.Operations.Select(o => o.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs new file mode 100644 index 0000000..3a11e76 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class OperationImportPathItemGeneratorTest + { + private OperationImportPathItemHandler _pathItemHandler = new OperationImportPathItemHandler(); + + [Theory] + [InlineData("GetNearestAirport", OperationType.Get)] + [InlineData("ResetDataSource", OperationType.Post)] + public void CreatePathItemForOperationImportReturnsCorrectPathItem(string operationImport, + OperationType operationType) + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + IEdmOperationImport edmOperationImport = model.EntityContainer + .OperationImports().FirstOrDefault(o => o.Name == operationImport); + Assert.NotNull(edmOperationImport); // guard + string expectSummary = "Invoke " + + (edmOperationImport.IsActionImport() ? "action " : "function ") + operationImport; + ODataPath path = new ODataPath(new ODataOperationImportSegment(edmOperationImport)); + + // Act + OpenApiPathItem pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Operations); + var operationKeyValue = Assert.Single(pathItem.Operations); + Assert.Equal(operationType, operationKeyValue.Key); + Assert.NotNull(operationKeyValue.Value); + + Assert.Equal(expectSummary, operationKeyValue.Value.Summary); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs new file mode 100644 index 0000000..523942e --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class OperationPathItemGeneratorTest + { + private OperationPathItemHandler _pathItemHandler = new OperationPathItemHandler(); + + [Theory] + [InlineData("GetFriendsTrips", "People", OperationType.Get)] + [InlineData("ShareTrip", "People", OperationType.Post)] + public void CreatePathItemForOperationReturnsCorrectPathItem(string operationName, string entitySet, + OperationType operationType) + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new ODataContext(model); + IEdmNavigationSource navigationSource = model.EntityContainer.FindEntitySet(entitySet); + Assert.NotNull(navigationSource); // guard + IEdmOperation edmOperation = model.SchemaElements.OfType() + .FirstOrDefault(o => o.Name == operationName); + Assert.NotNull(edmOperation); // guard + string expectSummary = "Invoke " + + (edmOperation.IsAction() ? "action " : "function ") + operationName; + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(navigationSource), new ODataOperationSegment(edmOperation)); + + // Act + OpenApiPathItem pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Operations); + var operationKeyValue = Assert.Single(pathItem.Operations); + Assert.Equal(operationType, operationKeyValue.Key); + Assert.NotNull(operationKeyValue.Value); + + Assert.Equal(expectSummary, operationKeyValue.Value.Summary); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/PathItemHandlerProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/PathItemHandlerProviderTests.cs new file mode 100644 index 0000000..cbd7d7d --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/PathItemHandlerProviderTests.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.OpenApi.OData.Edm; +using Xunit; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class PathItemHandlerProviderTests + { + [Theory] + [InlineData(ODataPathType.EntitySet, typeof(EntitySetPathItemHandler))] + [InlineData(ODataPathType.Entity, typeof(EntityPathItemHandler))] + [InlineData(ODataPathType.Singleton, typeof(SingletonPathItemHandler))] + [InlineData(ODataPathType.NavigationProperty, typeof(NavigationPropertyPathItemHandler))] + [InlineData(ODataPathType.Operation, typeof(OperationPathItemHandler))] + [InlineData(ODataPathType.OperationImport, typeof(OperationImportPathItemHandler))] + public void GetHandlerReturnsCorrectHandlerType(ODataPathType pathType, Type handlerType) + { + // Arrange + PathItemHandlerProvider provider = new PathItemHandlerProvider(); + + // Act + IPathItemHandler hander = provider.GetHandler(pathType); + + // Assert + Assert.Same(handlerType, hander.GetType()); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs new file mode 100644 index 0000000..c0294d6 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Edm; +using Microsoft.OpenApi.OData.Tests; +using Xunit; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class SingletonPathItemGeneratorTest + { + private SingletonPathItemHandler _pathItemHandler = new SingletonPathItemHandler(); + + [Fact] + public void CreateSingletonPathItemReturnsCorrectPathItem() + { + // Arrange + IEdmModel model = EdmModelHelper.BasicEdmModel; + ODataContext context = new ODataContext(model); + IEdmSingleton singleton = model.EntityContainer.FindSingleton("Me"); + Assert.NotNull(singleton); // guard + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(singleton)); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + + Assert.NotNull(pathItem.Operations); + Assert.NotEmpty(pathItem.Operations); + Assert.Equal(2, pathItem.Operations.Count); + Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Patch }, + pathItem.Operations.Select(o => o.Key)); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources.cs index 65c0e05..de297bc 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using Microsoft.OpenApi.OData.Common; using System; using System.IO; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.json b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.json index 772e21f..d6e127b 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.json +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.json @@ -14,10 +14,10 @@ "/People": { "get": { "tags": [ - "People" + "People.Person" ], "summary": "Get entities from People", - "operationId": "GetEntitiesFromPeople", + "operationId": "People.Person.ListPerson", "parameters": [ { "$ref": "#/components/parameters/top" @@ -113,14 +113,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ - "People" + "People.Person" ], "summary": "Add new entity to People", - "operationId": "AddEntityToPeople", + "operationId": "People.Person.CreatePerson", "requestBody": { "description": "New entity", "content": { @@ -146,16 +147,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/People('{UserName}')": { + "/People/{UserName}": { "get": { "tags": [ - "People" + "People.Person" ], "summary": "Get entity from People by key", - "operationId": "GetEntityFromPeopleByKey", + "operationId": "People.Person.GetPerson", "parameters": [ { "name": "UserName", @@ -164,7 +166,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" }, { "name": "$select", @@ -214,14 +217,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "People" + "People.Person" ], "summary": "Update entity in People", - "operationId": "UpdateEntityInPeople", + "operationId": "People.Person.UpdatePerson", "parameters": [ { "name": "UserName", @@ -230,7 +234,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" } ], "requestBody": { @@ -251,14 +256,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ - "People" + "People.Person" ], "summary": "Delete entity from People", - "operationId": "DeleteEntityFromPeople", + "operationId": "People.Person.DeletePerson", "parameters": [ { "name": "UserName", @@ -267,7 +273,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" }, { "name": "If-Match", @@ -285,16 +292,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, "/City": { "get": { "tags": [ - "City" + "City.City" ], "summary": "Get entities from City", - "operationId": "GetEntitiesFromCity", + "operationId": "City.City.ListCity", "parameters": [ { "$ref": "#/components/parameters/top" @@ -381,14 +389,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ - "City" + "City.City" ], "summary": "Add new entity to City", - "operationId": "AddEntityToCity", + "operationId": "City.City.CreateCity", "requestBody": { "description": "New entity", "content": { @@ -414,16 +423,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/City('{Name}')": { + "/City/{Name}": { "get": { "tags": [ - "City" + "City.City" ], "summary": "Get entity from City by key", - "operationId": "GetEntityFromCityByKey", + "operationId": "City.City.GetCity", "parameters": [ { "name": "Name", @@ -432,7 +442,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "City" }, { "name": "$select", @@ -479,14 +490,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "City" + "City.City" ], "summary": "Update entity in City", - "operationId": "UpdateEntityInCity", + "operationId": "City.City.UpdateCity", "parameters": [ { "name": "Name", @@ -495,7 +507,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "City" } ], "requestBody": { @@ -516,14 +529,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ - "City" + "City.City" ], "summary": "Delete entity from City", - "operationId": "DeleteEntityFromCity", + "operationId": "City.City.DeleteCity", "parameters": [ { "name": "Name", @@ -532,7 +546,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "City" }, { "name": "If-Match", @@ -550,16 +565,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, "/CountryOrRegion": { "get": { "tags": [ - "CountryOrRegion" + "CountryOrRegion.CountryOrRegion" ], "summary": "Get entities from CountryOrRegion", - "operationId": "GetEntitiesFromCountryOrRegion", + "operationId": "CountryOrRegion.CountryOrRegion.ListCountryOrRegion", "parameters": [ { "$ref": "#/components/parameters/top" @@ -646,14 +662,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ - "CountryOrRegion" + "CountryOrRegion.CountryOrRegion" ], "summary": "Add new entity to CountryOrRegion", - "operationId": "AddEntityToCountryOrRegion", + "operationId": "CountryOrRegion.CountryOrRegion.CreateCountryOrRegion", "requestBody": { "description": "New entity", "content": { @@ -679,16 +696,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/CountryOrRegion('{Name}')": { + "/CountryOrRegion/{Name}": { "get": { "tags": [ - "CountryOrRegion" + "CountryOrRegion.CountryOrRegion" ], "summary": "Get entity from CountryOrRegion by key", - "operationId": "GetEntityFromCountryOrRegionByKey", + "operationId": "CountryOrRegion.CountryOrRegion.GetCountryOrRegion", "parameters": [ { "name": "Name", @@ -697,7 +715,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "CountryOrRegion" }, { "name": "$select", @@ -744,14 +763,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "CountryOrRegion" + "CountryOrRegion.CountryOrRegion" ], "summary": "Update entity in CountryOrRegion", - "operationId": "UpdateEntityInCountryOrRegion", + "operationId": "CountryOrRegion.CountryOrRegion.UpdateCountryOrRegion", "parameters": [ { "name": "Name", @@ -760,7 +780,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "CountryOrRegion" } ], "requestBody": { @@ -781,14 +802,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ - "CountryOrRegion" + "CountryOrRegion.CountryOrRegion" ], "summary": "Delete entity from CountryOrRegion", - "operationId": "DeleteEntityFromCountryOrRegion", + "operationId": "CountryOrRegion.CountryOrRegion.DeleteCountryOrRegion", "parameters": [ { "name": "Name", @@ -797,7 +819,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "CountryOrRegion" }, { "name": "If-Match", @@ -815,16 +838,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, "/Me": { "get": { "tags": [ - "Me" + "Me.Person" ], "summary": "Get Me", - "operationId": "GetMe", + "operationId": "Me.Person.GetPerson", "parameters": [ { "name": "$select", @@ -874,14 +898,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "Me" + "Me.Person" ], "summary": "Update Me", - "operationId": "UpdateMe", + "operationId": "Me.Person.UpdatePerson", "requestBody": { "description": "New property values", "content": { @@ -900,7 +925,8 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } } }, @@ -934,6 +960,20 @@ "$ref": "#/components/schemas/DefaultNs.Address" } } + }, + "example": { + "UserName": "String (identifier)", + "HomeAddress": { + "@odata.type": "DefaultNs.Address" + }, + "WorkAddress": { + "@odata.type": "DefaultNs.Address" + }, + "Addresses": [ + { + "@odata.type": "DefaultNs.Address" + } + ] } }, "DefaultNs.City": { @@ -943,6 +983,9 @@ "Name": { "type": "string" } + }, + "example": { + "Name": "String (identifier)" } }, "DefaultNs.CountryOrRegion": { @@ -952,6 +995,9 @@ "Name": { "type": "string" } + }, + "example": { + "Name": "String (identifier)" } }, "DefaultNs.Address": { @@ -967,6 +1013,12 @@ "City": { "$ref": "#/components/schemas/DefaultNs.City" } + }, + "example": { + "Id": "Int32", + "City": { + "@odata.type": "DefaultNs.City" + } } }, "DefaultNs.WorkAddress": { @@ -983,7 +1035,16 @@ } } } - ] + ], + "example": { + "Id": "Int32", + "City": { + "@odata.type": "DefaultNs.City" + }, + "CountryOrRegion": { + "@odata.type": "DefaultNs.CountryOrRegion" + } + } }, "odata.error": { "required": [ @@ -1103,17 +1164,20 @@ }, "tags": [ { - "name": "People", - "description": "People's description." + "name": "People.Person", + "x-ms-docs-toc-type": "page" }, { - "name": "City" + "name": "City.City", + "x-ms-docs-toc-type": "page" }, { - "name": "CountryOrRegion" + "name": "CountryOrRegion.CountryOrRegion", + "x-ms-docs-toc-type": "page" }, { - "name": "Me" + "name": "Me.Person", + "x-ms-docs-toc-type": "page" } ] } \ No newline at end of file diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.yaml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.yaml index 302e95c..fe919c7 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.yaml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Basic.OpenApi.yaml @@ -9,9 +9,9 @@ paths: /People: get: tags: - - People + - People.Person summary: Get entities from People - operationId: GetEntitiesFromPeople + operationId: People.Person.ListPerson parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' @@ -73,11 +73,12 @@ paths: $ref: '#/components/schemas/DefaultNs.Person' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation post: tags: - - People + - People.Person summary: Add new entity to People - operationId: AddEntityToPeople + operationId: People.Person.CreatePerson requestBody: description: New entity content: @@ -94,12 +95,13 @@ paths: $ref: '#/components/schemas/DefaultNs.Person' default: $ref: '#/components/responses/error' - '/People(''{UserName}'')': + x-ms-docs-operation-type: operation + '/People/{UserName}': get: tags: - - People + - People.Person summary: Get entity from People by key - operationId: GetEntityFromPeopleByKey + operationId: People.Person.GetPerson parameters: - name: UserName in: path @@ -107,6 +109,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person - name: $select in: query description: Select properties to be returned @@ -139,11 +142,12 @@ paths: $ref: '#/components/schemas/DefaultNs.Person' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - People + - People.Person summary: Update entity in People - operationId: UpdateEntityInPeople + operationId: People.Person.UpdatePerson parameters: - name: UserName in: path @@ -151,6 +155,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person requestBody: description: New property values content: @@ -163,11 +168,12 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation delete: tags: - - People + - People.Person summary: Delete entity from People - operationId: DeleteEntityFromPeople + operationId: People.Person.DeletePerson parameters: - name: UserName in: path @@ -175,6 +181,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person - name: If-Match in: header description: ETag @@ -185,12 +192,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation /City: get: tags: - - City + - City.City summary: Get entities from City - operationId: GetEntitiesFromCity + operationId: City.City.ListCity parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' @@ -243,11 +251,12 @@ paths: $ref: '#/components/schemas/DefaultNs.City' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation post: tags: - - City + - City.City summary: Add new entity to City - operationId: AddEntityToCity + operationId: City.City.CreateCity requestBody: description: New entity content: @@ -264,12 +273,13 @@ paths: $ref: '#/components/schemas/DefaultNs.City' default: $ref: '#/components/responses/error' - '/City(''{Name}'')': + x-ms-docs-operation-type: operation + '/City/{Name}': get: tags: - - City + - City.City summary: Get entity from City by key - operationId: GetEntityFromCityByKey + operationId: City.City.GetCity parameters: - name: Name in: path @@ -277,6 +287,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: City - name: $select in: query description: Select properties to be returned @@ -306,11 +317,12 @@ paths: $ref: '#/components/schemas/DefaultNs.City' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - City + - City.City summary: Update entity in City - operationId: UpdateEntityInCity + operationId: City.City.UpdateCity parameters: - name: Name in: path @@ -318,6 +330,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: City requestBody: description: New property values content: @@ -330,11 +343,12 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation delete: tags: - - City + - City.City summary: Delete entity from City - operationId: DeleteEntityFromCity + operationId: City.City.DeleteCity parameters: - name: Name in: path @@ -342,6 +356,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: City - name: If-Match in: header description: ETag @@ -352,12 +367,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation /CountryOrRegion: get: tags: - - CountryOrRegion + - CountryOrRegion.CountryOrRegion summary: Get entities from CountryOrRegion - operationId: GetEntitiesFromCountryOrRegion + operationId: CountryOrRegion.CountryOrRegion.ListCountryOrRegion parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' @@ -410,11 +426,12 @@ paths: $ref: '#/components/schemas/DefaultNs.CountryOrRegion' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation post: tags: - - CountryOrRegion + - CountryOrRegion.CountryOrRegion summary: Add new entity to CountryOrRegion - operationId: AddEntityToCountryOrRegion + operationId: CountryOrRegion.CountryOrRegion.CreateCountryOrRegion requestBody: description: New entity content: @@ -431,12 +448,13 @@ paths: $ref: '#/components/schemas/DefaultNs.CountryOrRegion' default: $ref: '#/components/responses/error' - '/CountryOrRegion(''{Name}'')': + x-ms-docs-operation-type: operation + '/CountryOrRegion/{Name}': get: tags: - - CountryOrRegion + - CountryOrRegion.CountryOrRegion summary: Get entity from CountryOrRegion by key - operationId: GetEntityFromCountryOrRegionByKey + operationId: CountryOrRegion.CountryOrRegion.GetCountryOrRegion parameters: - name: Name in: path @@ -444,6 +462,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: CountryOrRegion - name: $select in: query description: Select properties to be returned @@ -473,11 +492,12 @@ paths: $ref: '#/components/schemas/DefaultNs.CountryOrRegion' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - CountryOrRegion + - CountryOrRegion.CountryOrRegion summary: Update entity in CountryOrRegion - operationId: UpdateEntityInCountryOrRegion + operationId: CountryOrRegion.CountryOrRegion.UpdateCountryOrRegion parameters: - name: Name in: path @@ -485,6 +505,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: CountryOrRegion requestBody: description: New property values content: @@ -497,11 +518,12 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation delete: tags: - - CountryOrRegion + - CountryOrRegion.CountryOrRegion summary: Delete entity from CountryOrRegion - operationId: DeleteEntityFromCountryOrRegion + operationId: CountryOrRegion.CountryOrRegion.DeleteCountryOrRegion parameters: - name: Name in: path @@ -509,6 +531,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: CountryOrRegion - name: If-Match in: header description: ETag @@ -519,12 +542,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation /Me: get: tags: - - Me + - Me.Person summary: Get Me - operationId: GetMe + operationId: Me.Person.GetPerson parameters: - name: $select in: query @@ -558,11 +582,12 @@ paths: $ref: '#/components/schemas/DefaultNs.Person' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - Me + - Me.Person summary: Update Me - operationId: UpdateMe + operationId: Me.Person.UpdatePerson requestBody: description: New property values content: @@ -575,6 +600,7 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation components: schemas: DefaultNs.Color: @@ -598,18 +624,30 @@ components: type: array items: $ref: '#/components/schemas/DefaultNs.Address' + example: + UserName: String (identifier) + HomeAddress: + '@odata.type': DefaultNs.Address + WorkAddress: + '@odata.type': DefaultNs.Address + Addresses: + - '@odata.type': DefaultNs.Address DefaultNs.City: title: City type: object properties: Name: type: string + example: + Name: String (identifier) DefaultNs.CountryOrRegion: title: CountryOrRegion type: object properties: Name: type: string + example: + Name: String (identifier) DefaultNs.Address: title: Address type: object @@ -621,6 +659,10 @@ components: format: int32 City: $ref: '#/components/schemas/DefaultNs.City' + example: + Id: Int32 + City: + '@odata.type': DefaultNs.City DefaultNs.WorkAddress: allOf: - $ref: '#/components/schemas/DefaultNs.Address' @@ -629,6 +671,12 @@ components: properties: CountryOrRegion: $ref: '#/components/schemas/DefaultNs.CountryOrRegion' + example: + Id: Int32 + City: + '@odata.type': DefaultNs.City + CountryOrRegion: + '@odata.type': DefaultNs.CountryOrRegion odata.error: required: - error @@ -709,8 +757,11 @@ components: schema: type: string tags: - - name: People - description: People's description. - - name: City - - name: CountryOrRegion - - name: Me \ No newline at end of file + - name: People.Person + x-ms-docs-toc-type: page + - name: City.City + x-ms-docs-toc-type: page + - name: CountryOrRegion.CountryOrRegion + x-ms-docs-toc-type: page + - name: Me.Person + x-ms-docs-toc-type: page \ No newline at end of file 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 ee129b1..7e99a06 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.json @@ -14,10 +14,10 @@ "/People": { "get": { "tags": [ - "People" + "People.Person" ], "summary": "Get entities from People", - "operationId": "GetEntitiesFromPeople", + "operationId": "People.Person.ListPerson", "parameters": [ { "$ref": "#/components/parameters/top" @@ -137,14 +137,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ - "People" + "People.Person" ], "summary": "Add new entity to People", - "operationId": "AddEntityToPeople", + "operationId": "People.Person.CreatePerson", "requestBody": { "description": "New entity", "content": { @@ -170,16 +171,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/People('{UserName}')": { + "/People/{UserName}": { "get": { "tags": [ - "People" + "People.Person" ], "summary": "Get entity from People by key", - "operationId": "GetEntityFromPeopleByKey", + "operationId": "People.Person.GetPerson", "parameters": [ { "name": "UserName", @@ -188,7 +190,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" }, { "name": "$select", @@ -243,19 +246,40 @@ "$ref": "#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" } } + }, + "links": { + "Friends": { + "operationId": "People.Person.GetPerson", + "parameters": { + "UserName": "$request.path.UserName" + } + }, + "BestFriend": { + "operationId": "People.Person.GetPerson", + "parameters": { + "UserName": "$request.path.UserName" + } + }, + "Trips": { + "operationId": "People.Person.GetPerson", + "parameters": { + "UserName": "$request.path.UserName" + } + } } }, "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "People" + "People.Person" ], "summary": "Update entity in People", - "operationId": "UpdateEntityInPeople", + "operationId": "People.Person.UpdatePerson", "parameters": [ { "name": "UserName", @@ -264,7 +288,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" } ], "requestBody": { @@ -285,14 +310,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ - "People" + "People.Person" ], "summary": "Delete entity from People", - "operationId": "DeleteEntityFromPeople", + "operationId": "People.Person.DeletePerson", "parameters": [ { "name": "UserName", @@ -301,7 +327,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" }, { "name": "If-Match", @@ -319,16 +346,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/People('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()": { + "/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()": { "get": { "tags": [ - "People" + "People.Functions" ], "summary": "Invoke function GetFavoriteAirline", - "operationId": "InvokeGetFavoriteAirline", + "operationId": "People.0-GetFavoriteAirline", "parameters": [ { "name": "UserName", @@ -337,7 +365,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" } ], "responses": { @@ -359,22 +388,32 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, - "/People('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})": { + "/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})": { "get": { "tags": [ - "People" + "People.Functions" ], "summary": "Invoke function GetFriendsTrips", - "operationId": "InvokeGetFriendsTrips", + "operationId": "People.0-GetFriendsTrips", "parameters": [ { "name": "UserName", "in": "path", "description": "key: UserName", "required": true, + "schema": { + "type": "string" + }, + "x-ms-docs-key-type": "Person" + }, + { + "name": "userName", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -402,22 +441,32 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, - "/People('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})": { + "/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})": { "get": { "tags": [ - "People" + "People.Functions" ], "summary": "Invoke function UpdatePersonLastName", - "operationId": "InvokeUpdatePersonLastName", + "operationId": "People.0-UpdatePersonLastName", "parameters": [ { "name": "UserName", "in": "path", "description": "key: UserName", "required": true, + "schema": { + "type": "string" + }, + "x-ms-docs-key-type": "Person" + }, + { + "name": "lastName", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -438,16 +487,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, - "/People('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip": { + "/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip": { "post": { "tags": [ - "People" + "People.Actions" ], "summary": "Invoke action ShareTrip", - "operationId": "InvokeShareTrip", + "operationId": "People.0-ShareTrip", "parameters": [ { "name": "UserName", @@ -456,7 +506,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" } ], "requestBody": { @@ -488,16 +539,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "action" } }, "/Airlines": { "get": { "tags": [ - "Airlines" + "Airlines.Airline" ], "summary": "Get entities from Airlines", - "operationId": "GetEntitiesFromAirlines", + "operationId": "Airlines.Airline.ListAirline", "parameters": [ { "$ref": "#/components/parameters/top" @@ -587,14 +639,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ - "Airlines" + "Airlines.Airline" ], "summary": "Add new entity to Airlines", - "operationId": "AddEntityToAirlines", + "operationId": "Airlines.Airline.CreateAirline", "requestBody": { "description": "New entity", "content": { @@ -620,16 +673,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/Airlines('{AirlineCode}')": { + "/Airlines/{AirlineCode}": { "get": { "tags": [ - "Airlines" + "Airlines.Airline" ], "summary": "Get entity from Airlines by key", - "operationId": "GetEntityFromAirlinesByKey", + "operationId": "Airlines.Airline.GetAirline", "parameters": [ { "name": "AirlineCode", @@ -638,7 +692,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Airline" }, { "name": "$select", @@ -686,14 +741,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "Airlines" + "Airlines.Airline" ], "summary": "Update entity in Airlines", - "operationId": "UpdateEntityInAirlines", + "operationId": "Airlines.Airline.UpdateAirline", "parameters": [ { "name": "AirlineCode", @@ -702,7 +758,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Airline" } ], "requestBody": { @@ -723,14 +780,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ - "Airlines" + "Airlines.Airline" ], "summary": "Delete entity from Airlines", - "operationId": "DeleteEntityFromAirlines", + "operationId": "Airlines.Airline.DeleteAirline", "parameters": [ { "name": "AirlineCode", @@ -739,7 +797,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Airline" }, { "name": "If-Match", @@ -757,16 +816,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, "/Airports": { "get": { "tags": [ - "Airports" + "Airports.Airport" ], "summary": "Get entities from Airports", - "operationId": "GetEntitiesFromAirports", + "operationId": "Airports.Airport.ListAirport", "parameters": [ { "$ref": "#/components/parameters/top" @@ -862,14 +922,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ - "Airports" + "Airports.Airport" ], "summary": "Add new entity to Airports", - "operationId": "AddEntityToAirports", + "operationId": "Airports.Airport.CreateAirport", "requestBody": { "description": "New entity", "content": { @@ -895,16 +956,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/Airports('{IcaoCode}')": { + "/Airports/{IcaoCode}": { "get": { "tags": [ - "Airports" + "Airports.Airport" ], "summary": "Get entity from Airports by key", - "operationId": "GetEntityFromAirportsByKey", + "operationId": "Airports.Airport.GetAirport", "parameters": [ { "name": "IcaoCode", @@ -913,7 +975,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Airport" }, { "name": "$select", @@ -963,14 +1026,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "Airports" + "Airports.Airport" ], "summary": "Update entity in Airports", - "operationId": "UpdateEntityInAirports", + "operationId": "Airports.Airport.UpdateAirport", "parameters": [ { "name": "IcaoCode", @@ -979,7 +1043,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Airport" } ], "requestBody": { @@ -1000,14 +1065,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ - "Airports" + "Airports.Airport" ], "summary": "Delete entity from Airports", - "operationId": "DeleteEntityFromAirports", + "operationId": "Airports.Airport.DeleteAirport", "parameters": [ { "name": "IcaoCode", @@ -1016,7 +1082,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Airport" }, { "name": "If-Match", @@ -1034,16 +1101,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, "/NewComePeople": { "get": { "tags": [ - "NewComePeople" + "NewComePeople.Person" ], "summary": "Get entities from NewComePeople", - "operationId": "GetEntitiesFromNewComePeople", + "operationId": "NewComePeople.Person.ListPerson", "parameters": [ { "$ref": "#/components/parameters/top" @@ -1163,14 +1231,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "post": { "tags": [ - "NewComePeople" + "NewComePeople.Person" ], "summary": "Add new entity to NewComePeople", - "operationId": "AddEntityToNewComePeople", + "operationId": "NewComePeople.Person.CreatePerson", "requestBody": { "description": "New entity", "content": { @@ -1196,16 +1265,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/NewComePeople('{UserName}')": { + "/NewComePeople/{UserName}": { "get": { "tags": [ - "NewComePeople" + "NewComePeople.Person" ], "summary": "Get entity from NewComePeople by key", - "operationId": "GetEntityFromNewComePeopleByKey", + "operationId": "NewComePeople.Person.GetPerson", "parameters": [ { "name": "UserName", @@ -1214,7 +1284,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" }, { "name": "$select", @@ -1269,19 +1340,40 @@ "$ref": "#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" } } + }, + "links": { + "Friends": { + "operationId": "NewComePeople.Person.GetPerson", + "parameters": { + "UserName": "$request.path.UserName" + } + }, + "BestFriend": { + "operationId": "NewComePeople.Person.GetPerson", + "parameters": { + "UserName": "$request.path.UserName" + } + }, + "Trips": { + "operationId": "NewComePeople.Person.GetPerson", + "parameters": { + "UserName": "$request.path.UserName" + } + } } }, "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "NewComePeople" + "NewComePeople.Person" ], "summary": "Update entity in NewComePeople", - "operationId": "UpdateEntityInNewComePeople", + "operationId": "NewComePeople.Person.UpdatePerson", "parameters": [ { "name": "UserName", @@ -1290,7 +1382,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" } ], "requestBody": { @@ -1311,14 +1404,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "delete": { "tags": [ - "NewComePeople" + "NewComePeople.Person" ], "summary": "Delete entity from NewComePeople", - "operationId": "DeleteEntityFromNewComePeople", + "operationId": "NewComePeople.Person.DeletePerson", "parameters": [ { "name": "UserName", @@ -1327,7 +1421,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" }, { "name": "If-Match", @@ -1345,16 +1440,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, - "/NewComePeople('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()": { + "/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()": { "get": { "tags": [ - "NewComePeople" + "NewComePeople.Functions" ], "summary": "Invoke function GetFavoriteAirline", - "operationId": "InvokeGetFavoriteAirline", + "operationId": "NewComePeople.0-GetFavoriteAirline", "parameters": [ { "name": "UserName", @@ -1363,7 +1459,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" } ], "responses": { @@ -1385,22 +1482,32 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, - "/NewComePeople('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})": { + "/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})": { "get": { "tags": [ - "NewComePeople" + "NewComePeople.Functions" ], "summary": "Invoke function GetFriendsTrips", - "operationId": "InvokeGetFriendsTrips", + "operationId": "NewComePeople.0-GetFriendsTrips", "parameters": [ { "name": "UserName", "in": "path", "description": "key: UserName", "required": true, + "schema": { + "type": "string" + }, + "x-ms-docs-key-type": "Person" + }, + { + "name": "userName", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -1428,22 +1535,32 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, - "/NewComePeople('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})": { + "/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})": { "get": { "tags": [ - "NewComePeople" + "NewComePeople.Functions" ], "summary": "Invoke function UpdatePersonLastName", - "operationId": "InvokeUpdatePersonLastName", + "operationId": "NewComePeople.0-UpdatePersonLastName", "parameters": [ { "name": "UserName", "in": "path", "description": "key: UserName", "required": true, + "schema": { + "type": "string" + }, + "x-ms-docs-key-type": "Person" + }, + { + "name": "lastName", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -1464,16 +1581,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, - "/NewComePeople('{UserName}')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip": { + "/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip": { "post": { "tags": [ - "NewComePeople" + "NewComePeople.Actions" ], "summary": "Invoke action ShareTrip", - "operationId": "InvokeShareTrip", + "operationId": "NewComePeople.0-ShareTrip", "parameters": [ { "name": "UserName", @@ -1482,7 +1600,8 @@ "required": true, "schema": { "type": "string" - } + }, + "x-ms-docs-key-type": "Person" } ], "requestBody": { @@ -1514,16 +1633,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "action" } }, "/Me": { "get": { "tags": [ - "Me" + "Me.Person" ], "summary": "Get Me", - "operationId": "GetMe", + "operationId": "Me.Person.GetPerson", "parameters": [ { "name": "$select", @@ -1583,14 +1703,15 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" }, "patch": { "tags": [ - "Me" + "Me.Person" ], "summary": "Update Me", - "operationId": "UpdateMe", + "operationId": "Me.Person.UpdatePerson", "requestBody": { "description": "New property values", "content": { @@ -1609,16 +1730,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "operation" } }, "/Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()": { "get": { "tags": [ - "Me" + "Me.Functions" ], "summary": "Invoke function GetFavoriteAirline", - "operationId": "InvokeGetFavoriteAirline", + "operationId": "Me.0-GetFavoriteAirline", "responses": { "200": { "description": "Success", @@ -1638,16 +1760,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, "/Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})": { "get": { "tags": [ - "Me" + "Me.Functions" ], "summary": "Invoke function GetFriendsTrips", - "operationId": "InvokeGetFriendsTrips", + "operationId": "Me.0-GetFriendsTrips", "parameters": [ { "name": "userName", @@ -1680,16 +1803,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, "/Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})": { "get": { "tags": [ - "Me" + "Me.Functions" ], "summary": "Invoke function UpdatePersonLastName", - "operationId": "InvokeUpdatePersonLastName", + "operationId": "Me.0-UpdatePersonLastName", "parameters": [ { "name": "lastName", @@ -1715,16 +1839,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "function" } }, "/Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip": { "post": { "tags": [ - "Me" + "Me.Actions" ], "summary": "Invoke action ShareTrip", - "operationId": "InvokeShareTrip", + "operationId": "Me.0-ShareTrip", "requestBody": { "description": "Action parameters", "content": { @@ -1754,16 +1879,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "action" } }, - "/GetPersonWithMostFriends()": { + "/GetPersonWithMostFriends": { "get": { "tags": [ "People" ], "summary": "Invoke function GetPersonWithMostFriends", - "operationId": "InvokeGetPersonWithMostFriends", + "operationId": "OperationImport.0-GetPersonWithMostFriends", "responses": { "200": { "description": "Success", @@ -1783,16 +1909,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "functionImport" } }, - "/GetNearestAirport(lat={lat},lon={lon})": { + "/GetNearestAirport": { "get": { "tags": [ "Airports" ], "summary": "Invoke function GetNearestAirport", - "operationId": "InvokeGetNearestAirport", + "operationId": "OperationImport.0-GetNearestAirport", "parameters": [ { "name": "lat", @@ -1860,13 +1987,17 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "functionImport" } }, "/ResetDataSource": { "post": { + "tags": [ + "ResetDataSource" + ], "summary": "Invoke action ResetDataSource", - "operationId": "InvokeResetDataSource", + "operationId": "OperationImport.0-ResetDataSource", "responses": { "204": { "description": "Success" @@ -1874,7 +2005,8 @@ "default": { "$ref": "#/components/responses/error" } - } + }, + "x-ms-docs-operation-type": "actionImport" } } }, @@ -1980,6 +2112,48 @@ "$ref": "#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip" } } + }, + "example": { + "UserName": "String (identifier)", + "FirstName": "String", + "LastName": "String", + "MiddleName": "String", + "Gender": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender" + }, + "Age": "Int64", + "Emails": [ + "String" + ], + "AddressInfo": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location" + } + ], + "HomeAddress": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location" + }, + "FavoriteFeature": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature" + }, + "Features": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature" + } + ], + "Friends": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + } + ], + "BestFriend": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + }, + "Trips": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip" + } + ] } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airline": { @@ -1993,6 +2167,10 @@ "type": "string", "nullable": true } + }, + "example": { + "AirlineCode": "String (identifier)", + "Name": "String" } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport": { @@ -2018,6 +2196,14 @@ ], "nullable": true } + }, + "example": { + "Name": "String", + "IcaoCode": "String (identifier)", + "IataCode": "String", + "Location": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.AirportLocation" + } } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location": { @@ -2036,6 +2222,12 @@ ], "nullable": true } + }, + "example": { + "Address": "String", + "City": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.City" + } } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.City": { @@ -2054,6 +2246,11 @@ "type": "string", "nullable": true } + }, + "example": { + "Name": "String", + "CountryRegion": "String", + "Region": "String" } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.AirportLocation": { @@ -2070,7 +2267,14 @@ } } } - ] + ], + "example": { + "Address": "String", + "City": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.City" + }, + "Loc": "GeographyPoint" + } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.EventLocation": { "allOf": [ @@ -2087,7 +2291,14 @@ } } } - ] + ], + "example": { + "Address": "String", + "City": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.City" + }, + "BuildingInfo": "String" + } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip": { "title": "Trip", @@ -2153,6 +2364,23 @@ "$ref": "#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem" } } + }, + "example": { + "TripId": "Int32 (identifier)", + "ShareId": "Guid", + "Name": "String", + "Budget": "Single", + "Description": "String", + "Tags": [ + "String" + ], + "StartsAt": "DateTimeOffset (timestamp)", + "EndsAt": "DateTimeOffset (timestamp)", + "PlanItems": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem" + } + ] } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem": { @@ -2184,6 +2412,13 @@ "type": "string", "format": "duration" } + }, + "example": { + "PlanItemId": "Int32 (identifier)", + "ConfirmationCode": "String", + "StartsAt": "DateTimeOffset (timestamp)", + "EndsAt": "DateTimeOffset (timestamp)", + "Duration": "Duration" } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Event": { @@ -2209,7 +2444,18 @@ } } } - ] + ], + "example": { + "PlanItemId": "Int32 (identifier)", + "ConfirmationCode": "String", + "StartsAt": "DateTimeOffset (timestamp)", + "EndsAt": "DateTimeOffset (timestamp)", + "Duration": "Duration", + "OccursAt": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.EventLocation" + }, + "Description": "String" + } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.PublicTransportation": { "allOf": [ @@ -2226,7 +2472,15 @@ } } } - ] + ], + "example": { + "PlanItemId": "Int32 (identifier)", + "ConfirmationCode": "String", + "StartsAt": "DateTimeOffset (timestamp)", + "EndsAt": "DateTimeOffset (timestamp)", + "Duration": "Duration", + "SeatNumber": "String" + } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Flight": { "allOf": [ @@ -2267,7 +2521,25 @@ } } } - ] + ], + "example": { + "PlanItemId": "Int32 (identifier)", + "ConfirmationCode": "String", + "StartsAt": "DateTimeOffset (timestamp)", + "EndsAt": "DateTimeOffset (timestamp)", + "Duration": "Duration", + "SeatNumber": "String", + "FlightNumber": "String", + "Airline": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airline" + }, + "From": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport" + }, + "To": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport" + } + } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Employee": { "allOf": [ @@ -2297,7 +2569,55 @@ } } } - ] + ], + "example": { + "UserName": "String (identifier)", + "FirstName": "String", + "LastName": "String", + "MiddleName": "String", + "Gender": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender" + }, + "Age": "Int64", + "Emails": [ + "String" + ], + "AddressInfo": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location" + } + ], + "HomeAddress": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location" + }, + "FavoriteFeature": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature" + }, + "Features": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature" + } + ], + "Friends": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + } + ], + "BestFriend": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + }, + "Trips": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip" + } + ], + "Cost": "Int64", + "Peers": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + } + ] + } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Manager": { "allOf": [ @@ -2335,7 +2655,58 @@ } } } - ] + ], + "example": { + "UserName": "String (identifier)", + "FirstName": "String", + "LastName": "String", + "MiddleName": "String", + "Gender": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender" + }, + "Age": "Int64", + "Emails": [ + "String" + ], + "AddressInfo": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location" + } + ], + "HomeAddress": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location" + }, + "FavoriteFeature": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature" + }, + "Features": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature" + } + ], + "Friends": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + } + ], + "BestFriend": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + }, + "Trips": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip" + } + ], + "Budget": "Int64", + "BossOffice": { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location" + }, + "DirectReports": [ + { + "@odata.type": "Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person" + } + ] + } }, "Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender": { "title": "PersonGender", @@ -2686,22 +3057,60 @@ }, "tags": [ { - "name": "People" + "name": "People.Person", + "x-ms-docs-toc-type": "page" }, { - "name": "Airlines" + "name": "People.Functions", + "x-ms-docs-toc-type": "container" }, { - "name": "Airports" + "name": "People.Actions", + "x-ms-docs-toc-type": "container" }, { - "name": "NewComePeople" + "name": "Airlines.Airline", + "x-ms-docs-toc-type": "page" }, { - "name": "Me" + "name": "Airports.Airport", + "x-ms-docs-toc-type": "page" }, { - "name": "ResetDataSource" + "name": "NewComePeople.Person", + "x-ms-docs-toc-type": "page" + }, + { + "name": "NewComePeople.Functions", + "x-ms-docs-toc-type": "container" + }, + { + "name": "NewComePeople.Actions", + "x-ms-docs-toc-type": "container" + }, + { + "name": "Me.Person", + "x-ms-docs-toc-type": "page" + }, + { + "name": "Me.Functions", + "x-ms-docs-toc-type": "container" + }, + { + "name": "Me.Actions", + "x-ms-docs-toc-type": "container" + }, + { + "name": "People", + "x-ms-docs-toc-type": "container" + }, + { + "name": "Airports", + "x-ms-docs-toc-type": "container" + }, + { + "name": "ResetDataSource", + "x-ms-docs-toc-type": "container" } ] } \ No newline at end of file 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 dd74991..d589246 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OpenApi.yaml @@ -9,9 +9,9 @@ paths: /People: get: tags: - - People + - People.Person summary: Get entities from People - operationId: GetEntitiesFromPeople + operationId: People.Person.ListPerson parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' @@ -97,11 +97,12 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation post: tags: - - People + - People.Person summary: Add new entity to People - operationId: AddEntityToPeople + operationId: People.Person.CreatePerson requestBody: description: New entity content: @@ -118,12 +119,13 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' default: $ref: '#/components/responses/error' - '/People(''{UserName}'')': + x-ms-docs-operation-type: operation + '/People/{UserName}': get: tags: - - People + - People.Person summary: Get entity from People by key - operationId: GetEntityFromPeopleByKey + operationId: People.Person.GetPerson parameters: - name: UserName in: path @@ -131,6 +133,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person - name: $select in: query description: Select properties to be returned @@ -171,13 +174,27 @@ paths: application/json: schema: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' + links: + Friends: + operationId: People.Person.GetPerson + parameters: + UserName: $request.path.UserName + BestFriend: + operationId: People.Person.GetPerson + parameters: + UserName: $request.path.UserName + Trips: + operationId: People.Person.GetPerson + parameters: + UserName: $request.path.UserName default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - People + - People.Person summary: Update entity in People - operationId: UpdateEntityInPeople + operationId: People.Person.UpdatePerson parameters: - name: UserName in: path @@ -185,6 +202,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person requestBody: description: New property values content: @@ -197,11 +215,12 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation delete: tags: - - People + - People.Person summary: Delete entity from People - operationId: DeleteEntityFromPeople + operationId: People.Person.DeletePerson parameters: - name: UserName in: path @@ -209,6 +228,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person - name: If-Match in: header description: ETag @@ -219,12 +239,13 @@ paths: description: Success default: $ref: '#/components/responses/error' - '/People(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()': + x-ms-docs-operation-type: operation + '/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()': get: tags: - - People + - People.Functions summary: Invoke function GetFavoriteAirline - operationId: InvokeGetFavoriteAirline + operationId: People.0-GetFavoriteAirline parameters: - name: UserName in: path @@ -232,6 +253,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person responses: '200': description: Success @@ -243,12 +265,13 @@ paths: nullable: true default: $ref: '#/components/responses/error' - '/People(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})': + x-ms-docs-operation-type: function + '/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})': get: tags: - - People + - People.Functions summary: Invoke function GetFriendsTrips - operationId: InvokeGetFriendsTrips + operationId: People.0-GetFriendsTrips parameters: - name: UserName in: path @@ -256,6 +279,12 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person + - name: userName + in: path + required: true + schema: + type: string responses: '200': description: Success @@ -269,12 +298,13 @@ paths: nullable: true default: $ref: '#/components/responses/error' - '/People(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})': + x-ms-docs-operation-type: function + '/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})': get: tags: - - People + - People.Functions summary: Invoke function UpdatePersonLastName - operationId: InvokeUpdatePersonLastName + operationId: People.0-UpdatePersonLastName parameters: - name: UserName in: path @@ -282,6 +312,12 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person + - name: lastName + in: path + required: true + schema: + type: string responses: '200': description: Success @@ -292,12 +328,13 @@ paths: default: false default: $ref: '#/components/responses/error' - '/People(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip': + x-ms-docs-operation-type: function + '/People/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip': post: tags: - - People + - People.Actions summary: Invoke action ShareTrip - operationId: InvokeShareTrip + operationId: People.0-ShareTrip parameters: - name: UserName in: path @@ -305,6 +342,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person requestBody: description: Action parameters content: @@ -325,12 +363,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: action /Airlines: get: tags: - - Airlines + - Airlines.Airline summary: Get entities from Airlines - operationId: GetEntitiesFromAirlines + operationId: Airlines.Airline.ListAirline parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' @@ -386,11 +425,12 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airline' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation post: tags: - - Airlines + - Airlines.Airline summary: Add new entity to Airlines - operationId: AddEntityToAirlines + operationId: Airlines.Airline.CreateAirline requestBody: description: New entity content: @@ -407,12 +447,13 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airline' default: $ref: '#/components/responses/error' - '/Airlines(''{AirlineCode}'')': + x-ms-docs-operation-type: operation + '/Airlines/{AirlineCode}': get: tags: - - Airlines + - Airlines.Airline summary: Get entity from Airlines by key - operationId: GetEntityFromAirlinesByKey + operationId: Airlines.Airline.GetAirline parameters: - name: AirlineCode in: path @@ -420,6 +461,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Airline - name: $select in: query description: Select properties to be returned @@ -450,11 +492,12 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airline' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - Airlines + - Airlines.Airline summary: Update entity in Airlines - operationId: UpdateEntityInAirlines + operationId: Airlines.Airline.UpdateAirline parameters: - name: AirlineCode in: path @@ -462,6 +505,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Airline requestBody: description: New property values content: @@ -474,11 +518,12 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation delete: tags: - - Airlines + - Airlines.Airline summary: Delete entity from Airlines - operationId: DeleteEntityFromAirlines + operationId: Airlines.Airline.DeleteAirline parameters: - name: AirlineCode in: path @@ -486,6 +531,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Airline - name: If-Match in: header description: ETag @@ -496,12 +542,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation /Airports: get: tags: - - Airports + - Airports.Airport summary: Get entities from Airports - operationId: GetEntitiesFromAirports + operationId: Airports.Airport.ListAirport parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' @@ -563,11 +610,12 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation post: tags: - - Airports + - Airports.Airport summary: Add new entity to Airports - operationId: AddEntityToAirports + operationId: Airports.Airport.CreateAirport requestBody: description: New entity content: @@ -584,12 +632,13 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport' default: $ref: '#/components/responses/error' - '/Airports(''{IcaoCode}'')': + x-ms-docs-operation-type: operation + '/Airports/{IcaoCode}': get: tags: - - Airports + - Airports.Airport summary: Get entity from Airports by key - operationId: GetEntityFromAirportsByKey + operationId: Airports.Airport.GetAirport parameters: - name: IcaoCode in: path @@ -597,6 +646,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Airport - name: $select in: query description: Select properties to be returned @@ -629,11 +679,12 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - Airports + - Airports.Airport summary: Update entity in Airports - operationId: UpdateEntityInAirports + operationId: Airports.Airport.UpdateAirport parameters: - name: IcaoCode in: path @@ -641,6 +692,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Airport requestBody: description: New property values content: @@ -653,11 +705,12 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation delete: tags: - - Airports + - Airports.Airport summary: Delete entity from Airports - operationId: DeleteEntityFromAirports + operationId: Airports.Airport.DeleteAirport parameters: - name: IcaoCode in: path @@ -665,6 +718,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Airport - name: If-Match in: header description: ETag @@ -675,12 +729,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation /NewComePeople: get: tags: - - NewComePeople + - NewComePeople.Person summary: Get entities from NewComePeople - operationId: GetEntitiesFromNewComePeople + operationId: NewComePeople.Person.ListPerson parameters: - $ref: '#/components/parameters/top' - $ref: '#/components/parameters/skip' @@ -766,11 +821,12 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation post: tags: - - NewComePeople + - NewComePeople.Person summary: Add new entity to NewComePeople - operationId: AddEntityToNewComePeople + operationId: NewComePeople.Person.CreatePerson requestBody: description: New entity content: @@ -787,12 +843,13 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' default: $ref: '#/components/responses/error' - '/NewComePeople(''{UserName}'')': + x-ms-docs-operation-type: operation + '/NewComePeople/{UserName}': get: tags: - - NewComePeople + - NewComePeople.Person summary: Get entity from NewComePeople by key - operationId: GetEntityFromNewComePeopleByKey + operationId: NewComePeople.Person.GetPerson parameters: - name: UserName in: path @@ -800,6 +857,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person - name: $select in: query description: Select properties to be returned @@ -840,13 +898,27 @@ paths: application/json: schema: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' + links: + Friends: + operationId: NewComePeople.Person.GetPerson + parameters: + UserName: $request.path.UserName + BestFriend: + operationId: NewComePeople.Person.GetPerson + parameters: + UserName: $request.path.UserName + Trips: + operationId: NewComePeople.Person.GetPerson + parameters: + UserName: $request.path.UserName default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - NewComePeople + - NewComePeople.Person summary: Update entity in NewComePeople - operationId: UpdateEntityInNewComePeople + operationId: NewComePeople.Person.UpdatePerson parameters: - name: UserName in: path @@ -854,6 +926,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person requestBody: description: New property values content: @@ -866,11 +939,12 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation delete: tags: - - NewComePeople + - NewComePeople.Person summary: Delete entity from NewComePeople - operationId: DeleteEntityFromNewComePeople + operationId: NewComePeople.Person.DeletePerson parameters: - name: UserName in: path @@ -878,6 +952,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person - name: If-Match in: header description: ETag @@ -888,12 +963,13 @@ paths: description: Success default: $ref: '#/components/responses/error' - '/NewComePeople(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()': + x-ms-docs-operation-type: operation + '/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline()': get: tags: - - NewComePeople + - NewComePeople.Functions summary: Invoke function GetFavoriteAirline - operationId: InvokeGetFavoriteAirline + operationId: NewComePeople.0-GetFavoriteAirline parameters: - name: UserName in: path @@ -901,6 +977,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person responses: '200': description: Success @@ -912,12 +989,13 @@ paths: nullable: true default: $ref: '#/components/responses/error' - '/NewComePeople(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})': + x-ms-docs-operation-type: function + '/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})': get: tags: - - NewComePeople + - NewComePeople.Functions summary: Invoke function GetFriendsTrips - operationId: InvokeGetFriendsTrips + operationId: NewComePeople.0-GetFriendsTrips parameters: - name: UserName in: path @@ -925,6 +1003,12 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person + - name: userName + in: path + required: true + schema: + type: string responses: '200': description: Success @@ -938,12 +1022,13 @@ paths: nullable: true default: $ref: '#/components/responses/error' - '/NewComePeople(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})': + x-ms-docs-operation-type: function + '/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})': get: tags: - - NewComePeople + - NewComePeople.Functions summary: Invoke function UpdatePersonLastName - operationId: InvokeUpdatePersonLastName + operationId: NewComePeople.0-UpdatePersonLastName parameters: - name: UserName in: path @@ -951,6 +1036,12 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person + - name: lastName + in: path + required: true + schema: + type: string responses: '200': description: Success @@ -961,12 +1052,13 @@ paths: default: false default: $ref: '#/components/responses/error' - '/NewComePeople(''{UserName}'')/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip': + x-ms-docs-operation-type: function + '/NewComePeople/{UserName}/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip': post: tags: - - NewComePeople + - NewComePeople.Actions summary: Invoke action ShareTrip - operationId: InvokeShareTrip + operationId: NewComePeople.0-ShareTrip parameters: - name: UserName in: path @@ -974,6 +1066,7 @@ paths: required: true schema: type: string + x-ms-docs-key-type: Person requestBody: description: Action parameters content: @@ -994,12 +1087,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: action /Me: get: tags: - - Me + - Me.Person summary: Get Me - operationId: GetMe + operationId: Me.Person.GetPerson parameters: - name: $select in: query @@ -1043,11 +1137,12 @@ paths: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation patch: tags: - - Me + - Me.Person summary: Update Me - operationId: UpdateMe + operationId: Me.Person.UpdatePerson requestBody: description: New property values content: @@ -1060,12 +1155,13 @@ paths: description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation /Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFavoriteAirline(): get: tags: - - Me + - Me.Functions summary: Invoke function GetFavoriteAirline - operationId: InvokeGetFavoriteAirline + operationId: Me.0-GetFavoriteAirline responses: '200': description: Success @@ -1077,12 +1173,13 @@ paths: nullable: true default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: function '/Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName={userName})': get: tags: - - Me + - Me.Functions summary: Invoke function GetFriendsTrips - operationId: InvokeGetFriendsTrips + operationId: Me.0-GetFriendsTrips parameters: - name: userName in: path @@ -1102,12 +1199,13 @@ paths: nullable: true default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: function '/Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.UpdatePersonLastName(lastName={lastName})': get: tags: - - Me + - Me.Functions summary: Invoke function UpdatePersonLastName - operationId: InvokeUpdatePersonLastName + operationId: Me.0-UpdatePersonLastName parameters: - name: lastName in: path @@ -1124,12 +1222,13 @@ paths: default: false default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: function /Me/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip: post: tags: - - Me + - Me.Actions summary: Invoke action ShareTrip - operationId: InvokeShareTrip + operationId: Me.0-ShareTrip requestBody: description: Action parameters content: @@ -1150,12 +1249,13 @@ paths: description: Success default: $ref: '#/components/responses/error' - /GetPersonWithMostFriends(): + x-ms-docs-operation-type: action + /GetPersonWithMostFriends: get: tags: - People summary: Invoke function GetPersonWithMostFriends - operationId: InvokeGetPersonWithMostFriends + operationId: OperationImport.0-GetPersonWithMostFriends responses: '200': description: Success @@ -1167,12 +1267,13 @@ paths: nullable: true default: $ref: '#/components/responses/error' - '/GetNearestAirport(lat={lat},lon={lon})': + x-ms-docs-operation-type: functionImport + /GetNearestAirport: get: tags: - Airports summary: Invoke function GetNearestAirport - operationId: InvokeGetNearestAirport + operationId: OperationImport.0-GetNearestAirport parameters: - name: lat in: path @@ -1209,15 +1310,19 @@ paths: nullable: true default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: functionImport /ResetDataSource: post: + tags: + - ResetDataSource summary: Invoke action ResetDataSource - operationId: InvokeResetDataSource + operationId: OperationImport.0-ResetDataSource responses: '204': description: Success default: $ref: '#/components/responses/error' + x-ms-docs-operation-type: actionImport components: schemas: Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person: @@ -1278,6 +1383,30 @@ components: type: array items: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip' + example: + UserName: String (identifier) + FirstName: String + LastName: String + MiddleName: String + Gender: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender + Age: Int64 + Emails: + - String + AddressInfo: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location + HomeAddress: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location + FavoriteFeature: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature + Features: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature + Friends: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person + BestFriend: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person + Trips: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airline: title: Airline type: object @@ -1287,6 +1416,9 @@ components: Name: type: string nullable: true + example: + AirlineCode: String (identifier) + Name: String Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport: title: Airport type: object @@ -1303,6 +1435,12 @@ components: anyOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.AirportLocation' nullable: true + example: + Name: String + IcaoCode: String (identifier) + IataCode: String + Location: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.AirportLocation Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location: title: Location type: object @@ -1314,6 +1452,10 @@ components: anyOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.City' nullable: true + example: + Address: String + City: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.City Microsoft.OData.Service.Sample.TrippinInMemory.Models.City: title: City type: object @@ -1327,6 +1469,10 @@ components: Region: type: string nullable: true + example: + Name: String + CountryRegion: String + Region: String Microsoft.OData.Service.Sample.TrippinInMemory.Models.AirportLocation: allOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location' @@ -1335,6 +1481,11 @@ components: properties: Loc: $ref: '#/components/schemas/Edm.GeographyPoint' + example: + Address: String + City: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.City + Loc: GeographyPoint Microsoft.OData.Service.Sample.TrippinInMemory.Models.EventLocation: allOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location' @@ -1344,6 +1495,11 @@ components: BuildingInfo: type: string nullable: true + example: + Address: String + City: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.City + BuildingInfo: String Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip: title: Trip type: object @@ -1389,6 +1545,18 @@ components: type: array items: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem' + example: + TripId: Int32 (identifier) + ShareId: Guid + Name: String + Budget: Single + Description: String + Tags: + - String + StartsAt: DateTimeOffset (timestamp) + EndsAt: DateTimeOffset (timestamp) + PlanItems: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem: title: PlanItem type: object @@ -1413,6 +1581,12 @@ components: pattern: '^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$' type: string format: duration + example: + PlanItemId: Int32 (identifier) + ConfirmationCode: String + StartsAt: DateTimeOffset (timestamp) + EndsAt: DateTimeOffset (timestamp) + Duration: Duration Microsoft.OData.Service.Sample.TrippinInMemory.Models.Event: allOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem' @@ -1426,6 +1600,15 @@ components: Description: type: string nullable: true + example: + PlanItemId: Int32 (identifier) + ConfirmationCode: String + StartsAt: DateTimeOffset (timestamp) + EndsAt: DateTimeOffset (timestamp) + Duration: Duration + OccursAt: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.EventLocation + Description: String Microsoft.OData.Service.Sample.TrippinInMemory.Models.PublicTransportation: allOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.PlanItem' @@ -1435,6 +1618,13 @@ components: SeatNumber: type: string nullable: true + example: + PlanItemId: Int32 (identifier) + ConfirmationCode: String + StartsAt: DateTimeOffset (timestamp) + EndsAt: DateTimeOffset (timestamp) + Duration: Duration + SeatNumber: String Microsoft.OData.Service.Sample.TrippinInMemory.Models.Flight: allOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.PublicTransportation' @@ -1456,6 +1646,20 @@ components: anyOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport' nullable: true + example: + PlanItemId: Int32 (identifier) + ConfirmationCode: String + StartsAt: DateTimeOffset (timestamp) + EndsAt: DateTimeOffset (timestamp) + Duration: Duration + SeatNumber: String + FlightNumber: String + Airline: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airline + From: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport + To: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport Microsoft.OData.Service.Sample.TrippinInMemory.Models.Employee: allOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' @@ -1471,6 +1675,33 @@ components: type: array items: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' + example: + UserName: String (identifier) + FirstName: String + LastName: String + MiddleName: String + Gender: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender + Age: Int64 + Emails: + - String + AddressInfo: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location + HomeAddress: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location + FavoriteFeature: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature + Features: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature + Friends: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person + BestFriend: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person + Trips: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip + Cost: Int64 + Peers: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person Microsoft.OData.Service.Sample.TrippinInMemory.Models.Manager: allOf: - $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' @@ -1490,6 +1721,35 @@ components: type: array items: $ref: '#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person' + example: + UserName: String (identifier) + FirstName: String + LastName: String + MiddleName: String + Gender: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender + Age: Int64 + Emails: + - String + AddressInfo: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location + HomeAddress: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location + FavoriteFeature: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature + Features: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Feature + Friends: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person + BestFriend: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person + Trips: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Trip + Budget: Int64 + BossOffice: + '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Location + DirectReports: + - '@odata.type': Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person Microsoft.OData.Service.Sample.TrippinInMemory.Models.PersonGender: title: PersonGender enum: @@ -1719,9 +1979,31 @@ components: schema: type: string tags: + - name: People.Person + x-ms-docs-toc-type: page + - name: People.Functions + x-ms-docs-toc-type: container + - name: People.Actions + x-ms-docs-toc-type: container + - name: Airlines.Airline + x-ms-docs-toc-type: page + - name: Airports.Airport + x-ms-docs-toc-type: page + - name: NewComePeople.Person + x-ms-docs-toc-type: page + - name: NewComePeople.Functions + x-ms-docs-toc-type: container + - name: NewComePeople.Actions + x-ms-docs-toc-type: container + - name: Me.Person + x-ms-docs-toc-type: page + - name: Me.Functions + x-ms-docs-toc-type: container + - name: Me.Actions + x-ms-docs-toc-type: container - name: People - - name: Airlines + x-ms-docs-toc-type: container - name: Airports - - name: NewComePeople - - name: Me - - name: ResetDataSource \ No newline at end of file + x-ms-docs-toc-type: container + - name: ResetDataSource + x-ms-docs-toc-type: container \ No newline at end of file diff --git a/test/UpdateDocs/Program.cs b/test/UpdateDocs/Program.cs index 94926f4..ad9411c 100644 --- a/test/UpdateDocs/Program.cs +++ b/test/UpdateDocs/Program.cs @@ -63,7 +63,7 @@ namespace UpdateDocs File.WriteAllText(output, document.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0)); settings.KeyAsSegment = true; - settings.NavigationPropertyPathItem = true; + settings.EnableNavigationPropertyPath = true; output = oas30 + "/" + fileName + "_content.json"; document = model.ConvertToOpenApi(settings); File.WriteAllText(output, document.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0)); diff --git a/test/UpdateDocs/UpdateDocs.csproj b/test/UpdateDocs/UpdateDocs.csproj index c80aad3..b0c952f 100644 --- a/test/UpdateDocs/UpdateDocs.csproj +++ b/test/UpdateDocs/UpdateDocs.csproj @@ -32,11 +32,11 @@ 4 - - ..\..\packages\Microsoft.OData.Edm.7.3.1\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + ..\..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - ..\..\packages\Microsoft.OpenApi.1.0.0-beta012\lib\net46\Microsoft.OpenApi.dll + + ..\..\packages\Microsoft.OpenApi.1.0.1\lib\net46\Microsoft.OpenApi.dll diff --git a/test/UpdateDocs/packages.config b/test/UpdateDocs/packages.config index 7d4d870..aea1696 100644 --- a/test/UpdateDocs/packages.config +++ b/test/UpdateDocs/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file