99using  System . Linq ; 
1010using  Microsoft . OData . Edm ; 
1111using  Microsoft . OData . Edm . Vocabularies ; 
12+ using  Microsoft . OpenApi . OData . Common ; 
1213using  Microsoft . OpenApi . OData . Vocabulary . Capabilities ; 
1314
1415namespace  Microsoft . OpenApi . OData . Edm 
@@ -127,6 +128,7 @@ private void AppendPath(ODataPath path)
127128            ODataPathKind  kind  =  path . Kind ; 
128129            switch ( kind ) 
129130            { 
131+                 case  ODataPathKind . TypeCast : 
130132                case  ODataPathKind . DollarCount : 
131133                case  ODataPathKind . Entity : 
132134                case  ODataPathKind . EntitySet : 
@@ -186,11 +188,19 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource
186188            if  ( entitySet  !=  null ) 
187189            { 
188190                count  =  _model . GetRecord < CountRestrictionsType > ( entitySet ,  CapabilitiesConstants . CountRestrictions ) ; 
189-                 if ( count ? . Countable  ??  true ) 
191+                 if ( count ? . Countable  ??  true )   // ~/entitySet/$count 
190192                    CreateCountPath ( path ,  convertSettings ) ; 
191193
194+                 CreateTypeCastPaths ( path ,  convertSettings ,  entityType ,  entitySet ,  true ) ;  // ~/entitySet/subType 
195+ 
192196                path . Push ( new  ODataKeySegment ( entityType ) ) ; 
193197                AppendPath ( path . Clone ( ) ) ; 
198+ 
199+                 CreateTypeCastPaths ( path ,  convertSettings ,  entityType ,  entitySet ,  false ) ;  // ~/entitySet/{id}/subType 
200+             } 
201+             else  if  ( navigationSource  is  IEdmSingleton  singleton ) 
202+             {  // ~/singleton/subType 
203+                 CreateTypeCastPaths ( path ,  convertSettings ,  entityType ,  singleton ,  false ) ; 
194204            } 
195205
196206            // media entity 
@@ -285,13 +295,20 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
285295                IEdmEntityType  navEntityType  =  navigationProperty . ToEntityType ( ) ; 
286296                var  targetsMany  =  navigationProperty . TargetMultiplicity ( )  ==  EdmMultiplicity . Many ; 
287297                var  propertyPath  =  navigationProperty . GetPartnerPath ( ) ? . Path ; 
298+                 var  propertyPathIsEmpty  =  string . IsNullOrEmpty ( propertyPath ) ; 
288299
289-                 if  ( targetsMany  &&  ( string . IsNullOrEmpty ( propertyPath )  || 
290-                     ( count ? . IsNonCountableNavigationProperty ( propertyPath )  ??  true ) ) ) 
300+                 if  ( targetsMany )  
291301                { 
292-                     // ~/entityset/{key}/collection-valued-Nav/$count 
293-                     CreateCountPath ( currentPath ,  convertSettings ) ; 
302+                     if ( propertyPathIsEmpty  || 
303+                         ( count ? . IsNonCountableNavigationProperty ( propertyPath )  ??  true ) ) 
304+                     { 
305+                         // ~/entityset/{key}/collection-valued-Nav/$count 
306+                         CreateCountPath ( currentPath ,  convertSettings ) ; 
307+                     } 
294308                } 
309+                 // ~/entityset/{key}/collection-valued-Nav/subtype 
310+                 // ~/entityset/{key}/single-valued-Nav/subtype 
311+                 CreateTypeCastPaths ( currentPath ,  convertSettings ,  navigationProperty . DeclaringType ,  navigationProperty ,  targetsMany ) ; 
295312
296313                if  ( ! navigationProperty . ContainsTarget ) 
297314                { 
@@ -305,6 +322,8 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
305322                        // Collection-valued: DELETE ~/entityset/{key}/collection-valued-Nav/{key}/$ref 
306323                        currentPath . Push ( new  ODataKeySegment ( navEntityType ) ) ; 
307324                        CreateRefPath ( currentPath ) ; 
325+                         
326+                         CreateTypeCastPaths ( currentPath ,  convertSettings ,  navigationProperty . DeclaringType ,  navigationProperty ,  false ) ;  // ~/entityset/{key}/collection-valued-Nav/{id}/subtype 
308327                    } 
309328
310329                    // Get possible stream paths for the navigation entity type 
@@ -317,6 +336,8 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
317336                    { 
318337                        currentPath . Push ( new  ODataKeySegment ( navEntityType ) ) ; 
319338                        AppendPath ( currentPath . Clone ( ) ) ; 
339+ 
340+                         CreateTypeCastPaths ( currentPath ,  convertSettings ,  navigationProperty . DeclaringType ,  navigationProperty ,  false ) ;  // ~/entityset/{key}/collection-valued-Nav/{id}/subtype 
320341                    } 
321342
322343                    // Get possible stream paths for the navigation entity type 
@@ -393,6 +414,60 @@ private void CreateCountPath(ODataPath currentPath, OpenApiConvertSettings conve
393414            AppendPath ( countPath ) ; 
394415        } 
395416
417+         /// <summary> 
418+         /// Create OData type cast paths. 
419+         /// </summary> 
420+         /// <param name="currentPath">The current OData path.</param> 
421+         /// <param name="convertSettings">The settings for the current conversion.</param> 
422+         /// <param name="structuredType">The type that is being inherited from to which this method will add downcast path segments.</param> 
423+         /// <param name="annotable">The annotable navigation source to read cast annotations from.</param> 
424+         /// <param name="targetsMany">Whether the annotable navigation source targets many entities.</param> 
425+         private  void  CreateTypeCastPaths ( ODataPath  currentPath ,  OpenApiConvertSettings  convertSettings ,  IEdmStructuredType  structuredType ,  IEdmVocabularyAnnotatable  annotable ,  bool  targetsMany ) 
426+         { 
427+             if ( currentPath  ==  null )  throw  Error . ArgumentNull ( nameof ( currentPath ) ) ; 
428+             if ( convertSettings  ==  null )  throw  new  ArgumentNullException ( nameof ( convertSettings ) ) ; 
429+             if ( structuredType  ==  null )  throw  new  ArgumentNullException ( nameof ( structuredType ) ) ; 
430+             if ( annotable  ==  null )  throw  new  ArgumentNullException ( nameof ( annotable ) ) ; 
431+             if ( ! convertSettings . EnableODataTypeCast )  return ; 
432+ 
433+             var  annotedTypeNames  =  GetDerivedTypeConstaintTypeNames ( annotable ) ; 
434+             
435+             if ( ! annotedTypeNames . Any ( )  &&  convertSettings . RequireDerivedTypesConstraintForODataTypeCastSegments )  return ;  // we don't want to generate any downcast path item if there is no type cast annotation. 
436+ 
437+             var  annotedTypeNamesSet  =  new  HashSet < string > ( annotedTypeNames ,  StringComparer . OrdinalIgnoreCase ) ; 
438+ 
439+             bool  filter ( IEdmStructuredType  x )  => 
440+                 convertSettings . RequireDerivedTypesConstraintForODataTypeCastSegments  &&  annotedTypeNames . Contains ( x . FullTypeName ( ) )  || 
441+                 ! convertSettings . RequireDerivedTypesConstraintForODataTypeCastSegments  &&  ( 
442+                     ! annotedTypeNames . Any ( )  || 
443+                     annotedTypeNames . Contains ( x . FullTypeName ( ) ) 
444+                 ) ; 
445+ 
446+             var  targetTypes  =  _model 
447+                                 . FindAllDerivedTypes ( structuredType ) 
448+                                 . Where ( x =>  x . TypeKind  ==  EdmTypeKind . Entity  &&  filter ( x ) ) 
449+                                 . OfType < IEdmEntityType > ( ) 
450+                                 . ToArray ( ) ; 
451+ 
452+             foreach ( var  targetType  in  targetTypes )  
453+             { 
454+                 var  castPath  =  currentPath . Clone ( ) ; 
455+                 castPath . Push ( new  ODataTypeCastSegment ( targetType ) ) ; 
456+                 AppendPath ( castPath ) ; 
457+                 if ( targetsMany )  
458+                 { 
459+                     CreateCountPath ( castPath ,  convertSettings ) ; 
460+                 } 
461+                 else 
462+                 { 
463+                     foreach ( var  declaredNavigationProperty  in  targetType . DeclaredNavigationProperties ( ) ) 
464+                     { 
465+                         RetrieveNavigationPropertyPaths ( declaredNavigationProperty ,  null ,  castPath ,  convertSettings ) ; 
466+                     } 
467+                 } 
468+             } 
469+         } 
470+ 
396471        /// <summary> 
397472        /// Retrieve all bounding <see cref="IEdmOperation"/>. 
398473        /// </summary> 
@@ -419,26 +494,19 @@ private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
419494                } 
420495
421496                var  firstEntityType  =  bindingType . AsEntity ( ) . EntityDefinition ( ) ; 
422-                 var  allEntitiesForOperation =  new  List < IEdmEntityType > ( ) {  firstEntityType  } ; 
423497
424-                 System . Func < IEdmNavigationSource ,   bool >  filter   =   ( z )  => 
498+                 bool  filter ( IEdmNavigationSource   z )  => 
425499                    z . EntityType ( )  !=  firstEntityType  && 
426500                    z . EntityType ( ) . FindAllBaseTypes ( ) . Contains ( firstEntityType ) ; 
427501
428-                 //Search all EntitySets 
429-                 allEntitiesForOperation . AddRange ( 
430-                     _model . EntityContainer . EntitySets ( ) 
431-                             . Where ( filter ) . Select ( x =>  x . EntityType ( ) ) 
432-                 ) ; 
433- 
434-                 //Search all singletons 
435-                 allEntitiesForOperation . AddRange ( 
436-                     _model . EntityContainer . Singletons ( ) 
437-                             . Where ( filter ) . Select ( x =>  x . EntityType ( ) ) 
438-                 ) ; 
439- 
440-                 allEntitiesForOperation  =  allEntitiesForOperation . Distinct ( ) . ToList ( ) ; 
441- 
502+                 var  allEntitiesForOperation  =  new  IEdmEntityType [ ]  {  firstEntityType  } 
503+                     . Union ( _model . EntityContainer . EntitySets ( ) 
504+                             . Where ( filter ) . Select ( x =>  x . EntityType ( ) ) )  //Search all EntitySets 
505+                     . Union ( _model . EntityContainer . Singletons ( ) 
506+                             . Where ( filter ) . Select ( x =>  x . EntityType ( ) ) )  //Search all singletons 
507+                     . Distinct ( ) 
508+                     . ToList ( ) ; 
509+                 
442510                foreach  ( var  bindingEntityType  in  allEntitiesForOperation ) 
443511                { 
444512                    // 1. Search for corresponding navigation source path 
@@ -468,7 +536,7 @@ private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
468536                } 
469537            } 
470538        } 
471-         private  static readonly  HashSet < ODataPathKind >  _oDataPathKindsToSkipForOperations  =  new   HashSet < ODataPathKind > ( )  { 
539+         private  static readonly  HashSet < ODataPathKind >  _oDataPathKindsToSkipForOperationsWhenSingle  =  new ( )  { 
472540            ODataPathKind . EntitySet , 
473541            ODataPathKind . MediaEntity , 
474542            ODataPathKind . DollarCount 
@@ -483,8 +551,22 @@ private bool AppendBoundOperationOnNavigationSourcePath(IEdmOperation edmOperati
483551
484552                foreach  ( var  subPath  in  value ) 
485553                { 
486-                     if  ( ( isCollection  &&  subPath . Kind  ==  ODataPathKind . EntitySet )  || 
487-                             ( ! isCollection  &&  ! _oDataPathKindsToSkipForOperations . Contains ( subPath . Kind ) ) ) 
554+                     var  lastPathSegment  =  subPath . LastOrDefault ( ) ; 
555+                     var  secondLastPathSegment  =  subPath . Count  >  1  ?  subPath . ElementAt ( subPath . Count  -  2 )  :  null ; 
556+                     if  ( subPath . Kind  ==  ODataPathKind . TypeCast  && 
557+                         ! isCollection  && 
558+                         secondLastPathSegment  !=  null  && 
559+                         secondLastPathSegment  is  not ODataKeySegment  && 
560+                         ( secondLastPathSegment  is  not ODataNavigationSourceSegment  navSource  ||  navSource . NavigationSource  is  not IEdmSingleton )  && 
561+                         ( secondLastPathSegment  is  not ODataNavigationPropertySegment  navProp  ||  navProp . NavigationProperty . Type . IsCollection ( ) ) ) 
562+                     { // we don't want to add operations bound to single elements on type cast segments under collections, only under the key segment, singletons and nav props bound to singles. 
563+                         continue ; 
564+                     } 
565+                     else  if  ( ( lastPathSegment  is  not ODataTypeCastSegment  castSegment  || 
566+                                 castSegment . EntityType  ==  bindingEntityType  || 
567+                                 bindingEntityType . InheritsFrom ( castSegment . EntityType ) )  &&  // we don't want to add operations from the parent types under type cast segments because they already are present without the cast 
568+                         ( ( isCollection  &&  subPath . Kind  ==  ODataPathKind . EntitySet )  || 
569+                             ( ! isCollection  &&  ! _oDataPathKindsToSkipForOperationsWhenSingle . Contains ( subPath . Kind ) ) ) ) 
488570                    { 
489571                        ODataPath  newPath  =  subPath . Clone ( ) ; 
490572                        newPath . Push ( new  ODataOperationSegment ( edmOperation ,  isEscapedFunction ) ) ; 
@@ -611,9 +693,11 @@ private bool HasUnsatisfiedDerivedTypeConstraint(
611693            OpenApiConvertSettings  convertSettings ) 
612694        { 
613695            return  convertSettings . RequireDerivedTypesConstraintForBoundOperations  && 
614-                    ! ( _model . GetCollection ( annotatable ,   "Org.OData.Validation.V1.DerivedTypeConstraint" )   ??   Enumerable . Empty < string > ( ) ) 
696+                    ! GetDerivedTypeConstaintTypeNames ( annotatable ) 
615697                       . Any ( c =>  c . Equals ( baseType . FullName ( ) ,  StringComparison . OrdinalIgnoreCase ) ) ; 
616698        } 
699+         private  IEnumerable < string >  GetDerivedTypeConstaintTypeNames ( IEdmVocabularyAnnotatable  annotatable )  => 
700+             _model . GetCollection ( annotatable ,  "Org.OData.Validation.V1.DerivedTypeConstraint" )  ??  Enumerable . Empty < string > ( ) ; 
617701
618702        private  bool  AppendBoundOperationOnDerivedNavigationPropertyPath ( 
619703            IEdmOperation  edmOperation , 
0 commit comments