Skip to content

Commit 4db0cbc

Browse files
authored
Merge pull request #639 from microsoft/andrueastman/syncFixes
fix: port #597
2 parents 03812a5 + f7611ae commit 4db0cbc

9 files changed

+152
-58
lines changed

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

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Collections.Generic;
88
using System.Linq;
9+
using System.Security.Cryptography.X509Certificates;
910
using Microsoft.OData.Edm;
1011
using Microsoft.OData.Edm.Csdl;
1112
using Microsoft.OData.Edm.Vocabularies;
@@ -93,38 +94,40 @@ internal static bool NavigationRestrictionsAllowsNavigability(
9394
/// Generates the operation id from a navigation property path.
9495
/// </summary>
9596
/// <param name="path">The target <see cref="ODataPath"/>.</param>
97+
/// <param name="context">The OData context.</param>
9698
/// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued non-indexed or single-valued navigation property.</param>
9799
/// <returns>The operation id generated from the given navigation property path.</returns>
98-
internal static string GenerateNavigationPropertyPathOperationId(ODataPath path, string prefix = null)
100+
internal static string GenerateNavigationPropertyPathOperationId(ODataPath path, ODataContext context, string prefix = null)
99101
{
100-
IList<string> items = RetrieveNavigationPropertyPathsOperationIdSegments(path);
102+
IList<string> items = RetrieveNavigationPropertyPathsOperationIdSegments(path, context);
101103

102104
if (!items.Any())
103105
return null;
104106

105-
int lastItemIndex = items.Count - 1;
107+
int lastItemIndex = items[items.Count-1].StartsWith("-") ? items.Count - 2 : items.Count - 1;
106108

107109
if (!string.IsNullOrEmpty(prefix))
108110
{
109-
items[lastItemIndex] = prefix + Utils.UpperFirstChar(items.Last());
111+
items[lastItemIndex] = prefix + Utils.UpperFirstChar(items[lastItemIndex]);
110112
}
111113
else
112114
{
113-
items[lastItemIndex] = Utils.UpperFirstChar(items.Last());
115+
items[lastItemIndex] = Utils.UpperFirstChar(items[lastItemIndex]);
114116
}
115117

116-
return string.Join(".", items);
118+
return GenerateNavigationPropertyPathOperationId(items);
117119
}
118120

119121
/// <summary>
120122
/// Generates the operation id from a complex property path.
121123
/// </summary>
122124
/// <param name="path">The target <see cref="ODataPath"/>.</param>
125+
/// <param name="context">The OData context.</param>
123126
/// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued or single-valued complex property.</param>
124127
/// <returns>The operation id generated from the given complex property path.</returns>
125-
internal static string GenerateComplexPropertyPathOperationId(ODataPath path, string prefix = null)
128+
internal static string GenerateComplexPropertyPathOperationId(ODataPath path, ODataContext context, string prefix = null)
126129
{
127-
IList<string> items = RetrieveNavigationPropertyPathsOperationIdSegments(path);
130+
IList<string> items = RetrieveNavigationPropertyPathsOperationIdSegments(path, context);
128131

129132
if (!items.Any())
130133
return null;
@@ -141,15 +144,29 @@ internal static string GenerateComplexPropertyPathOperationId(ODataPath path, st
141144
items.Add(Utils.UpperFirstChar(lastSegment?.Identifier));
142145
}
143146

144-
return string.Join(".", items);
147+
return GenerateNavigationPropertyPathOperationId(items);
148+
}
149+
150+
/// <summary>
151+
/// Generates a navigation property operation id from a list of string values.
152+
/// </summary>
153+
/// <param name="items">The list of string values.</param>
154+
/// <returns>The generated navigation property operation id.</returns>
155+
private static string GenerateNavigationPropertyPathOperationId(IList<string> items)
156+
{
157+
if (!items.Any())
158+
return null;
159+
160+
return string.Join(".", items).Replace(".-", "-"); // Format any hashed value appropriately (this will be the last value)
145161
}
146162

147163
/// <summary>
148164
/// Retrieves the segments of an operation id generated from a navigation property path.
149165
/// </summary>
150166
/// <param name="path">The target <see cref="ODataPath"/>.</param>
167+
/// <param name="context">The OData context.</param>
151168
/// <returns>The segments of an operation id generated from the given navigation property path.</returns>
152-
internal static IList<string> RetrieveNavigationPropertyPathsOperationIdSegments(ODataPath path)
169+
internal static IList<string> RetrieveNavigationPropertyPathsOperationIdSegments(ODataPath path, ODataContext context)
153170
{
154171
Utils.CheckArgumentNull(path, nameof(path));
155172

@@ -173,6 +190,8 @@ s is ODataOperationSegment ||
173190
Utils.CheckArgumentNull(segments, nameof(segments));
174191

175192
string previousTypeCastSegmentId = null;
193+
string pathHash = string.Empty;
194+
176195
foreach (var segment in segments)
177196
{
178197
if (segment is ODataNavigationPropertySegment navPropSegment)
@@ -189,25 +208,38 @@ s is ODataOperationSegment ||
189208
previousTypeCastSegmentId = "As" + Utils.UpperFirstChar(schemaElement.Name);
190209
items.Add(previousTypeCastSegmentId);
191210
}
192-
else if (segment is ODataOperationSegment operationSegment)
193-
{
194-
// Navigation property generated via composable function
195-
items.Add(operationSegment.Identifier);
211+
else if (segment is ODataOperationSegment operationSegment)
212+
{
213+
// Navigation property generated via composable function
214+
if (operationSegment.Operation is IEdmFunction function && context.Model.IsOperationOverload(function))
215+
{
216+
// Hash the segment to avoid duplicate operationIds
217+
pathHash = string.IsNullOrEmpty(pathHash)
218+
? operationSegment.GetPathHash(context.Settings)
219+
: (pathHash + operationSegment.GetPathHash(context.Settings)).GetHashSHA256().Substring(0,4);
220+
}
221+
222+
items.Add(operationSegment.Identifier);
196223
}
197-
else if (segment is ODataKeySegment keySegment && keySegment.IsAlternateKey)
198-
{
199-
// We'll consider alternate keys in the operation id to eliminate potential duplicates with operation id of primary path
200-
if (segment == segments.Last())
201-
{
202-
items.Add("By" + string.Join("", keySegment.Identifier.Split(',').Select(static x => Utils.UpperFirstChar(x))));
203-
}
204-
else
205-
{
206-
items.Add(keySegment.Identifier);
207-
}
224+
else if (segment is ODataKeySegment keySegment && keySegment.IsAlternateKey)
225+
{
226+
// We'll consider alternate keys in the operation id to eliminate potential duplicates with operation id of primary path
227+
if (segment == segments.Last())
228+
{
229+
items.Add("By" + string.Join("", keySegment.Identifier.Split(',').Select(static x => Utils.UpperFirstChar(x))));
230+
}
231+
else
232+
{
233+
items.Add(keySegment.Identifier);
234+
}
208235
}
209236
}
210237

238+
if (!string.IsNullOrEmpty(pathHash))
239+
{
240+
items.Add("-" + pathHash);
241+
}
242+
211243
return items;
212244
}
213245

@@ -320,9 +352,10 @@ internal static string GenerateComplexPropertyPathTagName(ODataPath path, ODataC
320352
/// Generates the operation id prefix from an OData type cast path.
321353
/// </summary>
322354
/// <param name="path">The target <see cref="ODataPath"/>.</param>
355+
/// <param name="context">The OData context.</param>
323356
/// <param name="includeListOrGetPrefix">Optional: Whether to include the List or Get prefix to the generated operation id.</param>
324357
/// <returns>The operation id prefix generated from the OData type cast path.</returns>
325-
internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path, bool includeListOrGetPrefix = true)
358+
internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path, ODataContext context, bool includeListOrGetPrefix = true)
326359
{
327360
// Get the segment before the last OData type cast segment
328361
ODataTypeCastSegment typeCastSegment = path.Segments.OfType<ODataTypeCastSegment>()?.Last();
@@ -352,7 +385,7 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
352385
if (secondLastSegment is ODataComplexPropertySegment complexSegment)
353386
{
354387
string listOrGet = includeListOrGetPrefix ? (complexSegment.Property.Type.IsCollection() ? "List" : "Get") : null;
355-
operationId = GenerateComplexPropertyPathOperationId(path, listOrGet);
388+
operationId = GenerateComplexPropertyPathOperationId(path, context, listOrGet);
356389
}
357390
else if (secondLastSegment is ODataNavigationPropertySegment navPropSegment)
358391
{
@@ -362,27 +395,27 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
362395
prefix = navPropSegment?.NavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many ? "List" : "Get";
363396
}
364397

365-
operationId = GenerateNavigationPropertyPathOperationId(path, prefix);
398+
operationId = GenerateNavigationPropertyPathOperationId(path, context, prefix);
366399
}
367400
else if (secondLastSegment is ODataKeySegment keySegment)
368401
{
369-
if (isIndexedCollValuedNavProp)
370-
{
371-
operationId = GenerateNavigationPropertyPathOperationId(path, "Get");
402+
if (isIndexedCollValuedNavProp)
403+
{
404+
operationId = GenerateNavigationPropertyPathOperationId(path, context, "Get");
405+
}
406+
else
407+
{
408+
string entityTypeName = keySegment.EntityType.Name;
409+
string getPrefix = includeListOrGetPrefix ? "Get" : null;
410+
string operationName = $"{getPrefix}{Utils.UpperFirstChar(entityTypeName)}";
411+
if (keySegment.IsAlternateKey)
412+
{
413+
string alternateKeyName = string.Join("", keySegment.Identifier.Split(',').Select(static x => Utils.UpperFirstChar(x)));
414+
operationName = $"{operationName}By{alternateKeyName}";
415+
}
416+
operationId = (entitySet != null) ? entitySet.Name : singleton.Name;
417+
operationId += $".{entityTypeName}.{operationName}";
372418
}
373-
else
374-
{
375-
string entityTypeName = keySegment.EntityType.Name;
376-
string getPrefix = includeListOrGetPrefix ? "Get" : null;
377-
string operationName = $"{getPrefix}{Utils.UpperFirstChar(entityTypeName)}";
378-
if (keySegment.IsAlternateKey)
379-
{
380-
string alternateKeyName = string.Join("", keySegment.Identifier.Split(',').Select(static x => Utils.UpperFirstChar(x)));
381-
operationName = $"{operationName}By{alternateKeyName}";
382-
}
383-
operationId = (entitySet != null) ? entitySet.Name : singleton.Name;
384-
operationId += $".{entityTypeName}.{operationName}";
385-
}
386419
}
387420
else if (secondLastSegment is ODataNavigationSourceSegment)
388421
{

src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@
1515
<TargetFrameworks>netstandard2.0</TargetFrameworks>
1616
<PackageId>Microsoft.OpenApi.OData</PackageId>
1717
<SignAssembly>true</SignAssembly>
18-
<Version>1.7.0</Version>
18+
<Version>1.7.1</Version>
1919
<Description>This package contains the codes you need to convert OData CSDL to Open API Document of Model.</Description>
2020
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
2121
<PackageTags>Microsoft OpenApi OData EDM</PackageTags>
2222
<RepositoryUrl>https://github.com/Microsoft/OpenAPI.NET.OData</RepositoryUrl>
2323
<PackageReleaseNotes>
24-
- Updates tag names for actions/functions operations #627
25-
- Adds nullable to double schema conversion #628
26-
- Updates PUT operation ID prefix from Update to Set #629
27-
- Ensures unique operation IDs for composable functions #630
28-
</PackageReleaseNotes>
24+
- Further fix for generating unique operation ids for navigation property paths with composable overloaded functions #596
25+
</PackageReleaseNotes>
2926
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
3027
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
3128
<OutputPath Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">..\..\bin\Debug\</OutputPath>

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyGetOperationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected override void SetBasicInfo(OpenApiOperation operation)
3939
if (Context.Settings.EnableOperationId)
4040
{
4141
string prefix = ComplexPropertySegment.Property.Type.IsCollection() ? "List" : "Get";
42-
operation.OperationId = EdmModelHelper.GenerateComplexPropertyPathOperationId(Path, prefix);
42+
operation.OperationId = EdmModelHelper.GenerateComplexPropertyPathOperationId(Path, Context, prefix);
4343
}
4444

4545
// Summary and Description

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyPostOperationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected override void SetBasicInfo(OpenApiOperation operation)
4242
// OperationId
4343
if (Context.Settings.EnableOperationId)
4444
{
45-
operation.OperationId = EdmModelHelper.GenerateComplexPropertyPathOperationId(Path, "Set");
45+
operation.OperationId = EdmModelHelper.GenerateComplexPropertyPathOperationId(Path, Context, "Set");
4646
}
4747

4848
// Summary and Description

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyUpdateOperationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected override void SetBasicInfo(OpenApiOperation operation)
4141
if (Context.Settings.EnableOperationId)
4242
{
4343
string prefix = OperationType == OperationType.Patch ? "Update" : "Set";
44-
operation.OperationId = EdmModelHelper.GenerateComplexPropertyPathOperationId(Path, prefix);
44+
operation.OperationId = EdmModelHelper.GenerateComplexPropertyPathOperationId(Path, Context, prefix);
4545
}
4646
}
4747

src/Microsoft.OpenApi.OData.Reader/Operation/DollarCountGetOperationHandler.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,17 @@ protected override void SetBasicInfo(OpenApiOperation operation)
128128
}
129129
else if (SecondLastSegment is ODataNavigationPropertySegment)
130130
{
131-
var navPropOpId = string.Join(".", EdmModelHelper.RetrieveNavigationPropertyPathsOperationIdSegments(Path));
131+
var navPropOpId = string.Join(".", EdmModelHelper.RetrieveNavigationPropertyPathsOperationIdSegments(Path, Context));
132132
operation.OperationId = $"{navPropOpId}.GetCount-{Path.GetPathHash(Context.Settings)}";
133133
}
134134
else if (SecondLastSegment is ODataTypeCastSegment odataTypeCastSegment)
135135
{
136136
IEdmNamedElement targetStructuredType = odataTypeCastSegment.StructuredType as IEdmNamedElement;
137-
operation.OperationId = $"{EdmModelHelper.GenerateODataTypeCastPathOperationIdPrefix(Path, false)}.GetCount.As{Utils.UpperFirstChar(targetStructuredType.Name)}-{Path.GetPathHash(Context.Settings)}";
137+
operation.OperationId = $"{EdmModelHelper.GenerateODataTypeCastPathOperationIdPrefix(Path, Context, false)}.GetCount.As{Utils.UpperFirstChar(targetStructuredType.Name)}-{Path.GetPathHash(Context.Settings)}";
138138
}
139139
else if (SecondLastSegment is ODataComplexPropertySegment)
140140
{
141-
operation.OperationId = $"{EdmModelHelper.GenerateComplexPropertyPathOperationId(Path)}.GetCount-{Path.GetPathHash(Context.Settings)}";
141+
operation.OperationId = $"{EdmModelHelper.GenerateComplexPropertyPathOperationId(Path, Context)}.GetCount-{Path.GetPathHash(Context.Settings)}";
142142
}
143143
}
144144

src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ protected override void SetExtensions(OpenApiOperation operation)
107107

108108
internal string GetOperationId(string prefix = null)
109109
{
110-
return EdmModelHelper.GenerateNavigationPropertyPathOperationId(Path, prefix);
110+
return EdmModelHelper.GenerateNavigationPropertyPathOperationId(Path, Context, prefix);
111111
}
112112

113113
/// <inheritdoc/>

src/Microsoft.OpenApi.OData.Reader/Operation/ODataTypeCastGetOperationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ protected override void SetBasicInfo(OpenApiOperation operation)
188188

189189
// OperationId
190190
if (Context.Settings.EnableOperationId)
191-
operation.OperationId = EdmModelHelper.GenerateODataTypeCastPathOperationIdPrefix(Path) + $".As{Utils.UpperFirstChar(TargetSchemaElement.Name)}";
191+
operation.OperationId = EdmModelHelper.GenerateODataTypeCastPathOperationIdPrefix(Path, Context) + $".As{Utils.UpperFirstChar(TargetSchemaElement.Name)}";
192192

193193
base.SetBasicInfo(operation);
194194
}

0 commit comments

Comments
 (0)