diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp index 61869b7eb5..51e47840c9 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp @@ -110,7 +110,15 @@ namespace AZ if (result.GetProcessing() == Processing::Halted) { - return result; + ResultCode reportedResult = settings.m_reporting(R"(One of the patches couldn't be applied correctly.)", result, element); + if (reportedResult.GetOutcome() == Outcomes::PartialSkip) + { + result = reportedResult; + } + else + { + return result; + } } } diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp index ae2dea4e48..046bb88d76 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -43,7 +44,9 @@ namespace JsonSerializationTests } void CheckApplyPatchOutcome(const char* target, const char* patch, - AZ::JsonSerializationResult::Outcomes outcome, AZ::JsonSerializationResult::Processing processing) + AZ::JsonSerializationResult::Outcomes outcome, + AZ::JsonSerializationResult::Processing processing, + const AZ::JsonApplyPatchSettings& settings = AZ::JsonApplyPatchSettings{}) { m_jsonDocument->Parse(target); ASSERT_FALSE(m_jsonDocument->HasParseError()); @@ -53,12 +56,27 @@ namespace JsonSerializationTests ASSERT_FALSE(patchDocument.HasParseError()); AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(*m_jsonDocument, - m_jsonDocument->GetAllocator(), patchDocument, AZ::JsonMergeApproach::JsonPatch); + m_jsonDocument->GetAllocator(), patchDocument, AZ::JsonMergeApproach::JsonPatch, settings); EXPECT_EQ(result.GetTask(), AZ::JsonSerializationResult::Tasks::Merge); EXPECT_EQ(result.GetOutcome(), outcome); EXPECT_EQ(result.GetProcessing(), processing); } + void CheckApplyPatchOutcome( + const char* target, + const char* patch, + const char* expectedPatchedResult, + AZ::JsonSerializationResult::Outcomes outcome, + AZ::JsonSerializationResult::Processing processing, + const AZ::JsonApplyPatchSettings& settings = AZ::JsonApplyPatchSettings{}) + { + CheckApplyPatchOutcome(target, patch, outcome, processing, settings); + rapidjson::Document expectedPatchedDocument; + expectedPatchedDocument.Parse(expectedPatchedResult); + ASSERT_FALSE(expectedPatchedDocument.HasParseError()); + EXPECT_EQ(AZ::JsonSerialization::Compare(expectedPatchedDocument, *m_jsonDocument), AZ::JsonSerializerCompareResult::Equal); + } + void CheckCreatePatch_Core(const char* source, AZStd::string_view patch, const char* target, AZ::JsonMergeApproach approach) { @@ -262,6 +280,36 @@ namespace JsonSerializationTests Outcomes::TypeMismatch, Processing::Halted); } + TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchWithCustomReportingCallback_ReportPartialSkip) + { + using namespace AZ::JsonSerializationResult; + auto issueReportingCallback = [](AZStd::string_view, AZ::JsonSerializationResult::ResultCode result, + AZStd::string_view) -> AZ::JsonSerializationResult::ResultCode + { + using namespace AZ::JsonSerializationResult; + if (result.GetProcessing() == Processing::Halted) + { + return ResultCode(result.GetTask(), Outcomes::PartialSkip); + } + return result; + }; + + AZ::JsonApplyPatchSettings applyPatchSettings; + applyPatchSettings.m_reporting = AZStd::move(issueReportingCallback); + CheckApplyPatchOutcome( + R"({})", + R"([ + { "op": "add", "path": "/nonexistent_key/new_member", "value": "someValue" }, + { "op": "add", "path": "/test", "value": "someValue" } + ])", + R"( + { "test": "someValue" } + )", + Outcomes::PartialSkip, + Processing::Completed, + AZStd::move(applyPatchSettings)); + } + TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchAddUnnamedMember_ReportsSuccess) { CheckApplyPatch( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index 720c14ed36..03d4b14190 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -172,20 +172,23 @@ namespace AzToolsFramework PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId); //apply patch to template - AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(templateDomReference, - templateDomReference.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch); + AZ::JsonSerializationResult::ResultCode result = + PrefabDomUtils::ApplyPatches(templateDomReference, templateDomReference.GetAllocator(), providedPatch); //trigger propagation - if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success) + if (result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::Success) { - m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true); - m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude); - return true; + AZ_Error("Prefab", false, "Patch was not successfully applied."); + return false; } else { - AZ_Error("Prefab", false, "Patch was not successfully applied"); - return false; + AZ_Error( + "Prefab", result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip, + "Some of the patches are not successfully applied."); + m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true); + m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude); + return true; } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp index 1d27fa6375..333c3f2dbc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Link/Link.cpp @@ -176,12 +176,17 @@ namespace AzToolsFramework } else { - AZ::JsonSerializationResult::ResultCode applyPatchResult = AZ::JsonSerialization::ApplyPatch( - sourceTemplateDomCopy, - targetTemplatePrefabDom.GetAllocator(), - patchesReference->get(), - AZ::JsonMergeApproach::JsonPatch); + AZ::JsonSerializationResult::ResultCode applyPatchResult = + PrefabDomUtils::ApplyPatches(sourceTemplateDomCopy, targetTemplatePrefabDom.GetAllocator(), patchesReference->get()); linkedInstanceDom.CopyFrom(sourceTemplateDomCopy, targetTemplatePrefabDom.GetAllocator()); + + PrefabDomValueReference sourceTemplateName = + PrefabDomUtils::FindPrefabDomValue(sourceTemplateDomCopy, PrefabDomUtils::SourceName); + AZ_Assert(sourceTemplateName && sourceTemplateName->get().IsString(), "A valid source template name couldn't be found"); + PrefabDomValueReference targetTemplateName = + PrefabDomUtils::FindPrefabDomValue(targetTemplatePrefabDom, PrefabDomUtils::SourceName); + AZ_Assert(targetTemplateName && targetTemplateName->get().IsString(), "A valid target template name couldn't be found"); + if (applyPatchResult.GetProcessing() != AZ::JsonSerializationResult::Processing::Completed) { AZ_Error( @@ -190,6 +195,14 @@ namespace AzToolsFramework m_sourceTemplateId, m_targetTemplateId); return false; } + if (applyPatchResult.GetOutcome() == AZ::JsonSerializationResult::Outcomes::PartialSkip) + { + AZ_Error( + "Prefab", false, + "Link::UpdateTarget - Some of the patches couldn't be applied on the source template '%s' present under the " + "target Template '%s'.", + sourceTemplateName->get().GetString(), targetTemplateName->get().GetString()); + } } // This is a guardrail to ensure the linked instance dom always has the LinkId value diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp index bac63d94b4..86137ae547 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp @@ -236,6 +236,26 @@ namespace AzToolsFramework return findInstancesResult->get(); } + AZ::JsonSerializationResult::ResultCode ApplyPatches( + PrefabDomValue& prefabDomToApplyPatchesOn, PrefabDom::AllocatorType& allocator, const PrefabDomValue& patches) + { + auto issueReportingCallback = [](AZStd::string_view, AZ::JsonSerializationResult::ResultCode result, + AZStd::string_view) -> AZ::JsonSerializationResult::ResultCode + { + using namespace AZ::JsonSerializationResult; + if (result.GetProcessing() == Processing::Halted) + { + return ResultCode(result.GetTask(), Outcomes::PartialSkip); + } + return result; + }; + + AZ::JsonApplyPatchSettings applyPatchSettings; + applyPatchSettings.m_reporting = AZStd::move(issueReportingCallback); + return AZ::JsonSerialization::ApplyPatch( + prefabDomToApplyPatchesOn, allocator, patches, AZ::JsonMergeApproach::JsonPatch, applyPatchSettings); + } + void PrintPrefabDomValue( [[maybe_unused]] const AZStd::string_view printMessage, [[maybe_unused]] const PrefabDomValue& prefabDomValue) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h index 709461022e..4d4b7a3e32 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -122,6 +123,11 @@ namespace AzToolsFramework */ PrefabDomValueConstReference GetInstancesValue(const PrefabDomValue& prefabDom); + AZ::JsonSerializationResult::ResultCode ApplyPatches( + PrefabDomValue& prefabDomToApplyPatchesOn, + PrefabDom::AllocatorType& allocator, + const PrefabDomValue& patches); + /** * Prints the contents of the given prefab DOM value to the debug output console in a readable format. * @param printMessage The message that will be printed before printing the PrefabDomValue diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp index e29c33a21f..728b95f74b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp @@ -261,8 +261,13 @@ namespace AzToolsFramework instanceDom.CopyFrom(instanceDomRef->get(), instanceDom.GetAllocator()); //apply the patch to the template within the target - AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(instanceDom, - instanceDom.GetAllocator(), patch, AZ::JsonMergeApproach::JsonPatch); + AZ::JsonSerializationResult::ResultCode result = PrefabDomUtils::ApplyPatches(instanceDom, instanceDom.GetAllocator(), patch); + + AZ_Error( + "Prefab", + result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip && + result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::Success, + "Some of the patches are not successfully applied."); //remove the link id placed into the instance auto linkIdIter = instanceDom.FindMember(PrefabDomUtils::LinkIdName); diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUndoLinkTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUndoLinkTests.cpp index cdc5faaef0..253671189d 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUndoLinkTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabUndoLinkTests.cpp @@ -96,8 +96,8 @@ namespace UnitTest //apply the patch PrefabDom& templateDomReference = m_prefabSystemComponent->FindTemplateDom(nestedTemplateId); - AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(templateDomReference, - templateDomReference.GetAllocator(), patch, AZ::JsonMergeApproach::JsonPatch); + AZ::JsonSerializationResult::ResultCode result = + PrefabDomUtils::ApplyPatches(templateDomReference, templateDomReference.GetAllocator(), patch); AZ_Error("Prefab", result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success, "Patch was not successfully applied");