public void GenerateAndApplyPatch()
        {
            var initial = new TestPerson()
            {
                FirstName = "John",
                LastName  = "Doe",
                IsAdult   = true,
            };

            using var scope = DraftExtensions.CreateDraft(initial, out TestPerson draft);

            draft.FirstName  = "Jane";
            draft.LastName   = null;
            draft.FirstChild = new TestPerson()
            {
                FirstName = "Baby",
                LastName  = "Doe",
            };

            var patchGenerator = new ObjectPatchGenerator();
            var patches        = new JsonPatchDocument();
            var inversePatches = new JsonPatchDocument();

            patchGenerator.Generate((IDraft)draft, "/", patches, inversePatches);

            // inverse order of inverse patches.
            inversePatches.Operations.Reverse();

            var final = scope.FinishDraft <TestPerson, ITestPerson>(draft);

            var result = ITestPerson.Produce(initial, p =>
            {
                patches.ApplyTo(p);
            });

            Assert.Equal(final.FirstName, result.FirstName);
            Assert.Equal(final.LastName, result.LastName);
            Assert.Equal(final.FirstChild.FirstName, result.FirstChild.FirstName);
            Assert.Equal(final.FirstChild.LastName, result.FirstChild.LastName);
        }
        public void GenerateObjectPatch()
        {
            var initial = new TestPerson()
            {
                FirstName = "John",
                LastName  = "Doe",
                IsAdult   = true,
            };

            using var scope = DraftExtensions.CreateDraft(initial, out TestPerson draft);

            draft.FirstName  = "Jane";
            draft.LastName   = null;
            draft.FirstChild = new TestPerson()
            {
                FirstName = "Baby",
                LastName  = "Doe",
            };

            var patchGenerator = new ObjectPatchGenerator();
            var patches        = new JsonPatchDocument();
            var inversePatches = new JsonPatchDocument();

            patchGenerator.Generate((IDraft)draft, "/", patches, inversePatches);

            // inverse order of inverse patches.
            inversePatches.Operations.Reverse();

            JsonAssert.Equal(
                @"
            [
              {
                'value': 'Jane',
                'path': '/FirstName',
                'op': 'replace'
              },
              {
                'path': '/LastName',
                'op': 'remove'
              },
              {
                'value': {
                  'FirstName': 'Baby',
                  'LastName': 'Doe',
                  'IsAdult': false,
                  'FirstChild': null,
                  'SecondChild': null,
                  'Cars': null
                },
                'path': '/FirstChild',
                'op': 'add'
              }
            ]
            ", JsonConvert.SerializeObject(patches));

            JsonAssert.Equal(
                @"
            [
              {
                'path': '/FirstChild',
                'op': 'remove'
              },
              {
                'value': 'Doe',
                'path': '/LastName',
                'op': 'add'
              },
              {
                'value': 'John',
                'path': '/FirstName',
                'op': 'replace'
              }
            ]
            ", JsonConvert.SerializeObject(inversePatches));
        }
Exemplo n.º 3
0
        /// <summary>
        /// Finishes an instance.
        /// </summary>
        /// <param name="draft">The instance to finish.</param>
        /// <returns>The immutable variant of the instance.</returns>
        private object?FinishInstance(object?draft)
        {
            ObjectPatchGenerator?    objectPatchGenerator     = null;
            DictionaryPatchGenerator?dictionaryPatchGenerator = null;
            CollectionPatchGenerator?collectionPatchGenerator = null;
            JsonPatchDocument?       patches        = null;
            JsonPatchDocument?       inversePatches = null;

            if (this.Parent?.Patches != null && this.Patches == null)
            {
                patches        = new JsonPatchDocument();
                inversePatches = new JsonPatchDocument();
            }
            else
            {
                patches        = this.Patches;
                inversePatches = this.InversePatches;
            }

            if (patches != null && inversePatches != null)
            {
                objectPatchGenerator     = new ObjectPatchGenerator();
                dictionaryPatchGenerator = new DictionaryPatchGenerator();
                collectionPatchGenerator = new CollectionPatchGenerator(new DynamicLargestCommonSubsequence());
            }

            object?Reconcile(object?draft)
            {
                if (draft == null)
                {
                    return(null);
                }

                var draftType = draft.GetType();

                if (draftType.IsValueType || this.AllowedImmutableReferenceTypes.Contains(draftType))
                {
                    return(draft);
                }

                var proxyType = GetProxyType(draft);

                if (proxyType == null)
                {
                    throw new DraftException(draft, $"The object of type {draftType} cannot be made immutable.");
                }

                if (draft is IDraft idraft && this.drafts.Contains(draft))
                {
                    var delayedOperations = new List <Action>();

                    if (idraft.DraftState is ObjectDraftState objectDraftState)
                    {
                        foreach ((string propertyName, object child) in objectDraftState.ChildDrafts)
                        {
                            var immutable = Reconcile(child);

                            if (ReferenceEquals(immutable, child))
                            {
                                delayedOperations.Add(() =>
                                {
                                    // use reflection to set the property and trigger changed on the parent.
                                    draftType.GetProperty(propertyName).SetValue(draft, immutable);
                                });
                            }
                        }

                        objectPatchGenerator?.Generate(idraft, idraft.DraftState !.Path !.ToString(), patches !, inversePatches !);
                    }
                    else if (idraft.DraftState is CollectionDraftState collectionDraftState)
                    {
                        if (draft is IDictionary dictionary)
                        {
                            foreach (DictionaryEntry entry in dictionary)
                            {
                                if (InternalIsDraft(entry.Value) && this.drafts.Contains(entry.Value))
                                {
                                    var immutable = Reconcile(entry.Value);

                                    delayedOperations.Add(() =>
                                    {
                                        // draft turned into immutable.
                                        if (ReferenceEquals(immutable, entry.Value))
                                        {
                                            idraft.DraftState !.Changed = true;
                                        }

                                        // draft reverted to original.
                                        else
                                        {
                                            dictionary[entry.Key] = immutable;
                                        }
                                    });
                                }
                            }

                            dictionaryPatchGenerator?.Generate(idraft, idraft.DraftState !.Path !.ToString(), patches !, inversePatches !);
                        }
                        else if (draft is IList list)
                        {
                            // todo: handle sets.
                            for (int i = 0; i < list.Count; i++)
                            {
                                object?child = list[i];
                                if (InternalIsDraft(child) && this.drafts.Contains(child))
                                {
                                    var immutable = Reconcile(child);

                                    // capture i
                                    int captured = i;

                                    delayedOperations.Add(() =>
                                    {
                                        // draft turned into immutable.
                                        if (ReferenceEquals(immutable, child))
                                        {
                                            idraft.DraftState !.Changed = true;
                                        }

                                        // draft reverted to original.
                                        else
                                        {
                                            list[captured] = immutable;
                                        }
                                    });
                                }
                            }

                            collectionPatchGenerator?.Generate(idraft, idraft.DraftState !.Path !.ToString(), patches !, inversePatches !);
                        }
                    }

                    foreach (var toExecute in delayedOperations)
                    {
                        toExecute();
                    }

                    // not changed, return the original.
                    if (!idraft.DraftState !.Changed)
                    {
                        draft = idraft.DraftState.GetOriginal <object?>();
                    }
                }

                return(draft);
            }

            this.IsFinishing = true;
            try
            {
                draft = Reconcile(draft);

                if (draft is ILockable lockable)
                {
                    lockable.Lock();
                }

                if (this.Parent?.Patches != null)
                {
                    this.Parent.Patches.Operations.AddRange(patches !.Operations);
                    this.Parent.InversePatches !.Operations.AddRange(inversePatches !.Operations);
                    if (draft != null)
                    {
                        this.Parent.HasPatches.Add(draft);
                    }
                }
                else
                {
                    this.producerOptions.InversePatches?.Operations.Reverse();
                }

                return(draft);
            }
            finally
            {
                this.IsFinishing = false;
                this.Dispose();
            }
        }