Example #1
0
        /// <summary>
        /// Parse the string like "/users/{id | userPrincipalName}/contactFolders/{contactFolderId}/contacts"
        /// to segments
        /// </summary>
        /// <param name="requestUri">the request uri string.</param>
        /// <param name="model">the IEdm model.</param>
        /// <param name="settings">the setting.</param>
        /// <returns>Null or <see cref="UriParser"/>.</returns>
        public static UriPath ParsePath(string requestUri, IEdmModel model, PathParserSettings settings)
        {
            if (model == null || settings == null || String.IsNullOrEmpty(requestUri))
            {
                return(null);
            }

            // TODO: process the one-drive uri escape function call

            string[]            items    = requestUri.Split('/');
            IList <PathSegment> segments = new List <PathSegment>();

            foreach (var item in items)
            {
                string trimedItem = item.Trim();
                if (string.IsNullOrEmpty(trimedItem))
                {
                    continue;
                }

                if (segments.Count == 0)
                {
                    CreateFirstSegment(trimedItem, model, segments, settings);
                }
                else
                {
                    CreateNextSegment(trimedItem, model, segments, settings);
                }
            }

            return(new UriPath(segments));
        }
Example #2
0
        /// <summary>
        /// Try to bind the idenfier as property segment,
        /// Append it into path.
        /// </summary>
        private static bool TryBindPropertySegment(string identifier, string parenthesisExpressions, IEdmModel model,
                                                   IList <PathSegment> path,
                                                   PathParserSettings settings)
        {
            PathSegment preSegment = path.LastOrDefault();

            if (preSegment == null || !preSegment.IsSingle)
            {
                return(false);
            }

            IEdmStructuredType structuredType = preSegment.EdmType as IEdmStructuredType;

            if (structuredType == null)
            {
                return(false);
            }

            IEdmProperty property = structuredType.ResolveProperty(identifier, settings.EnableCaseInsensitive);

            if (property == null)
            {
                return(false);
            }

            PathSegment segment;

            if (property.PropertyKind == EdmPropertyKind.Navigation)
            {
                var navigationProperty = (IEdmNavigationProperty)property;

                IEdmNavigationSource navigationSource = null;
                if (preSegment.NavigationSource != null)
                {
                    IEdmPathExpression bindingPath;
                    navigationSource = preSegment.NavigationSource.FindNavigationTarget(navigationProperty, path, out bindingPath);
                }

                // Relationship between TargetMultiplicity and navigation property:
                //  1) EdmMultiplicity.Many <=> collection navigation property
                //  2) EdmMultiplicity.ZeroOrOne <=> nullable singleton navigation property
                //  3) EdmMultiplicity.One <=> non-nullable singleton navigation property
                segment = new NavigationSegment(navigationProperty, navigationSource, identifier);
            }
            else
            {
                segment = new PropertySegment((IEdmStructuralProperty)property, preSegment.NavigationSource, identifier);
            }

            path.Add(segment);

            if (parenthesisExpressions != null && !property.Type.IsCollection() && !property.Type.AsCollection().ElementType().IsEntity())
            {
                throw new Exception($"Invalid '{parenthesisExpressions}' after property '{identifier}'.");
            }

            TryBindKeySegment(parenthesisExpressions, path);
            return(true);
        }
Example #3
0
        /// <summary>
        /// Try to bind the idenfier as navigation source segment,
        /// Append it into path.
        /// </summary>
        internal static bool TryBindNavigationSource(string identifier,
                                                     string parenthesisExpressions, // the potention parenthesis expression after identifer
                                                     IEdmModel model,
                                                     IList <PathSegment> path, PathParserSettings settings)
        {
            IEdmNavigationSource source    = model.ResolveNavigationSource(identifier, settings.EnableCaseInsensitive);
            IEdmEntitySet        entitySet = source as IEdmEntitySet;
            IEdmSingleton        singleton = source as IEdmSingleton;

            if (entitySet != null)
            {
                path.Add(new EntitySetSegment(entitySet, identifier));

                // can append parenthesis after entity set. it should be the key
                if (parenthesisExpressions != null)
                {
                    if (!TryBindKeySegment(parenthesisExpressions, path))
                    {
                        throw new Exception($"Unknown parenthesis '{parenthesisExpressions}' after an entity set '{identifier}'.");
                    }
                }

                return(true);
            }
            else if (singleton != null)
            {
                path.Add(new SingletonSegment(singleton, identifier));

                // can't append parenthesis after singleton
                if (parenthesisExpressions != null)
                {
                    throw new Exception($"Unknown parenthesis '{parenthesisExpressions}' after a singleton '{identifier}'.");
                }

                return(true);
            }

            return(false);
        }
Example #4
0
        /// <summary>
        /// Process the first segment in the request uri.
        /// The first segment could be only singleton/entityset/operationimport, doesn't consider the $metadata, $batch
        /// </summary>
        /// <param name="identifier">the whole identifier of this segment</param>
        /// <param name="model">The Edm model.</param>
        /// <param name="path">The out put of the path, because it may include the key segment.</param>
        /// <param name="settings">The Uri parser settings</param>
        internal static void CreateFirstSegment(string identifier, IEdmModel model, IList <PathSegment> path, PathParserSettings settings)
        {
            // the identifier maybe include the key, for example: ~/users({id})
            identifier = identifier.ExtractParenthesis(out string parenthesisExpressions);

            // Try to bind entity set or singleton
            if (TryBindNavigationSource(identifier, parenthesisExpressions, model, path, settings))
            {
                return;
            }

            // Try to bind operation import
            if (TryBindOperationImport(identifier, parenthesisExpressions, model, path, settings))
            {
                return;
            }

            throw new Exception($"Unknown kind of first segment: '{identifier}'");
        }
Example #5
0
        /// <summary>
        /// Try to bind the idenfier as bound operation segment,
        /// Append it into path.
        /// </summary>
        internal static bool TryBindOperations(string identifier, string parenthesisExpressions,
                                               IEdmModel model, IList <PathSegment> path, PathParserSettings settings)
        {
            PathSegment preSegment = path.LastOrDefault();

            if (preSegment == null)
            {
                // bound operation cannot be the first segment.
                return(false);
            }

            IEdmType bindingType = preSegment.EdmType;

            // operation
            parenthesisExpressions.ExtractKeyValuePairs(out IDictionary <string, string> parameters, out string remaining);
            IList <string> parameterNames = parameters == null ? null : parameters.Keys.ToList();

            IEdmOperation operation = OperationHelper.ResolveOperations(identifier, parameterNames, bindingType, model, settings.EnableCaseInsensitive);

            if (operation != null)
            {
                IEdmEntitySetBase targetset = null;
                if (operation.ReturnType != null)
                {
                    IEdmNavigationSource source = preSegment == null ? null : preSegment.NavigationSource;
                    targetset = operation.GetTargetEntitySet(source, model);
                }

                path.Add(new OperationSegment(operation, targetset));

                if (remaining != null && operation.IsFunction())
                {
                    IEdmFunction function = (IEdmFunction)operation;
                    if (function.IsComposable)
                    {
                        // to process the ~/ .../ NS.Function(p1 ={ abc})({ id})
                        if (TryBindKeySegment(parenthesisExpressions, path))
                        {
                            return(true);
                        }
                    }
                }

                return(true);
            }

            return(false);
        }
Example #6
0
        /// <summary>
        /// Try to bind namespace-qualified type cast segment.
        /// </summary>
        internal static bool TryBindTypeCastSegment(string identifier, string parenthesisExpressions, IEdmModel model,
                                                    IList <PathSegment> path,
                                                    PathParserSettings settings)
        {
            if (identifier == null || identifier.IndexOf('.') < 0)
            {
                // type cast should be namespace-qualified name
                return(false);
            }

            PathSegment preSegment = path.LastOrDefault();

            if (preSegment == null)
            {
                // type cast should not be the first segment.
                return(false);
            }

            IEdmSchemaType schemaType = model.ResolveType(identifier, settings.EnableCaseInsensitive);

            if (schemaType == null)
            {
                return(false);
            }

            IEdmType targetEdmType = schemaType as IEdmType;

            if (targetEdmType == null)
            {
                return(false);
            }

            IEdmType previousEdmType = preSegment.EdmType;
            bool     isNullable      = false;

            if (previousEdmType.TypeKind == EdmTypeKind.Collection)
            {
                previousEdmType = ((IEdmCollectionType)previousEdmType).ElementType.Definition;
                isNullable      = ((IEdmCollectionType)previousEdmType).ElementType.IsNullable;
            }

            if (!targetEdmType.IsOrInheritsFrom(previousEdmType) && !previousEdmType.IsOrInheritsFrom(targetEdmType))
            {
                throw new Exception($"Type cast {targetEdmType.FullTypeName()} has no relationship with previous {previousEdmType.FullTypeName()}.");
            }

            // We want the type of the type segment to be a collection if the previous segment was a collection
            IEdmType actualTypeOfTheTypeSegment = targetEdmType;

            if (preSegment.EdmType.TypeKind == EdmTypeKind.Collection)
            {
                var actualEntityTypeOfTheTypeSegment = targetEdmType as IEdmEntityType;
                if (actualEntityTypeOfTheTypeSegment != null)
                {
                    actualTypeOfTheTypeSegment = new EdmCollectionType(new EdmEntityTypeReference(actualEntityTypeOfTheTypeSegment, isNullable));
                }
                else
                {
                    // Complex collection supports type cast too.
                    var actualComplexTypeOfTheTypeSegment = actualTypeOfTheTypeSegment as IEdmComplexType;
                    if (actualComplexTypeOfTheTypeSegment != null)
                    {
                        actualTypeOfTheTypeSegment = new EdmCollectionType(new EdmComplexTypeReference(actualComplexTypeOfTheTypeSegment, isNullable));
                    }
                    else
                    {
                        throw new Exception($"Invalid type cast of {identifier}, it should be entity or complex.");
                    }
                }
            }

            TypeSegment typeCast = new TypeSegment(actualTypeOfTheTypeSegment, preSegment.EdmType, preSegment.NavigationSource, identifier);

            path.Add(typeCast);

            TryBindKeySegment(parenthesisExpressions, path);

            return(true);
        }
Example #7
0
        /// <summary>
        /// Try to bind the idenfier as operation import (function import or action import) segment,
        /// Append it into path.
        /// </summary>
        private static bool TryBindOperationImport(string identifier, string parenthesisExpressions,
                                                   IEdmModel model, IList <PathSegment> path, PathParserSettings settings)
        {
            // split the parameter key/value pair
            parenthesisExpressions.ExtractKeyValuePairs(out IDictionary <string, string> parameters, out string remaining);
            IList <string> parameterNames = parameters == null ? null : parameters.Keys.ToList();

            IEdmOperationImport operationImport = OperationHelper.ResolveOperationImports(identifier, parameterNames, model, settings.EnableCaseInsensitive);

            if (operationImport != null)
            {
                operationImport.TryGetStaticEntitySet(model, out IEdmEntitySetBase entitySetBase);
                path.Add(new OperationImportSegment(operationImport, entitySetBase));
                if (remaining != null && operationImport.IsFunctionImport())
                {
                    IEdmFunction function = (IEdmFunction)operationImport.Operation;
                    if (function.IsComposable)
                    {
                        if (TryBindKeySegment(parenthesisExpressions, path))
                        {
                            return(true);
                        }
                    }
                }

                return(true);
            }

            return(false);
        }
Example #8
0
        /// <summary>
        /// Create the next segment for the request uri.
        /// </summary>
        /// <param name="identifier">The request segment uri literal</param>
        /// <param name="model">The Edm model.</param>
        /// <param name="path">The out of the path</param>
        /// <param name="settings">The parser settings</param>
        internal static void CreateNextSegment(string identifier, IEdmModel model, IList <PathSegment> path, PathParserSettings settings)
        {
            // GET /Users/{id}
            // GET /Users({id})
            // GET /me/outlook/supportedTimeZones(TimeZoneStandard=microsoft.graph.timeZoneStandard'{timezone_format}')

            // maybe key or function parameters
            identifier = identifier.ExtractParenthesis(out string parenthesisExpressions);

            // can be "property, navproperty"
            if (TryBindPropertySegment(identifier, parenthesisExpressions, model, path, settings))
            {
                return;
            }

            // bind to type cast.
            if (TryBindTypeCastSegment(identifier, parenthesisExpressions, model, path, settings))
            {
                return;
            }

            // bound operations
            if (TryBindOperations(identifier, parenthesisExpressions, model, path, settings))
            {
                return;
            }

            // Handle Key as Segment
            if (TryBindKeySegment("(" + identifier + ")", path))
            {
                return;
            }

            throw new Exception($"Unknown kind of segment: '{identifier}', previous segment: '{path.Last().Identifier}'.");
        }