diff --git a/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs b/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs index 32cdc3db35..ca1e4ac34b 100644 --- a/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs +++ b/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs @@ -115,109 +115,222 @@ namespace Microsoft.AspNet.JsonPatch.Adapters [NotNull] object objectToApplyTo, [NotNull] Operation operationToReport) { - // add, in this implementation, does not just "add" properties - that's - // technically impossible; It can however be used to add items to arrays, - // or to replace values. - // first up: if the path ends in a numeric value, we're inserting in a list and // that value represents the position; if the path ends in "-", we're appending // to the list. - var appendList = false; - var positionAsInteger = -1; - var actualPathToProperty = path; - if (path.EndsWith("/-")) - { - appendList = true; - actualPathToProperty = path.Substring(0, path.Length - 2); - } - else - { - positionAsInteger = GetNumericEnd(path); - - if (positionAsInteger > -1) - { - actualPathToProperty = path.Substring(0, - path.LastIndexOf('/' + positionAsInteger.ToString())); - } - } - - var patchProperty = FindPropertyAndParent(objectToApplyTo, actualPathToProperty); - - // does property at path exist? - if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path)) + // get path result + var pathResult = GetActualPropertyPath( + path, + objectToApplyTo, + operationToReport); + if (pathResult == null) { return; } - // it exists. If it' an array, add to that array. If it's not, we replace. - // is the path an array (but not a string (= char[]))? In this case, - // the path must end with "/position" or "/-", which we already determined before. - if (appendList || positionAsInteger > -1) + var appendList = pathResult.ExecuteAtEnd; + var positionAsInteger = pathResult.NumericEnd; + var actualPathToProperty = pathResult.PathToProperty; + + var treeAnalysisResult = new ObjectTreeAnalysisResult( + objectToApplyTo, + actualPathToProperty, + ContractResolver); + + if (!treeAnalysisResult.IsValidPathForAdd) { - // what if it's an array but there's no position?? - if (IsNonStringArray(patchProperty.Property.PropertyType)) + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatPropertyCannotBeAdded(path))); + return; + } + + if (treeAnalysisResult.UseDynamicLogic) + { + var container = treeAnalysisResult.Container; + if (container.ContainsCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent)) { - // now, get the generic type of the IList<> from Property type. - var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType); - - var conversionResult = ConvertToActualType(genericTypeOfArray, value); - - if (!CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path)) + // Existing property. + // If it's not an array, we need to check if the value fits the property type + // + // If it's an array, we need to check if the value fits in that array type, + // and add it at the correct position (if allowed). + if (appendList || positionAsInteger > -1) { - return; - } + // get the actual type + var propertyValue = container.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent); + var typeOfPathProperty = value.GetType(); - // get value (it can be cast, we just checked that) - var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); - - if (appendList) - { - array.Add(conversionResult.ConvertedInstance); - } - else - { - // specified index must not be greater than the amount of items in the array - if (positionAsInteger <= array.Count) - { - array.Insert(positionAsInteger, conversionResult.ConvertedInstance); - } - else + if (!IsNonStringArray(typeOfPathProperty)) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path))); + return; + } + // now, get the generic type of the enumerable + var genericTypeOfArray = GetIListType(typeOfPathProperty); + var conversionResult = ConvertToActualType(genericTypeOfArray, value); + if (!conversionResult.CanBeConverted) + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatInvalidValueForProperty(value, path))); + return; + } + + // get value (it can be cast, we just checked that) + var array = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey( + treeAnalysisResult.PropertyPathInParent) as IList; + + if (appendList) + { + array.Add(conversionResult.ConvertedInstance); + treeAnalysisResult.Container.SetValueForCaseInsensitiveKey( + treeAnalysisResult.PropertyPathInParent, array); + } + else + { + // specified index must not be greater than + // the amount of items in the array + if (positionAsInteger > array.Count) + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatInvalidIndexForArrayProperty( + operationToReport.op, + path))); + return; + } + + array.Insert(positionAsInteger, conversionResult.ConvertedInstance); + treeAnalysisResult.Container.SetValueForCaseInsensitiveKey( + treeAnalysisResult.PropertyPathInParent, array); + } + } + else + { + // get the actual type + var typeOfPathProperty = treeAnalysisResult.Container + .GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent).GetType(); + + // can the value be converted to the actual type? + var conversionResult = ConvertToActualType(typeOfPathProperty, value); + if (conversionResult.CanBeConverted) + { + treeAnalysisResult.Container.SetValueForCaseInsensitiveKey( + treeAnalysisResult.PropertyPathInParent, + conversionResult.ConvertedInstance); + } + else + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path))); return; } } } else { - LogError(new JsonPatchError( - objectToApplyTo, - operationToReport, - Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path))); - - return; + // New property - add it. + treeAnalysisResult.Container.Add(treeAnalysisResult.PropertyPathInParent, value); } } else { - var conversionResultTuple = ConvertToActualType( - patchProperty.Property.PropertyType, - value); + // If it's an array, add to that array. If it's not, we replace. - // Is conversion successful - if (!CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, path)) + // is the path an array (but not a string (= char[]))? In this case, + // the path must end with "/position" or "/-", which we already determined before. + + var patchProperty = treeAnalysisResult.JsonPatchProperty; + + if (appendList || positionAsInteger > -1) { - return; - } + if (!IsNonStringArray(patchProperty.Property.PropertyType)) + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path))); + return; + } - patchProperty.Property.ValueProvider.SetValue( + // now, get the generic type of the IList<> from Property type. + var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType); + var conversionResult = ConvertToActualType(genericTypeOfArray, value); + if (!conversionResult.CanBeConverted) + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path))); + return; + } + + if (!patchProperty.Property.Readable) + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatCannotReadProperty(path))); + return; + } + + var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); + if (appendList) + { + array.Add(conversionResult.ConvertedInstance); + } + else if (positionAsInteger <= array.Count) + { + array.Insert(positionAsInteger, conversionResult.ConvertedInstance); + } + else + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path))); + return; + } + } + else + { + var conversionResultTuple = ConvertToActualType( + patchProperty.Property.PropertyType, + value); + + if (!conversionResultTuple.CanBeConverted) + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatInvalidValueForProperty(value, path))); + return; + } + + if (!patchProperty.Property.Writable) + { + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatCannotUpdateProperty(path))); + return; + } + + patchProperty.Property.ValueProvider.SetValue( patchProperty.Parent, conversionResultTuple.ConvertedInstance); + } } } @@ -646,7 +759,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters // if property is of IList type then get the array index from splitPath and get the // object at the indexed position from the list. - if (GetIListType(property.PropertyType) != null) + if (GetIListType(property.PropertyType) != null) { var index = int.Parse(splitPath[++i]); targetObject = ((IList)targetObject)[index]; @@ -722,5 +835,45 @@ namespace Microsoft.AspNet.JsonPatch.Adapters return -1; } + + + private ActualPropertyPathResult GetActualPropertyPath( + [NotNull] string propertyPath, + [NotNull] object objectToApplyTo, + [NotNull] Operation operationToReport) + { + if (propertyPath.EndsWith("/-")) + { + return new ActualPropertyPathResult(-1, propertyPath.Substring(0, propertyPath.Length - 2), true); + } + else + { + var possibleIndex = propertyPath.Substring(propertyPath.LastIndexOf("/") + 1); + int castedIndex = -1; + if (int.TryParse(possibleIndex, out castedIndex)) + { + // has numeric end. + if (castedIndex > -1) + { + var pathToProperty = propertyPath.Substring( + 0, + propertyPath.LastIndexOf('/' + castedIndex.ToString())); + + return new ActualPropertyPathResult(castedIndex, pathToProperty, false); + } + else + { + // negative position - invalid path + LogError(new JsonPatchError( + objectToApplyTo, + operationToReport, + Resources.FormatNegativeIndexForArrayProperty(operationToReport.op, propertyPath))); + return null; + } + } + + return new ActualPropertyPathResult(-1, propertyPath, false); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.JsonPatch/Helpers/ActualPropertyPathResult.cs b/src/Microsoft.AspNet.JsonPatch/Helpers/ActualPropertyPathResult.cs new file mode 100644 index 0000000000..d22d1bf830 --- /dev/null +++ b/src/Microsoft.AspNet.JsonPatch/Helpers/ActualPropertyPathResult.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.JsonPatch.Helpers +{ + internal class ActualPropertyPathResult + { + public int NumericEnd { get; private set; } + public string PathToProperty { get; set; } + public bool ExecuteAtEnd { get; set; } + + public ActualPropertyPathResult( + int numericEnd, + string pathToProperty, + bool executeAtEnd) + { + NumericEnd = numericEnd; + PathToProperty = pathToProperty; + ExecuteAtEnd = executeAtEnd; + } + } +} diff --git a/src/Microsoft.AspNet.JsonPatch/Helpers/ExpandoObjectDictionaryExtensions.cs b/src/Microsoft.AspNet.JsonPatch/Helpers/ExpandoObjectDictionaryExtensions.cs new file mode 100644 index 0000000000..127d70bf36 --- /dev/null +++ b/src/Microsoft.AspNet.JsonPatch/Helpers/ExpandoObjectDictionaryExtensions.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.JsonPatch.Helpers +{ + // Helper methods to allow case-insensitive key search + internal static class ExpandoObjectDictionaryExtensions + { + internal static void SetValueForCaseInsensitiveKey(this IDictionary propertyDictionary, + string key, object value) + { + foreach (KeyValuePair kvp in propertyDictionary) + { + if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) + { + propertyDictionary[kvp.Key] = value; + break; + } + } + } + + internal static void RemoveValueForCaseInsensitiveKey(this IDictionary propertyDictionary, + string key) + { + string realKey = null; + foreach (KeyValuePair kvp in propertyDictionary) + { + if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) + { + realKey = kvp.Key; + break; + } + } + + if (realKey != null) + { + propertyDictionary.Remove(realKey); + } + } + + internal static object GetValueForCaseInsensitiveKey(this IDictionary propertyDictionary, + string key) + { + foreach (KeyValuePair kvp in propertyDictionary) + { + if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) + { + return kvp.Value; + } + } + + throw new ArgumentException(Resources.FormatDictionaryKeyNotFound(key)); + } + + internal static bool ContainsCaseInsensitiveKey(this IDictionary propertyDictionary, + string key) + { + foreach (KeyValuePair kvp in propertyDictionary) + { + if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + return false; + + } + } +} diff --git a/src/Microsoft.AspNet.JsonPatch/Helpers/ObjectTreeAnalyisResult.cs b/src/Microsoft.AspNet.JsonPatch/Helpers/ObjectTreeAnalyisResult.cs new file mode 100644 index 0000000000..91d5ee129d --- /dev/null +++ b/src/Microsoft.AspNet.JsonPatch/Helpers/ObjectTreeAnalyisResult.cs @@ -0,0 +1,210 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.AspNet.JsonPatch.Helpers +{ + internal class ObjectTreeAnalysisResult + { + // either the property is part of the container dictionary, + // or we have a direct reference to a JsonPatchProperty instance + + public bool UseDynamicLogic { get; private set; } + + public bool IsValidPathForAdd { get; private set; } + + public bool IsValidPathForRemove { get; private set; } + + public IDictionary Container { get; private set; } + + public string PropertyPathInParent { get; private set; } + + public JsonPatchProperty JsonPatchProperty { get; private set; } + + public ObjectTreeAnalysisResult( + object objectToSearch, + string propertyPath, + IContractResolver contractResolver) + { + // construct the analysis result. + + // split the propertypath, and if necessary, remove the first + // empty item (that's the case when it starts with a "/") + var propertyPathTree = propertyPath.Split( + new char[] { '/' }, + StringSplitOptions.RemoveEmptyEntries); + + // we've now got a split up property tree "base/property/otherproperty/..." + int lastPosition = 0; + object targetObject = objectToSearch; + for (int i = 0; i < propertyPathTree.Length; i++) + { + lastPosition = i; + + // if the current target object is an ExpandoObject (IDictionary), + // we cannot use the ContractResolver. + var dictionary = targetObject as IDictionary; + if (dictionary != null) + { + // find the value in the dictionary + if (dictionary.ContainsCaseInsensitiveKey(propertyPathTree[i])) + { + var possibleNewTargetObject = dictionary.GetValueForCaseInsensitiveKey(propertyPathTree[i]); + + // unless we're at the last item, we should set the targetobject + // to the new object. If we're at the last item, we need to stop + if (i != propertyPathTree.Length - 1) + { + targetObject = possibleNewTargetObject; + } + } + else + { + break; + } + } + else + { + // if the current part of the path is numeric, this means we're trying + // to get the propertyInfo of a specific object in an array. To allow + // for this, the previous value (targetObject) must be an IEnumerable, and + // the position must exist. + + int numericValue = -1; + if (int.TryParse(propertyPathTree[i], out numericValue)) + { + var element = GetElementAtFromObject(targetObject, numericValue); + if (element != null) + { + targetObject = element; + } + else + { + break; + } + } + else + { + var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType()); + + // does the property exist? + var attemptedProperty = jsonContract + .Properties + .FirstOrDefault(p => string.Equals(p.PropertyName, propertyPathTree[i], StringComparison.OrdinalIgnoreCase)); + + if (attemptedProperty != null) + { + // unless we're at the last item, we should continue searching. + // If we're at the last item, we need to stop + if ((i != propertyPathTree.Length - 1)) + { + targetObject = attemptedProperty.ValueProvider.GetValue(targetObject); + } + } + else + { + // property cannot be found, and we're not working with dynamics. + // Stop, and return invalid path. + break; + } + } + } + } + + if (propertyPathTree.Length - lastPosition != 1) + { + IsValidPathForAdd = false; + IsValidPathForRemove = false; + return; + } + + // two things can happen now. The targetproperty can be an IDictionary - in that + // case, it's valid for add if there's 1 item left in the propertyPathTree. + // + // it can also be a property info. In that case, if there's nothing left in the path + // tree we're at the end, if there's one left we can try and set that. + if (targetObject is IDictionary) + { + UseDynamicLogic = true; + + Container = (IDictionary)targetObject; + IsValidPathForAdd = true; + PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1]; + + // to be able to remove this property, it must exist + IsValidPathForRemove = Container.ContainsCaseInsensitiveKey(PropertyPathInParent); + } + else if (targetObject is IList) + { + System.Diagnostics.Debugger.Launch(); + UseDynamicLogic = false; + + int index; + if (!Int32.TryParse(propertyPathTree[propertyPathTree.Length - 1], out index)) + { + // We only support indexing into a list + IsValidPathForAdd = false; + IsValidPathForRemove = false; + return; + } + + IsValidPathForAdd = true; + IsValidPathForRemove = ((IList)targetObject).Count > index; + PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1]; + } + else + { + UseDynamicLogic = false; + + var property = propertyPathTree[propertyPathTree.Length - 1]; + var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType()); + var attemptedProperty = jsonContract + .Properties + .FirstOrDefault(p => string.Equals(p.PropertyName, property, StringComparison.OrdinalIgnoreCase)); + + if (attemptedProperty == null) + { + IsValidPathForAdd = false; + IsValidPathForRemove = false; + } + else + { + IsValidPathForAdd = true; + IsValidPathForRemove = true; + JsonPatchProperty = new JsonPatchProperty(attemptedProperty, targetObject); + PropertyPathInParent = property; + } + } + } + + private object GetElementAtFromObject(object targetObject, int numericValue) + { + if (numericValue > -1) + { + // Check if the targetobject is an IEnumerable, + // and if the position is valid. + if (targetObject is IEnumerable) + { + var indexable = ((IEnumerable)targetObject).Cast(); + + if (indexable.Count() >= numericValue) + { + return indexable.ElementAt(numericValue); + } + else { return null; } + } + else { return null; } + } + else + { + return null; + } + } + + } +} diff --git a/src/Microsoft.AspNet.JsonPatch/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.JsonPatch/Properties/Resources.Designer.cs index f31d03b7f1..68a888f7eb 100644 --- a/src/Microsoft.AspNet.JsonPatch/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.JsonPatch/Properties/Resources.Designer.cs @@ -58,6 +58,23 @@ namespace Microsoft.AspNet.JsonPatch return string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexForArrayProperty"), p0, p1); } + /// + /// For operation '{0}' on array property at path '{1}', the index is negative. + /// + internal static string NegativeIndexForArrayProperty + { + get { return GetString("NegativeIndexForArrayProperty"); } + } + + /// + /// For operation '{0}' on array property at path '{1}', the index is negative. + /// + internal static string FormatNegativeIndexForArrayProperty(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("NegativeIndexForArrayProperty"), p0, p1); + } + + /// /// The type '{0}' was malformed and could not be parsed. /// @@ -186,6 +203,23 @@ namespace Microsoft.AspNet.JsonPatch return string.Format(CultureInfo.CurrentCulture, GetString("PropertyDoesNotExist"), p0); } + /// + /// The key '{0}' was not found. + /// + internal static string DictionaryKeyNotFound + { + get { return GetString("DictionaryKeyNotFound"); } + } + + /// + /// The key '{0}' was not found. + /// + internal static string FormatDictionaryKeyNotFound(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("DictionaryKeyNotFound"), p0); + } + + /// /// The test operation is not supported. /// diff --git a/src/Microsoft.AspNet.JsonPatch/Resources.resx b/src/Microsoft.AspNet.JsonPatch/Resources.resx index 3718f837f9..47c2291a9d 100644 --- a/src/Microsoft.AspNet.JsonPatch/Resources.resx +++ b/src/Microsoft.AspNet.JsonPatch/Resources.resx @@ -123,6 +123,9 @@ The property at path '{0}' could not be updated. + + The key '{0}' was not found. + For operation '{0}' on array property at path '{1}', the index is larger than the array size. @@ -138,6 +141,9 @@ The value '{0}' is invalid for property at path '{1}'. + + For operation '{0}' on array property at path '{1}', the index is negative. + '{0}' must be of type '{1}'. diff --git a/test/Microsoft.AspNet.JsonPatch.Test/NestedObjectTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/NestedObjectTests.cs index 9aadadb16b..a1f73ec1bf 100644 --- a/test/Microsoft.AspNet.JsonPatch.Test/NestedObjectTests.cs +++ b/test/Microsoft.AspNet.JsonPatch.Test/NestedObjectTests.cs @@ -470,7 +470,9 @@ namespace Microsoft.AspNet.JsonPatch.Test // Act & Assert var exception = Assert.Throws(() => { patchDoc.ApplyTo(doc); }); - Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message); + Assert.Equal( + "For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.", + exception.Message); } [Fact] @@ -497,7 +499,9 @@ namespace Microsoft.AspNet.JsonPatch.Test { deserialized.ApplyTo(doc); }); - Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message); + Assert.Equal( + "For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.", + exception.Message); } [Fact] @@ -523,7 +527,9 @@ namespace Microsoft.AspNet.JsonPatch.Test //Assert - Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage); + Assert.Equal( + "For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.", + logger.ErrorMessage); } [Fact] @@ -1316,7 +1322,9 @@ namespace Microsoft.AspNet.JsonPatch.Test // Act & Assert var exception = Assert.Throws(() => { patchDoc.ApplyTo(doc); }); - Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message); + Assert.Equal( + "Property does not exist at path '/simpledto/integerlist/-1'.", + exception.Message); } [Fact] @@ -1340,7 +1348,9 @@ namespace Microsoft.AspNet.JsonPatch.Test // Act & Assert var exception = Assert.Throws(() => { deserialized.ApplyTo(doc); }); - Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message); + Assert.Equal( + "Property does not exist at path '/simpledto/integerlist/-1'.", + exception.Message); } [Fact] @@ -1366,7 +1376,9 @@ namespace Microsoft.AspNet.JsonPatch.Test // Assert - Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage); + Assert.Equal( + "For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.", + logger.ErrorMessage); } [Fact] diff --git a/test/Microsoft.AspNet.JsonPatch.Test/ObjectAdapterTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/ObjectAdapterTests.cs index f6652305e3..efdf09ea2e 100644 --- a/test/Microsoft.AspNet.JsonPatch.Test/ObjectAdapterTests.cs +++ b/test/Microsoft.AspNet.JsonPatch.Test/ObjectAdapterTests.cs @@ -312,7 +312,9 @@ namespace Microsoft.AspNet.JsonPatch.Test // Act & Assert var exception = Assert.Throws(() => { patchDoc.ApplyTo(doc); }); - Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message); + Assert.Equal( + "For operation 'add' on array property at path '/integerlist/-1', the index is negative.", + exception.Message); } [Fact] @@ -333,7 +335,9 @@ namespace Microsoft.AspNet.JsonPatch.Test // Act & Assert var exception = Assert.Throws(() => { deserialized.ApplyTo(doc); }); - Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message); + Assert.Equal( + "For operation 'add' on array property at path '/integerlist/-1', the index is negative.", + exception.Message); } [Fact] @@ -354,7 +358,9 @@ namespace Microsoft.AspNet.JsonPatch.Test patchDoc.ApplyTo(doc, logger.LogErrorMessage); // Assert - Assert.Equal("Property does not exist at path '/integerlist/-1'.", logger.ErrorMessage); + Assert.Equal( + "For operation 'add' on array property at path '/integerlist/-1', the index is negative.", + logger.ErrorMessage); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs index 231cb6dfa4..0102b229db 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"add\", " + - "\"path\": \"Customer/Orders/2\", " + + "\"path\": \"Orders/2\", " + "\"value\": { \"OrderName\": \"Name2\" }}]"; var request = new HttpRequestMessage { @@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"replace\", " + - "\"path\": \"Customer/Orders/0/OrderName\", " + + "\"path\": \"Orders/0/OrderName\", " + "\"value\": \"ReplacedOrder\" }]"; var request = new HttpRequestMessage { @@ -91,8 +91,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"copy\", " + - "\"path\": \"Customer/Orders/1/OrderName\", " + - "\"from\": \"Customer/Orders/0/OrderName\"}]"; + "\"path\": \"Orders/1/OrderName\", " + + "\"from\": \"Orders/0/OrderName\"}]"; var request = new HttpRequestMessage { Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"), @@ -120,8 +120,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"move\", " + - "\"path\": \"Customer/Orders/1/OrderName\", " + - "\"from\": \"Customer/Orders/0/OrderName\"}]"; + "\"path\": \"Orders/1/OrderName\", " + + "\"from\": \"Orders/0/OrderName\"}]"; var request = new HttpRequestMessage { Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"), @@ -151,7 +151,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"remove\", " + - "\"path\": \"Customer/Orders/1/OrderName\"}]"; + "\"path\": \"Orders/1/OrderName\"}]"; var request = new HttpRequestMessage { Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"), @@ -179,13 +179,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"add\", "+ - "\"path\": \"Customer/Orders/2\", " + + "\"path\": \"Orders/2\", " + "\"value\": { \"OrderName\": \"Name2\" }}, " + "{\"op\": \"copy\", " + - "\"from\": \"Customer/Orders/2\", " + - "\"path\": \"Customer/Orders/3\" }, " + + "\"from\": \"Orders/2\", " + + "\"path\": \"Orders/3\" }, " + "{\"op\": \"replace\", " + - "\"path\": \"Customer/Orders/2/OrderName\", " + + "\"path\": \"Orders/2/OrderName\", " + "\"value\": \"ReplacedName\" }]"; var request = new HttpRequestMessage { @@ -213,46 +213,46 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests new object[] { "http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch", "[{ \"op\": \"add\", " + - "\"path\": \"Customer/Orders/5\", " + + "\"path\": \"Orders/5\", " + "\"value\": { \"OrderName\": \"Name5\" }}]", "{\"Patch.Customer\":[\"For operation 'add' on array property at path " + - "'Customer/Orders/5', the index is larger than the array size.\"]}" + "'Orders/5', the index is larger than the array size.\"]}" }, new object[] { "http://localhost/jsonpatch/JsonPatchWithModelState", "[{ \"op\": \"add\", " + - "\"path\": \"Customer/Orders/5\", " + + "\"path\": \"Orders/5\", " + "\"value\": { \"OrderName\": \"Name5\" }}]", "{\"Customer\":[\"For operation 'add' on array property at path " + - "'Customer/Orders/5', the index is larger than the array size.\"]}" + "'Orders/5', the index is larger than the array size.\"]}" }, new object[] { "http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch", "[{ \"op\": \"add\", " + - "\"path\": \"Customer/Orders/2\", " + + "\"path\": \"Orders/2\", " + "\"value\": { \"OrderName\": \"Name2\" }}, " + "{\"op\": \"copy\", " + - "\"from\": \"Customer/Orders/4\", " + - "\"path\": \"Customer/Orders/3\" }, " + + "\"from\": \"Orders/4\", " + + "\"path\": \"Orders/3\" }, " + "{\"op\": \"replace\", " + - "\"path\": \"Customer/Orders/2/OrderName\", " + + "\"path\": \"Orders/2/OrderName\", " + "\"value\": \"ReplacedName\" }]", "{\"Patch.Customer\":[\"For operation 'copy' on array property at path " + - "'Customer/Orders/4', the index is larger than the array size.\"]}" + "'Orders/4', the index is larger than the array size.\"]}" }, new object[] { "http://localhost/jsonpatch/JsonPatchWithModelState", "[{ \"op\": \"add\", " + - "\"path\": \"Customer/Orders/2\", " + + "\"path\": \"Orders/2\", " + "\"value\": { \"OrderName\": \"Name2\" }}, " + "{\"op\": \"copy\", " + - "\"from\": \"Customer/Orders/4\", " + - "\"path\": \"Customer/Orders/3\" }, " + + "\"from\": \"Orders/4\", " + + "\"path\": \"Orders/3\" }, " + "{\"op\": \"replace\", " + - "\"path\": \"Customer/Orders/2/OrderName\", " + + "\"path\": \"Orders/2/OrderName\", " + "\"value\": \"ReplacedName\" }]", "{\"Customer\":[\"For operation 'copy' on array property at path " + - "'Customer/Orders/4', the index is larger than the array size.\"]}" + "'Orders/4', the index is larger than the array size.\"]}" } }; } @@ -288,7 +288,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "{ \"op\": \"add\", " + - "\"path\": \"Customer/Orders/2\", " + + "\"path\": \"Orders/2\", " + "\"value\": { \"OrderName\": \"Name2\" }}"; var request = new HttpRequestMessage { @@ -313,7 +313,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"add\", " + - "\"path\": \"Customer/Orders/2\", " + + "\"path\": \"Orders/2\", " + "\"value\": { \"OrderType\": \"Type2\" }}]"; var request = new HttpRequestMessage { @@ -339,7 +339,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var input = "[{ \"op\": \"add\", " + - "\"path\": \"Product/ProductCategory\", " + + "\"path\": \"ProductCategory\", " + "\"value\": { \"CategoryName\": \"Name2\" }}]"; var request = new HttpRequestMessage { diff --git a/test/Microsoft.AspNet.Mvc.Test/JsonPatchExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Test/JsonPatchExtensionsTest.cs index c521741e7b..83becebbef 100644 --- a/test/Microsoft.AspNet.Mvc.Test/JsonPatchExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/JsonPatchExtensionsTest.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc // Assert var error = Assert.Single(modelState["Customer"].Errors); - Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage); + Assert.Equal("The property at path 'Customer/CustomerId' could not be added.", error.ErrorMessage); } [Fact] @@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc // Assert var error = Assert.Single(modelState["jsonpatch.Customer"].Errors); - Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage); + Assert.Equal("The property at path 'Customer/CustomerId' could not be added.", error.ErrorMessage); } public class Customer