/// <summary> /// Resolve keys for certain entity set, this function would be called when key is specified as positional values. E.g. EntitySet('key') /// </summary> /// <param name="type">Type for current entityset.</param> /// <param name="positionalValues">The list of positional values.</param> /// <param name="convertFunc">The convert function to be used for value converting.</param> /// <returns>The resolved key list.</returns> public virtual IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IList <string> positionalValues, Func <IEdmTypeReference, string, object> convertFunc) { var keyProperties = type.Key().ToList(); // Throw an error if key size from url doesn't match that from model. // Other derived ODataUriResolver intended for alternative key resolution, such as the built in AlternateKeysODataUriResolver, // should override this ResolveKeys method. if (keyProperties.Count != positionalValues.Count) { throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(type.FullName())); } var keyPairList = new List <KeyValuePair <string, object> >(positionalValues.Count); for (int i = 0; i < keyProperties.Count; i++) { string valueText = positionalValues[i]; IEdmProperty keyProperty = keyProperties[i]; object convertedValue = convertFunc(keyProperty.Type, valueText); if (convertedValue == null) { throw ExceptionUtil.CreateSyntaxError(); } keyPairList.Add(new KeyValuePair <string, object>(keyProperty.Name, convertedValue)); } return(keyPairList); }
/// <summary> /// Recursively ensures that the maximum count/depth are not exceeded by walking the tree. /// </summary> /// <param name="expandTree">The expand tree to walk and validate.</param> /// <param name="currentDepth">The current depth of the tree walk.</param> private void EnsureMaximumCountAndDepthAreNotExceeded(SelectExpandClause expandTree, int currentDepth) { Debug.Assert(expandTree != null, "expandTree != null"); if (currentDepth > this.maxDepth) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandDepthExceeded(currentDepth, this.maxDepth)); } foreach (ExpandedNavigationSelectItem expandItem in expandTree.SelectedItems.Where(I => I.GetType() == typeof(ExpandedNavigationSelectItem))) { this.currentCount++; if (this.currentCount > this.maxCount) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandCountExceeded(this.currentCount, this.maxCount)); } this.EnsureMaximumCountAndDepthAreNotExceeded(expandItem.SelectAndExpand, currentDepth + 1); } this.currentCount += expandTree.SelectedItems.Where(I => I.GetType() == typeof(ExpandedReferenceSelectItem)).Count(); if (this.currentCount > this.maxCount) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandCountExceeded(this.currentCount, this.maxCount)); } }
/// <summary> /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') /// </summary> /// <param name="type">Type for current entityset.</param> /// <param name="namedValues">The dictionary of name value pairs.</param> /// <param name="convertFunc">The convert function to be used for value converting.</param> /// <returns>The resolved key list.</returns> public virtual IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IDictionary <string, string> namedValues, Func <IEdmTypeReference, string, object> convertFunc) { if (!TryResolveKeys(type, namedValues, convertFunc, out IEnumerable <KeyValuePair <string, object> > resolvedKeys)) { throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyMismatch(type.FullName())); } return(resolvedKeys); }
/// <summary> /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') /// </summary> /// <param name="type">Type for current entityset.</param> /// <param name="namedValues">The dictionary of name value pairs.</param> /// <param name="convertFunc">The convert function to be used for value converting.</param> /// <returns>The resolved key list.</returns> public virtual IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IDictionary <string, string> namedValues, Func <IEdmTypeReference, string, object> convertFunc) { var convertedPairs = new Dictionary <string, object>(StringComparer.Ordinal); var keyProperties = type.Key().ToList(); // Throw an error if key size from url doesn't match that from model. // Other derived ODataUriResolver intended for alternative key resolution, such as the built in AlternateKeysODataUriResolver, // should override this ResolveKeys method. if (keyProperties.Count != namedValues.Count) { throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(type.FullName())); } foreach (IEdmStructuralProperty property in keyProperties) { string valueText; if (!namedValues.TryGetValue(property.Name, out valueText)) { if (EnableCaseInsensitive) { var list = namedValues.Keys.Where(key => string.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count > 1) { throw new ODataException(Strings.UriParserMetadata_MultipleMatchingKeysFound(property.Name)); } else if (list.Count == 0) { throw ExceptionUtil.CreateSyntaxError(); } valueText = namedValues[list.Single()]; } else { throw ExceptionUtil.CreateSyntaxError(); } } object convertedValue = convertFunc(property.Type, valueText); if (convertedValue == null) { throw ExceptionUtil.CreateSyntaxError(); } convertedPairs[property.Name] = convertedValue; } return(convertedPairs); }
/// <summary> /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') /// </summary> /// <param name="type">Type for current entityset.</param> /// <param name="namedValues">The dictionary of name value pairs.</param> /// <param name="convertFunc">The convert function to be used for value converting.</param> /// <returns>The resolved key list.</returns> public override IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IDictionary <string, string> namedValues, Func <IEdmTypeReference, string, object> convertFunc) { if (base.TryResolveKeys(type, namedValues, convertFunc, out IEnumerable <KeyValuePair <string, object> > convertedPairs)) { return(convertedPairs); } if (!TryResolveAlternateKeys(type, namedValues, convertFunc, out IEnumerable <KeyValuePair <string, object> > alternateConvertedPairs)) { throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyOrAlternateKeyMismatch(type.FullName())); } return(alternateConvertedPairs); }
/// <summary> /// Parses the key properties based on the segment's target type, then creates a new segment for the key. /// </summary> /// <param name="segment">The segment to apply the key to.</param> /// <param name="previousKeySegment">The parent node's key segment.</param> /// <param name="key">The key to apply.</param> /// <param name="resolver">The resolver to use.</param> /// <returns>The newly created key segment.</returns> private static KeySegment CreateKeySegment(ODataPathSegment segment, KeySegment previousKeySegment, SegmentArgumentParser key, ODataUriResolver resolver) { Debug.Assert(segment != null, "segment != null"); Debug.Assert(key != null && !key.IsEmpty, "key != null && !key.IsEmpty"); Debug.Assert(segment.SingleResult == false, "segment.SingleResult == false"); IEdmEntityType targetEntityType = null; if (!(segment.TargetEdmType != null && segment.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType))) { throw ExceptionUtil.CreateSyntaxError(); } Debug.Assert(targetEntityType != null, "targetEntityType != null"); // Make sure the keys specified in the uri matches with the number of keys in the metadata var keyProperties = targetEntityType.Key().ToList(); if (keyProperties.Count != key.ValueCount) { NavigationPropertySegment currentNavPropSegment = segment as NavigationPropertySegment; if (currentNavPropSegment != null) { key = KeyFinder.FindAndUseKeysFromRelatedSegment(key, keyProperties, currentNavPropSegment.NavigationProperty, previousKeySegment); } // if we still didn't find any keys, then throw an error. if (keyProperties.Count != key.ValueCount && resolver.GetType() == typeof(ODataUriResolver)) { throw ExceptionUtil.CreateBadRequestError(ErrorStrings.BadRequest_KeyCountMismatch(targetEntityType.FullName())); } } if (!key.AreValuesNamed && key.ValueCount > 1 && resolver.GetType() == typeof(ODataUriResolver)) { throw ExceptionUtil.CreateBadRequestError(ErrorStrings.RequestUriProcessor_KeysMustBeNamed); } IEnumerable <KeyValuePair <string, object> > keyPairs; if (!key.TryConvertValues(targetEntityType, out keyPairs, resolver)) { throw ExceptionUtil.CreateSyntaxError(); } return(new KeySegment(segment, keyPairs, targetEntityType, segment.TargetEdmNavigationSource)); }
/// <summary> /// Try to resolve a function from the given inputs. /// </summary> /// <param name="identifier">The identifier of the function that we're trying to find</param> /// <param name="parameterNames">the names of the parameters to search for.</param> /// <param name="model">the model to use to look up the operation import</param> /// <param name="matchingOperationImport">The single matching function found.</param> /// <param name="resolver">Resolver to be used.</param> /// <returns>True if a function was matched, false otherwise. Will throw if the model has illegal operation imports.</returns> internal static bool ResolveOperationImportFromList(string identifier, IList <string> parameterNames, IEdmModel model, out IEdmOperationImport matchingOperationImport, ODataUriResolver resolver) { IEnumerable <IEdmOperationImport> candidateMatchingOperationImports = null; IList <IEdmOperationImport> foundActionImportsWhenLookingForFunctions = new List <IEdmOperationImport>(); try { if (parameterNames.Count > 0) { // In this case we have to return a function so filter out actions because the number of parameters > 0. candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier) .RemoveActionImports(out foundActionImportsWhenLookingForFunctions) .FilterOperationsByParameterNames(parameterNames, resolver.EnableCaseInsensitive); } else { candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier); } } catch (Exception exc) { if (!ExceptionUtils.IsCatchableExceptionType(exc)) { throw; } throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperationImport(identifier), exc); } if (foundActionImportsWhenLookingForFunctions.Count > 0) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL if (candidateMatchingOperationImports.Any(f => f.IsActionImport())) { if (candidateMatchingOperationImports.Count() > 1) { if (candidateMatchingOperationImports.Any(o => o.IsFunctionImport())) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier)); } else { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionImportOverloads(identifier)); } } if (parameterNames.Count() != 0) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } matchingOperationImport = candidateMatchingOperationImports.Single(); return(true); } // If parameter count is zero and there is one function import whose parameter count is zero, return this function import. if (candidateMatchingOperationImports.Count() > 1 && parameterNames.Count == 0) { candidateMatchingOperationImports = candidateMatchingOperationImports.Where(operationImport => operationImport.Operation.Parameters.Count() == 0); } if (!candidateMatchingOperationImports.HasAny()) { matchingOperationImport = null; return(false); } // If more than one overload matches, try to select based on optional parameters if (candidateMatchingOperationImports.Count() > 1) { candidateMatchingOperationImports = candidateMatchingOperationImports.FindBestOverloadBasedOnParameters(parameterNames); } if (candidateMatchingOperationImports.Count() > 1) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier)); } matchingOperationImport = candidateMatchingOperationImports.Single(); return(matchingOperationImport != null); }
/// <summary> /// Try to resolve a function from the given inputs. /// </summary> /// <param name="identifier">The identifier of the function that we're trying to find</param> /// <param name="parameterNames">the names of the parameters to search for.</param> /// <param name="bindingType">the type of the previous segment</param> /// <param name="model">the model to use to look up the operation import</param> /// <param name="matchingOperation">The single matching function found.</param> /// <param name="resolver">Resolver to be used.</param> /// <returns>True if a function was matched, false otherwise. Will throw if the model has illegal operation imports.</returns> internal static bool ResolveOperationFromList(string identifier, IEnumerable <string> parameterNames, IEdmType bindingType, IEdmModel model, out IEdmOperation matchingOperation, ODataUriResolver resolver) { // TODO: update code that is duplicate between operation and operation import, add more tests. // If the previous segment is an open type, the service action name is required to be fully qualified or else we always treat it as an open property name. if (bindingType != null) { // TODO: look up actual container names here? // When using extension, there may be function call with unqualified name. So loose the restriction here. if (bindingType.IsOpen() && !identifier.Contains(".") && resolver.GetType() == typeof(ODataUriResolver)) { matchingOperation = null; return(false); } } IEnumerable <IEdmOperation> candidateMatchingOperations = null; // The extension method FindBoundOperations & FindOperations call IEdmModel.FindDeclaredBoundOperations which can be implemented by anyone and it could throw any type of exception // so catching all of them and simply putting it in the inner exception. try { if (bindingType != null) { candidateMatchingOperations = resolver.ResolveBoundOperations(model, identifier, bindingType); } else { candidateMatchingOperations = resolver.ResolveUnboundOperations(model, identifier); } } catch (Exception exc) { if (ExceptionUtils.IsCatchableExceptionType(exc)) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperation(identifier), exc); } throw; } IList <IEdmOperation> foundActionsWhenLookingForFunctions = new List <IEdmOperation>(); bool hasParameters = parameterNames.Count() > 0; if (bindingType != null) { candidateMatchingOperations.EnsureOperationsBoundWithBindingParameter(); } // If the number of parameters > 0 then this has to be a function as actions can't have parameters on the uri only in the payload. Filter further by parameters in this case, otherwise don't. if (hasParameters) { // can only be a function as only functions have parameters on the uri. candidateMatchingOperations = candidateMatchingOperations.RemoveActions(out foundActionsWhenLookingForFunctions) .FilterOperationsByParameterNames(parameterNames, resolver.EnableCaseInsensitive); } else if (bindingType != null) { // Filter out functions with more than one parameter. Actions should not be filtered as the parameters are in the payload not the uri candidateMatchingOperations = candidateMatchingOperations.Where(o => (o.IsFunction() && (o.Parameters.Count() == 1 || o.Parameters.Skip(1).All(p => p is IEdmOptionalParameter))) || o.IsAction()); } else { // Filter out functions with any parameters candidateMatchingOperations = candidateMatchingOperations.Where(o => (o.IsFunction() && !o.Parameters.Any()) || o.IsAction()); } // Only filter if there is more than one and its needed. if (candidateMatchingOperations.Count() > 1) { candidateMatchingOperations = candidateMatchingOperations.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(bindingType); } // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL if (candidateMatchingOperations.Any(f => f.IsAction())) { if (candidateMatchingOperations.Count() > 1) { if (candidateMatchingOperations.Any(o => o.IsFunction())) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationOverloads(identifier)); } else { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionOverloads(identifier)); } } if (hasParameters) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } matchingOperation = candidateMatchingOperations.Single(); return(true); } if (foundActionsWhenLookingForFunctions.Count > 0) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } // If more than one overload matches, try to select based on optional parameters if (candidateMatchingOperations.Count() > 1) { candidateMatchingOperations = candidateMatchingOperations.FindBestOverloadBasedOnParameters(parameterNames); } if (candidateMatchingOperations.Count() > 1) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_NoSingleMatchFound(identifier, string.Join(",", parameterNames.ToArray()))); } matchingOperation = candidateMatchingOperations.SingleOrDefault(); return(matchingOperation != null); }
/// <summary> /// Try to resolve a function from the given inputs. /// </summary> /// <param name="identifier">The identifier of the function that we're trying to find</param> /// <param name="parameterNames">the names of the parameters to search for.</param> /// <param name="bindingType">the type of the previous segment</param> /// <param name="model">the model to use to look up the operation import</param> /// <param name="matchingOperation">The single matching function found.</param> /// <param name="resolver">Resolver to be used.</param> /// <returns>True if a function was matched, false otherwise. Will throw if the model has illegal operation imports.</returns> internal static bool ResolveOperationFromList(string identifier, IList <string> parameterNames, IEdmType bindingType, IEdmModel model, out IEdmOperation matchingOperation, ODataUriResolver resolver) { // TODO: update code that is duplicate between operation and operation import, add more tests. // If the previous segment is an open type, the service action name is required to be fully qualified or else we always treat it as an open property name. matchingOperation = null; if (bindingType != null) { // TODO: look up actual container names here? // When using extension, there may be function call with unqualified name. So loose the restriction here. if (bindingType.IsOpen() && !identifier.Contains(".") && resolver.GetType() == typeof(ODataUriResolver)) { return(false); } } IEnumerable <IEdmOperation> operationsFromModel; // The extension method FindBoundOperations & FindOperations call IEdmModel.FindDeclaredBoundOperations which can be implemented by anyone and it could throw any type of exception // so catching all of them and simply putting it in the inner exception. try { if (bindingType != null) { operationsFromModel = resolver.ResolveBoundOperations(model, identifier, bindingType); } else { operationsFromModel = resolver.ResolveUnboundOperations(model, identifier); } } catch (Exception exc) { if (ExceptionUtils.IsCatchableExceptionType(exc)) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperation(identifier), exc); } throw; } bool foundActionsWhenLookingForFunctions; // Filters candidates based on the parameter names specified in the uri, removes actions if there were parameters specified in the uri but set the out bool to indicate that. // If no parameters specified, then matches based on binding type or matches with operations with no parameters. IList <IEdmOperation> candidatesMatchingOperations = operationsFromModel.FilterOperationCandidatesBasedOnParameterList(bindingType, parameterNames, resolver.EnableCaseInsensitive, out foundActionsWhenLookingForFunctions); // Only filter if there is more than one and its needed. if (candidatesMatchingOperations.Count > 1) { candidatesMatchingOperations = candidatesMatchingOperations.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(bindingType) as IList <IEdmOperation>; // This will be only null when no candidates are left. In that case, we can return false here. if (candidatesMatchingOperations == null) { if (foundActionsWhenLookingForFunctions) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } return(false); } } // If any of the candidates are an action, it better be the only thing returned, and there can't be parameters in the URL if (ResolveActionFromCandidates(candidatesMatchingOperations, identifier, parameterNames.Count > 0, out matchingOperation)) { return(true); } if (foundActionsWhenLookingForFunctions) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } // If more than one overload matches, try to select based on optional parameters if (candidatesMatchingOperations.Count > 1) { candidatesMatchingOperations = candidatesMatchingOperations.FilterOverloadsBasedOnParameterCount(parameterNames.Count); } if (candidatesMatchingOperations.Count > 1) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_NoSingleMatchFound(identifier, string.Join(",", parameterNames.ToArray()))); } matchingOperation = candidatesMatchingOperations.Count > 0 ? candidatesMatchingOperations[0] : null; return(matchingOperation != null); }