diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp index 8c14d0a3..6d9003aa 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp @@ -274,6 +274,31 @@ WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable) return Result; } +///////////////////////////////////////////////////////////////////////////// +// +// Organize by Name +// +///////////////////////////////////////////////////////////////////////////// + +bool WingPropHandle::OrganizeByName(const Handles &HList, TMap> &Result) +{ + Result.Empty(); + TSet DuplicateNames; + for (const TSharedPtr &H : HList) + { + FName Name = H->GetProperty()->GetFName(); + if (Result.Contains(Name)) DuplicateNames.Add(Name); + else Result.Add(Name, H); + } + if (DuplicateNames.IsEmpty()) return true; + UWingServer::Print(TEXT("More than one property with name:")); + for (FName DupName : DuplicateNames) + { + UWingServer::Printf(TEXT(" %s"), *WingUtils::ExternalizeID(DupName)); + } + UWingServer::Print(TEXT("\n")); + return false; +} ///////////////////////////////////////////////////////////////////////////// // @@ -426,6 +451,59 @@ bool WingPropHandle::SetJson(IPropertyHandle& Handle, const TSharedPtr>& Props, const FJsonObject& Json, bool AllOptional) +{ + bool Ok = true; + + // Organize the properties by name. + TMap> OrganizedProps; + if (!OrganizeByName(Props, OrganizedProps)) Ok = false; + + // Parse the keys in the json, make sure they're syntactically valid and + // that they match the names of actual properties, and that there are no dups. + TSet Specified; + for (const auto& KV : Json.Values) + { + FName Name = WingUtils::CheckInternalizeID(KV.Key); + if (Name.IsNone()) { Ok = false; continue; } + if (!OrganizedProps.Contains(Name)) + { + UWingServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key); + Ok = false; + } + if (!WingUtils::FindNoDuplicateName(Specified, Name, TEXT("parameter"))) Ok = false; + } + + // Make sure that all required properties have been specified. + if (!AllOptional) + { + for (const TSharedPtr &H : Props) + { + if (H->HasMetaData(TEXT("Optional"))) continue; + FName Name = H->GetProperty()->GetFName(); + if (!Specified.Contains(Name)) + { + UWingServer::Printf(TEXT("Required parameter %s not specified\n"), + *WingUtils::ExternalizeID(Name)); + Ok = false; + } + } + } + + // If anything is wrong, return early without setting anything. + if (!Ok) return false; + + // Populate each property from JSON. This could fail too, but at this + // point, we're committed. + for (const auto& KV : Json.Values) + { + FName Name = WingUtils::CheckInternalizeID(KV.Key); + if (!SetJson(*OrganizedProps[Name], KV.Value)) Ok = false; + } + return Ok; +} + + ///////////////////////////////////////////////////////////////////////////// // // Print diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp index c440720a..9a66395c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp @@ -1,6 +1,6 @@ #include "WingServer.h" #include "WingHandler.h" -#include "WingProperty.h" +#include "WingPropHandle.h" #include "WingManual.h" #include "WingLogCapture.h" #include "WingUtils.h" @@ -328,7 +328,9 @@ void UWingServer::TryCallHandler(const FString &Line) Handler->ConfigurationObject = Found->Config.Get(); // Populate the handler object with the request parameters. - if (!FWingProperty::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request)) + WingPropHandle Props; + WingPropHandle::Handles Handles = Props.AllProperties(HandlerObj.Get(), true, CPF_None); + if (!WingPropHandle::PopulateFromJson(Handles, *Request, false)) { UWingServer::SuggestManual(WingManual::Section::HandlerHelp); return; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index dafc76ad..da36a9e4 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -56,9 +56,6 @@ // Name sanitization // ============================================================ -FName WingUtils::GetFName(const FWingProperty &Prop) { return Prop.Prop->GetFName(); } -FName WingUtils::GetFName(const TSharedPtr &H) { return H->GetProperty()->GetFName(); } - FString WingUtils::ExternalizeID(FName Name) { return WingTokenizer::ExternalizeID(Name); @@ -272,6 +269,17 @@ FString WingUtils::FormatName(const UWidget *Widget) return ExternalizeID(Widget->GetFName()); } +FName WingUtils::GetFName(const FWingProperty &Prop) +{ + return Prop.Prop->GetFName(); +} + +FName WingUtils::GetFName(const TSharedPtr &H) +{ + return H->GetProperty()->GetFName(); +} + + // ============================================================ // Formatting other things // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h b/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h index 8809b7d2..77011a8a 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h @@ -4,6 +4,9 @@ #include "IPropertyRowGenerator.h" #include "PropertyHandle.h" +class FJsonObject; +class FJsonValue; + // WingPropHandle: A module that provides easy access to // IPropertyHandle objects. To use this module, construct a // WingPropHandle, then use it to fetch properties from @@ -52,13 +55,27 @@ public: // expression properties), widgets (includes slot properties). Handles GetDetails(UObject* Obj, bool Mutable); - // Get/set text with special handling for enums (smarter prefix - // matching via WingUtils::StringToEnum) and FEdGraphPinType - // (human-readable format via UWingTypes). + // Organize properties by name. If there's more than one property + // of a given name, print an error and returns false, and returns a map + // that contains one of the two duplicates. + static bool OrganizeByName(const Handles &H, TMap> &Result); + + // Get/set text. Compared to the built in methods for getting and setting + // text, these support more concise enum values, FEdGraphPinType, and + // more concise Class properties. static FString GetText(IPropertyHandle& Handle); static bool SetText(IPropertyHandle& Handle, const FString& Text); + + // Store a Json value into a property handle. The Json value must be + // a string, number, or boolean. static bool SetJson(IPropertyHandle& Handle, const TSharedPtr& JsonValue); + // Populate a whole bunch of properties from a Json object. If + // AllOptional is true, the Json may supply a subset of the properties. + // If not, the Json must supply all of them, excepting properties that + // are explicitly marked Optional. + static bool PopulateFromJson(TArray>& Props, const FJsonObject& Json, bool AllOptional); + // Print a single property in a standardized format: // editable|readonly Type Name = Value static void Print(IPropertyHandle& Handle, FStringBuilderBase& Out);