Skip to content

Commit d7855be

Browse files
authored
Adds support for reading custom attributes (#214)
* Add and use dictionary that maps custom attribute names and extension names * Retrieve custom attribute values and add them into path extensions * Add tests to validate retrieving of custom attribute values * Update public API doc with new setting property * Refactor adding of custom attributes to path extension to a common class * Refactor out adding of custom attributes to path extension * Update tests and integration file * Minor refactor and update to comment * Get custom attribute for ODataTypeCast paths * Update access modifier * Refactor out common code into utility class for reusability * Retrieve custom attributes for properties * Add validation tests for getting custom attributes for properties * Method renaming; delete unused code; class access level updates * Add extension method that adds values to a dictionary * Fix missing comma
1 parent 682005d commit d7855be

24 files changed

+533
-53
lines changed

src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
using System;
77
using System.Collections.Generic;
88
using System.Linq;
9+
using Microsoft.OData.Edm;
10+
using Microsoft.OData.Edm.Vocabularies;
11+
using Microsoft.OpenApi.Any;
12+
using Microsoft.OpenApi.Interfaces;
913
using Microsoft.OpenApi.OData.Edm;
1014
using Microsoft.OpenApi.OData.Vocabulary;
1115

@@ -128,5 +132,95 @@ internal static string NavigationPropertyPath(this ODataPath path, string naviga
128132
path.Segments.OfType<ODataNavigationPropertySegment>().Select(e => e.Identifier));
129133
return navigationPropertyName == null ? value : $"{value}/{navigationPropertyName}";
130134
}
135+
136+
/// <summary>
137+
/// Attempts to add the specified key and value to the dictionary.
138+
/// </summary>
139+
/// <typeparam name="TKey">The type of the keys in the dictionary</typeparam>
140+
/// <typeparam name="TValue">The type of the values in the dictionary</typeparam>
141+
/// <param name="dictionary">A dictionary with keys of type TKey and values of type TValue.</param>
142+
/// <param name="key">The key of the element to add.</param>
143+
/// <param name="value">The value of the element to add.</param>
144+
/// <returns>true when the key and value are successfully added to the dictionary;
145+
/// false when the dictionary already contains the specified key,
146+
/// in which case nothing gets added.</returns>
147+
/// <exception cref="System.ArgumentNullException">dictionary is null.</exception>
148+
internal static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
149+
TKey key, TValue value)
150+
{
151+
CheckArgumentNull(dictionary, nameof(dictionary));
152+
153+
if (!dictionary.ContainsKey(key))
154+
{
155+
dictionary.Add(key, value);
156+
return true;
157+
}
158+
return false;
159+
}
160+
161+
/// <summary>
162+
/// Adds a mapping of custom extension values against custom attribute values for a given element to the provided
163+
/// extensions object.
164+
/// </summary>
165+
/// <param name="extensions">The target extensions object in which the mapped extensions and custom attribute
166+
/// values will be added to.</param>
167+
/// <param name="context">The OData context.</param>
168+
/// <param name="element">The target element.</param>
169+
internal static void AddCustomAtributesToExtensions(this IDictionary<string, IOpenApiExtension> extensions, ODataContext context, IEdmElement element)
170+
{
171+
if (extensions == null ||
172+
context == null ||
173+
element == null)
174+
{
175+
return;
176+
}
177+
178+
Dictionary<string, string> atrributesValueMap = GetCustomXMLAtrributesValueMapping(context.Model, element, context.Settings.CustomXMLAttributesMapping);
179+
180+
if (atrributesValueMap?.Any() ?? false)
181+
{
182+
foreach (var item in atrributesValueMap)
183+
{
184+
extensions.TryAdd(item.Key, new OpenApiString(item.Value));
185+
}
186+
}
187+
}
188+
189+
/// <summary>
190+
/// Correlates and retrieves custom attribute values for a given element in an Edm model
191+
/// from a provided dictionary mapping of attribute names and extension names.
192+
/// </summary>
193+
/// <param name="model">The Edm model.</param>
194+
/// <param name="element">The target element.</param>
195+
/// <param name="customXMLAttributesMapping">The dictionary mapping of attribute names and extension names.</param>
196+
/// <returns>A dictionary of extension names mapped to the custom attribute values.</returns>
197+
private static Dictionary<string, string> GetCustomXMLAtrributesValueMapping(IEdmModel model, IEdmElement element, Dictionary<string, string> customXMLAttributesMapping)
198+
{
199+
Dictionary<string, string> atrributesValueMap = new();
200+
201+
if ((!customXMLAttributesMapping?.Any() ?? true) ||
202+
model == null ||
203+
element == null)
204+
{
205+
return atrributesValueMap;
206+
}
207+
208+
foreach (var item in customXMLAttributesMapping)
209+
{
210+
string attributeName = item.Key.Split(':').Last(); // example, 'ags:IsHidden' --> 'IsHidden'
211+
string extensionName = item.Value;
212+
EdmStringConstant customXMLAttribute = model.DirectValueAnnotationsManager.GetDirectValueAnnotations(element)?
213+
.Where(x => x.Name.Equals(attributeName, StringComparison.OrdinalIgnoreCase))?
214+
.FirstOrDefault()?.Value as EdmStringConstant;
215+
string attributeValue = customXMLAttribute?.Value;
216+
217+
if (!string.IsNullOrEmpty(attributeValue))
218+
{
219+
atrributesValueMap.TryAdd(extensionName, attributeValue);
220+
}
221+
}
222+
223+
return atrributesValueMap;
224+
}
131225
}
132226
}

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,16 +327,18 @@ public static IDictionary<string, OpenApiSchema> CreateStructuredTypePropertiesS
327327
// structure properties
328328
foreach (var property in structuredType.DeclaredStructuralProperties())
329329
{
330-
// OpenApiSchema propertySchema = property.Type.CreateSchema();
331-
// propertySchema.Default = property.DefaultValueString != null ? new OpenApiString(property.DefaultValueString) : null;
332-
properties.Add(property.Name, context.CreatePropertySchema(property));
330+
OpenApiSchema propertySchema = context.CreatePropertySchema(property);
331+
propertySchema.Description = context.Model.GetDescriptionAnnotation(property);
332+
propertySchema.Extensions.AddCustomAtributesToExtensions(context, property);
333+
properties.Add(property.Name, propertySchema);
333334
}
334335

335336
// navigation properties
336337
foreach (var property in structuredType.DeclaredNavigationProperties())
337338
{
338339
OpenApiSchema propertySchema = context.CreateEdmTypeSchema(property.Type);
339340
propertySchema.Description = context.Model.GetDescriptionAnnotation(property);
341+
propertySchema.Extensions.AddCustomAtributesToExtensions(context, property);
340342
properties.Add(property.Name, propertySchema);
341343
}
342344

src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// ------------------------------------------------------------
55

66
using System;
7+
using System.Collections.Generic;
78
using Microsoft.OpenApi.OData.Common;
89
using Microsoft.OpenApi.OData.Edm;
910
using Microsoft.OpenApi.OData.Extensions;
@@ -242,6 +243,11 @@ public string PathPrefix
242243
/// </summary>
243244
public bool RequireRestrictionAnnotationsToGenerateComplexPropertyPaths { get; set; } = true;
244245

246+
/// <summary>
247+
/// Gets/sets a dictionary containing a mapping of custom atttribute names and extension names.
248+
/// </summary>
249+
public Dictionary<string, string> CustomXMLAttributesMapping { get; set; } = new();
250+
245251
internal OpenApiConvertSettings Clone()
246252
{
247253
var newSettings = new OpenApiConvertSettings
@@ -281,7 +287,8 @@ internal OpenApiConvertSettings Clone()
281287
ErrorResponsesAsDefault = this.ErrorResponsesAsDefault,
282288
InnerErrorComplexTypeName = this.InnerErrorComplexTypeName,
283289
RequireRestrictionAnnotationsToGenerateComplexPropertyPaths = this.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths,
284-
ExpandDerivedTypesNavigationProperties = this.ExpandDerivedTypesNavigationProperties
290+
ExpandDerivedTypesNavigationProperties = this.ExpandDerivedTypesNavigationProperties,
291+
CustomXMLAttributesMapping = this.CustomXMLAttributesMapping
285292
};
286293

287294
return newSettings;

src/Microsoft.OpenApi.OData.Reader/PathItem/ComplexPropertyItemHandler.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using Microsoft.OData.Edm;
77
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.OData.Common;
89
using Microsoft.OpenApi.OData.Edm;
910
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
1011

@@ -65,4 +66,10 @@ protected override void Initialize(ODataContext context, ODataPath path)
6566
ODataComplexPropertySegment navigationSourceSegment = path.LastSegment as ODataComplexPropertySegment;
6667
ComplexProperty = navigationSourceSegment.Property;
6768
}
69+
70+
/// <inheritdoc/>
71+
protected override void SetExtensions(OpenApiPathItem item)
72+
{
73+
item.Extensions.AddCustomAtributesToExtensions(Context, ComplexProperty);
74+
}
6875
}

src/Microsoft.OpenApi.OData.Reader/PathItem/EntityPathItemHandler.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// ------------------------------------------------------------
55

6+
using Microsoft.OData.Edm;
67
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.OData.Common;
79
using Microsoft.OpenApi.OData.Edm;
810
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
911

@@ -48,5 +50,12 @@ protected override void SetOperations(OpenApiPathItem item)
4850
AddOperation(item, OperationType.Delete);
4951
}
5052
}
53+
54+
/// <inheritdoc/>
55+
protected override void SetExtensions(OpenApiPathItem pathItem)
56+
{
57+
base.SetExtensions(pathItem);
58+
pathItem.Extensions.AddCustomAtributesToExtensions(Context, EntitySet.EntityType());
59+
}
5160
}
5261
}

src/Microsoft.OpenApi.OData.Reader/PathItem/EntitySetPathItemHandler.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using Microsoft.OData.Edm;
77
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.OData.Common;
89
using Microsoft.OpenApi.OData.Edm;
910
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
1011

@@ -54,5 +55,12 @@ protected override void SetBasicInfo(OpenApiPathItem pathItem)
5455
base.SetBasicInfo(pathItem);
5556
pathItem.Description = $"Provides operations to manage the collection of {EntitySet.EntityType().Name} entities.";
5657
}
58+
59+
/// <inheritdoc/>
60+
protected override void SetExtensions(OpenApiPathItem pathItem)
61+
{
62+
base.SetExtensions(pathItem);
63+
pathItem.Extensions.AddCustomAtributesToExtensions(Context, EntitySet);
64+
}
5765
}
5866
}

src/Microsoft.OpenApi.OData.Reader/PathItem/NavigationPropertyPathItemHandler.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// ------------------------------------------------------------
55

6-
using System;
76
using System.Diagnostics;
87
using System.Linq;
98
using System.Collections.Generic;
@@ -241,8 +240,11 @@ protected override void SetExtensions(OpenApiPathItem item)
241240
array.Add(new OpenApiString(p.GetPathItemName(settings)));
242241
}
243242

244-
item.Extensions.Add(Constants.xMsDosGroupPath, array);
243+
item.Extensions.Add(Constants.xMsDosGroupPath, array);
245244
}
245+
246+
base.SetExtensions(item);
247+
item.Extensions.AddCustomAtributesToExtensions(Context, NavigationProperty);
246248
}
247249
/// <inheritdoc/>
248250
protected override void SetBasicInfo(OpenApiPathItem pathItem)

src/Microsoft.OpenApi.OData.Reader/PathItem/ODataTypeCastPathItemHandler.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using Microsoft.OData.Edm;
77
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.OData.Common;
89
using Microsoft.OpenApi.OData.Edm;
910

1011
namespace Microsoft.OpenApi.OData.PathItem;
@@ -32,10 +33,18 @@ protected override void Initialize(ODataContext context, ODataPath path)
3233
}
3334
}
3435
private IEdmStructuredType StructuredType { get; set; }
36+
3537
/// <inheritdoc/>
3638
protected override void SetBasicInfo(OpenApiPathItem pathItem)
3739
{
3840
base.SetBasicInfo(pathItem);
3941
pathItem.Description = $"Casts the previous resource to {(StructuredType as IEdmNamedElement).Name}.";
4042
}
43+
44+
/// <inheritdoc/>
45+
protected override void SetExtensions(OpenApiPathItem pathItem)
46+
{
47+
base.SetExtensions(pathItem);
48+
pathItem.Extensions.AddCustomAtributesToExtensions(Context, StructuredType);
49+
}
4150
}

src/Microsoft.OpenApi.OData.Reader/PathItem/OperationImportPathItemHandler.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using Microsoft.OData.Edm;
77
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.OData.Common;
89
using Microsoft.OpenApi.OData.Edm;
910
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
1011

@@ -58,11 +59,19 @@ protected override void Initialize(ODataContext context, ODataPath path)
5859
ODataOperationImportSegment operationImportSegment = path.FirstSegment as ODataOperationImportSegment;
5960
EdmOperationImport = operationImportSegment.OperationImport;
6061
}
62+
6163
/// <inheritdoc/>
6264
protected override void SetBasicInfo(OpenApiPathItem pathItem)
6365
{
6466
base.SetBasicInfo(pathItem);
6567
pathItem.Description = $"Provides operations to call the {EdmOperationImport.Name} method.";
6668
}
69+
70+
/// <inheritdoc/>
71+
protected override void SetExtensions(OpenApiPathItem item)
72+
{
73+
base.SetExtensions(item);
74+
item.Extensions.AddCustomAtributesToExtensions(Context, EdmOperationImport);
75+
}
6776
}
6877
}

src/Microsoft.OpenApi.OData.Reader/PathItem/OperationPathItemHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ protected override void SetExtensions(OpenApiPathItem item)
9393

9494
item.Extensions.Add(Constants.xMsDosGroupPath, array);
9595
}
96+
97+
base.SetExtensions(item);
98+
item.Extensions.AddCustomAtributesToExtensions(Context, EdmOperation);
9699
}
100+
97101
/// <inheritdoc/>
98102
protected override void SetBasicInfo(OpenApiPathItem pathItem)
99103
{

src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
// ------------------------------------------------------------
1+
// ------------------------------------------------------------
22
// Copyright (c) Microsoft Corporation. All rights reserved.
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// ------------------------------------------------------------
55

66
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using Microsoft.OData.Edm;
10+
using Microsoft.OpenApi.Any;
11+
using Microsoft.OpenApi.Interfaces;
712
using Microsoft.OpenApi.Models;
813
using Microsoft.OpenApi.OData.Common;
914
using Microsoft.OpenApi.OData.Edm;

src/Microsoft.OpenApi.OData.Reader/PathItem/SingletonPathItemHandler.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using Microsoft.OData.Edm;
77
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.OData.Common;
89
using Microsoft.OpenApi.OData.Edm;
910
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
1011

@@ -49,11 +50,19 @@ protected override void Initialize(ODataContext context, ODataPath path)
4950
ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment;
5051
Singleton = navigationSourceSegment.NavigationSource as IEdmSingleton;
5152
}
53+
5254
/// <inheritdoc/>
5355
protected override void SetBasicInfo(OpenApiPathItem pathItem)
5456
{
5557
base.SetBasicInfo(pathItem);
5658
pathItem.Description = $"Provides operations to manage the {Singleton.EntityType().Name} singleton.";
5759
}
60+
61+
/// <inheritdoc/>
62+
protected override void SetExtensions(OpenApiPathItem pathItem)
63+
{
64+
base.SetExtensions(pathItem);
65+
pathItem.Extensions.AddCustomAtributesToExtensions(Context, Singleton);
66+
}
5867
}
5968
}

src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Microsoft.OpenApi.OData.Edm.EdmModelExtensions
77
Microsoft.OpenApi.OData.Edm.EdmTypeExtensions
88
Microsoft.OpenApi.OData.OpenApiConvertSettings.ExpandDerivedTypesNavigationProperties.get -> bool
99
Microsoft.OpenApi.OData.OpenApiConvertSettings.ExpandDerivedTypesNavigationProperties.set -> void
10+
Microsoft.OpenApi.OData.OpenApiConvertSettings.CustomXMLAttributesMapping.get -> System.Collections.Generic.Dictionary<string, string>
11+
Microsoft.OpenApi.OData.OpenApiConvertSettings.CustomXMLAttributesMapping.set -> void
1012
Microsoft.OpenApi.OData.OpenApiConvertSettings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths.get -> bool
1113
Microsoft.OpenApi.OData.OpenApiConvertSettings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths.set -> void
1214
static Microsoft.OpenApi.OData.Edm.EdmTypeExtensions.ShouldPathParameterBeQuoted(this Microsoft.OData.Edm.IEdmType edmType, Microsoft.OpenApi.OData.OpenApiConvertSettings settings) -> bool

0 commit comments

Comments
 (0)