/// <summary>
        /// Retrieve the paths for <see cref="IEdmNavigationSource"/>.
        /// </summary>
        /// <param name="navigationSource">The navigation source.</param>
        /// <param name="convertSettings">The settings for the current conversion.</param>
        private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource, OpenApiConvertSettings convertSettings)
        {
            Debug.Assert(navigationSource != null);

            // navigation source itself
            ODataPath path = new(new ODataNavigationSourceSegment(navigationSource));

            AppendPath(path.Clone());

            IEdmEntitySet         entitySet  = navigationSource as IEdmEntitySet;
            IEdmEntityType        entityType = navigationSource.EntityType();
            CountRestrictionsType count      = null;

            // for entity set, create a path with key and a $count path
            if (entitySet != null)
            {
                count = _model.GetRecord <CountRestrictionsType>(entitySet, CapabilitiesConstants.CountRestrictions);
                if (count?.Countable ?? true) // ~/entitySet/$count
                {
                    CreateCountPath(path, convertSettings);
                }

                CreateTypeCastPaths(path, convertSettings, entityType, entitySet, true); // ~/entitySet/subType

                path.Push(new ODataKeySegment(entityType));
                AppendPath(path.Clone());

                CreateTypeCastPaths(path, convertSettings, entityType, entitySet, false); // ~/entitySet/{id}/subType
            }
            else if (navigationSource is IEdmSingleton singleton)
            { // ~/singleton/subType
                CreateTypeCastPaths(path, convertSettings, entityType, singleton, false);
            }

            // media entity
            RetrieveMediaEntityStreamPaths(entityType, path);

            // properties of type complex
            RetrieveComplexPropertyPaths(entityType, path, convertSettings);

            // navigation property
            foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties())
            {
                if (CanFilter(np))
                {
                    RetrieveNavigationPropertyPaths(np, count, path, convertSettings);
                }
            }

            if (entitySet != null)
            {
                path.Pop(); // end of entity
            }

            path.Pop(); // end of navigation source.
            Debug.Assert(path.Any() == false);
        }
        public void GetRecordWorksForRecord()
        {
            // Arrange
            string qualifiedName = "Org.OData.Capabilities.V1.CountRestrictions";
            string annotation    = $@"
<Annotation Term=""{qualifiedName}"" >
  <Record>
    <PropertyValue Property=""Countable"" Bool=""true"" />
    <PropertyValue Property=""NonCountableProperties"" >
      <Collection>
        <PropertyPath>Emails</PropertyPath>
        <PropertyPath>mij</PropertyPath>
      </Collection>
    </PropertyValue>
    <PropertyValue Property=""NonCountableNavigationProperties"" >
      <Collection>
        <NavigationPropertyPath>RelatedEvents</NavigationPropertyPath>
        <NavigationPropertyPath>abc</NavigationPropertyPath>
      </Collection>
    </PropertyValue>
  </Record>
</Annotation>";

            IEdmModel model = GetEdmModel(annotation);

            Assert.NotNull(model); // guard

            // Act
            CountRestrictionsType count1 = model.GetRecord <CountRestrictionsType>(model.EntityContainer, qualifiedName);
            CountRestrictionsType count2 = model.GetRecord <CountRestrictionsType>(model.EntityContainer, qualifiedName);

            // Assert
            Assert.NotNull(count1);
            Assert.NotNull(count2);

            System.Action <CountRestrictionsType> verifyCount = count =>
            {
                // Countable
                Assert.NotNull(count.Countable);
                Assert.True(count.Countable.Value);

                // NonCountableProperties
                Assert.NotNull(count.NonCountableProperties);
                Assert.Equal(new[] { "Emails", "mij" }, count.NonCountableProperties);

                // NonCountableNavigationProperties
                Assert.NotNull(count.NonCountableNavigationProperties);
                Assert.Equal(new[] { "RelatedEvents", "abc" }, count.NonCountableNavigationProperties);
            };

            verifyCount(count1);
            verifyCount(count2);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Create the $count parameter.
        /// </summary>
        /// <param name="context">The OData context.</param>
        /// <param name="target">The Edm annotation target.</param>
        /// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
        public static OpenApiParameter CreateCount(this ODataContext context, IEdmVocabularyAnnotatable target)
        {
            Utils.CheckArgumentNull(context, nameof(context));
            Utils.CheckArgumentNull(target, nameof(target));

            CountRestrictionsType count = context.Model.GetRecord <CountRestrictionsType>(target, CapabilitiesConstants.CountRestrictions);

            if (count == null || count.IsCountable)
            {
                return(new OpenApiParameter
                {
                    Reference = new OpenApiReference {
                        Type = ReferenceType.Parameter, Id = "count"
                    }
                });
            }

            return(null);
        }
Ejemplo n.º 4
0
        private static void VerifyCountRestrictions(CountRestrictionsType count)
        {
            Assert.NotNull(count);

            Assert.NotNull(count.Countable);
            Assert.False(count.Countable.Value);
            Assert.False(count.IsCountable);

            Assert.NotNull(count.NonCountableProperties);
            Assert.Equal(2, count.NonCountableProperties.Count);
            Assert.Equal("Emails|mij", String.Join("|", count.NonCountableProperties));

            Assert.NotNull(count.NonCountableNavigationProperties);
            Assert.Equal(2, count.NonCountableNavigationProperties.Count);
            Assert.Equal("RelatedEvents,abc", String.Join(",", count.NonCountableNavigationProperties));

            Assert.False(count.IsNonCountableProperty("id"));
            Assert.True(count.IsNonCountableProperty("Emails"));
            Assert.True(count.IsNonCountableNavigationProperty("RelatedEvents"));
        }
Ejemplo n.º 5
0
        public void InitializeCountRestrictionsTypeWithRecordSuccess()
        {
            // Assert
            IEdmRecordExpression record = new EdmRecordExpression(
                new EdmPropertyConstructor("Countable", new EdmBooleanConstant(false)),
                new EdmPropertyConstructor("NonCountableProperties", new EdmCollectionExpression(
                                               new EdmPropertyPathExpression("Emails"),
                                               new EdmPropertyPathExpression("mij"))),
                new EdmPropertyConstructor("NonCountableNavigationProperties", new EdmCollectionExpression(
                                               new EdmNavigationPropertyPathExpression("RelatedEvents"),
                                               new EdmNavigationPropertyPathExpression("abc")))
                );

            // Act
            CountRestrictionsType count = new CountRestrictionsType();

            count.Initialize(record);

            // Assert
            VerifyCountRestrictions(count);
        }
Ejemplo n.º 6
0
        public void InitializeCountRestrictionsWorksWithCsdl()
        {
            // Arrange
            string countAnnotation = @"
                <Annotation Term=""Org.OData.Capabilities.V1.CountRestrictions"" >
                  <Record>
                    <PropertyValue Property=""Countable"" Bool=""false"" />
                    <PropertyValue Property=""NonCountableProperties"">
                      <Collection>
                        <PropertyPath>Emails</PropertyPath>
                        <PropertyPath>mij</PropertyPath>
                      </Collection>
                    </PropertyValue>
                    <PropertyValue Property=""NonCountableNavigationProperties"" >
                      <Collection>
                        <NavigationPropertyPath>RelatedEvents</NavigationPropertyPath>
                        <NavigationPropertyPath>abc</NavigationPropertyPath>
                      </Collection>
                    </PropertyValue>
                  </Record>
                </Annotation>";

            IEdmModel model = CapabilitiesModelHelper.GetEdmModelSetInline(countAnnotation);

            Assert.NotNull(model); // guard

            IEdmEntitySet calendars = model.EntityContainer.FindEntitySet("Calendars");

            Assert.NotNull(calendars); // guard

            // Act
            CountRestrictionsType count = model.GetRecord <CountRestrictionsType>(calendars);

            // Assert
            VerifyCountRestrictions(count);
        }
        public void GetGenericCollectionWorksForCollectionOfGenericRecord()
        {
            // Arrange
            string qualifiedName = "NS.MyCollectionCountRestrictions";
            string annotation    = $@"
<Annotation Term=""{qualifiedName}"" >
  <Collection>
    <Record>
      <PropertyValue Property=""Countable"" Bool=""true"" />
      <PropertyValue Property=""NonCountableProperties"" >
        <Collection>
          <PropertyPath>123</PropertyPath>
          <PropertyPath>abc</PropertyPath>
        </Collection>
      </PropertyValue>
      <PropertyValue Property=""NonCountableNavigationProperties"" >
        <Collection>
          <NavigationPropertyPath>234</NavigationPropertyPath>
          <NavigationPropertyPath>xyz</NavigationPropertyPath>
        </Collection>
      </PropertyValue>
    </Record>
    <Record>
      <PropertyValue Property=""Countable"" Bool=""false"" />
      <PropertyValue Property=""NonCountableProperties"" >
        <Collection>
          <PropertyPath>567</PropertyPath>
          <PropertyPath>mij</PropertyPath>
        </Collection>
      </PropertyValue>
      <PropertyValue Property=""NonCountableNavigationProperties"" >
        <Collection>
          <NavigationPropertyPath>789</NavigationPropertyPath>
          <NavigationPropertyPath>rst</NavigationPropertyPath>
        </Collection>
      </PropertyValue>
    </Record>
  </Collection>
</Annotation>";

            IEdmModel model = GetEdmModel(annotation);

            Assert.NotNull(model); // guard

            // Act
            IEnumerable <CountRestrictionsType> counts1 = model.GetCollection <CountRestrictionsType>(model.EntityContainer, qualifiedName);
            IEnumerable <CountRestrictionsType> counts2 = model.GetCollection <CountRestrictionsType>(model.EntityContainer, qualifiedName);

            // Assert
            Assert.NotNull(counts1);
            Assert.NotNull(counts2);

            Assert.Equal(2, counts1.Count());

            foreach (var countItem in new[] { counts1, counts2 })
            {
                CountRestrictionsType count = countItem.First();

                // Countable
                Assert.NotNull(count.Countable);
                Assert.True(count.Countable.Value);

                // NonCountableProperties
                Assert.NotNull(count.NonCountableProperties);
                Assert.Equal(new[] { "123", "abc" }, count.NonCountableProperties);

                // NonCountableNavigationProperties
                Assert.NotNull(count.NonCountableNavigationProperties);
                Assert.Equal(new[] { "234", "xyz" }, count.NonCountableNavigationProperties);

                // #2
                count = countItem.Last();

                // Countable
                Assert.NotNull(count.Countable);
                Assert.False(count.Countable.Value);

                // NonCountableProperties
                Assert.NotNull(count.NonCountableProperties);
                Assert.Equal(new[] { "567", "mij" }, count.NonCountableProperties);

                // NonCountableNavigationProperties
                Assert.NotNull(count.NonCountableNavigationProperties);
                Assert.Equal(new[] { "789", "rst" }, count.NonCountableNavigationProperties);
            }
        }
        /// <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();
        }