diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs index 94b75efc..6d600502 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs @@ -6,6 +6,10 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary; @@ -128,5 +132,95 @@ internal static string NavigationPropertyPath(this ODataPath path, string naviga path.Segments.OfType().Select(e => e.Identifier)); return navigationPropertyName == null ? value : $"{value}/{navigationPropertyName}"; } + + /// + /// Attempts to add the specified key and value to the dictionary. + /// + /// The type of the keys in the dictionary + /// The type of the values in the dictionary + /// A dictionary with keys of type TKey and values of type TValue. + /// The key of the element to add. + /// The value of the element to add. + /// true when the key and value are successfully added to the dictionary; + /// false when the dictionary already contains the specified key, + /// in which case nothing gets added. + /// dictionary is null. + internal static bool TryAdd(this IDictionary dictionary, + TKey key, TValue value) + { + CheckArgumentNull(dictionary, nameof(dictionary)); + + if (!dictionary.ContainsKey(key)) + { + dictionary.Add(key, value); + return true; + } + return false; + } + + /// + /// Adds a mapping of custom extension values against custom attribute values for a given element to the provided + /// extensions object. + /// + /// The target extensions object in which the mapped extensions and custom attribute + /// values will be added to. + /// The OData context. + /// The target element. + internal static void AddCustomAtributesToExtensions(this IDictionary extensions, ODataContext context, IEdmElement element) + { + if (extensions == null || + context == null || + element == null) + { + return; + } + + Dictionary atrributesValueMap = GetCustomXMLAtrributesValueMapping(context.Model, element, context.Settings.CustomXMLAttributesMapping); + + if (atrributesValueMap?.Any() ?? false) + { + foreach (var item in atrributesValueMap) + { + extensions.TryAdd(item.Key, new OpenApiString(item.Value)); + } + } + } + + /// + /// Correlates and retrieves custom attribute values for a given element in an Edm model + /// from a provided dictionary mapping of attribute names and extension names. + /// + /// The Edm model. + /// The target element. + /// The dictionary mapping of attribute names and extension names. + /// A dictionary of extension names mapped to the custom attribute values. + private static Dictionary GetCustomXMLAtrributesValueMapping(IEdmModel model, IEdmElement element, Dictionary customXMLAttributesMapping) + { + Dictionary atrributesValueMap = new(); + + if ((!customXMLAttributesMapping?.Any() ?? true) || + model == null || + element == null) + { + return atrributesValueMap; + } + + foreach (var item in customXMLAttributesMapping) + { + string attributeName = item.Key.Split(':').Last(); // example, 'ags:IsHidden' --> 'IsHidden' + string extensionName = item.Value; + EdmStringConstant customXMLAttribute = model.DirectValueAnnotationsManager.GetDirectValueAnnotations(element)? + .Where(x => x.Name.Equals(attributeName, StringComparison.OrdinalIgnoreCase))? + .FirstOrDefault()?.Value as EdmStringConstant; + string attributeValue = customXMLAttribute?.Value; + + if (!string.IsNullOrEmpty(attributeValue)) + { + atrributesValueMap.TryAdd(extensionName, attributeValue); + } + } + + return atrributesValueMap; + } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs index 49a6edc0..a60818c3 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs @@ -327,9 +327,10 @@ public static IDictionary CreateStructuredTypePropertiesS // structure properties foreach (var property in structuredType.DeclaredStructuralProperties()) { - // OpenApiSchema propertySchema = property.Type.CreateSchema(); - // propertySchema.Default = property.DefaultValueString != null ? new OpenApiString(property.DefaultValueString) : null; - properties.Add(property.Name, context.CreatePropertySchema(property)); + OpenApiSchema propertySchema = context.CreatePropertySchema(property); + propertySchema.Description = context.Model.GetDescriptionAnnotation(property); + propertySchema.Extensions.AddCustomAtributesToExtensions(context, property); + properties.Add(property.Name, propertySchema); } // navigation properties @@ -337,6 +338,7 @@ public static IDictionary CreateStructuredTypePropertiesS { OpenApiSchema propertySchema = context.CreateEdmTypeSchema(property.Type); propertySchema.Description = context.Model.GetDescriptionAnnotation(property); + propertySchema.Extensions.AddCustomAtributesToExtensions(context, property); properties.Add(property.Name, propertySchema); } diff --git a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs index 6526c3db..a07d923b 100644 --- a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs +++ b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using System; +using System.Collections.Generic; using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Extensions; @@ -242,6 +243,11 @@ public string PathPrefix /// public bool RequireRestrictionAnnotationsToGenerateComplexPropertyPaths { get; set; } = true; + /// + /// Gets/sets a dictionary containing a mapping of custom atttribute names and extension names. + /// + public Dictionary CustomXMLAttributesMapping { get; set; } = new(); + internal OpenApiConvertSettings Clone() { var newSettings = new OpenApiConvertSettings @@ -281,7 +287,8 @@ internal OpenApiConvertSettings Clone() ErrorResponsesAsDefault = this.ErrorResponsesAsDefault, InnerErrorComplexTypeName = this.InnerErrorComplexTypeName, RequireRestrictionAnnotationsToGenerateComplexPropertyPaths = this.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths, - ExpandDerivedTypesNavigationProperties = this.ExpandDerivedTypesNavigationProperties + ExpandDerivedTypesNavigationProperties = this.ExpandDerivedTypesNavigationProperties, + CustomXMLAttributesMapping = this.CustomXMLAttributesMapping }; return newSettings; diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs index 4d1a1459..6cf99c02 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs @@ -5,6 +5,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -65,4 +66,10 @@ protected override void Initialize(ODataContext context, ODataPath path) ODataComplexPropertySegment navigationSourceSegment = path.LastSegment as ODataComplexPropertySegment; ComplexProperty = navigationSourceSegment.Property; } + + /// + protected override void SetExtensions(OpenApiPathItem item) + { + item.Extensions.AddCustomAtributesToExtensions(Context, ComplexProperty); + } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs index 60bdcf59..b9ef9a88 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs @@ -3,7 +3,9 @@ // 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.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -48,5 +50,12 @@ protected override void SetOperations(OpenApiPathItem item) AddOperation(item, OperationType.Delete); } } + + /// + protected override void SetExtensions(OpenApiPathItem pathItem) + { + base.SetExtensions(pathItem); + pathItem.Extensions.AddCustomAtributesToExtensions(Context, EntitySet.EntityType()); + } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs index 259ddade..346fafc7 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs @@ -5,6 +5,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -54,5 +55,12 @@ protected override void SetBasicInfo(OpenApiPathItem pathItem) base.SetBasicInfo(pathItem); pathItem.Description = $"Provides operations to manage the collection of {EntitySet.EntityType().Name} entities."; } + + /// + protected override void SetExtensions(OpenApiPathItem pathItem) + { + base.SetExtensions(pathItem); + pathItem.Extensions.AddCustomAtributesToExtensions(Context, EntitySet); + } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs index 114ca131..dd5568de 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs @@ -3,7 +3,6 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ -using System; using System.Diagnostics; using System.Linq; using System.Collections.Generic; @@ -241,8 +240,11 @@ protected override void SetExtensions(OpenApiPathItem item) array.Add(new OpenApiString(p.GetPathItemName(settings))); } - item.Extensions.Add(Constants.xMsDosGroupPath, array); + item.Extensions.Add(Constants.xMsDosGroupPath, array); } + + base.SetExtensions(item); + item.Extensions.AddCustomAtributesToExtensions(Context, NavigationProperty); } /// protected override void SetBasicInfo(OpenApiPathItem pathItem) diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/ODataTypeCastPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/ODataTypeCastPathItemHandler.cs index bab91a27..d204e06e 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/ODataTypeCastPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/ODataTypeCastPathItemHandler.cs @@ -5,6 +5,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.PathItem; @@ -32,10 +33,18 @@ protected override void Initialize(ODataContext context, ODataPath path) } } private IEdmStructuredType StructuredType { get; set; } + /// protected override void SetBasicInfo(OpenApiPathItem pathItem) { base.SetBasicInfo(pathItem); pathItem.Description = $"Casts the previous resource to {(StructuredType as IEdmNamedElement).Name}."; } + + /// + protected override void SetExtensions(OpenApiPathItem pathItem) + { + base.SetExtensions(pathItem); + pathItem.Extensions.AddCustomAtributesToExtensions(Context, StructuredType); + } } diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs index 5fa421ed..c94f3161 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs @@ -5,6 +5,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -58,11 +59,19 @@ protected override void Initialize(ODataContext context, ODataPath path) ODataOperationImportSegment operationImportSegment = path.FirstSegment as ODataOperationImportSegment; EdmOperationImport = operationImportSegment.OperationImport; } + /// protected override void SetBasicInfo(OpenApiPathItem pathItem) { base.SetBasicInfo(pathItem); pathItem.Description = $"Provides operations to call the {EdmOperationImport.Name} method."; } + + /// + protected override void SetExtensions(OpenApiPathItem item) + { + base.SetExtensions(item); + item.Extensions.AddCustomAtributesToExtensions(Context, EdmOperationImport); + } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs index 49b52aec..d3ea0b10 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs @@ -93,7 +93,11 @@ protected override void SetExtensions(OpenApiPathItem item) item.Extensions.Add(Constants.xMsDosGroupPath, array); } + + base.SetExtensions(item); + item.Extensions.AddCustomAtributesToExtensions(Context, EdmOperation); } + /// protected override void SetBasicInfo(OpenApiPathItem pathItem) { diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs index 2f2ff067..c9f84c91 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs @@ -1,9 +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 System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs index d9f41ea4..cae8e092 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs @@ -5,6 +5,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -49,11 +50,19 @@ protected override void Initialize(ODataContext context, ODataPath path) ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; Singleton = navigationSourceSegment.NavigationSource as IEdmSingleton; } + /// protected override void SetBasicInfo(OpenApiPathItem pathItem) { base.SetBasicInfo(pathItem); pathItem.Description = $"Provides operations to manage the {Singleton.EntityType().Name} singleton."; } + + /// + protected override void SetExtensions(OpenApiPathItem pathItem) + { + base.SetExtensions(pathItem); + pathItem.Extensions.AddCustomAtributesToExtensions(Context, Singleton); + } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt index c35caee5..0fad6f71 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt @@ -7,6 +7,8 @@ Microsoft.OpenApi.OData.Edm.EdmModelExtensions Microsoft.OpenApi.OData.Edm.EdmTypeExtensions Microsoft.OpenApi.OData.OpenApiConvertSettings.ExpandDerivedTypesNavigationProperties.get -> bool Microsoft.OpenApi.OData.OpenApiConvertSettings.ExpandDerivedTypesNavigationProperties.set -> void +Microsoft.OpenApi.OData.OpenApiConvertSettings.CustomXMLAttributesMapping.get -> System.Collections.Generic.Dictionary +Microsoft.OpenApi.OData.OpenApiConvertSettings.CustomXMLAttributesMapping.set -> void Microsoft.OpenApi.OData.OpenApiConvertSettings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths.get -> bool Microsoft.OpenApi.OData.OpenApiConvertSettings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths.set -> void static Microsoft.OpenApi.OData.Edm.EdmTypeExtensions.ShouldPathParameterBeQuoted(this Microsoft.OData.Edm.IEdmType edmType, Microsoft.OpenApi.OData.OpenApiConvertSettings settings) -> bool diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs index 4abcf592..98f86e49 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs @@ -126,6 +126,70 @@ public void CreateStructuredTypeSchemaWithDiscriminatorValueEnabledReturnsCorrec }".ChangeLineBreaks(), json); } + [Fact] + public void CreateStructuredTypePropertiesSchemaWithCustomAttributeReturnsCorrectSchema() + { + // Arrange + IEdmModel model = EdmModelHelper.GraphBetaModel; + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping.Add("IsHidden", "x-ms-isHidden"); + IEdmEntityType entity = model.SchemaElements.OfType().First(t => t.Name == "userSettings"); + Assert.NotNull(entity); // Guard + + // Act + OpenApiSchema schema = context.CreateStructuredTypeSchema(entity); + string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + + // Assert + Assert.NotNull(json); + Assert.Equal(@"{ + ""allOf"": [ + { + ""$ref"": ""#/components/schemas/microsoft.graph.entity"" + }, + { + ""title"": ""userSettings"", + ""type"": ""object"", + ""properties"": { + ""contributionToContentDiscoveryAsOrganizationDisabled"": { + ""type"": ""boolean"", + ""x-ms-isHidden"": ""true"" + }, + ""contributionToContentDiscoveryDisabled"": { + ""type"": ""boolean"", + ""x-ms-isHidden"": ""true"" + }, + ""itemInsights"": { + ""anyOf"": [ + { + ""$ref"": ""#/components/schemas/microsoft.graph.userInsightsSettings"" + } + ], + ""nullable"": true, + ""x-ms-isHidden"": ""true"" + }, + ""regionalAndLanguageSettings"": { + ""anyOf"": [ + { + ""$ref"": ""#/components/schemas/microsoft.graph.regionalAndLanguageSettings"" + } + ], + ""nullable"": true + }, + ""shiftPreferences"": { + ""anyOf"": [ + { + ""$ref"": ""#/components/schemas/microsoft.graph.shiftPreferences"" + } + ], + ""nullable"": true + } + } + } + ] +}".ChangeLineBreaks(), json); + } + [Fact] public void CreateComplexTypeWithoutBaseSchemaReturnCorrectSchema() { diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/ComplexPropertyPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/ComplexPropertyPathItemHandlerTests.cs index ba471d98..d1d342a3 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/ComplexPropertyPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/ComplexPropertyPathItemHandlerTests.cs @@ -5,6 +5,8 @@ using System; using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Xunit; @@ -180,4 +182,29 @@ public void SetsPostOnCollectionProperties(bool useAnnotationToGeneratePath, boo Assert.False(pathItem.Operations.ContainsKey(OperationType.Post)); } } + [Fact] + public void CreateComplexPropertyPathItemAddsCustomAttributeValuesToPathExtensions() + { + // Arrange + var model = EntitySetPathItemHandlerTests.GetEdmModel(""); + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping.Add("ags:IsHidden", "x-ms-isHidden"); + var entitySet = model.EntityContainer.FindEntitySet("Customers"); + Assert.NotNull(entitySet); // guard + var entityType = entitySet.EntityType(); + var property = entityType.FindProperty("AlternativeAddresses"); + Assert.NotNull(property); // guard + var path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entityType), new ODataComplexPropertySegment(property as IEdmStructuralProperty)); + Assert.Equal(ODataPathKind.ComplexProperty, path.Kind); // guard + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out IOpenApiExtension isHiddenExtension); + string isHiddenValue = (isHiddenExtension as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + } } \ No newline at end of file diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs index 0e609c92..fc1adf7d 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs @@ -6,6 +6,8 @@ using System; using System.Linq; using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; @@ -251,6 +253,41 @@ private void VerifyPathItemOperations(string annotation, OperationType[] expecte Assert.NotEmpty(pathItem.Operations); Assert.Equal(expected, pathItem.Operations.Select(e => e.Key)); } + + [Fact] + public void CreateEntityPathItemAddsCustomAttributeValuesToPathExtensions() + { + // Arrange + IEdmModel model = EntitySetPathItemHandlerTests.GetEdmModel(annotation: ""); + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping = new() + { + { + "ags:IsHidden", "x-ms-isHidden" + }, + { + "isOwner", "x-ms-isOwner" + } + }; + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers"); + Assert.NotNull(entitySet); // guard + ODataPath path = new(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType())); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out IOpenApiExtension isHiddenExtension); + string isHiddenValue = (isHiddenExtension as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + + pathItem.Extensions.TryGetValue("x-ms-isOwner", out IOpenApiExtension isOwnerExtension); + string isOwnerValue = (isOwnerExtension as OpenApiString)?.Value; + Assert.Equal("true", isOwnerValue); + } } internal class MyEntityPathItemHandler : EntityPathItemHandler diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs index ec604a6a..5d907295 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntitySetPathItemHandlerTests.cs @@ -10,6 +10,7 @@ using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; using Microsoft.OData.Edm.Validation; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; @@ -153,25 +154,48 @@ private void VerifyPathItemOperations(string annotation, OperationType[] expecte Assert.Equal(expected, pathItem.Operations.Select(e => e.Key)); } - public static IEdmModel GetEdmModel(string annotation, string target = "\"NS.Default/Customers\"") + [Fact] + public void CreateEntitySetPathItemAddsCustomAttributeValuesToPathExtensions() + { + // Arrange + IEdmModel model = GetEdmModel(annotation: ""); + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping.Add("ags:IsHidden", "x-ms-isHidden"); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers"); + Assert.NotNull(entitySet); // guard + ODataPath path = new(new ODataNavigationSourceSegment(entitySet)); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out var value); + string isHiddenValue = (value as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + } + + public static IEdmModel GetEdmModel(string annotation, string target = "\"NS.Default/Customers\"", string customXMLAttribute = null) { - const string template = @" + const string template = @" - + - + - + {1} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs index 79d18955..3b442a22 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/NavigationPropertyPathItemHandlerTests.cs @@ -10,6 +10,7 @@ using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; using Microsoft.OData.Edm.Validation; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; @@ -402,29 +403,6 @@ public void CreatePathItemForNavigationPropertyAndUpdateRestrictions(bool hasRes expected = new[] { OperationType.Get }; } - //if (!isContainment || hasRestrictions) - //{ - // if (isCollection) - // { - // expected = new[] { OperationType.Get }; - // } - // else - // { - // expected = new[] { OperationType.Get, OperationType.Delete }; - // } - //} - //else - //{ - // if (isCollection) - // { - // expected = new[] { OperationType.Get, OperationType.Patch }; - // } - // else - // { - // expected = new[] { OperationType.Get, OperationType.Patch, OperationType.Delete }; - // } - //} - Assert.Equal(expected, pathItem.Operations.Select(o => o.Key)); } @@ -493,9 +471,32 @@ public void CreatePathItemForNavigationPropertyAndUpdateMethodUpdateRestrictions Assert.Equal(expected, pathItem.Operations.Select(o => o.Key)); } + [Fact] + public void CreateNavigationPropertyPathItemAddsCustomAttributeValuesToPathExtensions() + { + // Arrange + IEdmModel model = GetEdmModel(annotation: ""); + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping.Add("ags:IsHidden", "x-ms-isHidden"); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers"); + Assert.NotNull(entitySet); // guard + ODataPath path = CreatePath(entitySet, "MyOrder", false); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out var value); + string isHiddenValue = (value as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + } + public static IEdmModel GetEdmModel(string annotation) { - const string template = @" + const string template = @" @@ -506,7 +507,7 @@ public static IEdmModel GetEdmModel(string annotation) - + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/ODataTypeCastPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/ODataTypeCastPathItemHandlerTests.cs new file mode 100644 index 00000000..b4ee4c97 --- /dev/null +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/ODataTypeCastPathItemHandlerTests.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// 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.Tests; +using Microsoft.OpenApi.OData.Edm; +using System.Linq; +using Xunit; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Any; + +namespace Microsoft.OpenApi.OData.PathItem.Tests +{ + public class ODataTypeCastPathItemHandlerTests + { + private readonly ODataTypeCastPathItemHandler _pathItemHandler = new(); + + [Fact] + public void CreateODataTypeCastPathItemAddsCustomAttributeValuesToPathExtensions() + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping = new() + { + { + "ags:IsHidden", + "x-ms-isHidden" + }, + { + "WorkloadName", + "x-ms-workloadName" + } + }; + + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmEntityType person = model.SchemaElements.OfType().First(c => c.Name == "Person"); + IEdmEntityType employee = model.SchemaElements.OfType().First(c => c.Name == "Employee"); + IEdmNavigationProperty navProperty = person.DeclaredNavigationProperties().First(c => c.Name == "Friends"); + ODataPath path = new(new ODataNavigationSourceSegment(people), + new ODataKeySegment(people.EntityType()), + new ODataNavigationPropertySegment(navProperty), + new ODataTypeCastSegment(employee)); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out IOpenApiExtension isHiddenExtension); + string isHiddenValue = (isHiddenExtension as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + + pathItem.Extensions.TryGetValue("x-ms-workloadName", out IOpenApiExtension isOwnerExtension); + string isOwnerValue = (isOwnerExtension as OpenApiString)?.Value; + Assert.Equal("People", isOwnerValue); + } + } +} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs index b19d7f47..af717784 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationImportPathItemHandlerTests.cs @@ -8,6 +8,8 @@ using System.Xml.Linq; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; @@ -122,6 +124,32 @@ public void CreatePathItemForOperationImportWithReadRestrictionsReturnsCorrectPa } } + [Theory] + [InlineData("GetNearestAirport")] + [InlineData("ResetDataSource")] + public void CreateOperationImportPathItemAddsCustomAttributeValuesToPathExtensions(string operationImport) + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping.Add("ags:IsHidden", "x-ms-isHidden"); + IEdmOperationImport edmOperationImport = model.EntityContainer + .OperationImports().FirstOrDefault(o => o.Name == operationImport); + Assert.NotNull(edmOperationImport); // guard + ODataPath path = new(new ODataOperationImportSegment(edmOperationImport)); + + // Act + OpenApiPathItem pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out IOpenApiExtension isHiddenExtension); + string isHiddenValue = (isHiddenExtension as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + } + public static IEdmModel GetEdmModel(string annotation) { const string template = @" diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs index ef1082a0..0a39fb51 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/OperationPathItemHandlerTests.cs @@ -6,6 +6,8 @@ using System; using System.Linq; using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; @@ -84,5 +86,47 @@ public void CreatePathItemForOperationReturnsCorrectPathItem(string operationNam Assert.Equal(expectSummary, operationKeyValue.Value.Summary); Assert.NotEmpty(pathItem.Description); } + + [Theory] + [InlineData("GetFriendsTrips")] + [InlineData("ShareTrip")] + public void CreateOperationPathItemAddsCustomAttributeValuesToPathExtensions(string operationName) + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping = new() + { + { + "ags:IsHidden", + "x-ms-isHidden" + }, + { + "WorkloadName", + "x-ms-workloadName" + } + }; + IEdmNavigationSource navigationSource = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(navigationSource); // guard + IEdmOperation edmOperation = model.SchemaElements.OfType() + .FirstOrDefault(o => o.Name == operationName); + Assert.NotNull(edmOperation); // guard + ODataPath path = new(new ODataNavigationSourceSegment(navigationSource), new ODataOperationSegment(edmOperation)); + + // Act + OpenApiPathItem pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out IOpenApiExtension isHiddenExtension); + string isHiddenValue = (isHiddenExtension as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + + pathItem.Extensions.TryGetValue("x-ms-workloadName", out IOpenApiExtension isOwnerExtension); + string isOwnerValue = (isOwnerExtension as OpenApiString)?.Value; + Assert.Equal("People", isOwnerValue); + } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs index f43ee53f..da7ef6ab 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/SingletonPathItemHandlerTests.cs @@ -10,6 +10,7 @@ using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; using Microsoft.OData.Edm.Validation; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; @@ -134,9 +135,32 @@ private void VerifyPathItemOperations(string annotation, OperationType[] expecte Assert.Equal(expected, pathItem.Operations.Select(e => e.Key)); } + [Fact] + public void CreateSingletonPathItemAddsCustomAttributeValuesToPathExtensions() + { + // Arrange + IEdmModel model = GetEdmModel(annotation: ""); + ODataContext context = new(model); + context.Settings.CustomXMLAttributesMapping.Add("IsHidden", "x-ms-isHidden"); + 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.Extensions); + + pathItem.Extensions.TryGetValue("x-ms-isHidden", out var value); + string isHiddenValue = (value as OpenApiString)?.Value; + Assert.Equal("true", isHiddenValue); + } + private IEdmModel GetEdmModel(string annotation) { - const string template = @" + const string template = @" @@ -146,7 +170,7 @@ private IEdmModel GetEdmModel(string annotation) - + {0} diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml index be96950e..4812e04a 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml @@ -1,6 +1,5 @@  - - + @@ -8342,9 +8341,9 @@ - - - + + + @@ -8515,8 +8514,8 @@ - - + + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml index 01863792..d42013ed 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/TripService.OData.xml @@ -1,6 +1,6 @@  - - + + @@ -215,7 +215,7 @@ - + @@ -266,7 +266,7 @@ - + @@ -299,7 +299,7 @@ - + @@ -379,8 +379,8 @@ - - + +