6
6
using System ;
7
7
using System . Collections . Generic ;
8
8
using System . Linq ;
9
+ using System . Security . Cryptography . X509Certificates ;
9
10
using Microsoft . OData . Edm ;
10
11
using Microsoft . OData . Edm . Csdl ;
11
12
using Microsoft . OData . Edm . Vocabularies ;
@@ -93,38 +94,40 @@ internal static bool NavigationRestrictionsAllowsNavigability(
93
94
/// Generates the operation id from a navigation property path.
94
95
/// </summary>
95
96
/// <param name="path">The target <see cref="ODataPath"/>.</param>
97
+ /// <param name="context">The OData context.</param>
96
98
/// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued non-indexed or single-valued navigation property.</param>
97
99
/// <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 )
99
101
{
100
- IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path ) ;
102
+ IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path , context ) ;
101
103
102
104
if ( ! items . Any ( ) )
103
105
return null ;
104
106
105
- int lastItemIndex = items . Count - 1 ;
107
+ int lastItemIndex = items [ items . Count - 1 ] . StartsWith ( "-" ) ? items . Count - 2 : items . Count - 1 ;
106
108
107
109
if ( ! string . IsNullOrEmpty ( prefix ) )
108
110
{
109
- items [ lastItemIndex ] = prefix + Utils . UpperFirstChar ( items . Last ( ) ) ;
111
+ items [ lastItemIndex ] = prefix + Utils . UpperFirstChar ( items [ lastItemIndex ] ) ;
110
112
}
111
113
else
112
114
{
113
- items [ lastItemIndex ] = Utils . UpperFirstChar ( items . Last ( ) ) ;
115
+ items [ lastItemIndex ] = Utils . UpperFirstChar ( items [ lastItemIndex ] ) ;
114
116
}
115
117
116
- return string . Join ( "." , items ) ;
118
+ return GenerateNavigationPropertyPathOperationId ( items ) ;
117
119
}
118
120
119
121
/// <summary>
120
122
/// Generates the operation id from a complex property path.
121
123
/// </summary>
122
124
/// <param name="path">The target <see cref="ODataPath"/>.</param>
125
+ /// <param name="context">The OData context.</param>
123
126
/// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued or single-valued complex property.</param>
124
127
/// <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 )
126
129
{
127
- IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path ) ;
130
+ IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path , context ) ;
128
131
129
132
if ( ! items . Any ( ) )
130
133
return null ;
@@ -141,15 +144,29 @@ internal static string GenerateComplexPropertyPathOperationId(ODataPath path, st
141
144
items . Add ( Utils . UpperFirstChar ( lastSegment ? . Identifier ) ) ;
142
145
}
143
146
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)
145
161
}
146
162
147
163
/// <summary>
148
164
/// Retrieves the segments of an operation id generated from a navigation property path.
149
165
/// </summary>
150
166
/// <param name="path">The target <see cref="ODataPath"/>.</param>
167
+ /// <param name="context">The OData context.</param>
151
168
/// <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 )
153
170
{
154
171
Utils . CheckArgumentNull ( path , nameof ( path ) ) ;
155
172
@@ -173,6 +190,8 @@ s is ODataOperationSegment ||
173
190
Utils . CheckArgumentNull ( segments , nameof ( segments ) ) ;
174
191
175
192
string previousTypeCastSegmentId = null ;
193
+ string pathHash = string . Empty ;
194
+
176
195
foreach ( var segment in segments )
177
196
{
178
197
if ( segment is ODataNavigationPropertySegment navPropSegment )
@@ -189,25 +208,38 @@ s is ODataOperationSegment ||
189
208
previousTypeCastSegmentId = "As" + Utils . UpperFirstChar ( schemaElement . Name ) ;
190
209
items . Add ( previousTypeCastSegmentId ) ;
191
210
}
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 ) ;
196
223
}
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
+ }
208
235
}
209
236
}
210
237
238
+ if ( ! string . IsNullOrEmpty ( pathHash ) )
239
+ {
240
+ items . Add ( "-" + pathHash ) ;
241
+ }
242
+
211
243
return items ;
212
244
}
213
245
@@ -320,9 +352,10 @@ internal static string GenerateComplexPropertyPathTagName(ODataPath path, ODataC
320
352
/// Generates the operation id prefix from an OData type cast path.
321
353
/// </summary>
322
354
/// <param name="path">The target <see cref="ODataPath"/>.</param>
355
+ /// <param name="context">The OData context.</param>
323
356
/// <param name="includeListOrGetPrefix">Optional: Whether to include the List or Get prefix to the generated operation id.</param>
324
357
/// <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 )
326
359
{
327
360
// Get the segment before the last OData type cast segment
328
361
ODataTypeCastSegment typeCastSegment = path . Segments . OfType < ODataTypeCastSegment > ( ) ? . Last ( ) ;
@@ -352,7 +385,7 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
352
385
if ( secondLastSegment is ODataComplexPropertySegment complexSegment )
353
386
{
354
387
string listOrGet = includeListOrGetPrefix ? ( complexSegment . Property . Type . IsCollection ( ) ? "List" : "Get" ) : null ;
355
- operationId = GenerateComplexPropertyPathOperationId ( path , listOrGet ) ;
388
+ operationId = GenerateComplexPropertyPathOperationId ( path , context , listOrGet ) ;
356
389
}
357
390
else if ( secondLastSegment is ODataNavigationPropertySegment navPropSegment )
358
391
{
@@ -362,27 +395,27 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
362
395
prefix = navPropSegment ? . NavigationProperty . TargetMultiplicity ( ) == EdmMultiplicity . Many ? "List" : "Get" ;
363
396
}
364
397
365
- operationId = GenerateNavigationPropertyPathOperationId ( path , prefix ) ;
398
+ operationId = GenerateNavigationPropertyPathOperationId ( path , context , prefix ) ;
366
399
}
367
400
else if ( secondLastSegment is ODataKeySegment keySegment )
368
401
{
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 } ";
372
418
}
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
- }
386
419
}
387
420
else if ( secondLastSegment is ODataNavigationSourceSegment )
388
421
{
0 commit comments