/// <summary> /// Retrieves the paths for a media entity stream. /// </summary> /// <param name="entityType">The entity type.</param> /// <param name="currentPath">The current OData path.</param> private void RetrieveMediaEntityStreamPaths(IEdmEntityType entityType, ODataPath currentPath) { Debug.Assert(entityType != null); Debug.Assert(currentPath != null); bool createValuePath = true; foreach (IEdmStructuralProperty sp in entityType.DeclaredStructuralProperties()) { if (sp.Type.AsPrimitive().IsStream()) { currentPath.Push(new ODataStreamPropertySegment(sp.Name)); AppendPath(currentPath.Clone()); currentPath.Pop(); } if (sp.Name.Equals("content", System.StringComparison.OrdinalIgnoreCase)) { createValuePath = false; } } /* Create a /$value path only if entity has stream and * does not contain a structural property named Content */ if (createValuePath && entityType.HasStream) { currentPath.Push(new ODataStreamContentSegment()); AppendPath(currentPath.Clone()); currentPath.Pop(); } }
/// <summary> /// Retrieve the path for <see cref="IEdmNavigationProperty"/>. /// </summary> /// <param name="navigationProperty">The navigation property.</param> /// <param name="currentPath">The current OData path.</param> private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationProperty, ODataPath currentPath) { Debug.Assert(navigationProperty != null); Debug.Assert(currentPath != null); // test the expandable for the navigation property. bool shouldExpand = ShouldExpandNavigationProperty(navigationProperty, currentPath); // append a navigation property. currentPath.Push(new ODataNavigationPropertySegment(navigationProperty)); AppendPath(currentPath.Clone()); if (!navigationProperty.ContainsTarget) { // Non-Contained // Single-Valued: DELETE ~/entityset/{key}/single-valued-Nav/$ref // collection-valued: DELETE ~/entityset/{key}/collection-valued-Nav/$ref?$id ={ navKey} ODataPath newPath = currentPath.Clone(); newPath.Push(ODataRefSegment.Instance); // $ref AppendPath(newPath); } // append a navigation property key. IEdmEntityType navEntityType = navigationProperty.ToEntityType(); if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) { currentPath.Push(new ODataKeySegment(navEntityType)); AppendPath(currentPath.Clone()); if (!navigationProperty.ContainsTarget) { // TODO: Shall we add "$ref" after {key}, and only support delete? // ODataPath newPath = currentPath.Clone(); // newPath.Push(ODataRefSegment.Instance); // $ref // AppendPath(newPath); } } if (shouldExpand) { // expand to sub navigation properties foreach (IEdmNavigationProperty subNavProperty in navEntityType.DeclaredNavigationProperties()) { if (CanFilter(subNavProperty)) { RetrieveNavigationPropertyPaths(subNavProperty, currentPath); } } } if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) { currentPath.Pop(); } currentPath.Pop(); }
/// <summary> /// Create $ref paths. /// </summary> /// <param name="currentPath">The current OData path.</param> private void CreateRefPath(ODataPath currentPath) { ODataPath newPath = currentPath.Clone(); newPath.Push(ODataRefSegment.Instance); // $ref AppendPath(newPath); }
/// <summary> /// Retrieves the paths for properties of type complex type from entities /// </summary> /// <param name="entityType">The entity type.</param> /// <param name="currentPath">The current path.</param> /// <param name="convertSettings">The settings for the current conversion.</param> private void RetrieveComplexPropertyPaths(IEdmEntityType entityType, ODataPath currentPath, OpenApiConvertSettings convertSettings) { Debug.Assert(entityType != null); Debug.Assert(currentPath != null); Debug.Assert(convertSettings != null); if (!convertSettings.EnableNavigationPropertyPath) { return; } foreach (IEdmStructuralProperty sp in entityType.StructuralProperties() .Where(x => x.Type.IsComplex() || x.Type.IsCollection() && x.Type.Definition.AsElementType() is IEdmComplexType)) { if (!ShouldCreateComplexPropertyPaths(sp, convertSettings)) { continue; } currentPath.Push(new ODataComplexPropertySegment(sp)); AppendPath(currentPath.Clone()); if (sp.Type.IsCollection()) { CreateTypeCastPaths(currentPath, convertSettings, sp.Type.Definition.AsElementType() as IEdmComplexType, sp, true); var count = _model.GetRecord <CountRestrictionsType>(sp, CapabilitiesConstants.CountRestrictions); if (count?.IsCountable ?? true) { CreateCountPath(currentPath, convertSettings); } } else { var complexTypeReference = sp.Type.AsComplex(); var definition = complexTypeReference.ComplexDefinition(); CreateTypeCastPaths(currentPath, convertSettings, definition, sp, false); foreach (IEdmNavigationProperty np in complexTypeReference .DeclaredNavigationProperties() .Union(definition .FindAllBaseTypes() .SelectMany(x => x.DeclaredNavigationProperties())) .Distinct() .Where(CanFilter)) { var count = _model.GetRecord <CountRestrictionsType>(np, CapabilitiesConstants.CountRestrictions); RetrieveNavigationPropertyPaths(np, count, currentPath, convertSettings); } } currentPath.Pop(); } }
/// <summary> /// Retrieve the path for <see cref="IEdmNavigationProperty"/>. /// </summary> /// <param name="navigationProperty">The navigation property.</param> /// <param name="currentPath">The current OData path.</param> private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationProperty, ODataPath currentPath) { Debug.Assert(navigationProperty != null); Debug.Assert(currentPath != null); // test the expandable for the navigation property. bool shouldExpand = ShouldExpandNavigationProperty(navigationProperty, currentPath); // append a navigation property. currentPath.Push(new ODataNavigationPropertySegment(navigationProperty)); AppendPath(currentPath.Clone()); // append a navigation property key. IEdmEntityType navEntityType = navigationProperty.ToEntityType(); if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) { currentPath.Push(new ODataKeySegment(navEntityType)); AppendPath(currentPath.Clone()); } if (shouldExpand) { // expand to sub navigation properties foreach (IEdmNavigationProperty subNavProperty in navEntityType.DeclaredNavigationProperties()) { if (CanFilter(subNavProperty)) { RetrieveNavigationPropertyPaths(subNavProperty, currentPath); } } } if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) { currentPath.Pop(); } currentPath.Pop(); }
/// <summary> /// Retrieve the paths for <see cref="IEdmNavigationSource"/>. /// </summary> /// <param name="navigationSource">The navigation source.</param> private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource) { Debug.Assert(navigationSource != null); // navigation source itself ODataPath path = new ODataPath(new ODataNavigationSourceSegment(navigationSource)); AppendPath(path.Clone()); IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; IEdmEntityType entityType = navigationSource.EntityType(); // for entity set, create a path with key if (entitySet != null) { path.Push(new ODataKeySegment(entityType)); AppendPath(path.Clone()); } // media entity RetrieveMediaEntityStreamPaths(entityType, path); // navigation property foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties()) { if (CanFilter(np)) { RetrieveNavigationPropertyPaths(np, path); } } if (entitySet != null) { path.Pop(); // end of entity } path.Pop(); // end of navigation source. Debug.Assert(path.Any() == false); }
/// <summary> /// Create $count paths. /// </summary> /// <param name="currentPath">The current OData path.</param> /// <param name="convertSettings">The settings for the current conversion.</param> private void CreateCountPath(ODataPath currentPath, OpenApiConvertSettings convertSettings) { if (currentPath == null) { throw new ArgumentNullException(nameof(currentPath)); } if (convertSettings == null) { throw new ArgumentNullException(nameof(convertSettings)); } if (!convertSettings.EnableDollarCountPath) { return; } var countPath = currentPath.Clone(); countPath.Push(ODataDollarCountSegment.Instance); AppendPath(countPath); }
/// <summary> /// Create OData type cast paths. /// </summary> /// <param name="currentPath">The current OData path.</param> /// <param name="convertSettings">The settings for the current conversion.</param> /// <param name="structuredType">The type that is being inherited from to which this method will add downcast path segments.</param> /// <param name="annotable">The annotable navigation source to read cast annotations from.</param> /// <param name="targetsMany">Whether the annotable navigation source targets many entities.</param> private void CreateTypeCastPaths(ODataPath currentPath, OpenApiConvertSettings convertSettings, IEdmStructuredType structuredType, IEdmVocabularyAnnotatable annotable, bool targetsMany) { if (currentPath == null) { throw Error.ArgumentNull(nameof(currentPath)); } if (convertSettings == null) { throw Error.ArgumentNull(nameof(convertSettings)); } if (structuredType == null) { throw Error.ArgumentNull(nameof(structuredType)); } if (annotable == null) { throw Error.ArgumentNull(nameof(annotable)); } if (!convertSettings.EnableODataTypeCast) { return; } var annotedTypeNames = GetDerivedTypeConstaintTypeNames(annotable); if (!annotedTypeNames.Any() && convertSettings.RequireDerivedTypesConstraintForODataTypeCastSegments) { return; // we don't want to generate any downcast path item if there is no type cast annotation. } var annotedTypeNamesSet = new HashSet <string>(annotedTypeNames, StringComparer.OrdinalIgnoreCase); bool filter(IEdmStructuredType x) => convertSettings.RequireDerivedTypesConstraintForODataTypeCastSegments && annotedTypeNames.Contains(x.FullTypeName()) || !convertSettings.RequireDerivedTypesConstraintForODataTypeCastSegments && ( !annotedTypeNames.Any() || annotedTypeNames.Contains(x.FullTypeName()) ); var targetTypes = _model .FindAllDerivedTypes(structuredType) .Where(x => (x.TypeKind == EdmTypeKind.Entity || x.TypeKind == EdmTypeKind.Complex) && filter(x)) .OfType <IEdmStructuredType>() .ToArray(); foreach (var targetType in targetTypes) { var castPath = currentPath.Clone(); var targetTypeSegment = new ODataTypeCastSegment(targetType); castPath.Push(targetTypeSegment); AppendPath(castPath); if (targetsMany) { CreateCountPath(castPath, convertSettings); } else { if (convertSettings.ExpandDerivedTypesNavigationProperties) { foreach (var declaredNavigationProperty in targetType.DeclaredNavigationProperties()) { RetrieveNavigationPropertyPaths(declaredNavigationProperty, null, castPath, convertSettings); } } } } }
/// <summary> /// Retrieve the path for <see cref="IEdmNavigationProperty"/>. /// </summary> /// <param name="navigationProperty">The navigation property.</param> /// <param name="count">The count restrictions.</param> /// <param name="currentPath">The current OData path.</param> /// <param name="convertSettings">The settings for the current conversion.</param> /// <param name="visitedNavigationProperties">A stack that holds the visited navigation properties in the <paramref name="currentPath"/>.</param> private void RetrieveNavigationPropertyPaths( IEdmNavigationProperty navigationProperty, CountRestrictionsType count, ODataPath currentPath, OpenApiConvertSettings convertSettings, Stack <string> visitedNavigationProperties = null) { Debug.Assert(navigationProperty != null); Debug.Assert(currentPath != null); if (visitedNavigationProperties == null) { visitedNavigationProperties = new(); } string navPropFullyQualifiedName = $"{navigationProperty.DeclaringType.FullTypeName()}/{navigationProperty.Name}"; // Check whether the navigation property has already been navigated in the path. if (visitedNavigationProperties.Contains(navPropFullyQualifiedName)) { return; } // Get the annotatable navigation source for this navigation property. IEdmVocabularyAnnotatable annotatableNavigationSource = (currentPath.FirstSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable; NavigationRestrictionsType navSourceRestrictionType = null; NavigationRestrictionsType navPropRestrictionType = null; // Get the NavigationRestrictions referenced by this navigation property: Can be defined in the navigation source or in-lined in the navigation property. if (annotatableNavigationSource != null) { navSourceRestrictionType = _model.GetRecord <NavigationRestrictionsType>(annotatableNavigationSource, CapabilitiesConstants.NavigationRestrictions); navPropRestrictionType = _model.GetRecord <NavigationRestrictionsType>(navigationProperty, CapabilitiesConstants.NavigationRestrictions); } NavigationPropertyRestriction restriction = navSourceRestrictionType?.RestrictedProperties? .FirstOrDefault(r => r.NavigationProperty == currentPath.NavigationPropertyPath(navigationProperty.Name)) ?? navPropRestrictionType?.RestrictedProperties?.FirstOrDefault(); // Check whether the navigation property should be part of the path if (!EdmModelHelper.NavigationRestrictionsAllowsNavigability(navSourceRestrictionType, restriction) || !EdmModelHelper.NavigationRestrictionsAllowsNavigability(navPropRestrictionType, restriction)) { return; } // Whether to expand the navigation property. bool shouldExpand = navigationProperty.ContainsTarget; // Append a navigation property. currentPath.Push(new ODataNavigationPropertySegment(navigationProperty)); AppendPath(currentPath.Clone()); visitedNavigationProperties.Push(navPropFullyQualifiedName); // Check whether a collection-valued navigation property should be indexed by key value(s). bool indexableByKey = restriction?.IndexableByKey ?? true; if (indexableByKey) { IEdmEntityType navEntityType = navigationProperty.ToEntityType(); var targetsMany = navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many; var propertyPath = navigationProperty.GetPartnerPath()?.Path; var propertyPathIsEmpty = string.IsNullOrEmpty(propertyPath); if (targetsMany) { if (propertyPathIsEmpty || (count?.IsNonCountableNavigationProperty(propertyPath) ?? true)) { // ~/entityset/{key}/collection-valued-Nav/$count CreateCountPath(currentPath, convertSettings); } } // ~/entityset/{key}/collection-valued-Nav/subtype // ~/entityset/{key}/single-valued-Nav/subtype CreateTypeCastPaths(currentPath, convertSettings, navEntityType, navigationProperty, targetsMany); if ((navSourceRestrictionType?.Referenceable ?? false) || (navPropRestrictionType?.Referenceable ?? false)) { // Referenceable navigation properties // Single-Valued: ~/entityset/{key}/single-valued-Nav/$ref // Collection-valued: ~/entityset/{key}/collection-valued-Nav/$ref?$id='{navKey}' CreateRefPath(currentPath); if (targetsMany) { // Collection-valued: DELETE ~/entityset/{key}/collection-valued-Nav/{key}/$ref currentPath.Push(new ODataKeySegment(navEntityType)); CreateRefPath(currentPath); CreateTypeCastPaths(currentPath, convertSettings, navEntityType, navigationProperty, false); // ~/entityset/{key}/collection-valued-Nav/{id}/subtype } // Get possible stream paths for the navigation entity type RetrieveMediaEntityStreamPaths(navEntityType, currentPath); // Get the paths for the navigation property entity type properties of type complex RetrieveComplexPropertyPaths(navEntityType, currentPath, convertSettings); } else { // append a navigation property key. if (targetsMany) { currentPath.Push(new ODataKeySegment(navEntityType)); AppendPath(currentPath.Clone()); CreateTypeCastPaths(currentPath, convertSettings, navEntityType, navigationProperty, false); // ~/entityset/{key}/collection-valued-Nav/{id}/subtype } // Get possible stream paths for the navigation entity type RetrieveMediaEntityStreamPaths(navEntityType, currentPath); // Get the paths for the navigation property entity type properties of type complex RetrieveComplexPropertyPaths(navEntityType, currentPath, convertSettings); if (shouldExpand) { // expand to sub navigation properties foreach (IEdmNavigationProperty subNavProperty in navEntityType.DeclaredNavigationProperties()) { if (CanFilter(subNavProperty)) { RetrieveNavigationPropertyPaths(subNavProperty, count, currentPath, convertSettings, visitedNavigationProperties); } } } } if (targetsMany) { currentPath.Pop(); } } currentPath.Pop(); visitedNavigationProperties.Pop(); }