Skip to content

Commit 8eefe0f

Browse files
authored
Add Test Operation (#114)
Addresses #1
1 parent e245de2 commit 8eefe0f

21 files changed

+895
-189
lines changed

src/Microsoft.AspNetCore.JsonPatch/Adapters/IObjectAdapter.cs

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,105 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
88
/// <summary>
99
/// Defines the operations that can be performed on a JSON patch document.
1010
/// </summary>
11-
public interface IObjectAdapter
11+
public interface IObjectAdapter
1212
{
13+
/// <summary>
14+
/// Using the "add" operation a new value is inserted into the root of the target
15+
/// document, into the target array at the specified valid index, or to a target object at
16+
/// the specified location.
17+
///
18+
/// When adding to arrays, the specified index MUST NOT be greater than the number of elements in the array.
19+
/// To append the value to the array, the index of "-" character is used (see [RFC6901]).
20+
///
21+
/// When adding to an object, if an object member does not already exist, a new member is added to the object at the
22+
/// specified location or if an object member does exist, that member's value is replaced.
23+
///
24+
/// The operation object MUST contain a "value" member whose content
25+
/// specifies the value to be added.
26+
///
27+
/// For example:
28+
///
29+
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
30+
///
31+
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-4
32+
/// </summary>
33+
/// <param name="operation">The add operation.</param>
34+
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
1335
void Add(Operation operation, object objectToApplyTo);
36+
37+
/// <summary>
38+
/// Using the "copy" operation, a value is copied from a specified location to the
39+
/// target location.
40+
///
41+
/// The operation object MUST contain a "from" member, which references the location in the
42+
/// target document to copy the value from.
43+
///
44+
/// The "from" location MUST exist for the operation to be successful.
45+
///
46+
/// For example:
47+
///
48+
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
49+
///
50+
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
51+
/// </summary>
52+
/// <param name="operation">The copy operation.</param>
53+
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
1454
void Copy(Operation operation, object objectToApplyTo);
55+
56+
/// <summary>
57+
/// Using the "move" operation the value at a specified location is removed and
58+
/// added to the target location.
59+
///
60+
/// The operation object MUST contain a "from" member, which references the location in the
61+
/// target document to move the value from.
62+
///
63+
/// The "from" location MUST exist for the operation to be successful.
64+
///
65+
/// For example:
66+
///
67+
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
68+
///
69+
/// A location cannot be moved into one of its children.
70+
///
71+
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
72+
/// </summary>
73+
/// <param name="operation">The move operation.</param>
74+
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
1575
void Move(Operation operation, object objectToApplyTo);
76+
77+
/// <summary>
78+
/// Using the "remove" operation the value at the target location is removed.
79+
///
80+
/// The target location MUST exist for the operation to be successful.
81+
///
82+
/// For example:
83+
///
84+
/// { "op": "remove", "path": "/a/b/c" }
85+
///
86+
/// If removing an element from an array, any elements above the
87+
/// specified index are shifted one position to the left.
88+
///
89+
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
90+
/// </summary>
91+
/// <param name="operation">The remove operation.</param>
92+
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
1693
void Remove(Operation operation, object objectToApplyTo);
94+
95+
/// <summary>
96+
/// Using the "replace" operation he value at the target location is replaced
97+
/// with a new value. The operation object MUST contain a "value" member
98+
/// which specifies the replacement value.
99+
///
100+
/// The target location MUST exist for the operation to be successful.
101+
///
102+
/// For example:
103+
///
104+
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
105+
///
106+
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
107+
/// </summary>
108+
/// <param name="operation">The replace operation.</param>
109+
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
17110
void Replace(Operation operation, object objectToApplyTo);
18111
}
19112
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.JsonPatch.Operations;
5+
6+
namespace Microsoft.AspNetCore.JsonPatch.Adapters
7+
{
8+
/// <summary>
9+
/// Defines the operations that can be performed on a JSON patch document, including "test".
10+
/// </summary>
11+
public interface IObjectAdapterWithTest : IObjectAdapter
12+
{
13+
/// <summary>
14+
/// Using the "test" operation a value at the target location is compared for
15+
/// equality to a specified value.
16+
///
17+
/// The operation object MUST contain a "value" member that specifies
18+
/// value to be compared to the target location's value.
19+
///
20+
/// The target location MUST be equal to the "value" value for the
21+
/// operation to be considered successful.
22+
///
23+
/// For example:
24+
/// { "op": "test", "path": "/a/b/c", "value": "foo" }
25+
///
26+
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
27+
/// </summary>
28+
/// <param name="operation">The test operation.</param>
29+
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
30+
void Test(Operation operation, object objectToApplyTo);
31+
}
32+
}

src/Microsoft.AspNetCore.JsonPatch/Adapters/ObjectAdapter.cs

Lines changed: 32 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace Microsoft.AspNetCore.JsonPatch.Adapters
1010
{
1111
/// <inheritdoc />
12-
public class ObjectAdapter : IObjectAdapter
12+
public class ObjectAdapter : IObjectAdapterWithTest
1313
{
1414
/// <summary>
1515
/// Initializes a new instance of <see cref="ObjectAdapter"/>.
@@ -34,66 +34,6 @@ public ObjectAdapter(
3434
/// </summary>
3535
public Action<JsonPatchError> LogErrorAction { get; }
3636

37-
/// <summary>
38-
/// The "add" operation performs one of the following functions,
39-
/// depending upon what the target location references:
40-
///
41-
/// o If the target location specifies an array index, a new value is
42-
/// inserted into the array at the specified index.
43-
///
44-
/// o If the target location specifies an object member that does not
45-
/// already exist, a new member is added to the object.
46-
///
47-
/// o If the target location specifies an object member that does exist,
48-
/// that member's value is replaced.
49-
///
50-
/// The operation object MUST contain a "value" member whose content
51-
/// specifies the value to be added.
52-
///
53-
/// For example:
54-
///
55-
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
56-
///
57-
/// When the operation is applied, the target location MUST reference one
58-
/// of:
59-
///
60-
/// o The root of the target document - whereupon the specified value
61-
/// becomes the entire content of the target document.
62-
///
63-
/// o A member to add to an existing object - whereupon the supplied
64-
/// value is added to that object at the indicated location. If the
65-
/// member already exists, it is replaced by the specified value.
66-
///
67-
/// o An element to add to an existing array - whereupon the supplied
68-
/// value is added to the array at the indicated location. Any
69-
/// elements at or above the specified index are shifted one position
70-
/// to the right. The specified index MUST NOT be greater than the
71-
/// number of elements in the array. If the "-" character is used to
72-
/// index the end of the array (see [RFC6901]), this has the effect of
73-
/// appending the value to the array.
74-
///
75-
/// Because this operation is designed to add to existing objects and
76-
/// arrays, its target location will often not exist. Although the
77-
/// pointer's error handling algorithm will thus be invoked, this
78-
/// specification defines the error handling behavior for "add" pointers
79-
/// to ignore that error and add the value as specified.
80-
///
81-
/// However, the object itself or an array containing it does need to
82-
/// exist, and it remains an error for that not to be the case. For
83-
/// example, an "add" with a target location of "/a/b" starting with this
84-
/// document:
85-
///
86-
/// { "a": { "foo": 1 } }
87-
///
88-
/// is not an error, because "a" exists, and "b" will be added to its
89-
/// value. It is an error in this document:
90-
///
91-
/// { "q": { "bar": 2 } }
92-
///
93-
/// because "a" does not exist.
94-
/// </summary>
95-
/// <param name="operation">The add operation.</param>
96-
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
9737
public void Add(Operation operation, object objectToApplyTo)
9838
{
9939
if (operation == null)
@@ -153,29 +93,6 @@ private void Add(
15393
}
15494
}
15595

156-
/// <summary>
157-
/// The "move" operation removes the value at a specified location and
158-
/// adds it to the target location.
159-
///
160-
/// The operation object MUST contain a "from" member, which is a string
161-
/// containing a JSON Pointer value that references the location in the
162-
/// target document to move the value from.
163-
///
164-
/// The "from" location MUST exist for the operation to be successful.
165-
///
166-
/// For example:
167-
///
168-
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
169-
///
170-
/// This operation is functionally identical to a "remove" operation on
171-
/// the "from" location, followed immediately by an "add" operation at
172-
/// the target location with the value that was just removed.
173-
///
174-
/// The "from" location MUST NOT be a proper prefix of the "path"
175-
/// location; i.e., a location cannot be moved into one of its children.
176-
/// </summary>
177-
/// <param name="operation">The move operation.</param>
178-
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
17996
public void Move(Operation operation, object objectToApplyTo)
18097
{
18198
if (operation == null)
@@ -202,20 +119,6 @@ public void Move(Operation operation, object objectToApplyTo)
202119
}
203120
}
204121

205-
/// <summary>
206-
/// The "remove" operation removes the value at the target location.
207-
///
208-
/// The target location MUST exist for the operation to be successful.
209-
///
210-
/// For example:
211-
///
212-
/// { "op": "remove", "path": "/a/b/c" }
213-
///
214-
/// If removing an element from an array, any elements above the
215-
/// specified index are shifted one position to the left.
216-
/// </summary>
217-
/// <param name="operation">The remove operation.</param>
218-
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
219122
public void Remove(Operation operation, object objectToApplyTo)
220123
{
221124
if (operation == null)
@@ -259,26 +162,6 @@ private void Remove(string path, object objectToApplyTo, Operation operationToRe
259162
}
260163
}
261164

262-
/// <summary>
263-
/// The "replace" operation replaces the value at the target location
264-
/// with a new value. The operation object MUST contain a "value" member
265-
/// whose content specifies the replacement value.
266-
///
267-
/// The target location MUST exist for the operation to be successful.
268-
///
269-
/// For example:
270-
///
271-
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
272-
///
273-
/// This operation is functionally identical to a "remove" operation for
274-
/// a value, followed immediately by an "add" operation at the same
275-
/// location with the replacement value.
276-
///
277-
/// Note: even though it's the same functionally, we do not call remove + add
278-
/// for performance reasons (multiple checks of same requirements).
279-
/// </summary>
280-
/// <param name="operation">The replace operation.</param>
281-
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
282165
public void Replace(Operation operation, object objectToApplyTo)
283166
{
284167
if (operation == null)
@@ -310,28 +193,6 @@ public void Replace(Operation operation, object objectToApplyTo)
310193
}
311194
}
312195

313-
/// <summary>
314-
/// The "copy" operation copies the value at a specified location to the
315-
/// target location.
316-
///
317-
/// The operation object MUST contain a "from" member, which is a string
318-
/// containing a JSON Pointer value that references the location in the
319-
/// target document to copy the value from.
320-
///
321-
/// The "from" location MUST exist for the operation to be successful.
322-
///
323-
/// For example:
324-
///
325-
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
326-
///
327-
/// This operation is functionally identical to an "add" operation at the
328-
/// target location using the value specified in the "from" member.
329-
///
330-
/// Note: even though it's the same functionally, we do not call add with
331-
/// the value specified in from for performance reasons (multiple checks of same requirements).
332-
/// </summary>
333-
/// <param name="operation">The copy operation.</param>
334-
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
335196
public void Copy(Operation operation, object objectToApplyTo)
336197
{
337198
if (operation == null)
@@ -365,6 +226,37 @@ public void Copy(Operation operation, object objectToApplyTo)
365226
}
366227
}
367228

229+
public void Test(Operation operation, object objectToApplyTo)
230+
{
231+
if (operation == null)
232+
{
233+
throw new ArgumentNullException(nameof(operation));
234+
}
235+
236+
if (objectToApplyTo == null)
237+
{
238+
throw new ArgumentNullException(nameof(objectToApplyTo));
239+
}
240+
241+
var parsedPath = new ParsedPath(operation.path);
242+
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
243+
244+
var target = objectToApplyTo;
245+
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
246+
{
247+
var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
248+
ErrorReporter(error);
249+
return;
250+
}
251+
252+
if (!adapter.TryTest(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
253+
{
254+
var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
255+
ErrorReporter(error);
256+
return;
257+
}
258+
}
259+
368260
private bool TryGetValue(
369261
string fromLocation,
370262
object objectToGetValueFrom,

0 commit comments

Comments
 (0)