/// <summary>Attempts to parse key values from the specified text.</summary> /// <param name='text'>Text to parse (not null).</param> /// <param name='instance'>After invocation, the parsed key instance.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <returns> /// true if the key instance was parsed; false if there was a /// syntactic error. /// </returns> /// <remarks> /// The returned instance contains only string values. To get typed values, a call to /// TryConvertValues is necessary. /// </remarks> private static bool TryParseFromUri(string text, out SegmentArgumentParser instance, bool enableUriTemplateParsing) { Debug.Assert(text != null, "text != null"); Dictionary <string, string> namedValues = null; List <string> positionalValues = null; // parse keys just like function parameters ExpressionLexer lexer = new ExpressionLexer(string.Concat("(", text, ")"), true, false); UriQueryExpressionParser exprParser = new UriQueryExpressionParser(ODataUriParserSettings.DefaultFilterLimit /* default limit for parsing key value */, lexer); var tmp = (new FunctionCallParser(lexer, exprParser)).ParseArgumentListOrEntityKeyList(); if (lexer.CurrentToken.Kind != ExpressionTokenKind.End) { instance = null; return(false); } if (tmp.Length == 0) { instance = Empty; return(true); } foreach (FunctionParameterToken t in tmp) { string valueText = null; LiteralToken literalToken = t.ValueToken as LiteralToken; if (literalToken != null) { valueText = literalToken.OriginalText; // disallow "{...}" if enableUriTemplateParsing is false (which could have been seen as valid function parameter, e.g. array notation) if (!enableUriTemplateParsing && UriTemplateParser.IsValidTemplateLiteral(valueText)) { instance = null; return(false); } } else { DottedIdentifierToken dottedIdentifierToken = t.ValueToken as DottedIdentifierToken; // for enum if (dottedIdentifierToken != null) { valueText = dottedIdentifierToken.Identifier; } } if (valueText != null) { if (t.ParameterName == null) { if (namedValues != null) { instance = null; // We cannot mix named and non-named values. return(false); } CreateIfNull(ref positionalValues); positionalValues.Add(valueText); } else { if (positionalValues != null) { instance = null; // We cannot mix named and non-named values. return(false); } CreateIfNull(ref namedValues); namedValues.Add(t.ParameterName, valueText); } } else { instance = null; return(false); } } instance = new SegmentArgumentParser(namedValues, positionalValues, false, enableUriTemplateParsing); return(true); }
/// <summary>Attempts to parse nullable values (only positional values, no name-value pairs) from the specified text.</summary> /// <param name='text'>Text to parse (not null).</param> /// <param name='instance'>After invocation, the parsed key instance.</param> /// <returns> /// true if the given values were parsed; false if there was a /// syntactic error. /// </returns> /// <remarks> /// The returned instance contains only string values. To get typed values, a call to /// TryConvertValues is necessary. /// </remarks> public static bool TryParseNullableTokens(string text, out SegmentArgumentParser instance) { return(TryParseFromUri(text, out instance, false)); }
/// <summary> /// Find any related keys from the parent key segment, if it exists, and add them to the raw key values that /// we already have from the uri. /// </summary> /// <param name="rawKeyValuesFromUri">The raw key values as we've parsed them from the uri.</param> /// <param name="targetEntityKeyProperties">The list of key properties on the target entity.</param> /// <param name="currentNavigationProperty">The current navigation property that we're trying to follow using the raw key values</param> /// <param name="keySegmentOfParentEntity">The key segment of the parent entity in this path, if it exists. Null otherwise</param> /// <returns>A new SegmentArgumentParser with any keys that were found added to its list of NamedValues.</returns> /// <throws>Throws if the input currentNavigationProperty is null.</throws> public static SegmentArgumentParser FindAndUseKeysFromRelatedSegment(SegmentArgumentParser rawKeyValuesFromUri, IEnumerable <IEdmStructuralProperty> targetEntityKeyProperties, IEdmNavigationProperty currentNavigationProperty, KeySegment keySegmentOfParentEntity) { ExceptionUtils.CheckArgumentNotNull(currentNavigationProperty, "currentNavigationProperty"); ExceptionUtils.CheckArgumentNotNull(rawKeyValuesFromUri, "rawKeyValuesFromUri"); ReadOnlyCollection <IEdmStructuralProperty> targetKeyPropertyList = targetEntityKeyProperties != null ? new ReadOnlyCollection <IEdmStructuralProperty>(targetEntityKeyProperties.ToList()) : new ReadOnlyCollection <IEdmStructuralProperty>(new List <IEdmStructuralProperty>()); // should only get here if the number of raw parameters from the uri is different than the number of key properties for the target entity. Debug.Assert(rawKeyValuesFromUri.ValueCount < targetKeyPropertyList.Count, "rawKeyValuesFromUri.ValueCount < targetEntityKeyProperties.Count()"); // if the raw key from the uri has positional values, there must be only one of them // its important to cache this value here because we'll change it when we add new // named values below (the implementation of AreValuesNamed is just namedValues !=null) bool hasPositionalValues = !rawKeyValuesFromUri.AreValuesNamed; if (hasPositionalValues && rawKeyValuesFromUri.ValueCount > 1) { return(rawKeyValuesFromUri); } if (keySegmentOfParentEntity == null) { return(rawKeyValuesFromUri); } // TODO: p2 merge the below 2 pieces of codes // find out if any target entity key properties have referential constraints that link them to the previous rawKeyValuesFromUri. List <EdmReferentialConstraintPropertyPair> keysFromReferentialIntegrityConstraint = ExtractMatchingPropertyPairsFromNavProp(currentNavigationProperty, targetKeyPropertyList).ToList(); foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint) { KeyValuePair <string, object> valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.DependentProperty.Name); if (valueFromParent.Key != null) { // if the key from the referential integrity constraint is one of the target key properties // and that key property isn't already populated in the raw key values from the uri, then // we set that value to the value from the parent key segment. if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name)) { rawKeyValuesFromUri.AddNamedValue( keyFromReferentialIntegrityConstraint.PrincipalProperty.Name, ConvertKeyValueToUriLiteral(valueFromParent.Value, rawKeyValuesFromUri.KeyAsSegment)); } } } // also need to look to see if any nav props exist in the target set that refer back to this same set, which might have // referential constraints also. keysFromReferentialIntegrityConstraint.Clear(); IEdmNavigationProperty reverseNavProp = currentNavigationProperty.Partner; if (reverseNavProp != null) { keysFromReferentialIntegrityConstraint.AddRange(ExtractMatchingPropertyPairsFromReversedNavProp(reverseNavProp, targetKeyPropertyList)); } foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint) { KeyValuePair <string, object> valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name); if (valueFromParent.Key != null) { // if the key from the referential integrity constraint is one of the target key properties // and that key property isn't already populated in the raw key values from the uri, then // we set that value to the value from the parent key segment. if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.DependentProperty.Name)) { rawKeyValuesFromUri.AddNamedValue( keyFromReferentialIntegrityConstraint.DependentProperty.Name, ConvertKeyValueToUriLiteral(valueFromParent.Value, rawKeyValuesFromUri.KeyAsSegment)); } } } // if we had a positional value before, then we need to add that value as a new named value. // the name that we choose will be the only value from the target entity key properties // that isn't already set in the NamedValues list. if (hasPositionalValues) { if (rawKeyValuesFromUri.NamedValues != null) { List <IEdmStructuralProperty> unassignedProperties = targetKeyPropertyList.Where(x => !rawKeyValuesFromUri.NamedValues.ContainsKey(x.Name)).ToList(); if (unassignedProperties.Count == 1) { rawKeyValuesFromUri.AddNamedValue(unassignedProperties[0].Name, rawKeyValuesFromUri.PositionalValues[0]); } else { return(rawKeyValuesFromUri); } // clear out the positional value so that we keep a consistent state in the // raw keys from uri. rawKeyValuesFromUri.PositionalValues.Clear(); } else { return(rawKeyValuesFromUri); } } return(rawKeyValuesFromUri); }
/// <summary>Attempts to parse key values from the specified text.</summary> /// <param name='text'>Text to parse (not null).</param> /// <param name='instance'>After invocation, the parsed key instance.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <returns> /// true if the key instance was parsed; false if there was a /// syntactic error. /// </returns> /// <remarks> /// The returned instance contains only string values. To get typed values, a call to /// TryConvertValues is necessary. /// </remarks> public static bool TryParseKeysFromUri(string text, out SegmentArgumentParser instance, bool enableUriTemplateParsing) { return(TryParseFromUri(text, out instance, enableUriTemplateParsing)); }
/// <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)); }