/// <summary> /// Validates the path for the operation. /// </summary> protected static void ValidatePath(JsonPatchOperation operation) { // Path cannot be null, but it can be empty. if (operation.Path == null) { throw new VssPropertyValidationException("Path", PatchResources.PathCannotBeNull()); } // If it is not empty and does not start with /, this is an error per RFC. if (!operation.Path.StartsWith(PathSeparator) && !string.IsNullOrEmpty(operation.Path)) { throw new VssPropertyValidationException("Path", PatchResources.PathInvalidStartValue()); } // Ending in / is not valid.. if (operation.Path.EndsWith(PathSeparator)) { throw new VssPropertyValidationException("Path", PatchResources.PathInvalidEndValue()); } // Only add operations allow insert. if (operation.Operation != Operation.Add) { if (operation.Path.EndsWith(EndOfIndex)) { throw new VssPropertyValidationException("Path", PatchResources.InsertNotSupported(operation.Operation)); } } }
public static PatchOperation <TModel> CreateFromJson(JsonPatchOperation operation) { if (operation != null) { switch (operation.Operation) { case Operation.Add: return(AddPatchOperation <TModel> .CreateFromJson(operation)); case Operation.Replace: return(ReplacePatchOperation <TModel> .CreateFromJson(operation)); case Operation.Test: return(TestPatchOperation <TModel> .CreateFromJson(operation)); case Operation.Remove: return(RemovePatchOperation <TModel> .CreateFromJson(operation)); default: throw new PatchOperationFailedException(PatchResources.MoveCopyNotImplemented()); } } throw new VssPropertyValidationException("Operation", PatchResources.InvalidOperation()); }
/// <summary> /// Applies the Remove patch operation to the target /// </summary> /// <param name="target">The object to apply the operation to.</param> public override void Apply(TModel target) { this.Apply( target, (type, parent, current) => { if (type.IsList()) { var list = (IList)parent; int index; if (int.TryParse(current, out index) && list.Count > index) { list.RemoveAt(index); } else { // We can't remove outside the rangeof the list. throw new PatchOperationFailedException(PatchResources.IndexOutOfRange(this.Path)); } } else if (type.IsDictionary()) { ((IDictionary)parent).Remove(current); } else { type.SetMemberValue(current, parent, this.Value); } }); }
/// <summary> /// Validates and returns the type for the operation. /// </summary> /// <param name="operation"></param> /// <returns></returns> protected static Type ValidateAndGetType(JsonPatchOperation operation) { var type = GetType(typeof(TModel), operation.Path); if (type == null) { throw new VssPropertyValidationException("Path", PatchResources.UnableToEvaluatePath(operation.Path)); } return(type); }
public static new PatchOperation <TModel> CreateFromJson(JsonPatchOperation operation) { ValidatePath(operation); ValidateType(operation); if (operation.Value != null) { throw new VssPropertyValidationException("Value", PatchResources.ValueNotNull()); } return(new RemovePatchOperation <TModel>(operation.Path)); }
public static new PatchOperation <TModel> CreateFromJson(JsonPatchOperation operation) { ValidatePath(operation); var value = ValidateAndGetValue(operation); if (value == null) { throw new VssPropertyValidationException("Value", PatchResources.ValueCannotBeNull()); } return(new AddPatchOperation <TModel>(operation.Path, value)); }
/// <summary> /// Applies the Add patch operation to the target /// </summary> /// <param name="target">The object to apply the operation to.</param> public override void Apply(TModel target) { this.Apply( target, (type, parent, current) => { // Empty current means replace the whole object. if (string.IsNullOrEmpty(current)) { parent = this.Value; } else if (type.IsList()) { var list = (IList)parent; if (current == EndOfIndex) { list.Add(this.Value); } else { int index; // When index == list.Count it's the same // as doing an index append to the end. if (int.TryParse(current, out index) && list.Count >= index) { list.Insert(index, this.Value); } else { // We can't insert beyond the length of the list. throw new PatchOperationFailedException(PatchResources.IndexOutOfRange(this.Path)); } } } else if (type.IsDictionary()) { ((IDictionary)parent)[current] = this.Value; } else { type.SetMemberValue(current, parent, this.Value); } }); }
/// <summary> /// Applies the Replace patch operation to the target /// </summary> /// <param name="target">The object to apply the operation to.</param> public override void Apply(TModel target) { this.Apply( target, (type, parent, current) => { if (type.IsList()) { var list = (IList)parent; int index; if (int.TryParse(current, out index) && list.Count > index) { list[index] = this.Value; } else { throw new PatchOperationFailedException(PatchResources.CannotReplaceNonExistantValue(this.Path)); } } else if (type.IsDictionary()) { var dictionary = (IDictionary)parent; if (!dictionary.Contains(current)) { throw new InvalidPatchFieldNameException(PatchResources.InvalidFieldName(current)); } dictionary[current] = this.Value; } else { if (type.GetMemberValue(current, parent) == null) { throw new PatchOperationFailedException(PatchResources.CannotReplaceNonExistantValue(this.Path)); } type.SetMemberValue(current, parent, this.Value); } }); }
/// <summary> /// Deserializes the json value. /// </summary> /// <param name="type"></param> /// <param name="jsonValue">The json formatted value.</param> /// <returns>The strongly typed (best effort) value.</returns> private static object DeserializeValue(Type type, object jsonValue) { object value = null; if (jsonValue is JToken) { try { value = ((JToken)jsonValue).ToObject(type, serializer); } catch (JsonException ex) { throw new VssPropertyValidationException("Value", PatchResources.InvalidValue(jsonValue, type), ex); } } else { // Not a JToken, so it must be a primitive type. Will // attempt to convert to the requested type. if (type.IsAssignableOrConvertibleFrom(jsonValue)) { value = ConvertUtility.ChangeType(jsonValue, type); } else { Guid guidValue; if (Guid.TryParse((string)jsonValue, out guidValue)) { value = guidValue; } else { throw new VssPropertyValidationException("Value", PatchResources.InvalidValue(jsonValue, type)); } } } return(value); }
/// <summary> /// Applies the Test patch operation to the target /// </summary> /// <param name="target">The object to apply the operation to.</param> public override void Apply(TModel target) { this.Apply( target, (type, parent, current) => { object memberValue = null; if (type.IsList()) { var list = (IList)parent; int index; if (int.TryParse(current, out index) && list.Count > index) { memberValue = list[index]; } else { // We can't insert beyond the length of the list. throw new PatchOperationFailedException(PatchResources.IndexOutOfRange(this.Path)); } } else if (type.IsDictionary()) { var fieldDictionary = ((IDictionary)parent); if (!fieldDictionary.Contains(current)) { throw new InvalidPatchFieldNameException(PatchResources.InvalidFieldName(current)); } memberValue = fieldDictionary[current]; } else { memberValue = type.GetMemberValue(current, parent); } var success = false; if (memberValue != null) { if (memberValue is IList) { // TODO: Implement throw new PatchOperationFailedException(PatchResources.TestNotImplementedForList()); } else if (memberValue is IDictionary) { // TODO: Implement throw new PatchOperationFailedException(PatchResources.TestNotImplementedForDictionary()); } else if (memberValue.GetType().IsAssignableOrConvertibleFrom(this.Value)) { // We convert the objects since we need the values unboxed. var convertedMemberValue = ConvertUtility.ChangeType(memberValue, memberValue.GetType()); var convertedValue = ConvertUtility.ChangeType(this.Value, memberValue.GetType()); success = convertedMemberValue.Equals(convertedValue); } else { success = memberValue.Equals(this.Value); } } else { success = object.Equals(memberValue, this.Value); } if (!success) { throw new TestPatchOperationFailedException(PatchResources.TestFailed(this.Path, memberValue, this.Value)); } }); }
/// <summary> /// Evaluates the path on the target and applies an action to the result. /// </summary> /// <param name="target">The target object to apply the operation to.</param> /// <param name="path">The path to evaluate.</param> /// <param name="actionToApply">The action to apply to the result of the evaluation.</param> private void Apply(object target, IEnumerable <string> path, Action <Type, object, string> actionToApply) { var current = path.First(); var type = target.GetType(); // We're at the end, time to apply the action. if (path.Count() == 1) { if (PatchOperationApplying != null) { PatchOperationApplying(this, new PatchOperationApplyingEventArgs(this.EvaluatedPath, this.Operation)); } actionToApply(type, target, current); if (PatchOperationApplied != null) { PatchOperationApplied(this, new PatchOperationAppliedEventArgs(this.EvaluatedPath, this.Operation)); } } else { object newTarget = null; // The start of the path should always be an empty string after splitting. // We just assign target to new target and move down the path. if (string.IsNullOrEmpty(current)) { newTarget = target; } // If the next level is a dictionary, we want to get object at the key. else if (type.IsDictionary()) { var dictionary = ((IDictionary)target); if (dictionary.Contains(current)) { newTarget = dictionary[current]; } } else if (type.IsList()) { var list = (IList)target; int index; if (int.TryParse(current, out index) && list.Count > index) { newTarget = ((IList)target)[index]; } } else { newTarget = type.GetMemberValue(current, target); } if (newTarget == null) { // An extra layer of protection, since this should never happen because the earlier call to GetType would have failed. throw new PatchOperationFailedException(PatchResources.TargetCannotBeNull()); } this.Apply(newTarget, path.Skip(1), actionToApply); } }