Пример #1
0
        /// <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,
            object objectToGetValueFrom,
            Operation operationToReport)
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (objectToGetValueFrom == null)
            {
                throw new ArgumentNullException(nameof(objectToGetValueFrom));
            }

            if (operationToReport == null)
            {
                throw new ArgumentNullException(nameof(operationToReport));
            }

            // get path result
            var pathResult = GetActualPropertyPath(
                location,
                objectToGetValueFrom,
                operationToReport);

            if (pathResult == null)
            {
                return(new GetValueResult(null, true));
            }

            var getAtEndOfList       = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var treeAnalysisResult = new ObjectTreeAnalysisResult(
                objectToGetValueFrom,
                actualPathToProperty,
                ContractResolver);

            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 (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));
                }
            }
            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));
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Add is used by various operations (eg: add, copy, ...), yet through different operations;
        /// This method allows code reuse yet reporting the correct operation on error
        /// </summary>
        private void Add(
            string path,
            object value,
            object objectToApplyTo,
            Operation operationToReport)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (objectToApplyTo == null)
            {
                throw new ArgumentNullException(nameof(objectToApplyTo));
            }

            if (operationToReport == null)
            {
                throw new ArgumentNullException(nameof(operationToReport));
            }

            // 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.

            // get path result
            var pathResult = GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport);

            if (pathResult == null)
            {
                return;
            }

            var appendList           = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var treeAnalysisResult = new ObjectTreeAnalysisResult(
                objectToApplyTo,
                actualPathToProperty,
                ContractResolver);

            if (!treeAnalysisResult.IsValidPathForAdd)
            {
                LogError(new JsonPatchError(
                             objectToApplyTo,
                             operationToReport,
                             Resources.FormatPropertyCannotBeAdded(path)));
                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);
                }
            }
        }
Пример #3
0
        /// <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
        /// typed value to whatever method follows.
        /// </summary>
        private RemovedPropertyTypeResult Remove(string path, object objectToApplyTo, Operation operationToReport)
        {
            // get path result
            var pathResult = GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport);

            if (pathResult == null)
            {
                return(new RemovedPropertyTypeResult(null, true));
            }

            var removeFromList       = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var treeAnalysisResult = new ObjectTreeAnalysisResult(
                objectToApplyTo,
                actualPathToProperty,
                ContractResolver);

            if (!treeAnalysisResult.IsValidPathForRemove)
            {
                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));
                }
            }
        }
Пример #4
0
        /// <summary>
        /// Add is used by various operations (eg: add, copy, ...), yet through different operations;
        /// This method allows code reuse yet reporting the correct operation on error
        /// </summary>
        private void Add(string path, object value, object objectToApplyTo, Operation operationToReport)
        {
            // add, in this implementation, CAN add properties if the container is an
            // ExpandoObject.

            // 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.

            // get path result
            var pathResult = PropertyHelpers.GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport,
                true);

            var appendList           = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver);

            if (result.UseDynamicLogic)
            {
                if (result.IsValidPathForAdd)
                {
                    if (result.Container.ContainsCaseInsensitiveKey(result.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 typeOfPathProperty = result.Container
                                                     .GetValueForCaseInsensitiveKey(result.PropertyPathInParent).GetType();

                            if (PropertyHelpers.IsNonStringArray(typeOfPathProperty))
                            {
                                // now, get the generic type of the enumerable
                                var genericTypeOfArray = PropertyHelpers.GetIListType(typeOfPathProperty);
                                var conversionResult   = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);

                                if (!conversionResult.CanBeConverted)
                                {
                                    throw new JsonPatchException(
                                              new JsonPatchError(
                                                  objectToApplyTo,
                                                  operationToReport,
                                                  string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)),
                                              422);
                                }

                                // get value (it can be cast, we just checked that)
                                // var array = containerDictionary[finalPath] as IList;

                                var array = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent) as IList;

                                if (appendList)
                                {
                                    array.Add(conversionResult.ConvertedInstance);
                                    result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent, array);
                                }
                                else
                                {
                                    // specified index must not be greater than
                                    // the amount of items in the array
                                    if (positionAsInteger > array.Count)
                                    {
                                        throw new JsonPatchException(
                                                  new JsonPatchError(
                                                      objectToApplyTo,
                                                      operationToReport,
                                                      string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                                  422);
                                    }

                                    array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
                                    result.Container.SetValueForCaseInsensitiveKey(
                                        result.PropertyPathInParent, array);
                                }
                            }
                            else
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)),
                                          422);
                            }
                        }
                        else
                        {
                            // get the actual type
                            var typeOfPathProperty = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent).GetType();

                            // can the value be converted to the actual type?
                            var conversionResultTuple =
                                PropertyHelpers.ConvertToActualType(typeOfPathProperty, value);

                            // conversion successful
                            if (conversionResultTuple.CanBeConverted)
                            {
                                result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent,
                                                                               conversionResultTuple.ConvertedInstance);
                            }
                            else
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided value is invalid for property type at location path: {0}", path)),
                                          422);
                            }
                        }
                    }
                    else
                    {
                        // New property - add it.
                        result.Container.Add(result.PropertyPathInParent, value);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: cannot add to the parent of the property at location path: {0}.  To be able to dynamically add properties, the parent must be an ExpandoObject.", path)),
                              422);
                }
            }
            else
            {
                if (!result.IsValidPathForAdd)
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: the provided path is invalid: {0}.", path)),
                              422);
                }

                // If it' an array, add to that array.  If it's not, we replace.

                // is the path an array (but not a string (= char[]))?  In this case,
                // the path must end with "/position" or "/-", which we already determined before.

                var patchProperty = result.JsonPatchProperty;

                if (appendList || positionAsInteger > -1)
                {
                    if (PropertyHelpers.IsNonStringArray(patchProperty.Property.PropertyType))
                    {
                        // now, get the generic type of the IList<> from Property type.
                        var genericTypeOfArray = PropertyHelpers.GetIListType(patchProperty.Property.PropertyType);

                        var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);

                        if (!conversionResult.CanBeConverted)
                        {
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToApplyTo,
                                          operationToReport,
                                          string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)),
                                      422);
                        }

                        if (patchProperty.Property.Readable)
                        {
                            var array = (IList)patchProperty.Property.ValueProvider
                                        .GetValue(patchProperty.Parent);

                            if (appendList)
                            {
                                array.Add(conversionResult.ConvertedInstance);
                            }
                            else
                            {
                                // specified index must not be greater than the amount of items in the
                                // array
                                if (positionAsInteger <= array.Count)
                                {
                                    array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
                                }
                                else
                                {
                                    throw new JsonPatchException(
                                              new JsonPatchError(
                                                  objectToApplyTo,
                                                  operationToReport,
                                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                              422);
                                }
                            }
                        }
                        else
                        {
                            // cannot read the property
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToApplyTo,
                                          operationToReport,
                                          string.Format("Patch failed: cannot get property value at path {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                      422);
                        }
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)),
                                  422);
                    }
                }
                else
                {
                    var conversionResultTuple = PropertyHelpers.ConvertToActualType(
                        patchProperty.Property.PropertyType,
                        value);

                    if (conversionResultTuple.CanBeConverted)
                    {
                        if (patchProperty.Property.Writable)
                        {
                            patchProperty.Property.ValueProvider.SetValue(
                                patchProperty.Parent,
                                conversionResultTuple.ConvertedInstance);
                        }
                        else
                        {
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToApplyTo,
                                          operationToReport,
                                          string.Format("Patch failed: property at path location cannot be set: {0}.  Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)),
                                      422);
                        }
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: property value cannot be converted to type of path location {0}.", path)),
                                  422);
                    }
                }
            }
        }
Пример #5
0
        private GetValueResult GetValueAtLocation(string location, object objectToGetValueFrom, Operation operationToReport)
        {
            // get value from "objectToGetValueFrom" at location "location"
            object valueAtLocation = null;

            var pathResult = PropertyHelpers.GetActualPropertyPath(
                location,
                objectToGetValueFrom,
                operationToReport, false);

            var positionAsInteger  = pathResult.NumericEnd;
            var actualFromProperty = pathResult.PathToProperty;

            // first, analyze the tree.
            var result = new ObjectTreeAnalysisResult(objectToGetValueFrom, actualFromProperty, ContractResolver);

            if (result.UseDynamicLogic)
            {
                // find the property
                if (result.Container.ContainsCaseInsensitiveKey(result.PropertyPathInParent))
                {
                    if (positionAsInteger > -1)
                    {
                        // get the actual type
                        var typeOfPathProperty = result.Container
                                                 .GetValueForCaseInsensitiveKey(result.PropertyPathInParent).GetType();

                        if (PropertyHelpers.IsNonStringArray(typeOfPathProperty))
                        {
                            // now, get the generic type of the enumerable
                            var genericTypeOfArray = PropertyHelpers.GetIListType(typeOfPathProperty);

                            // get value
                            var array = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent) as IList;

                            if (positionAsInteger >= array.Count)
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToGetValueFrom,
                                              operationToReport,
                                              string.Format("Patch failed: property at location from: {0} does not exist", location)),
                                          422);
                            }

                            valueAtLocation = array[positionAsInteger];
                        }
                        else
                        {
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToGetValueFrom,
                                          operationToReport,
                                          string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array", location)),
                                      422);
                        }
                    }
                    else
                    {
                        // get the value
                        valueAtLocation =
                            result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToGetValueFrom,
                                  operationToReport,
                                  string.Format("Patch failed: property at location from: {0} does not exist.", location)),
                              422);
                }
            }
            else
            {
                // not dynamic.
                var patchProperty = result.JsonPatchProperty;

                // is the path an array (but not a string (= char[]))?  In this case,
                // the path must end with "/position" or "/-", which we already determined before.
                if (positionAsInteger > -1)
                {
                    if (PropertyHelpers.IsNonStringArray(patchProperty.Property.PropertyType))
                    {
                        // now, get the generic type of the enumerable
                        if (patchProperty.Property.Readable)
                        {
                            var array = (IList)patchProperty.Property.ValueProvider
                                        .GetValue(patchProperty.Parent);

                            if (positionAsInteger >= array.Count)
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToGetValueFrom,
                                              operationToReport,
                                              string.Format("Patch failed: property at location from: {0} does not exist", location)),
                                          422);
                            }

                            valueAtLocation = array[positionAsInteger];
                        }
                        else
                        {
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToGetValueFrom,
                                          operationToReport,
                                          string.Format("Patch failed: cannot get property at location from from: {0}. Possible cause: the property doesn't have an accessible getter.", location)),
                                      422);
                        }
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToGetValueFrom,
                                      operationToReport,
                                      string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array", location)),
                                  422);
                    }
                }
                else
                {
                    if (!patchProperty.Property.Readable)
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToGetValueFrom,
                                      operationToReport,
                                      string.Format("Patch failed: cannot get property at location from from: {0}. Possible cause: the property doesn't have an accessible getter.", location)),
                                  422);
                    }
                    valueAtLocation = patchProperty.Property.ValueProvider
                                      .GetValue(patchProperty.Parent);
                }
            }
            return(new GetValueResult(valueAtLocation, false));
        }
Пример #6
0
        /// <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
        /// typed value to whatever method follows.
        /// </summary>
        private RemovedPropertyTypeResult Remove(string path, object objectToApplyTo, Operation operationToReport)
        {
            // remove, in this implementation, CAN remove properties if the container is an
            // ExpandoObject.
            var pathResult = PropertyHelpers.GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport,
                true);

            var removeFromList       = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver);

            if (result.UseDynamicLogic)
            {
                if (result.IsValidPathForRemove)
                {
                    // 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 valueOfPathProperty = result.Container
                                                  .GetValueForCaseInsensitiveKey(result.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 (valueOfPathProperty == null)
                        {
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToApplyTo,
                                          operationToReport,
                                          string.Format("Patch failed: cannot determine array property type at location path: {0}.", path)),
                                      422);
                        }

                        var typeOfPathProperty = valueOfPathProperty.GetType();


                        if (PropertyHelpers.IsNonStringArray(typeOfPathProperty))
                        {
                            // now, get the generic type of the enumerable
                            var genericTypeOfArray = PropertyHelpers.GetIListType(typeOfPathProperty);

                            var array = (IList)result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent);

                            if (removeFromList)
                            {
                                if (array.Count == 0)
                                {
                                    // if the array is empty, we should throw an error
                                    throw new JsonPatchException(
                                              new JsonPatchError(
                                                  objectToApplyTo,
                                                  operationToReport,
                                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                              422);
                                }

                                array.RemoveAt(array.Count - 1);
                                result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent, array);

                                // return the type of the value that has been removed.
                                return(new RemovedPropertyTypeResult(genericTypeOfArray, false));
                            }
                            else
                            {
                                if (positionAsInteger >= array.Count)
                                {
                                    throw new JsonPatchException(
                                              new JsonPatchError(
                                                  objectToApplyTo,
                                                  operationToReport,
                                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                              422);
                                }

                                array.RemoveAt(positionAsInteger);
                                result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent, array);

                                // return the type of the value that has been removed.
                                return(new RemovedPropertyTypeResult(genericTypeOfArray, false));
                            }
                        }
                        else
                        {
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToApplyTo,
                                          operationToReport,
                                          string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)),
                                      422);
                        }
                    }
                    else
                    {
                        // get the property
                        var getResult = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent);

                        // remove the property
                        result.Container.RemoveValueForCaseInsensitiveKey(result.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
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: cannot remove property at location path: {0}.  To be able to dynamically remove properties, the parent must be an ExpandoObject.", path)),
                              422);
                }
            }
            else
            {
                // not dynamic
                if (!result.IsValidPathForRemove)
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: the provided path is invalid: {0}.", path)),
                              422);
                }

                var patchProperty = result.JsonPatchProperty;

                if (removeFromList || positionAsInteger > -1)
                {
                    if (PropertyHelpers.IsNonStringArray(patchProperty.Property.PropertyType))
                    {
                        // now, get the generic type of the IList<> from Property type.
                        var genericTypeOfArray = PropertyHelpers.GetIListType(patchProperty.Property.PropertyType);

                        if (patchProperty.Property.Readable)
                        {
                            var array = (IList)patchProperty.Property.ValueProvider
                                        .GetValue(patchProperty.Parent);

                            if (removeFromList)
                            {
                                if (array.Count == 0)
                                {
                                    // if the array is empty, we should throw an error
                                    throw new JsonPatchException(
                                              new JsonPatchError(
                                                  objectToApplyTo,
                                                  operationToReport,
                                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                              422);
                                }

                                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)
                                {
                                    throw new JsonPatchException(
                                              new JsonPatchError(
                                                  objectToApplyTo,
                                                  operationToReport,
                                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                              422);
                                }

                                array.RemoveAt(positionAsInteger);

                                // return the type of the value that has been removed
                                return(new RemovedPropertyTypeResult(genericTypeOfArray, false));
                            }
                        }
                        else
                        {
                            throw new JsonPatchException(
                                      new JsonPatchError(
                                          objectToApplyTo,
                                          operationToReport,
                                          string.Format("Patch failed: cannot get property value at path {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                      422);
                        }
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array.", path)),
                                  422);
                    }
                }
                else
                {
                    if (!patchProperty.Property.Writable)
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: property at path location cannot be set: {0}.  Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)),
                                  422);
                    }

                    // 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));
                }
            }
        }
Пример #7
0
        /// <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
        /// </summary>
        private RemovedPropertyTypeResult Remove(string path, T objectToApplyTo, Operation <T> operationToReport)
        {
            // get path result
            var pathResult = PropertyHelpers.GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport,
                false);

            var removeFromList       = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;
            var expressionToUse      = pathResult.ExpressionEnd;
            var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty,
                                                      ContractResolver);

            if (!result.IsValidPathForRemove)
            {
                throw new JsonPatchException(
                          new JsonPatchError(
                              objectToApplyTo,
                              operationToReport,
                              string.Format("Patch failed: the provided path is invalid: {0}.", path)),
                          422);
            }

            var patchProperty = result.JsonPatchProperty;

            if (removeFromList || positionAsInteger > -1)
            {
                if (expressionToUse != null)
                {
                    // we have an expression.
                    var    elementType = PropertyHelpers.GetCollectionType(patchProperty.Property.PropertyType);
                    object coll        = patchProperty.Property.ValueProvider
                                         .GetValue(patchProperty.Parent);
                    var remMethod   = typeof(ICollection <>).MakeGenericType(elementType).GetMethod("Remove");
                    var objToRemove = ExpressionHelpers.GetElementAtFromObjectExpression(coll, expressionToUse);
                    var remResult   = remMethod.Invoke(coll, new object[] { objToRemove });
                    if ((bool)remResult != true)
                    {
                        throw new Exception("D'oh");
                    }
                    return(new RemovedPropertyTypeResult(elementType, false));
                }
                else if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType))
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);

                    if (patchProperty.Property.Readable)
                    {
                        var array = (IList)patchProperty.Property.ValueProvider
                                    .GetValue(patchProperty.Parent);

                        if (removeFromList)
                        {
                            if (array.Count == 0)
                            {
                                // if the array is empty, we should throw an error
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                          422);
                            }
                            SetValueEventArgs eArg = new SetValueEventArgs(operationToReport)
                            {
                                Cancel = false,
                                //NewValue = conversionResultTuple.ConvertedInstance,
                                ParentObject = patchProperty.Parent,
                                Property     = patchProperty.Property,
                                Index        = array.Count - 1,
                            };
                            InvokeWithBeforeSetValue(eArg, () => array.RemoveAt(eArg.Index.Value));

                            // return the type of the value that has been removed
                            return(new RemovedPropertyTypeResult(genericTypeOfArray, false));
                        }
                        else
                        {
                            if (positionAsInteger >= array.Count)
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                          422);
                            }
                            SetValueEventArgs eArg = new SetValueEventArgs(operationToReport)
                            {
                                Cancel = false,
                                //NewValue = conversionResultTuple.ConvertedInstance,
                                ParentObject = patchProperty.Parent,
                                Property     = patchProperty.Property,
                                Index        = positionAsInteger
                            };
                            InvokeWithBeforeSetValue(eArg, () => array.RemoveAt(eArg.Index.Value));

                            // return the type of the value that has been removed
                            return(new RemovedPropertyTypeResult(genericTypeOfArray, false));
                        }
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: cannot get property value at path {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                  422);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array.", path)),
                              422);
                }
            }
            else
            {
                if (!patchProperty.Property.Writable)
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: property at path location cannot be set: {0}.  Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)),
                              422);
                }

                // set value to null, or for non-nullable value types, to its default value.
                object value = null;

                if (patchProperty.Property.PropertyType.GetType().IsValueType &&
                    Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null)
                {
                    value = Activator.CreateInstance(patchProperty.Property.PropertyType);
                }

                // check if it can be converted.
                var conversionResultTuple = PropertyHelpers.ConvertToActualType(
                    patchProperty.Property.PropertyType,
                    value);
                SetValueEventArgs eArg = new SetValueEventArgs(operationToReport)
                {
                    Cancel = false,
                    //NewValue = conversionResultTuple.ConvertedInstance,
                    ParentObject          = patchProperty.Parent,
                    Property              = patchProperty.Property,
                    IsRemoveStepOfReplace = operationToReport.OperationType == OperationType.Replace
                };

                if (!conversionResultTuple.CanBeConverted)
                {
                    // conversion failed, so use reflection (somewhat slower) to
                    // create a new default instance of the property type to set as value

                    eArg.NewValue = Activator.CreateInstance(patchProperty.Property.PropertyType);
                    //patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent,
                    //    Activator.CreateInstance(patchProperty.Property.PropertyType));

                    //return new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false);
                }
                else
                {
                    eArg.NewValue = conversionResultTuple.ConvertedInstance;
                }
                InvokeWithBeforeSetValue(eArg, () => patchProperty.Property.ValueProvider.SetValue(eArg.ParentObject,
                                                                                                   eArg.NewValue));

                return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false));
            }
        }
Пример #8
0
        /// <summary>
        /// Add is used by various operations (eg: add, copy, ...), yet through different operations;
        /// This method allows code reuse yet reporting the correct operation on error
        /// </summary>
        private void Add(string path, object value, T objectToApplyTo, Operation <T> operationToReport)
        {
            // add, in this implementation, does not just "add" properties - that's
            // technically impossible;  It can however be used to add items to arrays,
            // or to replace values.

            // first up: if the path ends in a numeric value, we're inserting in a list and
            // that value represents the position; if the path ends in "-", we're appending
            // to the list.

            // get path result
            var pathResult = PropertyHelpers.GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport,
                true);

            var appendList           = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver);

            if (!result.IsValidPathForAdd)
            {
                throw new JsonPatchException(
                          new JsonPatchError(
                              objectToApplyTo,
                              operationToReport,
                              string.Format("Patch failed: the provided path is invalid: {0}.", path)),
                          422);
            }

            // If it' an array, add to that array.  If it's not, we replace.

            // is the path an array (but not a string (= char[]))?  In this case,
            // the path must end with "/position" or "/-", which we already determined before.

            var patchProperty = result.JsonPatchProperty;

            if (appendList || positionAsInteger > -1)
            {
                if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType))
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);

                    var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);

                    if (!conversionResult.CanBeConverted)
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)),
                                  422);
                    }

                    if (patchProperty.Property.Readable)
                    {
                        var array = (IList)patchProperty.Property.ValueProvider
                                    .GetValue(patchProperty.Parent);
                        if (array == null)
                        {
                            array = (IList)Activator.CreateInstance(typeof(List <>).MakeGenericType(genericTypeOfArray));
                            patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, array);
                        }
                        if (appendList)
                        {
                            SetValueEventArgs eArg = new SetValueEventArgs(operationToReport)
                            {
                                Cancel       = false,
                                Index        = array.Count + 1,
                                NewValue     = conversionResult.ConvertedInstance,
                                ParentObject = patchProperty.Parent,
                                Property     = patchProperty.Property
                            };
                            InvokeWithBeforeSetValue(eArg, () => array.Add(eArg.NewValue));
                        }
                        else
                        {
                            // specified index must not be greater than the amount of items in the
                            // array
                            if (positionAsInteger <= array.Count)
                            {
                                SetValueEventArgs eArg = new SetValueEventArgs(operationToReport)
                                {
                                    Cancel       = false,
                                    Index        = positionAsInteger,
                                    NewValue     = conversionResult.ConvertedInstance,
                                    ParentObject = patchProperty.Parent,
                                    Property     = patchProperty.Property
                                };
                                InvokeWithBeforeSetValue(eArg, () => array.Insert(positionAsInteger, eArg.NewValue));
                            }
                            else
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                          422);
                            }
                        }
                    }
                    else
                    {
                        // cannot read the property
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: cannot get property value at path {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                  422);
                    }
                }
                else if (PropertyHelpers.IsNonStringCollection(patchProperty.Property.PropertyType))
                {
                    // we need to do a little work with collections here. All generic collections have an "Add" operation. In this case, we should ONLY
                    // support adding to the end.
                    if (!(positionAsInteger == -1 || appendList))
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: can only insert at end of array when target type is ICollection", path)),
                                  422);
                    }
                    var genericTypeOfCollection = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);
                    ConversionResult conversionResult;// = PropertyHelpers.ConvertToActualType(genericTypeOfCollection, value);

                    if (!genericTypeOfCollection.IsValueType && genericTypeOfCollection != typeof(string) && value != null && value.GetType() == typeof(string))
                    {
                        object newObj;
                        if (CustomDeserializationHandler != null)
                        {
                            newObj = CustomDeserializationHandler(value.ToString(), genericTypeOfCollection);
                        }
                        else
                        {
                            newObj = JsonConvert.DeserializeObject(value.ToString(), genericTypeOfCollection);
                        }
                        conversionResult = new ConversionResult(true, newObj);
                    }
                    else if (value != null && genericTypeOfCollection.IsAssignableFrom(value.GetType()))
                    {
                        conversionResult = new ConversionResult(true, value);
                    }
                    else
                    {
                        conversionResult = PropertyHelpers.ConvertToActualType(
                            genericTypeOfCollection,
                            value);
                    }

                    if (!conversionResult.CanBeConverted)
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)),
                                  422);
                    }
                    if (patchProperty.Property.Readable)
                    {
                        var array = patchProperty.Property.ValueProvider
                                    .GetValue(patchProperty.Parent);
                        if (array == null)
                        {
                            // the array was null. This might mean we are dealing with a fresh object. This is okay. Lets initialize it with a basic collection.
                            array = Activator.CreateInstance(typeof(Collection <>).MakeGenericType(genericTypeOfCollection));
                            patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, array);
                        }
                        var addMethod          = patchProperty.Property.PropertyType.GetMethod("Add");
                        SetValueEventArgs eArg = new SetValueEventArgs(operationToReport)
                        {
                            Cancel       = false,
                            Index        = -1,
                            NewValue     = conversionResult.ConvertedInstance,
                            ParentObject = patchProperty.Parent,
                            Property     = patchProperty.Property
                        };
                        InvokeWithBeforeSetValue(eArg, () => addMethod.Invoke(array, new object[] { eArg.NewValue }));
                    }
                    else
                    {
                        // cannot read the property
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: cannot get property value at path {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                  422);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)),
                              422);
                }
            }
            else
            {
                Type             propType = patchProperty.Property.PropertyType;
                ConversionResult conversionResultTuple;
                if (!propType.IsValueType && propType != typeof(string) && value != null && value.GetType() == typeof(string))
                {
                    object newObj;
                    if (CustomDeserializationHandler != null)
                    {
                        newObj = CustomDeserializationHandler(value.ToString(), propType);
                    }
                    else
                    {
                        newObj = JsonConvert.DeserializeObject(value.ToString(), propType);
                    }
                    conversionResultTuple = new ConversionResult(true, newObj);
                }
                else if (value != null && patchProperty.Property.PropertyType.IsAssignableFrom(value.GetType()))
                {
                    conversionResultTuple = new ConversionResult(true, value);
                }
                else
                {
                    conversionResultTuple = PropertyHelpers.ConvertToActualType(
                        patchProperty.Property.PropertyType,
                        value);
                }

                if (conversionResultTuple.CanBeConverted)
                {
                    if (patchProperty.Property.Writable)
                    {
                        SetValueEventArgs eArg = new SetValueEventArgs(operationToReport)
                        {
                            Cancel       = false,
                            NewValue     = conversionResultTuple.ConvertedInstance,
                            ParentObject = patchProperty.Parent,
                            Property     = patchProperty.Property
                        };
                        InvokeWithBeforeSetValue(eArg, () => patchProperty.Property.ValueProvider.SetValue(
                                                     eArg.ParentObject,
                                                     eArg.NewValue));
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: property at path location cannot be set: {0}.  Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)),
                                  422);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: property value cannot be converted to type of path location {0}.", path)),
                              422);
                }
            }
        }
Пример #9
0
        /// <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
        /// </summary>
        private RemovedPropertyTypeResult Remove(string path, T objectToApplyTo, Operation <T> operationToReport)
        {
            // get path result
            var pathResult = PropertyHelpers.GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport,
                false);

            var removeFromList       = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty,
                                                      ContractResolver);

            if (!result.IsValidPathForRemove)
            {
                throw new JsonPatchException(
                          new JsonPatchError(
                              objectToApplyTo,
                              operationToReport,
                              string.Format("Patch failed: the provided path is invalid: {0}.", path)),
                          422);
            }

            var patchProperty = result.JsonPatchProperty;

            if (removeFromList || positionAsInteger > -1)
            {
                if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType))
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);

                    if (patchProperty.Property.Readable)
                    {
                        var array = (IList)patchProperty.Property.ValueProvider
                                    .GetValue(patchProperty.Parent);

                        if (removeFromList)
                        {
                            if (array.Count == 0)
                            {
                                // if the array is empty, we should throw an error
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                          422);
                            }

                            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)
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                          422);
                            }

                            array.RemoveAt(positionAsInteger);

                            // return the type of the value that has been removed
                            return(new RemovedPropertyTypeResult(genericTypeOfArray, false));
                        }
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: cannot get property value at path {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                  422);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array.", path)),
                              422);
                }
            }
            else
            {
                if (!patchProperty.Property.Writable)
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: property at path location cannot be set: {0}.  Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)),
                              422);
                }

                // set value to null, or for non-nullable value types, to its default value.
                object value = null;

                if (patchProperty.Property.PropertyType.GetType().IsValueType &&
                    Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null)
                {
                    value = Activator.CreateInstance(patchProperty.Property.PropertyType);
                }

                // check if it can be converted.
                var conversionResultTuple = PropertyHelpers.ConvertToActualType(
                    patchProperty.Property.PropertyType,
                    value);

                if (!conversionResultTuple.CanBeConverted)
                {
                    // conversion failed, so use reflection (somewhat slower) to
                    // create a new default instance of the property type to set as value
                    patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent,
                                                                  Activator.CreateInstance(patchProperty.Property.PropertyType));

                    return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false));
                }

                patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent,
                                                              conversionResultTuple.ConvertedInstance);

                return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false));
            }
        }
Пример #10
0
        /// <summary>
        /// Add is used by various operations (eg: add, copy, ...), yet through different operations;
        /// This method allows code reuse yet reporting the correct operation on error
        /// </summary>
        private void Add(string path, object value, T objectToApplyTo, Operation <T> operationToReport)
        {
            // add, in this implementation, does not just "add" properties - that's
            // technically impossible;  It can however be used to add items to arrays,
            // or to replace values.

            // first up: if the path ends in a numeric value, we're inserting in a list and
            // that value represents the position; if the path ends in "-", we're appending
            // to the list.

            // get path result
            var pathResult = PropertyHelpers.GetActualPropertyPath(
                path,
                objectToApplyTo,
                operationToReport,
                true);

            var appendList           = pathResult.ExecuteAtEnd;
            var positionAsInteger    = pathResult.NumericEnd;
            var actualPathToProperty = pathResult.PathToProperty;

            var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver);

            if (!result.IsValidPathForAdd)
            {
                throw new JsonPatchException(
                          new JsonPatchError(
                              objectToApplyTo,
                              operationToReport,
                              string.Format("Patch failed: the provided path is invalid: {0}.", path)),
                          422);
            }

            // If it' an array, add to that array.  If it's not, we replace.

            // is the path an array (but not a string (= char[]))?  In this case,
            // the path must end with "/position" or "/-", which we already determined before.

            var patchProperty = result.JsonPatchProperty;

            if (appendList || positionAsInteger > -1)
            {
                if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType))
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);

                    var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);

                    if (!conversionResult.CanBeConverted)
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)),
                                  422);
                    }

                    if (patchProperty.Property.Readable)
                    {
                        var array = (IList)patchProperty.Property.ValueProvider
                                    .GetValue(patchProperty.Parent);

                        if (appendList)
                        {
                            array.Add(conversionResult.ConvertedInstance);
                        }
                        else
                        {
                            // specified index must not be greater than the amount of items in the
                            // array
                            if (positionAsInteger <= array.Count)
                            {
                                array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
                            }
                            else
                            {
                                throw new JsonPatchException(
                                          new JsonPatchError(
                                              objectToApplyTo,
                                              operationToReport,
                                              string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)),
                                          422);
                            }
                        }
                    }
                    else
                    {
                        // cannot read the property
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: cannot get property value at path {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                  422);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)),
                              422);
                }
            }
            else
            {
                var conversionResultTuple = PropertyHelpers.ConvertToActualType(
                    patchProperty.Property.PropertyType,
                    value);

                if (conversionResultTuple.CanBeConverted)
                {
                    if (patchProperty.Property.Writable)
                    {
                        patchProperty.Property.ValueProvider.SetValue(
                            patchProperty.Parent,
                            conversionResultTuple.ConvertedInstance);
                    }
                    else
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: property at path location cannot be set: {0}.  Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)),
                                  422);
                    }
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: property value cannot be converted to type of path location {0}.", path)),
                              422);
                }
            }
        }