[Fixes #33] Dictionary operations fail due to contract issues
This commit is contained in:
parent
9e8aee2478
commit
393c25988a
|
|
@ -1,14 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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.Reflection;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Microsoft.AspNetCore.JsonPatch.Helpers;
|
||||
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||
|
|
@ -127,7 +124,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
string path,
|
||||
object value,
|
||||
object objectToApplyTo,
|
||||
Operation operationToReport)
|
||||
Operation operation)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
|
|
@ -139,228 +136,30 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
if (operationToReport == null)
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operationToReport));
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
// 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 parsedPath = new ParsedPath(path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
// get path result
|
||||
var pathResult = GetActualPropertyPath(
|
||||
path,
|
||||
objectToApplyTo,
|
||||
operationToReport);
|
||||
if (pathResult == null)
|
||||
IAdapter adapter;
|
||||
var target = objectToApplyTo;
|
||||
string errorMessage;
|
||||
if (!visitor.TryVisit(ref target, out adapter, out errorMessage))
|
||||
{
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, path, operation, errorMessage);
|
||||
ReportError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var appendList = pathResult.ExecuteAtEnd;
|
||||
var positionAsInteger = pathResult.NumericEnd;
|
||||
var actualPathToProperty = pathResult.PathToProperty;
|
||||
|
||||
var treeAnalysisResult = new ObjectTreeAnalysisResult(
|
||||
objectToApplyTo,
|
||||
actualPathToProperty,
|
||||
ContractResolver);
|
||||
|
||||
if (!treeAnalysisResult.IsValidPathForAdd)
|
||||
if (!adapter.TryAdd(target, parsedPath.LastSegment, ContractResolver, value, out errorMessage))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatPropertyCannotBeAdded(path)));
|
||||
var error = CreateOperationFailedError(objectToApplyTo, path, operation, errorMessage);
|
||||
ReportError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (treeAnalysisResult.UseDynamicLogic)
|
||||
{
|
||||
var container = treeAnalysisResult.Container;
|
||||
if (container.ContainsCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// get the actual type
|
||||
var propertyValue = container.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
|
||||
var typeOfPathProperty = propertyValue.GetType();
|
||||
|
||||
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
|
||||
{
|
||||
// New property - add it.
|
||||
treeAnalysisResult.Container.Add(treeAnalysisResult.PropertyPathInParent, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it's 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.
|
||||
|
||||
var patchProperty = treeAnalysisResult.JsonPatchProperty;
|
||||
|
||||
if (appendList || positionAsInteger > -1)
|
||||
{
|
||||
if (!IsNonStringArray(patchProperty.Property.PropertyType))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -398,30 +197,19 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
var valueAtFromLocationResult = GetValueAtLocation(operation.from, objectToApplyTo, operation);
|
||||
|
||||
if (valueAtFromLocationResult.HasError)
|
||||
object propertyValue;
|
||||
// Get value at 'from' location and add that value to the 'path' location
|
||||
if (TryGetValue(operation.from, objectToApplyTo, operation, out propertyValue))
|
||||
{
|
||||
// Error has already been logged in GetValueAtLocation. We
|
||||
// must return, because remove / add should not be allowed to continue
|
||||
return;
|
||||
// remove that value
|
||||
Remove(operation.from, objectToApplyTo, operation);
|
||||
|
||||
// add that value to the path location
|
||||
Add(operation.path,
|
||||
propertyValue,
|
||||
objectToApplyTo,
|
||||
operation);
|
||||
}
|
||||
|
||||
// remove that value
|
||||
var removeResult = Remove(operation.from, objectToApplyTo, operation);
|
||||
|
||||
if (removeResult.HasError)
|
||||
{
|
||||
// Return => error has already been logged in remove method. We must
|
||||
// return, because add should not be allowed to continue
|
||||
return;
|
||||
}
|
||||
|
||||
// add that value to the path location
|
||||
Add(operation.path,
|
||||
valueAtFromLocationResult.PropertyValue,
|
||||
objectToApplyTo,
|
||||
operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -453,240 +241,33 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
Remove(operation.path, objectToApplyTo, operation);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Remove is used by various operations (eg: remove, move, ...), yet through different operations;
|
||||
/// This method allows code reuse yet reporting the correct operation on error. The return value
|
||||
/// contains the type of the item that has been removed (and a bool possibly signifying an error)
|
||||
/// This can be used by other methods, like replace, to ensure that we can pass in the correctly
|
||||
/// This can be used by other methods, like replace, to ensure that we can pass in the correctly
|
||||
/// typed value to whatever method follows.
|
||||
/// </summary>
|
||||
private RemovedPropertyTypeResult Remove(string path, object objectToApplyTo, Operation operationToReport)
|
||||
private void Remove(string path, object objectToApplyTo, Operation operationToReport)
|
||||
{
|
||||
// get path result
|
||||
var pathResult = GetActualPropertyPath(
|
||||
path,
|
||||
objectToApplyTo,
|
||||
operationToReport);
|
||||
var parsedPath = new ParsedPath(path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
if (pathResult == null)
|
||||
IAdapter adapter;
|
||||
var target = objectToApplyTo;
|
||||
string errorMessage;
|
||||
if (!visitor.TryVisit(ref target, out adapter, out errorMessage))
|
||||
{
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, path, operationToReport, errorMessage);
|
||||
ReportError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var removeFromList = pathResult.ExecuteAtEnd;
|
||||
var positionAsInteger = pathResult.NumericEnd;
|
||||
var actualPathToProperty = pathResult.PathToProperty;
|
||||
|
||||
var treeAnalysisResult = new ObjectTreeAnalysisResult(
|
||||
objectToApplyTo,
|
||||
actualPathToProperty,
|
||||
ContractResolver);
|
||||
|
||||
if (!treeAnalysisResult.IsValidPathForRemove)
|
||||
if (!adapter.TryRemove(target, parsedPath.LastSegment, ContractResolver, out errorMessage))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatPropertyCannotBeRemoved(path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
if (treeAnalysisResult.UseDynamicLogic)
|
||||
{
|
||||
// if it's not an array, we can remove the property from
|
||||
// the dictionary. If it's an array, we need to check the position first.
|
||||
if (removeFromList || positionAsInteger > -1)
|
||||
{
|
||||
var propertyValue = treeAnalysisResult.Container
|
||||
.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
|
||||
|
||||
// we cannot continue when the value is null, because to be able to
|
||||
// continue we need to be able to check if the array is a non-string array
|
||||
if (propertyValue == null)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatCannotDeterminePropertyType(path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
var typeOfPathProperty = propertyValue.GetType();
|
||||
|
||||
if (!IsNonStringArray(typeOfPathProperty))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
// now, get the generic type of the enumerable (we'll return this type)
|
||||
var genericTypeOfArray = GetIListType(typeOfPathProperty);
|
||||
|
||||
// get the array
|
||||
var array = (IList)treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent);
|
||||
|
||||
if (array.Count == 0)
|
||||
{
|
||||
// if the array is empty, we should throw an error
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(
|
||||
operationToReport.op,
|
||||
path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
if (removeFromList)
|
||||
{
|
||||
array.RemoveAt(array.Count - 1);
|
||||
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent, array);
|
||||
|
||||
// return the type of the value that has been removed.
|
||||
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (positionAsInteger >= array.Count)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(
|
||||
operationToReport.op,
|
||||
path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
array.RemoveAt(positionAsInteger);
|
||||
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent, array);
|
||||
|
||||
// return the type of the value that has been removed.
|
||||
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the property
|
||||
var getResult = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent);
|
||||
|
||||
// remove the property
|
||||
treeAnalysisResult.Container.RemoveValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent);
|
||||
|
||||
// value is not null, we can determine the type
|
||||
if (getResult != null)
|
||||
{
|
||||
var actualType = getResult.GetType();
|
||||
return new RemovedPropertyTypeResult(actualType, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RemovedPropertyTypeResult(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not dynamic
|
||||
var patchProperty = treeAnalysisResult.JsonPatchProperty;
|
||||
|
||||
if (removeFromList || positionAsInteger > -1)
|
||||
{
|
||||
if (!IsNonStringArray(patchProperty.Property.PropertyType))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
// now, get the generic type of the IList<> from Property type.
|
||||
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
|
||||
|
||||
if (!patchProperty.Property.Readable)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatCannotReadProperty(path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
var array = (IList)patchProperty.Property.ValueProvider
|
||||
.GetValue(patchProperty.Parent);
|
||||
|
||||
if (array.Count == 0)
|
||||
{
|
||||
// if the array is empty, we should throw an error
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(
|
||||
operationToReport.op,
|
||||
path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
if (removeFromList)
|
||||
{
|
||||
array.RemoveAt(array.Count - 1);
|
||||
|
||||
// return the type of the value that has been removed
|
||||
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (positionAsInteger >= array.Count)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(
|
||||
operationToReport.op,
|
||||
path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
array.RemoveAt(positionAsInteger);
|
||||
|
||||
// return the type of the value that has been removed
|
||||
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!patchProperty.Property.Writable)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatCannotUpdateProperty(path)));
|
||||
return new RemovedPropertyTypeResult(null, true);
|
||||
}
|
||||
|
||||
// setting the value to "null" will use the default value in case of value types, and
|
||||
// null in case of reference types
|
||||
object value = null;
|
||||
|
||||
if (patchProperty.Property.PropertyType.GetTypeInfo().IsValueType
|
||||
&& Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null)
|
||||
{
|
||||
value = Activator.CreateInstance(patchProperty.Property.PropertyType);
|
||||
}
|
||||
|
||||
patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, value);
|
||||
return new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false);
|
||||
}
|
||||
var error = CreateOperationFailedError(objectToApplyTo, path, operationToReport, errorMessage);
|
||||
ReportError(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -722,37 +303,25 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
var removeResult = Remove(operation.path, objectToApplyTo, operation);
|
||||
var parsedPath = new ParsedPath(operation.path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
if (removeResult.HasError)
|
||||
IAdapter adapter;
|
||||
var target = objectToApplyTo;
|
||||
string errorMessage;
|
||||
if (!visitor.TryVisit(ref target, out adapter, out errorMessage))
|
||||
{
|
||||
// return => error has already been logged in remove method
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ReportError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeResult.HasError && removeResult.ActualType == null)
|
||||
if (!adapter.TryReplace(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
|
||||
{
|
||||
// the remove operation completed succesfully, but we could not determine the type.
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operation,
|
||||
Resources.FormatCannotDeterminePropertyType(operation.from)));
|
||||
var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ReportError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var conversionResult = ConvertToActualType(removeResult.ActualType, operation.value);
|
||||
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
// invalid value for path
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operation,
|
||||
Resources.FormatInvalidValueForProperty(operation.value, operation.path)));
|
||||
return;
|
||||
}
|
||||
|
||||
Add(operation.path, conversionResult.ConvertedInstance, objectToApplyTo, operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -789,36 +358,26 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
// get value at from location and add that value to the path location
|
||||
var valueAtFromLocationResult = GetValueAtLocation(operation.from, objectToApplyTo, operation);
|
||||
|
||||
if (valueAtFromLocationResult.HasError)
|
||||
object propertyValue;
|
||||
// Get value at 'from' location and add that value to the 'path' location
|
||||
if (TryGetValue(operation.from, objectToApplyTo, operation, out propertyValue))
|
||||
{
|
||||
// Return, error has already been logged in GetValueAtLocation
|
||||
return;
|
||||
Add(operation.path,
|
||||
propertyValue,
|
||||
objectToApplyTo,
|
||||
operation);
|
||||
}
|
||||
|
||||
Add(operation.path,
|
||||
valueAtFromLocationResult.PropertyValue,
|
||||
objectToApplyTo,
|
||||
operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method is used by Copy and Move to avoid duplicate code
|
||||
/// </summary>
|
||||
/// <param name="location">Location where value should be</param>
|
||||
/// <param name="objectToGetValueFrom">Object to inspect for the desired value</param>
|
||||
/// <param name="operationToReport">Operation to report in case of an error</param>
|
||||
/// <returns>GetValueResult containing value and a bool signifying a possible error</returns>
|
||||
private GetValueResult GetValueAtLocation(
|
||||
string location,
|
||||
private bool TryGetValue(
|
||||
string fromLocation,
|
||||
object objectToGetValueFrom,
|
||||
Operation operationToReport)
|
||||
Operation operation,
|
||||
out object propertyValue)
|
||||
{
|
||||
if (location == null)
|
||||
if (fromLocation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(location));
|
||||
throw new ArgumentNullException(nameof(fromLocation));
|
||||
}
|
||||
|
||||
if (objectToGetValueFrom == null)
|
||||
|
|
@ -826,280 +385,62 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
throw new ArgumentNullException(nameof(objectToGetValueFrom));
|
||||
}
|
||||
|
||||
if (operationToReport == null)
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operationToReport));
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
// get path result
|
||||
var pathResult = GetActualPropertyPath(
|
||||
location,
|
||||
objectToGetValueFrom,
|
||||
operationToReport);
|
||||
propertyValue = null;
|
||||
|
||||
if (pathResult == null)
|
||||
var parsedPath = new ParsedPath(fromLocation);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
IAdapter adapter;
|
||||
var target = objectToGetValueFrom;
|
||||
string errorMessage;
|
||||
if (!visitor.TryVisit(ref target, out adapter, out errorMessage))
|
||||
{
|
||||
return new GetValueResult(null, true);
|
||||
var error = CreatePathNotFoundError(objectToGetValueFrom, fromLocation, operation, errorMessage);
|
||||
ReportError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
var getAtEndOfList = pathResult.ExecuteAtEnd;
|
||||
var positionAsInteger = pathResult.NumericEnd;
|
||||
var actualPathToProperty = pathResult.PathToProperty;
|
||||
|
||||
var treeAnalysisResult = new ObjectTreeAnalysisResult(
|
||||
objectToGetValueFrom,
|
||||
actualPathToProperty,
|
||||
ContractResolver);
|
||||
|
||||
if (treeAnalysisResult.UseDynamicLogic)
|
||||
if (!adapter.TryGet(target, parsedPath.LastSegment, ContractResolver, out propertyValue, out errorMessage))
|
||||
{
|
||||
// if it's not an array, we can remove the property from
|
||||
// the dictionary. If it's an array, we need to check the position first.
|
||||
if (getAtEndOfList || positionAsInteger > -1)
|
||||
{
|
||||
var propertyValue = treeAnalysisResult.Container
|
||||
.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
|
||||
|
||||
// we cannot continue when the value is null, because to be able to
|
||||
// continue we need to be able to check if the array is a non-string array
|
||||
if (propertyValue == null)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToGetValueFrom,
|
||||
operationToReport,
|
||||
Resources.FormatCannotDeterminePropertyType(location)));
|
||||
return new GetValueResult(null, true);
|
||||
}
|
||||
|
||||
var typeOfPathProperty = propertyValue.GetType();
|
||||
|
||||
if (!IsNonStringArray(typeOfPathProperty))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToGetValueFrom,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location)));
|
||||
return new GetValueResult(null, true);
|
||||
}
|
||||
|
||||
// get the array
|
||||
var array = (IList)treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent);
|
||||
|
||||
if (positionAsInteger >= array.Count)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToGetValueFrom,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(
|
||||
operationToReport.op,
|
||||
location)));
|
||||
return new GetValueResult(null, true);
|
||||
}
|
||||
|
||||
if (getAtEndOfList)
|
||||
{
|
||||
return new GetValueResult(array[array.Count - 1], false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new GetValueResult(array[positionAsInteger], false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the property
|
||||
var propertyValueAtLocation = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent);
|
||||
|
||||
return new GetValueResult(propertyValueAtLocation, false);
|
||||
}
|
||||
var error = CreateOperationFailedError(objectToGetValueFrom, fromLocation, operation, errorMessage);
|
||||
ReportError(error);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not dynamic
|
||||
var patchProperty = treeAnalysisResult.JsonPatchProperty;
|
||||
|
||||
if (getAtEndOfList || positionAsInteger > -1)
|
||||
{
|
||||
if (!IsNonStringArray(patchProperty.Property.PropertyType))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToGetValueFrom,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location)));
|
||||
return new GetValueResult(null, true);
|
||||
}
|
||||
|
||||
if (!patchProperty.Property.Readable)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToGetValueFrom,
|
||||
operationToReport,
|
||||
Resources.FormatCannotReadProperty(location)));
|
||||
return new GetValueResult(null, true);
|
||||
}
|
||||
|
||||
var array = (IList)patchProperty.Property.ValueProvider
|
||||
.GetValue(patchProperty.Parent);
|
||||
|
||||
if (positionAsInteger >= array.Count)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToGetValueFrom,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(
|
||||
operationToReport.op,
|
||||
location)));
|
||||
return new GetValueResult(null, true);
|
||||
}
|
||||
|
||||
if (getAtEndOfList)
|
||||
{
|
||||
return new GetValueResult(array[array.Count - 1], false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new GetValueResult(array[positionAsInteger], false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!patchProperty.Property.Readable)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToGetValueFrom,
|
||||
operationToReport,
|
||||
Resources.FormatCannotReadProperty(
|
||||
location)));
|
||||
return new GetValueResult(null, true);
|
||||
}
|
||||
|
||||
var propertyValueAtLocation = patchProperty.Property.ValueProvider
|
||||
.GetValue(patchProperty.Parent);
|
||||
|
||||
return new GetValueResult(propertyValueAtLocation, false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsNonStringArray(Type type)
|
||||
{
|
||||
if (GetIListType(type) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return (!(type == typeof(string)) && typeof(IList).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()));
|
||||
}
|
||||
|
||||
private void LogError(JsonPatchError jsonPatchError)
|
||||
private void ReportError(JsonPatchError error)
|
||||
{
|
||||
if (LogErrorAction != null)
|
||||
{
|
||||
LogErrorAction(jsonPatchError);
|
||||
LogErrorAction(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException(jsonPatchError);
|
||||
throw new JsonPatchException(error);
|
||||
}
|
||||
}
|
||||
|
||||
private ConversionResult ConvertToActualType(Type propertyType, object value)
|
||||
private JsonPatchError CreateOperationFailedError(object target, string path, Operation operation, string errorMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
var o = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), propertyType);
|
||||
|
||||
return new ConversionResult(true, o);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new ConversionResult(false, null);
|
||||
}
|
||||
return new JsonPatchError(
|
||||
target,
|
||||
operation,
|
||||
errorMessage ?? Resources.FormatCannotPerformOperation(operation.op, path));
|
||||
}
|
||||
|
||||
private Type GetIListType(Type type)
|
||||
private JsonPatchError CreatePathNotFoundError(object target, string path, Operation operation, string errorMessage)
|
||||
{
|
||||
if (IsGenericListType(type))
|
||||
{
|
||||
return type.GetTypeInfo().GenericTypeArguments[0];
|
||||
}
|
||||
|
||||
foreach (Type interfaceType in type.GetTypeInfo().ImplementedInterfaces)
|
||||
{
|
||||
if (IsGenericListType(interfaceType))
|
||||
{
|
||||
return interfaceType.GetTypeInfo().GenericTypeArguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsGenericListType(Type type)
|
||||
{
|
||||
if (type.GetTypeInfo().IsGenericType &&
|
||||
type.GetGenericTypeDefinition() == typeof(IList<>))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ActualPropertyPathResult GetActualPropertyPath(
|
||||
string propertyPath,
|
||||
object objectToApplyTo,
|
||||
Operation operationToReport)
|
||||
{
|
||||
if (propertyPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyPath));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
if (operationToReport == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(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);
|
||||
}
|
||||
return new JsonPatchError(
|
||||
target,
|
||||
operation,
|
||||
errorMessage ?? Resources.FormatTargetLocationNotFound(operation.op, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// 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.AspNetCore.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
// 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.AspNetCore.JsonPatch.Helpers
|
||||
{
|
||||
// Helper methods to allow case-insensitive key search
|
||||
internal static class ExpandoObjectDictionaryExtensions
|
||||
{
|
||||
internal static void SetValueForCaseInsensitiveKey(
|
||||
this IDictionary<string, object> propertyDictionary,
|
||||
string key,
|
||||
object value)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
|
||||
{
|
||||
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
propertyDictionary[kvp.Key] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RemoveValueForCaseInsensitiveKey(
|
||||
this IDictionary<string, object> propertyDictionary,
|
||||
string key)
|
||||
{
|
||||
string realKey = null;
|
||||
foreach (KeyValuePair<string, object> 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<string, object> propertyDictionary,
|
||||
string key)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> 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<string, object> propertyDictionary,
|
||||
string key)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
|
||||
{
|
||||
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
// 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.AspNetCore.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<string, object> 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<string, object>),
|
||||
// we cannot use the ContractResolver.
|
||||
var dictionary = targetObject as IDictionary<string, object>;
|
||||
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<string, object>)
|
||||
{
|
||||
UseDynamicLogic = true;
|
||||
|
||||
Container = (IDictionary<string, object>)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)
|
||||
{
|
||||
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<object>();
|
||||
|
||||
if (indexable.Count() >= numericValue)
|
||||
{
|
||||
return indexable.ElementAt(numericValue);
|
||||
}
|
||||
else { return null; }
|
||||
}
|
||||
else { return null; }
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Return value for Remove operation. The combination tells us what to do next (if this operation
|
||||
/// is called from inside another operation, eg: Replace, Copy.
|
||||
///
|
||||
/// Possible combo:
|
||||
/// - ActualType contains type: operation succesfully completed, can continue when called from inside
|
||||
/// another operation
|
||||
/// - ActualType null and HasError true: operation not completed succesfully, should not be allowed to continue
|
||||
/// - ActualType null and HasError false: operation completed succesfully, but we should not be allowed to
|
||||
/// continue when called from inside another method as we could not verify the type of the removed property.
|
||||
/// This happens when the value of an item in an ExpandoObject dictionary is null.
|
||||
/// </summary>
|
||||
internal class RemovedPropertyTypeResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the removed property (value)
|
||||
/// </summary>
|
||||
public Type ActualType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// HasError: true when an error occurred, the operation didn't complete succesfully
|
||||
/// </summary>
|
||||
public bool HasError { get; set; }
|
||||
|
||||
public RemovedPropertyTypeResult(Type actualType, bool hasError)
|
||||
{
|
||||
ActualType = actualType;
|
||||
HasError = hasError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
// 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.AspNetCore.JsonPatch.Helpers
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
internal class ConversionResult
|
||||
public class ConversionResult
|
||||
{
|
||||
public bool CanBeConverted { get; private set; }
|
||||
public object ConvertedInstance { get; private set; }
|
||||
|
||||
public ConversionResult(bool canBeConverted, object convertedInstance)
|
||||
{
|
||||
CanBeConverted = canBeConverted;
|
||||
ConvertedInstance = convertedInstance;
|
||||
}
|
||||
|
||||
public bool CanBeConverted { get; }
|
||||
public object ConvertedInstance { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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 Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public static class ConversionResultProvider
|
||||
{
|
||||
public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
|
||||
return new ConversionResult(true, deserialized);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ConversionResult(canBeConverted: false, convertedInstance: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// 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.Collections;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class DictionaryAdapter : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary)target;
|
||||
|
||||
// As per JsonPatch spec, if a key already exists, adding should replace the existing value
|
||||
dictionary[segment] = value;
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary)target;
|
||||
|
||||
value = dictionary[segment];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary)target;
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for remove to be successful
|
||||
if (!dictionary.Contains(segment))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary.Remove(segment);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary)target;
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for remove to be successful
|
||||
if (!dictionary.Contains(segment))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary[segment] = value;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object nextTarget,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = target as IDictionary;
|
||||
if (dictionary == null)
|
||||
{
|
||||
nextTarget = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dictionary.Contains(segment))
|
||||
{
|
||||
nextTarget = dictionary[segment];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextTarget = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ExpandoObjectAdapter : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary<string, object>)target;
|
||||
|
||||
var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
|
||||
|
||||
// As per JsonPatch spec, if a key already exists, adding should replace the existing value
|
||||
dictionary[key] = ConvertValue(dictionary, key, value);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary<string, object>)target;
|
||||
|
||||
var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
|
||||
value = dictionary[key];
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary<string, object>)target;
|
||||
|
||||
var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for remove to be successful
|
||||
if (!dictionary.ContainsKey(key))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary.Remove(key);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var dictionary = (IDictionary<string, object>)target;
|
||||
|
||||
var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for remove to be successful
|
||||
if (!dictionary.ContainsKey(key))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary[key] = ConvertValue(dictionary, key, value);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object nextTarget,
|
||||
out string errorMessage)
|
||||
{
|
||||
var expandoObject = target as ExpandoObject;
|
||||
if (expandoObject == null)
|
||||
{
|
||||
errorMessage = null;
|
||||
nextTarget = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var dictionary = (IDictionary<string, object>)expandoObject;
|
||||
|
||||
var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
|
||||
|
||||
if (dictionary.ContainsKey(key))
|
||||
{
|
||||
nextTarget = dictionary[key];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextTarget = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private object ConvertValue(IDictionary<string, object> dictionary, string key, object newValue)
|
||||
{
|
||||
object existingValue = null;
|
||||
if (dictionary.TryGetValue(key, out existingValue))
|
||||
{
|
||||
if (existingValue != null)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(newValue, existingValue.GetType());
|
||||
if (conversionResult.CanBeConverted)
|
||||
{
|
||||
return conversionResult.ConvertedInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
// Helper methods to allow case-insensitive key search
|
||||
public static class ExpandoObjectDictionaryExtensions
|
||||
{
|
||||
internal static string GetKeyUsingCaseInsensitiveSearch(
|
||||
this IDictionary<string, object> propertyDictionary,
|
||||
string key)
|
||||
{
|
||||
foreach (var keyInDictionary in propertyDictionary.Keys)
|
||||
{
|
||||
if (string.Equals(key, keyInDictionary, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return keyInDictionary;
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
// 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;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Helpers
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
internal static class ExpressionHelpers
|
||||
public static class ExpressionHelpers
|
||||
{
|
||||
public static string GetPath<TModel, TProp>(Expression<Func<TModel, TProp>> expr) where TModel : class
|
||||
{
|
||||
|
|
@ -59,7 +58,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Helpers
|
|||
}
|
||||
else
|
||||
{
|
||||
// Get property name, respecting JsonProperty attribute
|
||||
// Get property name, respecting JsonProperty attribute
|
||||
return GetPropertyNameFromMemberExpression(memberExpression);
|
||||
}
|
||||
case ExpressionType.Parameter:
|
||||
|
|
@ -73,7 +72,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Helpers
|
|||
private static string GetPropertyNameFromMemberExpression(MemberExpression memberExpression)
|
||||
{
|
||||
// if there's a JsonProperty attribute, we must return the PropertyName
|
||||
// from the attribute rather than the member name
|
||||
// from the attribute rather than the member name
|
||||
var jsonPropertyAttribute =
|
||||
memberExpression.Member.GetCustomAttribute(
|
||||
typeof(JsonPropertyAttribute), true);
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public interface IAdapter
|
||||
{
|
||||
bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object nextTarget,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
// 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 Microsoft.Extensions.Internal;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ListAdapter : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
Type typeArgument = null;
|
||||
if (!TryGetListTypeArgument(list, out typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PositionInfo positionInfo;
|
||||
if (!TryGetPositionInfo(list, segment, out positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
object convertedValue = null;
|
||||
if (!TryConvertValue(value, typeArgument, segment, out convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
list.Add(convertedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Insert(positionInfo.Index, convertedValue);
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
Type typeArgument = null;
|
||||
if (!TryGetListTypeArgument(list, out typeArgument, out errorMessage))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
PositionInfo positionInfo;
|
||||
if (!TryGetPositionInfo(list, segment, out positionInfo, out errorMessage))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
value = list[list.Count - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
value = list[positionInfo.Index];
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
Type typeArgument = null;
|
||||
if (!TryGetListTypeArgument(list, out typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PositionInfo positionInfo;
|
||||
if (!TryGetPositionInfo(list, segment, out positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
list.RemoveAt(list.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.RemoveAt(positionInfo.Index);
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
Type typeArgument = null;
|
||||
if (!TryGetListTypeArgument(list, out typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PositionInfo positionInfo;
|
||||
if (!TryGetPositionInfo(list, segment, out positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
object convertedValue = null;
|
||||
if (!TryConvertValue(value, typeArgument, segment, out convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
list[list.Count - 1] = convertedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
list[positionInfo.Index] = convertedValue;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = target as IList;
|
||||
if (list == null)
|
||||
{
|
||||
value = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
if (!int.TryParse(segment, out index))
|
||||
{
|
||||
value = null;
|
||||
errorMessage = Resources.FormatInvalidIndexValue(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= list.Count)
|
||||
{
|
||||
value = null;
|
||||
errorMessage = Resources.FormatIndexOutOfBounds(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = list[index];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryConvertValue(
|
||||
object originalValue,
|
||||
Type listTypeArgument,
|
||||
string segment,
|
||||
out object convertedValue,
|
||||
out string errorMessage)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument);
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
convertedValue = null;
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(originalValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedValue = conversionResult.ConvertedInstance;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetListTypeArgument(IList list, out Type listTypeArgument, out string errorMessage)
|
||||
{
|
||||
// Arrays are not supported as they have fixed size and operations like Add, Insert do not make sense
|
||||
var listType = list.GetType();
|
||||
if (listType.IsArray)
|
||||
{
|
||||
errorMessage = Resources.FormatPatchNotSupportedForArrays(listType.FullName);
|
||||
listTypeArgument = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var genericList = ClosedGenericMatcher.ExtractGenericInterface(listType, typeof(IList<>));
|
||||
if (genericList == null)
|
||||
{
|
||||
errorMessage = Resources.FormatPatchNotSupportedForNonGenericLists(listType.FullName);
|
||||
listTypeArgument = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
listTypeArgument = genericList.GenericTypeArguments[0];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetPositionInfo(IList list, string segment, out PositionInfo positionInfo, out string errorMessage)
|
||||
{
|
||||
if (segment == "-")
|
||||
{
|
||||
positionInfo = new PositionInfo(PositionType.EndOfList, -1);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
int position = -1;
|
||||
if (int.TryParse(segment, out position))
|
||||
{
|
||||
if (position >= 0 && position < list.Count)
|
||||
{
|
||||
positionInfo = new PositionInfo(PositionType.Index, position);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
positionInfo = default(PositionInfo);
|
||||
errorMessage = Resources.FormatIndexOutOfBounds(segment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
positionInfo = default(PositionInfo);
|
||||
errorMessage = Resources.FormatInvalidIndexValue(segment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private struct PositionInfo
|
||||
{
|
||||
public PositionInfo(PositionType type, int index)
|
||||
{
|
||||
Type = type;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public PositionType Type { get; }
|
||||
public int Index { get; }
|
||||
}
|
||||
|
||||
private enum PositionType
|
||||
{
|
||||
Index, // valid index
|
||||
EndOfList, // '-'
|
||||
Invalid, // Ex: not an integer
|
||||
OutOfBounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// 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.Dynamic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ObjectVisitor
|
||||
{
|
||||
private readonly IContractResolver _contractResolver;
|
||||
private readonly ParsedPath _path;
|
||||
|
||||
public ObjectVisitor(ParsedPath path, IContractResolver contractResolver)
|
||||
{
|
||||
if (contractResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contractResolver));
|
||||
}
|
||||
|
||||
_path = path;
|
||||
_contractResolver = contractResolver;
|
||||
}
|
||||
|
||||
public bool TryVisit(ref object target, out IAdapter adapter, out string errorMessage)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
adapter = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
adapter = SelectAdapater(target);
|
||||
|
||||
// Traverse until the penultimate segment to get the target object and adapter
|
||||
for (var i = 0; i < _path.Segments.Count - 1; i++)
|
||||
{
|
||||
object next;
|
||||
if (!adapter.TryTraverse(target, _path.Segments[i], _contractResolver, out next, out errorMessage))
|
||||
{
|
||||
adapter = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
target = next;
|
||||
adapter = SelectAdapater(target);
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private IAdapter SelectAdapater(object targetObject)
|
||||
{
|
||||
if (targetObject is ExpandoObject)
|
||||
{
|
||||
return new ExpandoObjectAdapter();
|
||||
}
|
||||
else if (targetObject is IDictionary)
|
||||
{
|
||||
return new DictionaryAdapter();
|
||||
}
|
||||
else if (targetObject is IList)
|
||||
{
|
||||
return new ListAdapter();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PocoAdapter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// 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.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public struct ParsedPath
|
||||
{
|
||||
private static readonly string[] Empty = null;
|
||||
|
||||
private readonly string[] _segments;
|
||||
|
||||
public ParsedPath(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
_segments = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public string LastSegment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_segments == null || _segments.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _segments[_segments.Length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> Segments => _segments ?? Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Helpers
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
internal static class PathHelpers
|
||||
{
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
// 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.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class PocoAdapter : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
JsonProperty jsonProperty = null;
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Writable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotUpdateProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
object convertedValue = null;
|
||||
if (!TryConvertValue(value, jsonProperty.PropertyType, out convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonProperty.ValueProvider.SetValue(target, convertedValue);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
JsonProperty jsonProperty = null;
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Readable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotReadProperty(segment);
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = jsonProperty.ValueProvider.GetValue(target);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
JsonProperty jsonProperty = null;
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Writable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotUpdateProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setting the value to "null" will use the default value in case of value types, and
|
||||
// null in case of reference types
|
||||
object value = null;
|
||||
if (jsonProperty.PropertyType.GetTypeInfo().IsValueType
|
||||
&& Nullable.GetUnderlyingType(jsonProperty.PropertyType) == null)
|
||||
{
|
||||
value = Activator.CreateInstance(jsonProperty.PropertyType);
|
||||
}
|
||||
|
||||
jsonProperty.ValueProvider.SetValue(target, value);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver
|
||||
contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
JsonProperty jsonProperty = null;
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Writable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotUpdateProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
object convertedValue = null;
|
||||
if (!TryConvertValue(value, jsonProperty.PropertyType, out convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonProperty.ValueProvider.SetValue(target, convertedValue);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
value = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonProperty jsonProperty = null;
|
||||
if (TryGetJsonProperty(target, contractResolver, segment, out jsonProperty))
|
||||
{
|
||||
value = jsonProperty.ValueProvider.GetValue(target);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetJsonProperty(
|
||||
object target,
|
||||
IContractResolver contractResolver,
|
||||
string segment,
|
||||
out JsonProperty jsonProperty)
|
||||
{
|
||||
var jsonObjectContract = contractResolver.ResolveContract(target.GetType()) as JsonObjectContract;
|
||||
if (jsonObjectContract != null)
|
||||
{
|
||||
var pocoProperty = jsonObjectContract
|
||||
.Properties
|
||||
.FirstOrDefault(p => string.Equals(p.PropertyName, segment, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (pocoProperty != null)
|
||||
{
|
||||
jsonProperty = pocoProperty;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
jsonProperty = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
convertedValue = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedValue = conversionResult.ConvertedInstance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.JsonPatch.Adapters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Converters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Helpers;
|
||||
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
|
@ -13,7 +13,7 @@ using Newtonsoft.Json.Serialization;
|
|||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
// Implementation details: the purpose of this type of patch document is to allow creation of such
|
||||
// documents for cases where there's no class/DTO to work on. Typical use case: backend not built in
|
||||
// documents for cases where there's no class/DTO to work on. Typical use case: backend not built in
|
||||
// .NET or architecture doesn't contain a shared DTO layer.
|
||||
[JsonConverter(typeof(JsonPatchDocumentConverter))]
|
||||
public class JsonPatchDocument : IJsonPatchDocument
|
||||
|
|
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
public void ApplyTo(object objectToApplyTo)
|
||||
|
|
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="logErrorAction">Action to log errors</param>
|
||||
|
|
@ -174,7 +174,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Linq.Expressions;
|
||||
using Microsoft.AspNetCore.JsonPatch.Adapters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Converters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Helpers;
|
||||
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
|
@ -502,7 +502,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
/// Copy from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="positionTo">position</param>
|
||||
/// <returns></returns>
|
||||
|
|
@ -623,7 +623,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
public void ApplyTo(TModel objectToApplyTo)
|
||||
|
|
@ -637,7 +637,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="logErrorAction">Action to log errors</param>
|
||||
|
|
@ -652,7 +652,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,22 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' operation at path '{1}' could not be performed.
|
||||
/// </summary>
|
||||
internal static string CannotPerformOperation
|
||||
{
|
||||
get { return GetString("CannotPerformOperation"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' operation at path '{1}' could not be performed.
|
||||
/// </summary>
|
||||
internal static string FormatCannotPerformOperation(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("CannotPerformOperation"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at '{0}' could not be read.
|
||||
/// </summary>
|
||||
|
|
@ -59,35 +75,35 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key '{0}' was not found.
|
||||
/// The index value provided by path segment '{0}' is out of bounds of the array size.
|
||||
/// </summary>
|
||||
internal static string DictionaryKeyNotFound
|
||||
internal static string IndexOutOfBounds
|
||||
{
|
||||
get { return GetString("DictionaryKeyNotFound"); }
|
||||
get { return GetString("IndexOutOfBounds"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key '{0}' was not found.
|
||||
/// The index value provided by path segment '{0}' is out of bounds of the array size.
|
||||
/// </summary>
|
||||
internal static string FormatDictionaryKeyNotFound(object p0)
|
||||
internal static string FormatIndexOutOfBounds(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("DictionaryKeyNotFound"), p0);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("IndexOutOfBounds"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}' on array property at path '{1}', the index is larger than the array size.
|
||||
/// The path segment '{0}' is invalid for an array index.
|
||||
/// </summary>
|
||||
internal static string InvalidIndexForArrayProperty
|
||||
internal static string InvalidIndexValue
|
||||
{
|
||||
get { return GetString("InvalidIndexForArrayProperty"); }
|
||||
get { return GetString("InvalidIndexValue"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}' on array property at path '{1}', the index is larger than the array size.
|
||||
/// The path segment '{0}' is invalid for an array index.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidIndexForArrayProperty(object p0, object p1)
|
||||
internal static string FormatInvalidIndexValue(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexForArrayProperty"), p0, p1);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexValue"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -106,22 +122,6 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidJsonPatchDocument"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}', the provided path is invalid for array property at path '{1}'.
|
||||
/// </summary>
|
||||
internal static string InvalidPathForArrayProperty
|
||||
{
|
||||
get { return GetString("InvalidPathForArrayProperty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}', the provided path is invalid for array property at path '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidPathForArrayProperty(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidPathForArrayProperty"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided string '{0}' is an invalid path.
|
||||
/// </summary>
|
||||
|
|
@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid for property at path '{1}'.
|
||||
/// The value '{0}' is invalid for target location.
|
||||
/// </summary>
|
||||
internal static string InvalidValueForProperty
|
||||
{
|
||||
|
|
@ -147,27 +147,11 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid for property at path '{1}'.
|
||||
/// The value '{0}' is invalid for target location.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidValueForProperty(object p0, object p1)
|
||||
internal static string FormatInvalidValueForProperty(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}' on array property at path '{1}', the index is negative.
|
||||
/// </summary>
|
||||
internal static string NegativeIndexForArrayProperty
|
||||
{
|
||||
get { return GetString("NegativeIndexForArrayProperty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}' on array property at path '{1}', the index is negative.
|
||||
/// </summary>
|
||||
internal static string FormatNegativeIndexForArrayProperty(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("NegativeIndexForArrayProperty"), p0, p1);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -187,51 +171,67 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at path '{0}' could not be added.
|
||||
/// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
|
||||
/// </summary>
|
||||
internal static string PropertyCannotBeAdded
|
||||
internal static string PatchNotSupportedForArrays
|
||||
{
|
||||
get { return GetString("PropertyCannotBeAdded"); }
|
||||
get { return GetString("PatchNotSupportedForArrays"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at path '{0}' could not be added.
|
||||
/// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
|
||||
/// </summary>
|
||||
internal static string FormatPropertyCannotBeAdded(object p0)
|
||||
internal static string FormatPatchNotSupportedForArrays(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyCannotBeAdded"), p0);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForArrays"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at path '{0}' could not be removed.
|
||||
/// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
|
||||
/// </summary>
|
||||
internal static string PropertyCannotBeRemoved
|
||||
internal static string PatchNotSupportedForNonGenericLists
|
||||
{
|
||||
get { return GetString("PropertyCannotBeRemoved"); }
|
||||
get { return GetString("PatchNotSupportedForNonGenericLists"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at path '{0}' could not be removed.
|
||||
/// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
|
||||
/// </summary>
|
||||
internal static string FormatPropertyCannotBeRemoved(object p0)
|
||||
internal static string FormatPatchNotSupportedForNonGenericLists(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyCannotBeRemoved"), p0);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForNonGenericLists"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Property does not exist at path '{0}'.
|
||||
/// The target location specified by path segment '{0}' was not found.
|
||||
/// </summary>
|
||||
internal static string PropertyDoesNotExist
|
||||
internal static string TargetLocationAtPathSegmentNotFound
|
||||
{
|
||||
get { return GetString("PropertyDoesNotExist"); }
|
||||
get { return GetString("TargetLocationAtPathSegmentNotFound"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Property does not exist at path '{0}'.
|
||||
/// The target location specified by path segment '{0}' was not found.
|
||||
/// </summary>
|
||||
internal static string FormatPropertyDoesNotExist(object p0)
|
||||
internal static string FormatTargetLocationAtPathSegmentNotFound(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyDoesNotExist"), p0);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationAtPathSegmentNotFound"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}', the target location specified by path '{1}' was not found.
|
||||
/// </summary>
|
||||
internal static string TargetLocationNotFound
|
||||
{
|
||||
get { return GetString("TargetLocationNotFound"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}', the target location specified by path '{1}' was not found.
|
||||
/// </summary>
|
||||
internal static string FormatTargetLocationNotFound(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationNotFound"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -120,44 +120,44 @@
|
|||
<data name="CannotDeterminePropertyType" xml:space="preserve">
|
||||
<value>The type of the property at path '{0}' could not be determined.</value>
|
||||
</data>
|
||||
<data name="CannotPerformOperation" xml:space="preserve">
|
||||
<value>The '{0}' operation at path '{1}' could not be performed.</value>
|
||||
</data>
|
||||
<data name="CannotReadProperty" xml:space="preserve">
|
||||
<value>The property at '{0}' could not be read.</value>
|
||||
</data>
|
||||
<data name="CannotUpdateProperty" xml:space="preserve">
|
||||
<value>The property at path '{0}' could not be updated.</value>
|
||||
</data>
|
||||
<data name="DictionaryKeyNotFound" xml:space="preserve">
|
||||
<value>The key '{0}' was not found.</value>
|
||||
<data name="IndexOutOfBounds" xml:space="preserve">
|
||||
<value>The index value provided by path segment '{0}' is out of bounds of the array size.</value>
|
||||
</data>
|
||||
<data name="InvalidIndexForArrayProperty" xml:space="preserve">
|
||||
<value>For operation '{0}' on array property at path '{1}', the index is larger than the array size.</value>
|
||||
<data name="InvalidIndexValue" xml:space="preserve">
|
||||
<value>The path segment '{0}' is invalid for an array index.</value>
|
||||
</data>
|
||||
<data name="InvalidJsonPatchDocument" xml:space="preserve">
|
||||
<value>The type '{0}' was malformed and could not be parsed.</value>
|
||||
</data>
|
||||
<data name="InvalidPathForArrayProperty" xml:space="preserve">
|
||||
<value>For operation '{0}', the provided path is invalid for array property at path '{1}'.</value>
|
||||
</data>
|
||||
<data name="InvalidValueForPath" xml:space="preserve">
|
||||
<value>The provided string '{0}' is an invalid path.</value>
|
||||
</data>
|
||||
<data name="InvalidValueForProperty" xml:space="preserve">
|
||||
<value>The value '{0}' is invalid for property at path '{1}'.</value>
|
||||
</data>
|
||||
<data name="NegativeIndexForArrayProperty" xml:space="preserve">
|
||||
<value>For operation '{0}' on array property at path '{1}', the index is negative.</value>
|
||||
<value>The value '{0}' is invalid for target location.</value>
|
||||
</data>
|
||||
<data name="ParameterMustMatchType" xml:space="preserve">
|
||||
<value>'{0}' must be of type '{1}'.</value>
|
||||
</data>
|
||||
<data name="PropertyCannotBeAdded" xml:space="preserve">
|
||||
<value>The property at path '{0}' could not be added.</value>
|
||||
<data name="PatchNotSupportedForArrays" xml:space="preserve">
|
||||
<value>The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.</value>
|
||||
</data>
|
||||
<data name="PropertyCannotBeRemoved" xml:space="preserve">
|
||||
<value>The property at path '{0}' could not be removed.</value>
|
||||
<data name="PatchNotSupportedForNonGenericLists" xml:space="preserve">
|
||||
<value>The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.</value>
|
||||
</data>
|
||||
<data name="PropertyDoesNotExist" xml:space="preserve">
|
||||
<value>Property does not exist at path '{0}'.</value>
|
||||
<data name="TargetLocationAtPathSegmentNotFound" xml:space="preserve">
|
||||
<value>The target location specified by path segment '{0}' was not found.</value>
|
||||
</data>
|
||||
<data name="TargetLocationNotFound" xml:space="preserve">
|
||||
<value>For operation '{0}', the target location specified by path '{1}' was not found.</value>
|
||||
</data>
|
||||
<data name="TestOperationNotSupported" xml:space="preserve">
|
||||
<value>The test operation is not supported.</value>
|
||||
|
|
|
|||
|
|
@ -22,14 +22,18 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.6.1-*",
|
||||
"Newtonsoft.Json": "9.0.1"
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"Microsoft.Extensions.ClosedGenericMatcher.Sources": {
|
||||
"type": "build",
|
||||
"version": "1.1.0-*"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard1.1": {
|
||||
"net451": {},
|
||||
"netstandard1.3": {
|
||||
"dependencies": {
|
||||
"Microsoft.CSharp": "4.3.0-*",
|
||||
"System.ComponentModel.TypeConverter": "4.3.0-*",
|
||||
"System.Runtime.Serialization.Primitives": "4.3.0-*"
|
||||
"System.Reflection.TypeExtensions": "4.1.0-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
// 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;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class DictionaryAdapterTest
|
||||
{
|
||||
[Fact]
|
||||
public void Add_KeyWhichAlreadyExists_ReplacesExistingValue()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
dictionary[nameKey] = "Mike";
|
||||
var dictionaryAdapter = new DictionaryAdapter();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, nameKey, resolver.Object, "James", out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(1, dictionary.Count);
|
||||
Assert.Equal("James", dictionary[nameKey]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_UsingCaseSensitiveKey_FailureScenario()
|
||||
{
|
||||
// Arrange
|
||||
var dictionaryAdapter = new DictionaryAdapter();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
string message = null;
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, nameKey, resolver.Object, "James", out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(1, dictionary.Count);
|
||||
Assert.Equal("James", dictionary[nameKey]);
|
||||
|
||||
// Act
|
||||
object outValue = null;
|
||||
addStatus = dictionaryAdapter.TryGet(dictionary, nameKey.ToUpper(), resolver.Object, out outValue, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Null(outValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_UsingCaseSensitiveKey_SuccessScenario()
|
||||
{
|
||||
// Arrange
|
||||
var dictionaryAdapter = new DictionaryAdapter();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
string message = null;
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, nameKey, resolver.Object, "James", out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(1, dictionary.Count);
|
||||
Assert.Equal("James", dictionary[nameKey]);
|
||||
|
||||
// Act
|
||||
object outValue = null;
|
||||
addStatus = dictionaryAdapter.TryGet(dictionary, nameKey, resolver.Object, out outValue, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal("James", outValue?.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacingExistingItem()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
dictionary.Add(nameKey, "Mike");
|
||||
var dictionaryAdapter = new DictionaryAdapter();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var replaceStatus = dictionaryAdapter.TryReplace(dictionary, nameKey, resolver.Object, "James", out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(1, dictionary.Count);
|
||||
Assert.Equal("James", dictionary[nameKey]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_NonExistingKey_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
var dictionaryAdapter = new DictionaryAdapter();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var replaceStatus = dictionaryAdapter.TryReplace(dictionary, nameKey, resolver.Object, "Mike", out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal(
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", nameKey),
|
||||
message);
|
||||
Assert.Equal(0, dictionary.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_NonExistingKey_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
var dictionaryAdapter = new DictionaryAdapter();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var removeStatus = dictionaryAdapter.TryRemove(dictionary, nameKey, resolver.Object, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(removeStatus);
|
||||
Assert.Equal(
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", nameKey),
|
||||
message);
|
||||
Assert.Equal(0, dictionary.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_RemovesFromDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
dictionary[nameKey] = "James";
|
||||
var dictionaryAdapter = new DictionaryAdapter();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var removeStatus = dictionaryAdapter.TryRemove(dictionary, nameKey, resolver.Object, out message);
|
||||
|
||||
//Assert
|
||||
Assert.True(removeStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(0, dictionary.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/NewInt' could not be added.",
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", "NewInt"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/Nested/NewInt' could not be added.",
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", "NewInt"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/Nested/NewInt' could not be added.",
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", "NewInt"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/ComplexProperty' could not be added.",
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", "ComplexProperty"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/StringProperty' could not be updated.",
|
||||
string.Format("The property at path '{0}' could not be updated.", "StringProperty"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +336,10 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/DynamicProperty/OtherProperty/IntProperty' could not be added.",
|
||||
string.Format(
|
||||
"For operation '{0}', the target location specified by path '{1}' was not found.",
|
||||
"add",
|
||||
"/DynamicProperty/OtherProperty/IntProperty"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -374,7 +377,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/baz/bat' could not be added.",
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", "baz"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -434,7 +437,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -478,30 +481,10 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/IntegerList/4', the index is larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "4"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToListAtEndWithSerialization()
|
||||
{
|
||||
var doc = new
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
// create patch
|
||||
JsonPatchDocument patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("IntegerList/3", 4);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
|
||||
|
||||
deserialized.ApplyTo(doc);
|
||||
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, doc.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToListAtBeginning()
|
||||
{
|
||||
|
|
@ -542,7 +525,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/ListOfSimpleDTO/-1/IntegerList/0' could not be added.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/ListOfSimpleDTO/20/IntegerList/0' could not be added.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "20"),
|
||||
exception.Message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/Test' could not be updated.",
|
||||
string.Format("The property at path '{0}' could not be updated.", "Test"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"The property at path '/NonExisting' could not be removed.",
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", "NonExisting"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/3', the index is larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -275,8 +275,8 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/-1', the index is negative.",
|
||||
exception.Message);
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/IntegerList/3', the index is larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/IntegerList/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -187,8 +187,8 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/3', the index is larger than the array size.",
|
||||
exception.Message);
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
||||
|
|
@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
JsonPatchDocument patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Replace("GuidValue", newGuid);
|
||||
|
||||
// serialize & deserialize
|
||||
// serialize & deserialize
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserizalized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
|
||||
|
||||
|
|
@ -45,7 +46,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
JsonPatchDocument patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Replace("GuidValue", newGuid);
|
||||
|
||||
// serialize & deserialize
|
||||
// serialize & deserialize
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserizalized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
JsonPatchDocument patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Replace("nestedobject/GuidValue", newGuid);
|
||||
|
||||
// serialize & deserialize
|
||||
// serialize & deserialize
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserizalized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
|
||||
|
||||
|
|
@ -98,7 +99,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
JsonPatchDocument patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Replace("SimpleDTO", newDTO);
|
||||
|
||||
// serialize & deserialize
|
||||
// serialize & deserialize
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
|
||||
|
||||
|
|
@ -201,6 +202,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test.Dynamic
|
|||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
|
||||
|
||||
deserialized.ApplyTo(doc);
|
||||
|
||||
Assert.Equal(new List<int>() { 4, 5, 6 }, doc.IntegerList);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,302 @@
|
|||
// 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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ListAdapterTest
|
||||
{
|
||||
[Fact]
|
||||
public void Patch_OnArrayObject_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new[] { 20, 30 };
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "0", resolver.Object, "40", out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal(
|
||||
string.Format(
|
||||
"The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.",
|
||||
targetObject.GetType().FullName),
|
||||
message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Patch_OnNonGenericListObject_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new ArrayList();
|
||||
targetObject.Add(20);
|
||||
targetObject.Add(30);
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, "40", out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal(
|
||||
string.Format(
|
||||
"The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.",
|
||||
targetObject.GetType().FullName),
|
||||
message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-1")]
|
||||
[InlineData("-2")]
|
||||
[InlineData("2")]
|
||||
[InlineData("3")]
|
||||
public void Patch_WithOutOfBoundsIndex_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<string>() { "James", "Mike" };
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, "40", out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", position),
|
||||
message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("_")]
|
||||
[InlineData("blah")]
|
||||
public void Patch_WithInvalidPositionFormat_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<string>() { "James", "Mike" };
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, "40", out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal(
|
||||
string.Format("The path segment '{0}' is invalid for an array index.", position),
|
||||
message);
|
||||
}
|
||||
|
||||
public static TheoryData<List<int>, List<int>> AppendAtEndOfListData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<List<int>, List<int>>()
|
||||
{
|
||||
{
|
||||
new List<int>() { },
|
||||
new List<int>() { 20 }
|
||||
},
|
||||
{
|
||||
new List<int>() { 5, 10 },
|
||||
new List<int>() { 5, 10, 20 }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AppendAtEndOfListData))]
|
||||
public void Add_Appends_AtTheEnd(List<int> targetObject, List<int> expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, "20", out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected.Count, targetObject.Count);
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_NullObject_ToReferenceTypeListWorks()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
var targetObject = new List<string>() { "James", "Mike" };
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, value: null, errorMessage: out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(3, targetObject.Count);
|
||||
Assert.Equal(new List<string>() { "James", "Mike", null }, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_NonCompatibleType_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = (new List<int>() { 10, 20 }).AsReadOnly();
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, "James", out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal(string.Format("The value '{0}' is invalid for target location.", "James"), message);
|
||||
}
|
||||
|
||||
public static TheoryData<IList, object, string, IList> AddingDifferentComplexTypeWorksData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<IList, object, string, IList>()
|
||||
{
|
||||
{
|
||||
new List<string>() { },
|
||||
"a",
|
||||
"-",
|
||||
new List<string>() { "a" }
|
||||
},
|
||||
{
|
||||
new List<string>() { "a", "b" },
|
||||
"c",
|
||||
"-",
|
||||
new List<string>() { "a", "b", "c" }
|
||||
},
|
||||
{
|
||||
new List<string>() { "a", "b" },
|
||||
"c",
|
||||
"0",
|
||||
new List<string>() { "c", "a", "b" }
|
||||
},
|
||||
{
|
||||
new List<string>() { "a", "b" },
|
||||
"c",
|
||||
"1",
|
||||
new List<string>() { "a", "c", "b" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AddingDifferentComplexTypeWorksData))]
|
||||
public void Add_DifferentComplexTypeWorks(IList targetObject, object value, string position, IList expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, value, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected.Count, targetObject.Count);
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_NonCompatibleType_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = (new List<int>() { 10, 20 }).AsReadOnly();
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var replaceStatus = listAdapter.TryReplace(targetObject, "-", resolver.Object, "James", out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal(
|
||||
string.Format("The value '{0}' is invalid for target location.", "James"),
|
||||
message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_ReplacesValue_AtTheEnd()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var replaceStatus = listAdapter.TryReplace(targetObject, "-", resolver.Object, "30", out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(new List<int>() { 10, 30 }, targetObject);
|
||||
}
|
||||
|
||||
public static TheoryData<string, List<int>> ReplacesValuesAtPositionData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string, List<int>>()
|
||||
{
|
||||
{
|
||||
"0",
|
||||
new List<int>() { 30, 20 }
|
||||
},
|
||||
{
|
||||
"1",
|
||||
new List<int>() { 10, 30 }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReplacesValuesAtPositionData))]
|
||||
public void Replace_ReplacesValue_AtGivenPosition(string position, List<int> expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var replaceStatus = listAdapter.TryReplace(targetObject, position, resolver.Object, "30", out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// 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.AspNetCore.JsonPatch.Test
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class NestedDTO
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
|||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Test
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class NestedObjectTests
|
||||
{
|
||||
|
|
@ -386,8 +386,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/4', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "4"),
|
||||
exception.Message);
|
||||
|
||||
}
|
||||
|
|
@ -417,8 +416,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/4', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "4"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -445,8 +443,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
//Assert
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/4', the index is larger than " +
|
||||
"the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "4"),
|
||||
logger.ErrorMessage);
|
||||
|
||||
}
|
||||
|
|
@ -470,7 +467,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -499,7 +496,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -527,7 +524,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
//Assert
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
|
|
@ -697,8 +694,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -727,8 +723,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -754,8 +749,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/simpledto/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
|
|
@ -777,7 +771,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/-1', the index is negative.", exception.Message);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -804,7 +800,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
{
|
||||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/-1', the index is negative.", exception.Message);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -829,7 +827,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/-1', the index is negative.", logger.ErrorMessage);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1239,8 +1239,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -1269,8 +1268,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -1298,8 +1296,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/simpledto/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
|
|
@ -1321,7 +1318,8 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal("For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -1347,7 +1345,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -1375,7 +1373,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
|||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Test
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||
{
|
||||
public class ObjectAdapterTests
|
||||
{
|
||||
|
|
@ -157,8 +157,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/4', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "4"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -181,8 +180,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/4', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "4"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -206,54 +204,10 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/4', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "4"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToListAtEnd()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTO()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTO>();
|
||||
patchDoc.Add<int>(o => o.IntegerList, 4, 3);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, doc.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToListAtEndWithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTO()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTO>();
|
||||
patchDoc.Add<int>(o => o.IntegerList, 4, 3);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleDTO>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, doc.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToListAtBeginning()
|
||||
{
|
||||
|
|
@ -313,7 +267,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +290,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -359,7 +313,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
|
|
@ -508,8 +462,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -532,8 +485,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -557,8 +509,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"For operation 'remove' on array property at path '/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
|
|
@ -577,7 +528,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal("For operation 'remove' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -598,7 +551,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
||||
Assert.Equal("For operation 'remove' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -621,7 +576,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
|
||||
|
||||
// Assert
|
||||
Assert.Equal("For operation 'remove' on array property at path '/integerlist/-1', the index is negative.", logger.ErrorMessage);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1123,8 +1080,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
patchDoc.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -1149,8 +1105,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/integerlist/3', the index is " +
|
||||
"larger than the array size.",
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "3"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -1172,7 +1127,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
{
|
||||
patchDoc.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal("For operation 'replace' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1196,7 +1153,9 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
{
|
||||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal("For operation 'replace' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", "-1"),
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1733,5 +1692,577 @@ namespace Microsoft.AspNetCore.JsonPatch.Test
|
|||
Assert.Equal(0, doc.IntegerValue);
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.IntegerList);
|
||||
}
|
||||
|
||||
private class Class6
|
||||
{
|
||||
public IDictionary<string, int> DictionaryOfStringToInteger { get; } = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_WhenDictionary_ValueIsNonObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new Class6();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("/DictionaryOfStringToInteger/three", 3);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
|
||||
Assert.Equal(2, model.DictionaryOfStringToInteger["two"]);
|
||||
Assert.Equal(3, model.DictionaryOfStringToInteger["three"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_WhenDictionary_ValueIsNonObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new Class6();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("/DictionaryOfStringToInteger/two");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_WhenDictionary_ValueIsNonObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new Class6();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("/DictionaryOfStringToInteger/two", 20);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
|
||||
Assert.Equal(20, model.DictionaryOfStringToInteger["two"]);
|
||||
}
|
||||
|
||||
private class Customer
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Address Address { get; set; }
|
||||
}
|
||||
|
||||
private class Address
|
||||
{
|
||||
public string City { get; set; }
|
||||
}
|
||||
|
||||
private class Class8
|
||||
{
|
||||
public IDictionary<string, Customer> DictionaryOfStringToCustomer { get; } = new Dictionary<string, Customer>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_WhenDictionary_ValueAPocoType_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "key1";
|
||||
var value1 = new Customer() { Name = "Jamesss" };
|
||||
var key2 = "key2";
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new Class8();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace($"/DictionaryOfStringToCustomer/{key1}/Name", "James");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||
Assert.NotNull(actualValue1);
|
||||
Assert.Equal("James", actualValue1.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_WhenDictionary_ValueAPocoType_Succeeds_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "key1";
|
||||
var value1 = new Customer() { Name = "Jamesss" };
|
||||
var key2 = "key2";
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new Class8();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace($"/DictionaryOfStringToCustomer/{key1}/Name", "James");
|
||||
var serialized = JsonConvert.SerializeObject(patchDocument);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<Class8>>(serialized);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||
Assert.NotNull(actualValue1);
|
||||
Assert.Equal("James", actualValue1.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_DeepNested_DictionaryValue_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "key1";
|
||||
var value1 = new Customer() { Name = "Jamesss" };
|
||||
var key2 = "key2";
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new Class8();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace($"/DictionaryOfStringToCustomer/{key1}/Name", "James");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||
Assert.NotNull(actualValue1);
|
||||
Assert.Equal("James", actualValue1.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_DeepNested_DictionaryValue_Succeeds_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "key1";
|
||||
var value1 = new Customer() { Name = "James", Address = new Address { City = "Redmond" } };
|
||||
var key2 = "key2";
|
||||
var value2 = new Customer() { Name = "Mike", Address = new Address { City = "Seattle" } };
|
||||
var model = new Class8();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace($"/DictionaryOfStringToCustomer/{key1}/Address/City", "Bellevue");
|
||||
var serialized = JsonConvert.SerializeObject(patchDocument);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<Class8>>(serialized);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||
Assert.NotNull(actualValue1);
|
||||
Assert.Equal("James", actualValue1.Name);
|
||||
var address = actualValue1.Address;
|
||||
Assert.NotNull(address);
|
||||
Assert.Equal("Bellevue", address.City);
|
||||
}
|
||||
|
||||
class Class9
|
||||
{
|
||||
public List<string> StringList { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToNonIntegerListAtEnd()
|
||||
{
|
||||
// Arrange
|
||||
var model = new Class9()
|
||||
{
|
||||
StringList = new List<string>()
|
||||
};
|
||||
model.StringList.Add("string1");
|
||||
model.StringList.Add("string2");
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/StringList/0", "string3");
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<string>() { "string3", "string1", "string2" }, model.StringList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_OnPOCO_WithNullPropertyValue_ShouldAddPropertyValue()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTO()
|
||||
{
|
||||
StringProperty = null
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTO>();
|
||||
patchDoc.Add<string>(o => o.StringProperty, "B");
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", doc.StringProperty);
|
||||
}
|
||||
|
||||
private class Class1
|
||||
{
|
||||
public IDictionary<string, string> USStates { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_OnDictionaryProperty_ShouldAddKeyValueMember()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Washington";
|
||||
var model = new Class1();
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/USStates/WA", expected);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.USStates.Count);
|
||||
Assert.Equal(expected, model.USStates["WA"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_OnDictionaryProperty_ShouldAddKeyValueMember_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Washington";
|
||||
var model = new Class1();
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/USStates/WA", expected);
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<Class1>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.USStates.Count);
|
||||
Assert.Equal(expected, model.USStates["WA"]);
|
||||
}
|
||||
|
||||
private class Class2
|
||||
{
|
||||
public Class1 Class1Property { get; set; } = new Class1();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_OnDictionaryPropertyDeeplyNested_ShouldAddKeyValueMember()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Washington";
|
||||
var model = new Class2();
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/Class1Property/USStates/WA", expected);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.Class1Property.USStates.Count);
|
||||
Assert.Equal(expected, model.Class1Property.USStates["WA"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_OnDictionaryPropertyDeeplyNested_ShouldAddKeyValueMember_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Washington";
|
||||
var model = new Class2();
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/Class1Property/USStates/WA", expected);
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<Class2>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.Class1Property.USStates.Count);
|
||||
Assert.Equal(expected, model.Class1Property.USStates["WA"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_OnDictionaryObjectDirectly_ShouldAddKeyValueMember()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Washington";
|
||||
var model = new Dictionary<string, string>();
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/WA", expected);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.Count);
|
||||
Assert.Equal(expected, model["WA"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMember_OnDictionaryObjectDirectly_ShouldAddKeyValueMember_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "Washington";
|
||||
var model = new Dictionary<string, string>();
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/WA", expected);
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<Dictionary<string, string>>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.Count);
|
||||
Assert.Equal(expected, model["WA"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToListDirectly_ShouldAppendValue()
|
||||
{
|
||||
// Arrange
|
||||
var model = new List<int>() { 1, 2, 3 };
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/-", value: 4);
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<List<int>>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToListDirectly_ShouldAppendValue_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var model = new List<int>() { 1, 2, 3 };
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/-", value: 4);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToListDirectly_ShouldAddValue_AtSuppliedPosition()
|
||||
{
|
||||
// Arrange
|
||||
var model = new List<int>() { 1, 2, 3 };
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/0", value: 4);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToListDirectly_ShouldAddValue_AtSuppliedPosition_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var model = new List<int>() { 1, 2, 3 };
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/0", value: 4);
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<List<int>>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, model);
|
||||
}
|
||||
|
||||
class ListOnDictionary
|
||||
{
|
||||
public IDictionary<string, List<int>> NamesAndBadgeIds { get; set; } = new Dictionary<string, List<int>>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToList_OnDictionary_ShouldAddValue_AtSuppliedPosition()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ListOnDictionary();
|
||||
model.NamesAndBadgeIds["James"] = new List<int>();
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/NamesAndBadgeIds/James/-", 200);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
var list = model.NamesAndBadgeIds["James"];
|
||||
Assert.NotNull(list);
|
||||
Assert.Equal(new List<int>() { 200 }, list);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToList_OnPOCO_ShouldAddValue_AtSuppliedPosition()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTO()
|
||||
{
|
||||
IntegerIList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTO>();
|
||||
patchDoc.Add<int>(o => o.IntegerIList, 4, 0);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.IntegerIList);
|
||||
}
|
||||
|
||||
class Class3
|
||||
{
|
||||
public SimpleDTO SimpleDTOProperty { get; set; } = new SimpleDTO();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToDeeplyNestedListProperty_OnPOCO_ShouldAddValue_AtSuppliedPosition()
|
||||
{
|
||||
// Arrange
|
||||
var model = new Class3()
|
||||
{
|
||||
SimpleDTOProperty = new SimpleDTO()
|
||||
{
|
||||
IntegerIList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
var patchDoc = new JsonPatchDocument<Class3>();
|
||||
patchDoc.Add<int>(o => o.SimpleDTOProperty.IntegerIList, value: 4, position: 0);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, model.SimpleDTOProperty.IntegerIList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddElement_ToDeeplyNestedListProperty_OnPOCO_ShouldAddValue_AtSuppliedPosition_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var model = new Class3()
|
||||
{
|
||||
SimpleDTOProperty = new SimpleDTO()
|
||||
{
|
||||
IntegerIList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
var patchDoc = new JsonPatchDocument<Class3>();
|
||||
patchDoc.Add<int>(o => o.SimpleDTOProperty.IntegerIList, value: 4, position: 0);
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<Class3>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, model.SimpleDTOProperty.IntegerIList);
|
||||
}
|
||||
|
||||
class Class4
|
||||
{
|
||||
public int IntegerProperty { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_OnNonReferenceType_POCOProperty_ShouldSetDefaultValue()
|
||||
{
|
||||
// Arrange
|
||||
var model = new Class4()
|
||||
{
|
||||
IntegerProperty = 10
|
||||
};
|
||||
var patchDoc = new JsonPatchDocument<Class4>();
|
||||
patchDoc.Remove<int>(o => o.IntegerProperty);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, model.IntegerProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_OnNonReferenceType_POCOProperty_ShouldSetDefaultValue_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTO()
|
||||
{
|
||||
StringProperty = "A"
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTO>();
|
||||
patchDoc.Remove<string>(o => o.StringProperty);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleDTO>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(null, doc.StringProperty);
|
||||
}
|
||||
|
||||
class ClassWithPrivateProperties
|
||||
{
|
||||
public string Name { get; set; }
|
||||
private int Age { get; set; } = 45;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_OnPrivateProperties_FailesWithException()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new ClassWithPrivateProperties()
|
||||
{
|
||||
Name = "James"
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument();
|
||||
patchDoc.Add("/Age", 30);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => patchDoc.ApplyTo(doc));
|
||||
Assert.Equal(
|
||||
string.Format("The target location specified by path segment '{0}' was not found.", "Age"),
|
||||
exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ObjectVisitorTest
|
||||
{
|
||||
private class Class1
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public IList<string> States { get; set; } = new List<string>();
|
||||
public IDictionary<string, string> Countries = new Dictionary<string, string>();
|
||||
public dynamic Items { get; set; } = new ExpandoObject();
|
||||
}
|
||||
|
||||
private class Class1Nested
|
||||
{
|
||||
public List<Class1> Customers { get; set; } = new List<Class1>();
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsListAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/States/-", model.States };
|
||||
yield return new object[] { model.States, "/-", model.States };
|
||||
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/States/-", nestedModel.Customers[0].States };
|
||||
yield return new object[] { nestedModel, "/Customers/0/States/0", nestedModel.Customers[0].States };
|
||||
yield return new object[] { nestedModel.Customers, "/0/States/-", nestedModel.Customers[0].States };
|
||||
yield return new object[] { nestedModel.Customers[0], "/States/-", nestedModel.Customers[0].States };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsListAdapterData))]
|
||||
public void Visit_ValidPathToArray_ReturnsListAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.IsType<ListAdapter>(adapter);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsDictionaryAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/Countries/USA", model.Countries };
|
||||
yield return new object[] { model.Countries, "/USA", model.Countries };
|
||||
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/Countries/USA", nestedModel.Customers[0].Countries };
|
||||
yield return new object[] { nestedModel.Customers, "/0/Countries/USA", nestedModel.Customers[0].Countries };
|
||||
yield return new object[] { nestedModel.Customers[0], "/Countries/USA", nestedModel.Customers[0].Countries };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsDictionaryAdapterData))]
|
||||
public void Visit_ValidPathToDictionary_ReturnsDictionaryAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.IsType<DictionaryAdapter>(adapter);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsExpandoAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/Items/Name", model.Items };
|
||||
yield return new object[] { model.Items, "/Name", model.Items };
|
||||
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/Items/Name", nestedModel.Customers[0].Items };
|
||||
yield return new object[] { nestedModel.Customers, "/0/Items/Name", nestedModel.Customers[0].Items };
|
||||
yield return new object[] { nestedModel.Customers[0], "/Items/Name", nestedModel.Customers[0].Items };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsExpandoAdapterData))]
|
||||
public void Visit_ValidPathToExpandoObject_ReturnsExpandoAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.IsType<ExpandoObjectAdapter>(adapter);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsPocoAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/Name", model };
|
||||
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/Name", nestedModel.Customers[0] };
|
||||
yield return new object[] { nestedModel.Customers, "/0/Name", nestedModel.Customers[0] };
|
||||
yield return new object[] { nestedModel.Customers[0], "/Name", nestedModel.Customers[0] };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsPocoAdapterData))]
|
||||
public void Visit_ValidPath_ReturnsExpandoAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.IsType<PocoAdapter>(adapter);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0")]
|
||||
[InlineData("-1")]
|
||||
public void Visit_InvalidIndexToArray_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{position}/States/-"), new DefaultContractResolver());
|
||||
var automobileDepartment = new Class1Nested();
|
||||
object targetObject = automobileDepartment;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Equal(
|
||||
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", position),
|
||||
message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-")]
|
||||
[InlineData("foo")]
|
||||
public void Visit_InvalidIndexFormatToArray_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{position}/States/-"), new DefaultContractResolver());
|
||||
var automobileDepartment = new Class1Nested();
|
||||
object targetObject = automobileDepartment;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Equal(string.Format(
|
||||
"The path segment '{0}' is invalid for an array index.", position),
|
||||
message);
|
||||
}
|
||||
|
||||
// The adapter takes care of the responsibility of validating the final segment
|
||||
[Fact]
|
||||
public void Visit_DoesNotValidate_FinalPathSegment()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/NonExisting"), new DefaultContractResolver());
|
||||
var model = new Class1();
|
||||
object targetObject = model;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.IsType<PocoAdapter>(adapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ObjectVisitorTest
|
||||
{
|
||||
private class Class1
|
||||
{
|
||||
public IList<string> States { get; set; } = new List<string>();
|
||||
public IDictionary<string, string> Countries = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_ValidPathToArray_ReturnsListAdapter()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath("/States/-"), new DefaultContractResolver());
|
||||
var model = new Class1();
|
||||
object targetObject = model;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(model.States, targetObject);
|
||||
Assert.IsType<ListAdapter>(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_ValidPathToDictionary_ReturnsDictionaryAdapter()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath("/Countries/USA"), new DefaultContractResolver());
|
||||
var model = new Class1();
|
||||
object targetObject = model;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(model.Countries, targetObject);
|
||||
Assert.IsType<ListAdapter>(adapter);
|
||||
}
|
||||
|
||||
private class AutomobileDepartment
|
||||
{
|
||||
public List<Class1> Customers { get; set; } = new List<Class1>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_ValidPathToArray_ReturnsListAdapter_ForDeepNestedPath()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath("/Customers/0/States/-"), new DefaultContractResolver());
|
||||
var customer = new Class1();
|
||||
var automobileDepartment = new AutomobileDepartment();
|
||||
automobileDepartment.Customers.Add(customer);
|
||||
object targetObject = automobileDepartment;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(customer.States, targetObject);
|
||||
Assert.IsType<ListAdapter>(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_InvalidPathToArray_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var invalidIndex = 2;
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{invalidIndex}/States/-"), new DefaultContractResolver());
|
||||
var automobileDepartment = new AutomobileDepartment();
|
||||
object targetObject = automobileDepartment;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Equal(string.Format(ErrorMessageFormats.IndexOutOfBounds, invalidIndex), message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_DoesNotValidate_FinalPathSegment()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/NonExisting"), new DefaultContractResolver());
|
||||
var model = new Class1();
|
||||
object targetObject = model;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Equal(string.Format(ErrorMessageFormats.TargetLocationAtPathSegmentNotFound, "NonExisting"), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ObjectVisitorTest
|
||||
{
|
||||
private class Class1
|
||||
{
|
||||
public IList<string> States { get; set; } = new List<string>();
|
||||
public IDictionary<string, string> Countries = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsListAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/States/-", model.States };
|
||||
|
||||
yield return new object[] { model.States, "/-", model.States };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsListAdapterData))]
|
||||
public void Visit_ValidPathToArray_ReturnsListAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.IsType<ListAdapter>(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_ValidPathToDictionary_ReturnsDictionaryAdapter()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath("/Countries/USA"), new DefaultContractResolver());
|
||||
var model = new Class1();
|
||||
object targetObject = model;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(model.Countries, targetObject);
|
||||
Assert.IsType<DictionaryAdapter>(adapter);
|
||||
}
|
||||
|
||||
private class AutomobileDepartment
|
||||
{
|
||||
public List<Class1> Customers { get; set; } = new List<Class1>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_ValidPathToArray_ReturnsListAdapter_ForDeepNestedPath()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath("/Customers/0/States/-"), new DefaultContractResolver());
|
||||
var customer = new Class1();
|
||||
var automobileDepartment = new AutomobileDepartment();
|
||||
automobileDepartment.Customers.Add(customer);
|
||||
object targetObject = automobileDepartment;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(customer.States, targetObject);
|
||||
Assert.IsType<ListAdapter>(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_InvalidPathToArray_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var invalidIndex = 2;
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{invalidIndex}/States/-"), new DefaultContractResolver());
|
||||
var automobileDepartment = new AutomobileDepartment();
|
||||
object targetObject = automobileDepartment;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Equal(string.Format(ErrorMessageFormats.IndexOutOfBounds, invalidIndex), message);
|
||||
}
|
||||
|
||||
// The adapter takes care of the responsibility of validating the final segment
|
||||
[Fact]
|
||||
public void Visit_DoesNotValidate_FinalPathSegment()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/NonExisting"), new DefaultContractResolver());
|
||||
var model = new Class1();
|
||||
object targetObject = model;
|
||||
IAdapter adapter = null;
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out adapter, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.IsType<PocoAdapter>(adapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Test
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class SimpleDTO
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Test
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class SimpleDTOWithNestedDTO
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// 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.AspNetCore.JsonPatch.Test
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class TestErrorLogger<T> where T : class
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue