Skip to content

Commit

Permalink
Add Test Operation (#114)
Browse files Browse the repository at this point in the history
Addresses #1
  • Loading branch information
jbagga authored Oct 19, 2017
1 parent e245de2 commit 8eefe0f
Show file tree
Hide file tree
Showing 21 changed files with 895 additions and 189 deletions.
95 changes: 94 additions & 1 deletion src/Microsoft.AspNetCore.JsonPatch/Adapters/IObjectAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
172 changes: 32 additions & 140 deletions src/Microsoft.AspNetCore.JsonPatch/Adapters/ObjectAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Microsoft.AspNetCore.JsonPatch.Adapters
{
/// <inheritdoc />
public class ObjectAdapter : IObjectAdapter
public class ObjectAdapter : IObjectAdapterWithTest
{
/// <summary>
/// Initializes a new instance of <see cref="ObjectAdapter"/>.
Expand All @@ -34,66 +34,6 @@ public ObjectAdapter(
/// </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)
Expand Down Expand Up @@ -153,29 +93,6 @@ private void Add(
}
}

/// <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)
Expand All @@ -202,20 +119,6 @@ public void Move(Operation operation, object objectToApplyTo)
}
}

/// <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)
Expand Down Expand Up @@ -259,26 +162,6 @@ private void Remove(string path, object objectToApplyTo, Operation operationToRe
}
}

/// <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)
Expand Down Expand Up @@ -310,28 +193,6 @@ public void Replace(Operation operation, object objectToApplyTo)
}
}

/// <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)
Expand Down Expand Up @@ -365,6 +226,37 @@ public void Copy(Operation operation, object objectToApplyTo)
}
}

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,
Expand Down
Loading

0 comments on commit 8eefe0f

Please sign in to comment.