/// <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);
                }
            }
        }
Exemple #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(
            [NotNull] string path,
            object value,
            [NotNull] object objectToApplyTo,
            [NotNull] Operation 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 = value.GetType();

                        if (!IsNonStringArray(typeOfPathProperty))
                        {
                            LogError(new JsonPatchError(
                                objectToApplyTo,
                                operationToReport,
                                Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
                            return;
                        }

                        // now, get the generic type of the enumerable
                        var genericTypeOfArray = GetIListType(typeOfPathProperty);
                        var conversionResult = ConvertToActualType(genericTypeOfArray, value);
                        if (!conversionResult.CanBeConverted)
                        {
                            LogError(new JsonPatchError(
                                objectToApplyTo,
                                operationToReport,
                                Resources.FormatInvalidValueForProperty(value, path)));
                            return;
                        }

                        // get value (it can be cast, we just checked that) 
                        var array = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
                            treeAnalysisResult.PropertyPathInParent) as IList;

                        if (appendList)
                        {
                            array.Add(conversionResult.ConvertedInstance);
                            treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
                                treeAnalysisResult.PropertyPathInParent, array);
                        }
                        else
                        {
                            // specified index must not be greater than 
                            // the amount of items in the array
                            if (positionAsInteger > array.Count)
                            {
                                LogError(new JsonPatchError(
                                    objectToApplyTo,
                                    operationToReport,
                                    Resources.FormatInvalidIndexForArrayProperty(
                                        operationToReport.op,
                                        path)));
                                return;
                            }

                            array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
                            treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
                                treeAnalysisResult.PropertyPathInParent, array);
                        }
                    }
                    else
                    {
                        // get the actual type
                        var typeOfPathProperty = treeAnalysisResult.Container
                            .GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent).GetType();

                        // can the value be converted to the actual type?
                        var conversionResult = ConvertToActualType(typeOfPathProperty, value);
                        if (conversionResult.CanBeConverted)
                        {
                            treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
                                    treeAnalysisResult.PropertyPathInParent,
                                    conversionResult.ConvertedInstance);
                        }
                        else
                        {
                            LogError(new JsonPatchError(
                               objectToApplyTo,
                               operationToReport,
                               Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path)));
                            return;
                        }
                    }
                }
                else
                {
                    // New property - add it.  
                    treeAnalysisResult.Container.Add(treeAnalysisResult.PropertyPathInParent, value);
                }
            }
            else
            {
                // If it's an array, add to that array.  If it's not, we replace.

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

                var patchProperty = treeAnalysisResult.JsonPatchProperty;

                if (appendList || positionAsInteger > -1)
                {
                    if (!IsNonStringArray(patchProperty.Property.PropertyType))
                    {
                        LogError(new JsonPatchError(
                           objectToApplyTo,
                           operationToReport,
                           Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
                        return;
                    }

                    // now, get the generic type of the IList<> from Property type.
                    var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
                    var conversionResult = ConvertToActualType(genericTypeOfArray, value);
                    if (!conversionResult.CanBeConverted)
                    {
                        LogError(new JsonPatchError(
                              objectToApplyTo,
                              operationToReport,
                              Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path)));
                        return;
                    }

                    if (!patchProperty.Property.Readable)
                    {
                        LogError(new JsonPatchError(
                                 objectToApplyTo,
                                 operationToReport,
                                 Resources.FormatCannotReadProperty(path)));
                        return;
                    }

                    var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
                    if (appendList)
                    {
                        array.Add(conversionResult.ConvertedInstance);
                    }
                    else if (positionAsInteger <= array.Count)
                    {
                        array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
                    }
                    else
                    {
                        LogError(new JsonPatchError(
                            objectToApplyTo,
                            operationToReport,
                            Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
                        return;
                    }
                }
                else
                {
                    var conversionResultTuple = ConvertToActualType(
                        patchProperty.Property.PropertyType,
                        value);

                    if (!conversionResultTuple.CanBeConverted)
                    {
                        LogError(new JsonPatchError(
                            objectToApplyTo,
                            operationToReport,
                            Resources.FormatInvalidValueForProperty(value, path)));
                        return;
                    }

                    if (!patchProperty.Property.Writable)
                    {
                        LogError(new JsonPatchError(
                            objectToApplyTo,
                            operationToReport,
                            Resources.FormatCannotUpdateProperty(path)));
                        return;
                    }

                    patchProperty.Property.ValueProvider.SetValue(
                        patchProperty.Parent,
                        conversionResultTuple.ConvertedInstance);
                }
            }
        }
        /// <summary>
        /// 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);
                }
            }
        }