parent
e245de21fc
commit
8eefe0fdc2
|
|
@ -8,12 +8,105 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
/// <summary>
|
||||
/// Defines the operations that can be performed on a JSON patch document.
|
||||
/// </summary>
|
||||
public interface IObjectAdapter
|
||||
public interface IObjectAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Using the "add" operation a new value is inserted into the root of the target
|
||||
/// document, into the target array at the specified valid index, or to a target object at
|
||||
/// the specified location.
|
||||
///
|
||||
/// When adding to arrays, the specified index MUST NOT be greater than the number of elements in the array.
|
||||
/// To append the value to the array, the index of "-" character is used (see [RFC6901]).
|
||||
///
|
||||
/// When adding to an object, if an object member does not already exist, a new member is added to the object at the
|
||||
/// specified location or if an object member does exist, that member's value is replaced.
|
||||
///
|
||||
/// The operation object MUST contain a "value" member whose content
|
||||
/// specifies the value to be added.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-4
|
||||
/// </summary>
|
||||
/// <param name="operation">The add operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Add(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "copy" operation, a value is copied from a specified location to the
|
||||
/// target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which references the location in the
|
||||
/// target document to copy the value from.
|
||||
///
|
||||
/// The "from" location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
|
||||
/// </summary>
|
||||
/// <param name="operation">The copy operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Copy(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "move" operation the value at a specified location is removed and
|
||||
/// added to the target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which references the location in the
|
||||
/// target document to move the value from.
|
||||
///
|
||||
/// The "from" location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
///
|
||||
/// A location cannot be moved into one of its children.
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
|
||||
/// </summary>
|
||||
/// <param name="operation">The move operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Move(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "remove" operation the value at the target location is removed.
|
||||
///
|
||||
/// The target location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
///
|
||||
/// If removing an element from an array, any elements above the
|
||||
/// specified index are shifted one position to the left.
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
|
||||
/// </summary>
|
||||
/// <param name="operation">The remove operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Remove(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "replace" operation he value at the target location is replaced
|
||||
/// with a new value. The operation object MUST contain a "value" member
|
||||
/// which specifies the replacement value.
|
||||
///
|
||||
/// The target location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
|
||||
/// </summary>
|
||||
/// <param name="operation">The replace operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Replace(Operation operation, object objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// 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 Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the operations that can be performed on a JSON patch document, including "test".
|
||||
/// </summary>
|
||||
public interface IObjectAdapterWithTest : IObjectAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Using the "test" operation a value at the target location is compared for
|
||||
/// equality to a specified value.
|
||||
///
|
||||
/// The operation object MUST contain a "value" member that specifies
|
||||
/// value to be compared to the target location's value.
|
||||
///
|
||||
/// The target location MUST be equal to the "value" value for the
|
||||
/// operation to be considered successful.
|
||||
///
|
||||
/// For example:
|
||||
/// { "op": "test", "path": "/a/b/c", "value": "foo" }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
|
||||
/// </summary>
|
||||
/// <param name="operation">The test operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Test(Operation operation, object objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ using Newtonsoft.Json.Serialization;
|
|||
namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ObjectAdapter : IObjectAdapter
|
||||
public class ObjectAdapter : IObjectAdapterWithTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ObjectAdapter"/>.
|
||||
|
|
@ -34,66 +34,6 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
/// </summary>
|
||||
public Action<JsonPatchError> LogErrorAction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The "add" operation performs one of the following functions,
|
||||
/// depending upon what the target location references:
|
||||
///
|
||||
/// o If the target location specifies an array index, a new value is
|
||||
/// inserted into the array at the specified index.
|
||||
///
|
||||
/// o If the target location specifies an object member that does not
|
||||
/// already exist, a new member is added to the object.
|
||||
///
|
||||
/// o If the target location specifies an object member that does exist,
|
||||
/// that member's value is replaced.
|
||||
///
|
||||
/// The operation object MUST contain a "value" member whose content
|
||||
/// specifies the value to be added.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
///
|
||||
/// When the operation is applied, the target location MUST reference one
|
||||
/// of:
|
||||
///
|
||||
/// o The root of the target document - whereupon the specified value
|
||||
/// becomes the entire content of the target document.
|
||||
///
|
||||
/// o A member to add to an existing object - whereupon the supplied
|
||||
/// value is added to that object at the indicated location. If the
|
||||
/// member already exists, it is replaced by the specified value.
|
||||
///
|
||||
/// o An element to add to an existing array - whereupon the supplied
|
||||
/// value is added to the array at the indicated location. Any
|
||||
/// elements at or above the specified index are shifted one position
|
||||
/// to the right. The specified index MUST NOT be greater than the
|
||||
/// number of elements in the array. If the "-" character is used to
|
||||
/// index the end of the array (see [RFC6901]), this has the effect of
|
||||
/// appending the value to the array.
|
||||
///
|
||||
/// Because this operation is designed to add to existing objects and
|
||||
/// arrays, its target location will often not exist. Although the
|
||||
/// pointer's error handling algorithm will thus be invoked, this
|
||||
/// specification defines the error handling behavior for "add" pointers
|
||||
/// to ignore that error and add the value as specified.
|
||||
///
|
||||
/// However, the object itself or an array containing it does need to
|
||||
/// exist, and it remains an error for that not to be the case. For
|
||||
/// example, an "add" with a target location of "/a/b" starting with this
|
||||
/// document:
|
||||
///
|
||||
/// { "a": { "foo": 1 } }
|
||||
///
|
||||
/// is not an error, because "a" exists, and "b" will be added to its
|
||||
/// value. It is an error in this document:
|
||||
///
|
||||
/// { "q": { "bar": 2 } }
|
||||
///
|
||||
/// because "a" does not exist.
|
||||
/// </summary>
|
||||
/// <param name="operation">The add operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
public void Add(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
|
|
@ -153,29 +93,6 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "move" operation removes the value at a specified location and
|
||||
/// adds it to the target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which is a string
|
||||
/// containing a JSON Pointer value that references the location in the
|
||||
/// target document to move the value from.
|
||||
///
|
||||
/// The "from" location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
///
|
||||
/// This operation is functionally identical to a "remove" operation on
|
||||
/// the "from" location, followed immediately by an "add" operation at
|
||||
/// the target location with the value that was just removed.
|
||||
///
|
||||
/// The "from" location MUST NOT be a proper prefix of the "path"
|
||||
/// location; i.e., a location cannot be moved into one of its children.
|
||||
/// </summary>
|
||||
/// <param name="operation">The move operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
public void Move(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
|
|
@ -202,20 +119,6 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "remove" operation removes the value at the target location.
|
||||
///
|
||||
/// The target location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
///
|
||||
/// If removing an element from an array, any elements above the
|
||||
/// specified index are shifted one position to the left.
|
||||
/// </summary>
|
||||
/// <param name="operation">The remove operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
public void Remove(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
|
|
@ -259,26 +162,6 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "replace" operation replaces the value at the target location
|
||||
/// with a new value. The operation object MUST contain a "value" member
|
||||
/// whose content specifies the replacement value.
|
||||
///
|
||||
/// The target location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
///
|
||||
/// This operation is functionally identical to a "remove" operation for
|
||||
/// a value, followed immediately by an "add" operation at the same
|
||||
/// location with the replacement value.
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call remove + add
|
||||
/// for performance reasons (multiple checks of same requirements).
|
||||
/// </summary>
|
||||
/// <param name="operation">The replace operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
public void Replace(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
|
|
@ -310,28 +193,6 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "copy" operation copies the value at a specified location to the
|
||||
/// target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which is a string
|
||||
/// containing a JSON Pointer value that references the location in the
|
||||
/// target document to copy the value from.
|
||||
///
|
||||
/// The "from" location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
///
|
||||
/// This operation is functionally identical to an "add" operation at the
|
||||
/// target location using the value specified in the "from" member.
|
||||
///
|
||||
/// Note: even though it's the same functionally, we do not call add with
|
||||
/// the value specified in from for performance reasons (multiple checks of same requirements).
|
||||
/// </summary>
|
||||
/// <param name="operation">The copy operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
public void Copy(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
|
|
@ -365,6 +226,37 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
}
|
||||
}
|
||||
|
||||
public void Test(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
var parsedPath = new ParsedPath(operation.path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
var target = objectToApplyTo;
|
||||
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
|
||||
{
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!adapter.TryTest(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
|
||||
{
|
||||
var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetValue(
|
||||
string fromLocation,
|
||||
object objectToGetValueFrom,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
|
|
@ -126,6 +128,55 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
|
||||
var key = contract.DictionaryKeyResolver(segment);
|
||||
var dictionary = (IDictionary<TKey, TValue>)target;
|
||||
|
||||
if (!TryConvertKey(key, out var convertedKey, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for test to be successful
|
||||
if (!dictionary.ContainsKey(convertedKey))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = dictionary[convertedKey];
|
||||
|
||||
// The target segment does not have an assigned value to compare the test value with
|
||||
if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
|
||||
{
|
||||
errorMessage = Resources.FormatValueForTargetSegmentCannotBeNullOrEmpty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ using System.Collections.Generic;
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.CSharp.RuntimeBinder;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using CSharpBinder = Microsoft.CSharp.RuntimeBinder;
|
||||
|
||||
|
|
@ -103,6 +105,36 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, property.GetType(), out var convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(property), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueNotEqualToTestValue(property, value, segment);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
|
|
|
|||
|
|
@ -40,5 +40,12 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
|
|
@ -150,6 +152,43 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = list[positionInfo.Index];
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueAtListPositionNotEqualToTestValue(currentValue, value, positionInfo.Index);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Microsoft.AspNetCore.JsonPatch.Adapters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
|
|
@ -30,7 +27,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
return false;
|
||||
}
|
||||
|
||||
adapter = SelectAdapater(target);
|
||||
adapter = SelectAdapter(target);
|
||||
|
||||
// Traverse until the penultimate segment to get the target object and adapter
|
||||
for (var i = 0; i < _path.Segments.Count - 1; i++)
|
||||
|
|
@ -42,14 +39,14 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
}
|
||||
|
||||
target = next;
|
||||
adapter = SelectAdapater(target);
|
||||
adapter = SelectAdapter(target);
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private IAdapter SelectAdapater(object targetObject)
|
||||
private IAdapter SelectAdapter(object targetObject)
|
||||
{
|
||||
var jsonContract = _contractResolver.ResolveContract(targetObject.GetType());
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
|
|
@ -132,6 +134,43 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver
|
||||
contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Readable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotReadProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = jsonProperty.ValueProvider.GetValue(target);
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,24 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value. Will result in, for example,
|
||||
/// { "op": "test", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument Test(string path, object value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation("test", PathHelpers.NormalizePath(path), null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes value at specified location and add it to the target location. Will result in, for example:
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
|
|
|
|||
|
|
@ -247,6 +247,77 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value. Will result in, for example,
|
||||
/// { "op": "test", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Test<TProp>(Expression<Func<TModel, TProp>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"test",
|
||||
GetPath(path, null),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value in a list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Test<TProp>(Expression<Func<TModel, IList<TProp>>> path,
|
||||
TProp value, int position)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"test",
|
||||
GetPath(path, position.ToString()),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value at end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Test<TProp>(Expression<Func<TModel, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"test",
|
||||
GetPath(path, "-"),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes value at specified location and add it to the target location. Will result in, for example:
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
|
|
|
|||
|
|
@ -58,7 +58,15 @@ namespace Microsoft.AspNetCore.JsonPatch.Operations
|
|||
adapter.Copy(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Test:
|
||||
throw new NotSupportedException(Resources.TestOperationNotSupported);
|
||||
if (adapter is IObjectAdapterWithTest adapterWithTest)
|
||||
{
|
||||
adapterWithTest.Test(this, objectToApplyTo);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(Resources.TestOperationNotSupported);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,15 @@ namespace Microsoft.AspNetCore.JsonPatch.Operations
|
|||
adapter.Copy(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Test:
|
||||
throw new JsonPatchException(new JsonPatchError(objectToApplyTo, this, Resources.TestOperationNotSupported));
|
||||
if (adapter is IObjectAdapterWithTest adapterWithTest)
|
||||
{
|
||||
adapterWithTest.Test(this, objectToApplyTo);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException(new JsonPatchError(objectToApplyTo, this, Resources.TestOperationNotSupported));
|
||||
}
|
||||
case OperationType.Invalid:
|
||||
throw new JsonPatchException(
|
||||
Resources.FormatInvalidJsonPatchOperation(op), innerException: null);
|
||||
|
|
@ -82,6 +90,5 @@ namespace Microsoft.AspNetCore.JsonPatch.Operations
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
=> string.Format(CultureInfo.CurrentCulture, GetString("CannotUpdateProperty"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The expression '{0}' is not supported.
|
||||
/// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
|
||||
/// </summary>
|
||||
internal static string ExpressionTypeNotSupported
|
||||
{
|
||||
|
|
@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The expression '{0}' is not supported.
|
||||
/// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
|
||||
/// </summary>
|
||||
internal static string FormatExpressionTypeNotSupported(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ExpressionTypeNotSupported"), p0);
|
||||
|
|
@ -276,6 +276,48 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
internal static string FormatTestOperationNotSupported()
|
||||
=> GetString("TestOperationNotSupported");
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string ValueAtListPositionNotEqualToTestValue
|
||||
{
|
||||
get => GetString("ValueAtListPositionNotEqualToTestValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatValueAtListPositionNotEqualToTestValue(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ValueAtListPositionNotEqualToTestValue"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// The value at '{0}' cannot be null or empty to perform the test operation.
|
||||
/// </summary>
|
||||
internal static string ValueForTargetSegmentCannotBeNullOrEmpty
|
||||
{
|
||||
get => GetString("ValueForTargetSegmentCannotBeNullOrEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value at '{0}' cannot be null or empty to perform the test operation.
|
||||
/// </summary>
|
||||
internal static string FormatValueForTargetSegmentCannotBeNullOrEmpty(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ValueForTargetSegmentCannotBeNullOrEmpty"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string ValueNotEqualToTestValue
|
||||
{
|
||||
get => GetString("ValueNotEqualToTestValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatValueNotEqualToTestValue(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ValueNotEqualToTestValue"), p0, p1, p2);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -174,4 +174,13 @@
|
|||
<data name="TestOperationNotSupported" xml:space="preserve">
|
||||
<value>The test operation is not supported.</value>
|
||||
</data>
|
||||
<data name="ValueAtListPositionNotEqualToTestValue" xml:space="preserve">
|
||||
<value>The current value '{0}' at position '{2}' is not equal to the test value '{1}'.</value>
|
||||
</data>
|
||||
<data name="ValueForTargetSegmentCannotBeNullOrEmpty" xml:space="preserve">
|
||||
<value>The value at '{0}' cannot be null or empty to perform the test operation.</value>
|
||||
</data>
|
||||
<data name="ValueNotEqualToTestValue" xml:space="preserve">
|
||||
<value>The current value '{0}' at path '{2}' is not equal to the test value '{1}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -274,5 +274,42 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Empty(dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_DoesNotThrowException_IfTestIsSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var key = "Name";
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
dictionary[key] = "James";
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var testStatus = dictionaryAdapter.TryTest(dictionary, key, resolver, "James", out var message);
|
||||
|
||||
//Assert
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var key = "Name";
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
dictionary[key] = "James";
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
var expectedErrorMessage = "The current value 'James' at path 'Name' is not equal to the test value 'John'.";
|
||||
|
||||
// Act
|
||||
var testStatus = dictionaryAdapter.TryTest(dictionary, key, resolver, "John", out var errorMessage);
|
||||
|
||||
//Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -227,5 +227,42 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
Assert.False(removeStatus);
|
||||
Assert.Equal($"The target location specified by path segment '{segment}' was not found.", removeErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_DoesNotThrowException_IfTestSuccessful()
|
||||
{
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.NewProperty = "Joana";
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(target, segment, resolver, "Joana", out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joana", target.NewProperty);
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.NewProperty = "Joana";
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
var expectedErrorMessage = $"The current value 'Joana' at path '{segment}' is not equal to the test value 'John'.";
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(target, segment, resolver, "John", out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -459,5 +459,55 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_DoesNotThrowException_IfTestIsSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var testStatus = listAdapter.TryTest(targetObject, "0", resolver.Object, "10", out var message);
|
||||
|
||||
//Assert
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
var expectedErrorMessage = "The current value '20' at position '1' is not equal to the test value '10'.";
|
||||
|
||||
// Act
|
||||
var testStatus = listAdapter.TryTest(targetObject, "1", resolver.Object, "10", out var errorMessage);
|
||||
|
||||
//Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_ThrowsJsonPatchException_IfListPositionOutOfBounds()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
var expectedErrorMessage = "The index value provided by path segment '2' is out of bounds of the array size.";
|
||||
|
||||
// Act
|
||||
var testStatus = listAdapter.TryTest(targetObject, "2", resolver.Object, "10", out var errorMessage);
|
||||
|
||||
//Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
// 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 Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class PocoAdapterTest
|
||||
{
|
||||
[Fact]
|
||||
public void TryAdd_ReplacesExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var addStatus = adapter.TryAdd(model, "Name", contractResolver, "John", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("John", model.Name);
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var addStatus = adapter.TryAdd(model, "LastName", contractResolver, "Smith", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var getStatus = adapter.TryGet(model, "Name", contractResolver, out var value, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joana", value);
|
||||
Assert.True(getStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var getStatus = adapter.TryGet(model, "LastName", contractResolver, out var value, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Null(value);
|
||||
Assert.False(getStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_SetsPropertyToNull()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var removeStatus = adapter.TryRemove(model, "Name", contractResolver, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Null(model.Name);
|
||||
Assert.True(removeStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var removeStatus = adapter.TryRemove(model, "LastName", contractResolver, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(removeStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_OverwritesExistingValue()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var replaceStatus = adapter.TryReplace(model, "Name", contractResolver, "John", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("John", model.Name);
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ThrowsJsonPatchException_IfNewValueIsInvalidType()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Age = 25
|
||||
};
|
||||
|
||||
var expectedErrorMessage = "The value 'TwentySix' is invalid for target location.";
|
||||
|
||||
// Act
|
||||
var replaceStatus = adapter.TryReplace(model, "Age", contractResolver, "TwentySix", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(25, model.Age);
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var replaceStatus = adapter.TryReplace(model, "LastName", contractResolver, "Smith", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joana", model.Name);
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_DoesNotThrowException_IfTestSuccessful()
|
||||
{
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(model, "Name", contractResolver, "Joana", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joana", model.Name);
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The current value 'Joana' at path 'Name' is not equal to the test value 'John'.";
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(model, "Name", contractResolver, "John", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
private class Customer
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class DynamicObjectIntegrationTests
|
||||
public class DynamicObjectTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddResults_ShouldReplaceExistingPropertyValue_InNestedDynamicObject()
|
||||
|
|
@ -259,5 +259,45 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 5, 6 }, dynamicTestObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPropertyValue_FromListToNonList_InNestedTypedObject_InDynamicObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.Nested = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Test("Nested/IntegerList/1", 2);
|
||||
|
||||
// Act & Assert
|
||||
patchDoc.ApplyTo(dynamicTestObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPropertyValue_FromListToNonList_InNestedTypedObject_InDynamicObject_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.Nested = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Test("Nested/IntegerList/0", 2);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDoc.ApplyTo(dynamicTestObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The current value '1' at position '0' is not equal to the test value '2'.", exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,42 +9,6 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
{
|
||||
public class JsonPatchDocumentTest
|
||||
{
|
||||
[Fact]
|
||||
public void TestOperation_ThrowsException_CallsIntoLogErrorAction()
|
||||
{
|
||||
// Arrange
|
||||
var serialized = "[{\"value\":\"John\",\"path\":\"/Name\",\"op\":\"test\"}]";
|
||||
var jsonPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument<Customer>>(serialized);
|
||||
var model = new Customer();
|
||||
var expectedErrorMessage = "The test operation is not supported.";
|
||||
string actualErrorMessage = null;
|
||||
|
||||
// Act
|
||||
jsonPatchDocument.ApplyTo(model, (jsonPatchError) =>
|
||||
{
|
||||
actualErrorMessage = jsonPatchError.ErrorMessage;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedErrorMessage, actualErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestOperation_NoLogErrorAction_ThrowsJsonPatchException()
|
||||
{
|
||||
// Arrange
|
||||
var serialized = "[{\"value\":\"John\",\"path\":\"/Name\",\"op\":\"test\"}]";
|
||||
var jsonPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument<Customer>>(serialized);
|
||||
var model = new Customer();
|
||||
var expectedErrorMessage = "The test operation is not supported.";
|
||||
|
||||
// Act
|
||||
var jsonPatchException = Assert.Throws<JsonPatchException>(() => jsonPatchDocument.ApplyTo(model));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedErrorMessage, jsonPatchException.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidOperation_ThrowsException_CallsIntoLogErrorAction()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue