// Copyright Epic Games, Inc. All Rights Reserved. #include "WebAPILiquidJSCodeGenerator.h" #include "HttpModule.h" #include "WebAPILiquidJSModule.h" #include "WebAPILiquidJSSettings.h" #include "Algo/ForEach.h" #include "Algo/Transform.h" #include "Async/Async.h" #include "CodeGen/WebAPICodeGenerator.h" #include "CodeGen/Dom/WebAPICodeGenFile.h" #include "CodeGen/Dom/WebAPICodeGenFunction.h" #include "CodeGen/Dom/WebAPICodeGenSettings.h" #include "Dom/WebAPIType.h" #include "Interfaces/IHttpResponse.h" #include "Misc/FileHelper.h" #include "Modules/ModuleManager.h" #include "Policies/CondensedJsonPrintPolicy.h" #include "Serialization/JsonSerializer.h" #define LOCTEXT_NAMESPACE "WebAPILiquidJSCodeGenerator" namespace UE { namespace WebAPI { namespace Generator { namespace LiquidJS { /** Various functionality for converting the provided CodeGen objects to implementation-specific Json objects. */ namespace Private { template TSharedPtr ToJson(const TSharedPtr& InCodeGenObject); template TSharedPtr ToJson(const CodeGenType& InCodeGenObject); template void ToJson(const TArray>& InCodeGenObjects, TArray>& OutJson) { OutJson.Reserve(InCodeGenObjects.Num()); for(const TSharedPtr& Element : InCodeGenObjects) { TSharedPtr ElementJson = ToJson(Element); OutJson.Add(MakeShared(ElementJson)); } } template <> void ToJson(const TArray>& InCodeGenObjects, TArray>& OutJson); void ToJson(const TSet& InArray, TArray>& OutJson) { OutJson.Reserve(InArray.Num()); for(const FString& Element : InArray) { TSharedPtr ElementJson = MakeShared(Element); OutJson.Add(MoveTemp(ElementJson)); } } void ToJson(const TArray& InArray, TArray>& OutJson) { OutJson.Reserve(InArray.Num()); for(const FString& Element : InArray) { TSharedPtr ElementJson = MakeShared(Element); OutJson.Add(MoveTemp(ElementJson)); } } /** EnumValue Array ToJson */ void ToJson(const TArray& InArray, TArray>& OutJson) { OutJson.Reserve(InArray.Num()); for(const FWebAPICodeGenEnumValue& Element : InArray) { TSharedPtr ElementJson = MakeShared(); ElementJson->SetStringField(TEXT("name"), Element.Name); ElementJson->SetStringField(TEXT("displayName"), Element.DisplayName); ElementJson->SetStringField(TEXT("jsonName"), Element.JsonName); ElementJson->SetStringField(TEXT("description"), Element.Description); if(Element.IntegralValue >= 0) { ElementJson->SetNumberField(TEXT("explicitValue"), Element.IntegralValue); } OutJson.Add(MakeShared(ElementJson)); } } void ToJson(const TMap& InMap, TArray>& OutJson) { OutJson.Reserve(InMap.Num()); for(const TPair& Element : InMap) { const TSharedPtr ElementJson = MakeShared(); ElementJson->SetStringField(TEXT("key"), Element.Key); if(!Element.Value.IsEmpty()) { ElementJson->SetStringField(TEXT("value"), Element.Value); } OutJson.Add(MakeShared(ElementJson)); } } /** TypeNameVariant ToJson */ template <> TSharedPtr ToJson(const FWebAPITypeNameVariant& InCodeGenObject) { check(InCodeGenObject.IsValid()); TSharedPtr JsonObject = MakeShared(); if(InCodeGenObject.HasTypeInfo()) { JsonObject->SetStringField(TEXT("name"), InCodeGenObject.TypeInfo->Name); JsonObject->SetStringField(TEXT("jsonName"), InCodeGenObject.TypeInfo->JsonName); JsonObject->SetStringField(TEXT("jsonType"), InCodeGenObject.TypeInfo->JsonType.ToString()); JsonObject->SetStringField(TEXT("jsonPropertyToSerialize"), InCodeGenObject.TypeInfo->JsonPropertyToSerialize); JsonObject->SetStringField(TEXT("printFormatSpecifier"), InCodeGenObject.TypeInfo->PrintFormatSpecifier); JsonObject->SetStringField(TEXT("printFormatExpression"), InCodeGenObject.TypeInfo->PrintFormatExpression); JsonObject->SetStringField(TEXT("namespace"), InCodeGenObject.TypeInfo->Namespace); JsonObject->SetStringField(TEXT("prefix"), InCodeGenObject.TypeInfo->Prefix); JsonObject->SetStringField(TEXT("declarationType"), InCodeGenObject.TypeInfo->DeclarationType.ToString()); JsonObject->SetBoolField(TEXT("isBuiltinType"), InCodeGenObject.TypeInfo->bIsBuiltinType); } return JsonObject; } /** NameVariant ToJson */ template <> TSharedPtr ToJson(const FWebAPINameVariant& InCodeGenObject) { check(InCodeGenObject.IsValid()); TSharedPtr JsonObject = MakeShared(); if(InCodeGenObject.HasNameInfo()) { JsonObject->SetStringField(TEXT("name"), InCodeGenObject.NameInfo.Name); JsonObject->SetStringField(TEXT("jsonName"), InCodeGenObject.NameInfo.JsonName); JsonObject->SetStringField(TEXT("prefix"), InCodeGenObject.NameInfo.Prefix); } return JsonObject; } /** Base ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); TSharedPtr JsonObject = MakeShared(); JsonObject->SetStringField(TEXT("description"), InCodeGenObject->Description); JsonObject->SetStringField(TEXT("module"), InCodeGenObject->Module); JsonObject->SetStringField(TEXT("namespace"), InCodeGenObject->Namespace); TArray> SpecifiersJson; ToJson(InCodeGenObject->Specifiers, SpecifiersJson); JsonObject->SetArrayField(TEXT("specifiers"), SpecifiersJson); TArray> MetadataJson; ToJson(InCodeGenObject->Metadata, MetadataJson); JsonObject->SetArrayField(TEXT("metadata"), MetadataJson); return JsonObject; } /** Property ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasNameInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } if(InCodeGenObject->Type.HasTypeInfo()) { const TSharedPtr TypeJson = ToJson(InCodeGenObject->Type); JsonObject->SetObjectField(TEXT("type"), TypeJson); FString DefaultValue = InCodeGenObject->Type.TypeInfo->DefaultValue; // Wrap in array initializer, if it's an array if(InCodeGenObject->bIsArray) { DefaultValue = TEXT("{ }"); } JsonObject->SetStringField(TEXT("defaultValue"), DefaultValue); } else { JsonObject->SetStringField(TEXT("type"), InCodeGenObject->Type.ToString(true)); } if(!InCodeGenObject->DefaultValue.IsEmpty()) { JsonObject->SetStringField(TEXT("defaultValue"), InCodeGenObject->DefaultValue); } JsonObject->SetBoolField(TEXT("isRequired"), InCodeGenObject->bIsRequired); JsonObject->SetBoolField(TEXT("isArray"), InCodeGenObject->bIsArray); JsonObject->SetBoolField(TEXT("isMixin"), InCodeGenObject->bIsMixin); return JsonObject; } /** Enum ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasTypeInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } TArray> ValuesJson; ToJson(InCodeGenObject->Values, ValuesJson); JsonObject->SetArrayField(TEXT("values"), ValuesJson); return JsonObject; } /** Struct ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasTypeInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } TArray> PropertiesJson; ToJson(InCodeGenObject->Properties, PropertiesJson); JsonObject->SetArrayField(TEXT("properties"), PropertiesJson); return JsonObject; } /** Class ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); TArray> FunctionsJson; ToJson(InCodeGenObject->Functions, FunctionsJson); JsonObject->SetArrayField(TEXT("functions"), FunctionsJson); return JsonObject; } /** FunctionParameter ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); return JsonObject; } /** Function ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasNameInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } JsonObject->SetBoolField(TEXT("isOverride"), InCodeGenObject->bIsOverride); JsonObject->SetBoolField(TEXT("isConst"), InCodeGenObject->bIsOverride); if(InCodeGenObject->ReturnType.HasTypeInfo()) { const TSharedPtr TypeJson = ToJson(InCodeGenObject->ReturnType); JsonObject->SetObjectField(TEXT("returnType"), TypeJson); } else { JsonObject->SetStringField(TEXT("returnType"), InCodeGenObject->ReturnType.ToString(true)); } JsonObject->SetBoolField(TEXT("isReturnTypeConst"), InCodeGenObject->bIsConstReturnType); JsonObject->SetStringField(TEXT("body"), InCodeGenObject->Body); TArray> ParameterJson; ToJson(InCodeGenObject->Parameters, ParameterJson); JsonObject->SetArrayField(TEXT("parameters"), ParameterJson); return JsonObject; } /** OperationParameter ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); JsonObject->SetStringField(TEXT("storage"), UE::WebAPI::WebAPIParameterStorage::ToString(InCodeGenObject->Storage)); JsonObject->SetStringField(TEXT("mediaType"), InCodeGenObject->MediaType); return JsonObject; } /** OperationRequest ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasTypeInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } TArray> ParametersJson; ToJson(InCodeGenObject->Parameters, ParametersJson); JsonObject->SetArrayField(TEXT("properties"), ParametersJson); // encode as properties return JsonObject; } /** OperationResponse ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasTypeInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } JsonObject->SetNumberField(TEXT("responseCode"), InCodeGenObject->ResponseCode); JsonObject->SetStringField(TEXT("message"), InCodeGenObject->Message); checkf(InCodeGenObject->Base.IsValid(), TEXT("OperationResponse should have a base type set.")); const TSharedPtr BaseJson = ToJson(InCodeGenObject->Base); JsonObject->SetObjectField(TEXT("base"), BaseJson); return JsonObject; } /** Operation ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasTypeInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } JsonObject->SetStringField(TEXT("path"), InCodeGenObject->Path); JsonObject->SetStringField(TEXT("service"), InCodeGenObject->ServiceName); JsonObject->SetStringField(TEXT("verb"), InCodeGenObject->Verb); TArray> RequestsJson; ToJson(InCodeGenObject->Requests, RequestsJson); JsonObject->SetArrayField(TEXT("requests"), RequestsJson); TArray> ResponsesJson; ToJson(InCodeGenObject->Responses, ResponsesJson); JsonObject->SetArrayField(TEXT("responses"), ResponsesJson); JsonObject->SetStringField(TEXT("settingsTypeName"), InCodeGenObject->SettingsTypeName); return JsonObject; } /** Settings ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TSharedPtr JsonObject = ToJson(StaticCastSharedPtr(InCodeGenObject)); if(InCodeGenObject->Name.HasTypeInfo()) { const TSharedPtr NameJson = ToJson(InCodeGenObject->Name); JsonObject->SetObjectField(TEXT("name"), NameJson); } else { JsonObject->SetStringField(TEXT("name"), InCodeGenObject->Name.ToString(true)); } if(InCodeGenObject->ParentType.IsValid()) { JsonObject->SetStringField(TEXT("base"), InCodeGenObject->ParentType->GetName()); } JsonObject->SetStringField(TEXT("host"), InCodeGenObject->Host); JsonObject->SetStringField(TEXT("baseUrl"), InCodeGenObject->BaseUrl); JsonObject->SetStringField(TEXT("userAgent"), InCodeGenObject->UserAgent); JsonObject->SetStringField(TEXT("dateTimeFormat"), InCodeGenObject->DateTimeFormat); TArray> SchemesJson; ToJson(InCodeGenObject->Schemes, SchemesJson); JsonObject->SetArrayField(TEXT("schemes"), SchemesJson); return JsonObject; } /** File ToJson */ template <> TSharedPtr ToJson(const TSharedPtr& InCodeGenObject) { check(InCodeGenObject); const TFunction MakeForwardDeclarationString = [](const FWebAPITypeNameVariant& InTypeName) { const FString ObjectType = InTypeName.TypeInfo->IsEnum() ? TEXT("enum class") : InTypeName.TypeInfo->Prefix == TEXT("F") ? TEXT("struct") : TEXT("class"); const FString Suffix = InTypeName.TypeInfo->IsEnum() ? TEXT(" : uint8") : TEXT(""); return FString::Printf(TEXT("%s %s%s"), *ObjectType, *InTypeName.ToString(), *Suffix); }; const TSharedPtr JsonObject = MakeShared(); JsonObject->SetStringField(TEXT("baseFilePath"), InCodeGenObject->BaseFilePath); JsonObject->SetStringField(TEXT("relativeFilePath"), InCodeGenObject->RelativeFilePath); JsonObject->SetStringField(TEXT("fileName"), InCodeGenObject->FileName); JsonObject->SetStringField(TEXT("fileType"), InCodeGenObject->FileType); JsonObject->SetStringField(TEXT("namespace"), InCodeGenObject->Namespace); JsonObject->SetStringField(TEXT("module"), InCodeGenObject->Module); JsonObject->SetStringField(TEXT("copyrightNotice"), InCodeGenObject->CopyrightNotice); TArray> IncludePathJson; ToJson(InCodeGenObject->IncludePaths, IncludePathJson); JsonObject->SetArrayField(TEXT("includePaths"), IncludePathJson); TArray ForwardDeclarations; // Find and transform Enums TArray> EnumsJson; Algo::TransformIf( InCodeGenObject->SubItems, EnumsJson, [](const TSharedPtr& InItem) { return InItem->GetTypeName() == TEXT("Enum"); }, [&](const TSharedPtr& InItem) { const TSharedPtr Enum = StaticCastSharedPtr(InItem); ForwardDeclarations.Add(MakeForwardDeclarationString(Enum->Name)); TSharedPtr EnumJson = UE::WebAPI::Generator::LiquidJS::Private::ToJson(Enum); return MakeShared(EnumJson); }); // Find and transform Structs TArray> StructsJson; Algo::TransformIf( InCodeGenObject->SubItems, StructsJson, [](const TSharedPtr& InItem) { return InItem->GetTypeName() == TEXT("Struct"); }, [&](const TSharedPtr& InItem) { const TSharedPtr Struct = StaticCastSharedPtr(InItem); ForwardDeclarations.Add(MakeForwardDeclarationString(Struct->Name)); TSharedPtr StructJson = UE::WebAPI::Generator::LiquidJS::Private::ToJson(Struct); return MakeShared(StructJson); }); // Find and transform Classes TArray> ClassesJson; Algo::TransformIf( InCodeGenObject->SubItems, ClassesJson, [](const TSharedPtr& InItem) { return InItem->GetTypeName() == TEXT("Class"); }, [&](const TSharedPtr& InItem) { const TSharedPtr Class = StaticCastSharedPtr(InItem); ForwardDeclarations.Add(MakeForwardDeclarationString(Class->Name)); TSharedPtr ClassJson = UE::WebAPI::Generator::LiquidJS::Private::ToJson(Class); return MakeShared(ClassJson); }); // Find and transform Operations TArray> OperationsJson; TArray> RequestsJson; TArray> ResponsesJson; Algo::TransformIf( InCodeGenObject->SubItems, OperationsJson, [](const TSharedPtr& InItem) { return InItem->GetTypeName() == TEXT("Operation"); }, [&](const TSharedPtr& InItem) { const TSharedPtr Operation = StaticCastSharedPtr(InItem); TSharedPtr OperationJson = UE::WebAPI::Generator::LiquidJS::Private::ToJson(Operation); ForwardDeclarations.Add(MakeForwardDeclarationString(Operation->Name)); RequestsJson.Append(OperationJson->GetArrayField(TEXT("requests"))); ResponsesJson.Append(OperationJson->GetArrayField(TEXT("responses"))); return MakeShared(OperationJson); }); // Find and transform Settings TArray> SettingsJsonArray; Algo::TransformIf( InCodeGenObject->SubItems, SettingsJsonArray, [](const TSharedPtr& InItem) { return InItem->GetTypeName() == TEXT("Settings"); }, [](const TSharedPtr& InItem) { const TSharedPtr Settings = StaticCastSharedPtr(InItem); TSharedPtr SettingsJson = UE::WebAPI::Generator::LiquidJS::Private::ToJson(Settings); return SettingsJson; }); // Set here as Operations can add additional structs/enums JsonObject->SetArrayField(TEXT("enums"), EnumsJson); JsonObject->SetArrayField(TEXT("structs"), StructsJson); JsonObject->SetArrayField(TEXT("classes"), ClassesJson); JsonObject->SetArrayField(TEXT("operations"), OperationsJson); JsonObject->SetArrayField(TEXT("requests"), RequestsJson); JsonObject->SetArrayField(TEXT("responses"), ResponsesJson); TArray> ForwardDeclarationsJson; ToJson(ForwardDeclarations, ForwardDeclarationsJson); JsonObject->SetArrayField(TEXT("forwardDeclarations"), ForwardDeclarationsJson); if(!SettingsJsonArray.IsEmpty()) { JsonObject->SetObjectField(TEXT("settings"), SettingsJsonArray.Last()); } return JsonObject; } template <> void ToJson(const TArray>& InCodeGenObjects, TArray>& OutJson) { OutJson.Reserve(InCodeGenObjects.Num()); for(const TSharedPtr& CodeGenObject : InCodeGenObjects) { TSharedPtr ElementJson = MakeShared(); const FName& CodeGenObjectType = CodeGenObject->GetTypeName(); if(CodeGenObjectType == TEXT("Enum")) { ElementJson = ToJson(StaticCastSharedPtr(CodeGenObject)); } else if(CodeGenObjectType == TEXT("Struct")) { ElementJson = ToJson(StaticCastSharedPtr(CodeGenObject)); } else if(CodeGenObjectType == TEXT("Operation")) { ElementJson = ToJson(StaticCastSharedPtr(CodeGenObject)); } else if(CodeGenObjectType == TEXT("Class")) { ElementJson = ToJson(StaticCastSharedPtr(CodeGenObject)); } OutJson.Add(MakeShared(ElementJson)); } } /** Runs the given function on the GameThread, returning a Future. */ template static TFuture ExecuteOnGameThread(const TFunction& Function) { TSharedRef, ESPMode::ThreadSafe> Promise = MakeShared>(); TFunction PromiseKeeper = [Promise, Function] { Promise->SetValue(Function()); }; if (!IsInGameThread()) { AsyncTask(ENamedThreads::GameThread, MoveTemp(PromiseKeeper)); } else { PromiseKeeper(); } return Promise->GetFuture(); } } } } } } TFuture UWebAPILiquidJSCodeGenerator::IsAvailable() { const TSharedRef, ESPMode::ThreadSafe> Promise = MakeShared, ESPMode::ThreadSafe>(); FHttpModule& HttpModule = FHttpModule::Get(); const FString URL = GetMutableDefault()->GetServiceUrl(TEXT("/api/ping")); const TSharedRef Request = HttpModule.CreateRequest(); Request->SetVerb(TEXT("GET")); Request->SetURL(URL); Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent")); Request->SetHeader(TEXT("Accept"), TEXT("application/json")); Request->SetHeader(TEXT("Accept-Encoding"), TEXT("identity")); Request->SetTimeout(10.0f); // 10 second timeout std::atomic bHasRetried = false; Request->OnProcessRequestComplete().BindLambda( [Promise, &bHasRetried, Request](FHttpRequestPtr, FHttpResponsePtr InResponse, bool bInWasSuccessful) { if(bInWasSuccessful && EHttpResponseCodes::IsOk(InResponse->GetResponseCode())) { Promise->SetValue(true); } else { if(!bHasRetried.load(std::memory_order_relaxed)) { // Try starting bool bRetryResult = FModuleManager::Get().GetModulePtr(TEXT("WebAPILiquidJS"))->TryStartWebApp(); bHasRetried.store(true, std::memory_order_relaxed); Request->ProcessRequest(); return; } Promise->SetValue(false); } }); Request->ProcessRequest(); return Promise->GetFuture(); } TFuture UWebAPILiquidJSCodeGenerator::GenerateFile(const TWeakObjectPtr& InDefinition, const TSharedPtr& InFile) { check(InDefinition.IsValid()); const TSharedRef, ESPMode::ThreadSafe> Promise = MakeShared, ESPMode::ThreadSafe>(); FHttpModule& HttpModule = FHttpModule::Get(); const FString FileType = InFile->FileType.EndsWith(TEXT("h")) ? TEXT("decl") : TEXT("defn"); const FString URL = GetDefault()->GetServiceUrl(TEXT("api/gen/") + FileType); const TSharedRef Request = HttpModule.CreateRequest(); Request->SetVerb(TEXT("POST")); Request->SetURL(URL); Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent")); Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); Request->SetHeader(TEXT("Accept"), TEXT("application/json")); Request->SetHeader(TEXT("Accept-Encoding"), TEXT("identity")); const TSharedPtr FileJson = UE::WebAPI::Generator::LiquidJS::Private::ToJson(InFile); #if WITH_WEBAPI_DEBUG bool bWriteResult = InDefinition->GetGeneratorSettings().bWriteResult; #else bool bWriteResult = true; #endif FString JsonString; const TSharedRef>> Writer = TJsonWriterFactory>::Create(&JsonString); FJsonSerializer::Serialize(FileJson.ToSharedRef(), Writer); Request->SetContentAsString(JsonString); Request->OnProcessRequestComplete().BindLambda( [Promise, InFile, bWriteResult, InDefinition](FHttpRequestPtr, FHttpResponsePtr InResponse, bool bInWasSuccessful) { if(bInWasSuccessful && InResponse->GetResponseCode() == 200) { const FString GeneratedCodeString = InResponse->GetContentAsString(); // Write the result to the original schema objects (allows for visualization and debug of generated code) Algo::ForEach(InFile->SchemaObjects, [&GeneratedCodeString](const TWeakObjectPtr& InSchemaObject) { if(InSchemaObject.IsValid()) { const TScriptInterface AsInterface = InSchemaObject.Get(); AsInterface.GetInterface()->AppendCodeText(GeneratedCodeString); } }); if(bWriteResult) { FFileHelper::SaveStringToFile(GeneratedCodeString, *InFile->GetFullPath()); } Promise->SetValue(EWebAPIGenerationResult::Succeeded); } else { const FString JsonString = InResponse->GetContentAsString(); const TSharedRef> Reader = TJsonReaderFactory::Create(JsonString); FFormatNamedArguments Args; Args.Add(TEXT("Code"), InResponse->GetResponseCode()); Args.Add(TEXT("Url"), FText::FromString(InResponse->GetURL())); TSharedPtr JsonObject; if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid()) { FString ErrorName; JsonObject->TryGetStringField(TEXT("errorName"), ErrorName); FString Message; JsonObject->TryGetStringField(TEXT("message"), Message); FString Stack; JsonObject->TryGetStringField(TEXT("stack"), Stack); Args.Add(TEXT("Error"), FText::FromString(ErrorName)); Args.Add(TEXT("Message"), FText::FromString(Message)); Args.Add(TEXT("StackTrace"), FText::FromString(Stack)); InDefinition->GetMessageLog()->LogError(FText::Format(LOCTEXT("HttpErrorWithMessage", "HTTP Response Code: {Code}\nError: {Error}\nMessage: {Message}\nStackTrace: {StackTrace}"), Args), UWebAPILiquidJSCodeGenerator::LogName); } else { InDefinition->GetMessageLog()->LogError(FText::Format(LOCTEXT("HttpError", "HTTP Response Code: {Code}\nUrl: {Url}"), Args), UWebAPILiquidJSCodeGenerator::LogName); } Promise->SetValue(EWebAPIGenerationResult::Failed); } }); Request->ProcessRequest(); return Promise->GetFuture(); } #undef LOCTEXT_NAMESPACE