/// <summary> /// Retrieve CollectionNode bound with given query token. /// </summary> /// <param name="queryToken">The query token</param> /// <param name="expectedType">The expected type that this collection holds</param> /// <param name="model">The Edm model</param> /// <returns>The corresponding CollectionNode</returns> private CollectionNode GetCollectionOperandFromToken(QueryToken queryToken, IEdmTypeReference expectedType, IEdmModel model) { CollectionNode operand = null; LiteralToken literalToken = queryToken as LiteralToken; if (literalToken != null) { string originalLiteralText = literalToken.OriginalText; // Parentheses-based collections are not standard JSON but bracket-based ones are. // Temporarily switch our collection to bracket-based so that the JSON reader will // correctly parse the collection. Then pass the original literal text to the token. string bracketLiteralText = originalLiteralText; if (bracketLiteralText[0] == '(') { Debug.Assert(bracketLiteralText[bracketLiteralText.Length - 1] == ')', "Collection with opening '(' should have corresponding ')'"); StringBuilder replacedText = new StringBuilder(bracketLiteralText); replacedText[0] = '['; replacedText[replacedText.Length - 1] = ']'; bracketLiteralText = replacedText.ToString(); Debug.Assert(expectedType.IsCollection()); string expectedTypeFullName = expectedType.Definition.AsElementType().FullTypeName(); if (expectedTypeFullName.Equals("Edm.String")) { // For collection of strings, need to convert single-quoted string to double-quoted string, // and also, per ABNF, a single quote within a string literal is "encoded" as two consecutive single quotes in either // literal or percent - encoded representation. // Sample: ['a''bc','''def','xyz'''] ==> ["a'bc","'def","xyz'"], which is legitimate Json format. bracketLiteralText = NormalizeStringCollectionItems(bracketLiteralText); } else if (expectedTypeFullName.Equals("Edm.Guid")) { // For collection of Guids, need to convert the Guid literals to single-quoted form, so that it is compatible // with the Json reader used for deserialization. // Sample: [D01663CF-EB21-4A0E-88E0-361C10ACE7FD, 492CF54A-84C9-490C-A7A4-B5010FAD8104] // ==> ['D01663CF-EB21-4A0E-88E0-361C10ACE7FD', '492CF54A-84C9-490C-A7A4-B5010FAD8104'] bracketLiteralText = NormalizeGuidCollectionItems(bracketLiteralText); } } object collection = ODataUriConversionUtils.ConvertFromCollectionValue(bracketLiteralText, model, expectedType); LiteralToken collectionLiteralToken = new LiteralToken(collection, originalLiteralText, expectedType); operand = this.bindMethod(collectionLiteralToken) as CollectionConstantNode; } else { operand = this.bindMethod(queryToken) as CollectionNode; } if (operand == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_RightOperandNotCollectionValue); } return(operand); }
/// <summary> /// If the source node is not of the specified type, then we check if type promotion is possible and inject a convert node. /// If the source node is the same type as the target type (or if the target type is null), we just return the source node as is. /// </summary> /// <param name="source">The source node to apply the convertion to.</param> /// <param name="targetTypeReference">The target primitive type. May be null - this method will do nothing in that case.</param> /// <returns>The converted query node, or the original source node unchanged.</returns> internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference) { Debug.Assert(source != null, "source != null"); if (targetTypeReference == null) { return(source); } if (source.TypeReference != null) { if (source.TypeReference.IsEquivalentTo(targetTypeReference)) { return(source); } if (!TypePromotionUtils.CanConvertTo(source, source.TypeReference, targetTypeReference)) { throw new ODataException(ODataErrorStrings.MetadataBinder_CannotConvertToType(source.TypeReference.ODataFullName(), targetTypeReference.ODataFullName())); } else { ConstantNode constantNode = source as ConstantNode; if (source.TypeReference.IsEnum() && constantNode != null) { return(new ConstantNode(constantNode.Value, ODataUriUtils.ConvertToUriLiteral(constantNode.Value, ODataVersion.V4), targetTypeReference)); } object primitiveValue; if (MetadataUtilsCommon.TryGetConstantNodePrimitiveLDMF(source, out primitiveValue) && (primitiveValue != null)) { // L F D M types : directly create a ConvertNode. // 1. NodeToExpressionTranslator.cs won't allow implicitly converting single/double to decimal, which should be done here at Node tree level. // 2. And prevent losing precision in float -> double, e.g. (double)1.234f => 1.2339999675750732d not 1.234d object primitiveValue2 = ODataUriConversionUtils.CoerceNumericType(primitiveValue, targetTypeReference.AsPrimitive().Definition as IEdmPrimitiveType); if (string.IsNullOrEmpty(constantNode.LiteralText)) { return(new ConstantNode(primitiveValue2)); } return(new ConstantNode(primitiveValue2, constantNode.LiteralText)); } else { // other type conversion : ConvertNode return(new ConvertNode(source, targetTypeReference)); } } } else { // If the source doesn't have a type (possibly an open property), then it's possible to convert it // cause we don't know for sure. return(new ConvertNode(source, targetTypeReference)); } }
public void NullCollectionTypeShouldThrow() { const string text = "(1,2,3)"; var expectedType = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false))); object collection = ODataUriConversionUtils.ConvertFromCollectionValue("[1,2,3]", HardCodedTestModel.TestModel, expectedType); LiteralToken literalToken = new LiteralToken(collection, text, expectedType); Action target = () => new CollectionConstantNode((literalToken.Value as ODataCollectionValue)?.Items, text, null); target.ShouldThrow <ArgumentNullException>().Where(e => e.Message.Contains("collectionType")); }
public void NullValueShouldThrow() { const string text = "(1,2,3)"; var expectedType = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false))); object collection = ODataUriConversionUtils.ConvertFromCollectionValue("[1,2,3]", HardCodedTestModel.TestModel, expectedType); LiteralToken literalToken = new LiteralToken(collection, text, expectedType); Action target = () => new CollectionConstantNode(null, text, expectedType); Assert.Throws <ArgumentNullException>("objectCollection", target); }
public void ItemTypeIsSetCorrectly() { const string text = "(1,2,3)"; var expectedType = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false))); object collection = ODataUriConversionUtils.ConvertFromCollectionValue("[1,2,3]", HardCodedTestModel.TestModel, expectedType); LiteralToken literalToken = new LiteralToken(collection, text, expectedType); CollectionConstantNode collectionConstantNode = new CollectionConstantNode( (literalToken.Value as ODataCollectionValue)?.Items, text, expectedType); collectionConstantNode.ItemType.FullName().Should().Be("Edm.Int32"); }
public void KindIsSetCorrectly() { const string text = "(1,2,3)"; var expectedType = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false))); object collection = ODataUriConversionUtils.ConvertFromCollectionValue("[1,2,3]", HardCodedTestModel.TestModel, expectedType); LiteralToken literalToken = new LiteralToken(collection, text, expectedType); CollectionConstantNode collectionConstantNode = new CollectionConstantNode( (literalToken.Value as ODataCollectionValue)?.Items, text, expectedType); Assert.Equal(InternalQueryNodeKind.CollectionConstant, collectionConstantNode.InternalKind); }
/// <summary> /// Convert the given key value to a URI literal. /// </summary> /// <param name="value">The key value to convert.</param> /// <param name="keyAsSegment">Whether the KeyAsSegment convention is enabled.</param> /// <returns>The converted URI literal for the given key value.</returns> private static string ConvertKeyValueToUriLiteral(object value, bool keyAsSegment) { // For Default convention, // ~/Customers('Peter') => key value is "Peter" => URI literal is "'Peter'" // // For KeyAsSegment convention, // ~/Customers/Peter => key value is "Peter" => URI literal is "Peter" string stringValue = value as string; if (keyAsSegment && stringValue != null) { return(stringValue); } // All key values are primitives so use this instead of ODataUriUtils.ConvertToUriLiteral() // to improve efficiency. return(ODataUriConversionUtils.ConvertToUriPrimitiveLiteral(value, ODataVersion.V4)); }
public void NullableCollectionTypeSetsConstantNodeCorrectly() { const string text = "('abc','def', null)"; var expectedType = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetString(true))); object collection = ODataUriConversionUtils.ConvertFromCollectionValue("['abc','def', null]", HardCodedTestModel.TestModel, expectedType); LiteralToken literalToken = new LiteralToken(collection, text, expectedType); CollectionConstantNode collectionConstantNode = new CollectionConstantNode( (literalToken.Value as ODataCollectionValue)?.Items, text, expectedType); var expectedList = new ConstantNode[] { new ConstantNode("abc", "abc", EdmCoreModel.Instance.GetString(true)), new ConstantNode("def", "def", EdmCoreModel.Instance.GetString(true)), new ConstantNode(null, "null", EdmCoreModel.Instance.GetString(true)), }; collectionConstantNode.Collection.ShouldBeEquivalentTo(expectedList); }
public void NumberCollectionThroughLiteralTokenIsSetCorrectly() { const string text = "(1,2,3)"; var expectedType = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false))); object collection = ODataUriConversionUtils.ConvertFromCollectionValue("[1,2,3]", HardCodedTestModel.TestModel, expectedType); LiteralToken literalToken = new LiteralToken(collection, text, expectedType); CollectionConstantNode collectionConstantNode = new CollectionConstantNode( (literalToken.Value as ODataCollectionValue)?.Items, text, expectedType); var expectedList = new ConstantNode[] { new ConstantNode(1, "1", EdmCoreModel.Instance.GetInt32(false)), new ConstantNode(2, "2", EdmCoreModel.Instance.GetInt32(false)), new ConstantNode(3, "3", EdmCoreModel.Instance.GetInt32(false)), }; collectionConstantNode.Collection.ShouldBeEquivalentTo(expectedList); }
public void StringCollectionThroughLiteralTokenIsSetCorrectly() { const string text = "('abc','def','ghi')"; var expectedType = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetString(true))); object collection = ODataUriConversionUtils.ConvertFromCollectionValue("['abc','def','ghi']", HardCodedTestModel.TestModel, expectedType); LiteralToken literalToken = new LiteralToken(collection, text, expectedType); CollectionConstantNode collectionConstantNode = new CollectionConstantNode( (literalToken.Value as ODataCollectionValue)?.Items, text, expectedType); var expectedList = new ConstantNode[] { new ConstantNode("abc", "abc", EdmCoreModel.Instance.GetString(true)), new ConstantNode("def", "def", EdmCoreModel.Instance.GetString(true)), new ConstantNode("ghi", "ghi", EdmCoreModel.Instance.GetString(true)), }; VerifyCollectionConstantNode(collectionConstantNode.Collection, expectedList); }
/// <summary> /// Retrieve CollectionNode bound with given query token. /// </summary> /// <param name="queryToken">The query token</param> /// <param name="expectedType">The expected type that this collection holds</param> /// <param name="model">The Edm model</param> /// <returns>The corresponding CollectionNode</returns> private CollectionNode GetCollectionOperandFromToken(QueryToken queryToken, IEdmTypeReference expectedType, IEdmModel model) { CollectionNode operand = null; LiteralToken literalToken = queryToken as LiteralToken; if (literalToken != null) { string originalLiteralText = literalToken.OriginalText; // Parentheses-based collections are not standard JSON but bracket-based ones are. // Temporarily switch our collection to bracket-based so that the JSON reader will // correctly parse the collection. Then pass the original literal text to the token. string bracketLiteralText = originalLiteralText; if (bracketLiteralText[0] == '(') { Debug.Assert(bracketLiteralText[bracketLiteralText.Length - 1] == ')', "Collection with opening '(' should have corresponding ')'"); StringBuilder replacedText = new StringBuilder(bracketLiteralText); replacedText[0] = '['; replacedText[replacedText.Length - 1] = ']'; bracketLiteralText = replacedText.ToString(); } object collection = ODataUriConversionUtils.ConvertFromCollectionValue(bracketLiteralText, model, expectedType); LiteralToken collectionLiteralToken = new LiteralToken(collection, originalLiteralText, expectedType); operand = this.bindMethod(collectionLiteralToken) as CollectionConstantNode; } else { operand = this.bindMethod(queryToken) as CollectionNode; } if (operand == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_RightOperandNotCollectionValue); } return(operand); }
/// <summary> /// If the source node is not of the specified type, then we check if type promotion is possible and inject a convert node. /// If the source node is the same type as the target type (or if the target type is null), we just return the source node as is. /// </summary> /// <param name="source">The source node to apply the conversion to.</param> /// <param name="targetTypeReference">The target primitive type. May be null - this method will do nothing in that case.</param> /// <returns>The converted query node, or the original source node unchanged.</returns> internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference) { Debug.Assert(source != null, "source != null"); if (targetTypeReference == null) { return(source); } if (source.TypeReference != null) { if (source.TypeReference.IsEquivalentTo(targetTypeReference)) { // For source is type definition, if source's underlying type == target type. // We create a conversion node from source to its underlying type (target type) // so that the service can convert value of source clr type to underlying clr type. if (source.TypeReference.IsTypeDefinition()) { return(new ConvertNode(source, targetTypeReference)); } return(source); } // Structured type in url will be translated into a node with raw string value. // We create a conversion node from string to structured type. if (targetTypeReference.IsStructured() || targetTypeReference.IsStructuredCollectionType()) { return(new ConvertNode(source, targetTypeReference)); } ConstantNode constantNode = source as ConstantNode; if (constantNode != null && constantNode.Value != null && source.TypeReference.IsString() && targetTypeReference.IsEnum()) { string memberName = constantNode.Value.ToString(); IEdmEnumType enumType = targetTypeReference.Definition as IEdmEnumType; if (enumType.Members.Any(m => string.Compare(m.Name, memberName, StringComparison.Ordinal) == 0)) { string literalText = ODataUriUtils.ConvertToUriLiteral(constantNode.Value, default(ODataVersion)); return(new ConstantNode(new ODataEnumValue(constantNode.Value.ToString(), targetTypeReference.Definition.ToString()), literalText, targetTypeReference)); } else { throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(memberName)); } } if (!TypePromotionUtils.CanConvertTo(source, source.TypeReference, targetTypeReference)) { throw new ODataException(ODataErrorStrings.MetadataBinder_CannotConvertToType(source.TypeReference.FullName(), targetTypeReference.FullName())); } else { if (source.TypeReference.IsEnum() && constantNode != null) { return(new ConstantNode(constantNode.Value, ODataUriUtils.ConvertToUriLiteral(constantNode.Value, ODataVersion.V4), targetTypeReference)); } object originalPrimitiveValue; if (MetadataUtilsCommon.TryGetConstantNodePrimitiveLDMF(source, out originalPrimitiveValue) && (originalPrimitiveValue != null)) { // L F D M types : directly create a ConvertNode. // 1. NodeToExpressionTranslator.cs won't allow implicitly converting single/double to decimal, which should be done here at Node tree level. // 2. And prevent losing precision in float -> double, e.g. (double)1.234f => 1.2339999675750732d not 1.234d object targetPrimitiveValue = ODataUriConversionUtils.CoerceNumericType(originalPrimitiveValue, targetTypeReference.AsPrimitive().Definition as IEdmPrimitiveType); if (string.IsNullOrEmpty(constantNode.LiteralText)) { return(new ConstantNode(targetPrimitiveValue)); } var candidate = new ConstantNode(targetPrimitiveValue, constantNode.LiteralText); var decimalType = candidate.TypeReference as IEdmDecimalTypeReference; if (decimalType != null) { var targetDecimalType = (IEdmDecimalTypeReference)targetTypeReference; return(decimalType.Precision == targetDecimalType.Precision && decimalType.Scale == targetDecimalType.Scale ? (SingleValueNode)candidate : (SingleValueNode)(new ConvertNode(candidate, targetTypeReference))); } else { return(candidate); } } else { // other type conversion : ConvertNode return(new ConvertNode(source, targetTypeReference)); } } } else { // If the source doesn't have a type (possibly an open property), then it's possible to convert it // cause we don't know for sure. return(new ConvertNode(source, targetTypeReference)); } }
/// <summary> /// If the source node is not of the specified type, then we check if type promotion is possible and inject a convert node. /// If the source node is the same type as the target type (or if the target type is null), we just return the source node as is. /// </summary> /// <param name="source">The source node to apply the convertion to.</param> /// <param name="targetTypeReference">The target primitive type. May be null - this method will do nothing in that case.</param> /// <returns>The converted query node, or the original source node unchanged.</returns> internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference) { Debug.Assert(source != null, "source != null"); if (targetTypeReference == null) { return(source); } if (source.TypeReference != null) { if (source.TypeReference.IsEquivalentTo(targetTypeReference)) { // For source is type definition, if source's underlying type == target type. // We create a conversion node from source to its underlying type (target type) // so that the service can convert value of source clr type to underlying clr type. if (source.TypeReference.IsTypeDefinition()) { return(new ConvertNode(source, targetTypeReference)); } return(source); } if (!TypePromotionUtils.CanConvertTo(source, source.TypeReference, targetTypeReference)) { throw new ODataException(ODataErrorStrings.MetadataBinder_CannotConvertToType(source.TypeReference.ODataFullName(), targetTypeReference.ODataFullName())); } else { ConstantNode constantNode = source as ConstantNode; if (source.TypeReference.IsEnum() && constantNode != null) { return(new ConstantNode(constantNode.Value, ODataUriUtils.ConvertToUriLiteral(constantNode.Value, ODataVersion.V4), targetTypeReference)); } object originalPrimitiveValue; if (MetadataUtilsCommon.TryGetConstantNodePrimitiveDate(source, out originalPrimitiveValue) && (originalPrimitiveValue != null)) { // DateTimeOffset -> Date when (target is Date) and (originalValue match Date format) and (ConstantNode) object targetPrimitiveValue = ODataUriConversionUtils.CoerceTemporalType(originalPrimitiveValue, targetTypeReference.AsPrimitive().Definition as IEdmPrimitiveType); if (targetPrimitiveValue != null) { if (string.IsNullOrEmpty(constantNode.LiteralText)) { return(new ConstantNode(targetPrimitiveValue)); } return(new ConstantNode(targetPrimitiveValue, constantNode.LiteralText, targetTypeReference)); } } if (MetadataUtilsCommon.TryGetConstantNodePrimitiveLDMF(source, out originalPrimitiveValue) && (originalPrimitiveValue != null)) { // L F D M types : directly create a ConvertNode. // 1. NodeToExpressionTranslator.cs won't allow implicitly converting single/double to decimal, which should be done here at Node tree level. // 2. And prevent losing precision in float -> double, e.g. (double)1.234f => 1.2339999675750732d not 1.234d object targetPrimitiveValue = ODataUriConversionUtils.CoerceNumericType(originalPrimitiveValue, targetTypeReference.AsPrimitive().Definition as IEdmPrimitiveType); if (string.IsNullOrEmpty(constantNode.LiteralText)) { return(new ConstantNode(targetPrimitiveValue)); } return(new ConstantNode(targetPrimitiveValue, constantNode.LiteralText)); } else { // other type conversion : ConvertNode return(new ConvertNode(source, targetTypeReference)); } } } else { // If the source doesn't have a type (possibly an open property), then it's possible to convert it // cause we don't know for sure. return(new ConvertNode(source, targetTypeReference)); } }
private static string PrimitiveLiteral(object value) { return(ODataUriConversionUtils.ConvertToUriPrimitiveLiteral(value, ODataVersion.V4)); }
[InlineData(-100D, "-100.0")] // DoubleLiteralShouldHaveDecimalPointForWholeNumber public void ConvertToUriPrimitiveLiteralUsingPrimitiveValueWorks(object value, string expect) { string actual = ODataUriConversionUtils.ConvertToUriPrimitiveLiteral(value, ODataVersion.V4); Assert.Equal(expect, actual); }