Files
speckle-unreal/Source/SpeckleUnreal/Private/API/ClientAPI.cpp
T

195 lines
6.6 KiB
C++

#include "API/ClientAPI.h"
#include "HttpModule.h"
#include "JsonObjectConverter.h"
#include "Interfaces/IHttpResponse.h"
#include "LogSpeckle.h"
#include "Mixpanel.h"
void FClientAPI::MakeGraphQLRequest(const FString& ServerUrl, const FString& AuthToken,
const FString& ResponsePropertyName,
const FString& PostPayload, const FString& RequestLogName,
const FAPIResponceDelegate OnCompleteAction, const FErrorDelegate OnErrorAction)
{
auto OnError = [=](const FString& Message) mutable
{
UE_LOG(LogSpeckle, Warning, TEXT("%s: failed - %s"), *RequestLogName , *Message);
ensureAlwaysMsgf(OnErrorAction.ExecuteIfBound(Message), TEXT("%s: Unhandled error - %s"), *RequestLogName , *Message);
};
auto ResponseHandler = [=](FHttpRequestPtr, FHttpResponsePtr Response, bool bWasSuccessful) mutable
{
if(CheckRequestFailed(bWasSuccessful, Response, RequestLogName, OnError)) return;
TSharedPtr<FJsonObject> Obj;
if(!GetResponseAsJSON(Response, RequestLogName, Obj, OnError)) return;
const TSharedPtr<FJsonObject>* DataObjectPtr;
if(!Obj->TryGetObjectField("data", DataObjectPtr))
{
OnError(TEXT("%s responce was invalid, expected to find a \"data\" property in response"));
return;
}
const TSharedPtr<FJsonObject> DataObject = *DataObjectPtr;
const TSharedPtr<FJsonObject>* RequestedJson;
if(!DataObject->TryGetObjectField(ResponsePropertyName, RequestedJson))
{
//Requested property has a null or non-object value
if(DataObject->HasField(ResponsePropertyName))
{
FString Message = FString::Printf(TEXT("%s: Response data contained the requested property \"%s\", but the value was null was or not an object."),
*RequestLogName, *ResponsePropertyName);
OnError(Message);
return;
}
//Requested property was missing
TArray<FString> Options;
Options.Reserve(DataObject->Values.Num());
for(const auto& Properties : DataObject->Values)
{
Options.Add(FString::Printf(TEXT("\"%s\" "), *Properties.Key));
}
FString Message = FString::Printf(TEXT("%s: Could not find the requested property name \"%s\" in responce data.\n Got { %s} instead."),
*RequestLogName, *ResponsePropertyName, *FString::Join(Options, TEXT(", ")));
OnError(Message);
return;
}
FString OutputString;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
ensureAlways(FJsonSerializer::Serialize(RequestedJson->ToSharedRef(), Writer));
// Commented out, deserialisation done by caller
//void* DeserializedData;
//FSpeckleUser DeserializedData = FSpeckleUser( UserJSONObject );
// if(!FJsonObjectConverter::JsonObjectToUStruct(*RequestedJson, OutStructType::StaticStruct(), &DeserializedData, 0, 0))
// {
// OnError("Failed to deserialize user object");
// return;
// }
UE_LOG(LogSpeckle, Log, TEXT("Operation %s completed successfully"), *RequestLogName);
ensureAlwaysMsgf(OnCompleteAction.ExecuteIfBound(OutputString), TEXT("%s: Complete handler was not bound properly"), *RequestLogName);
};
const FHttpRequestRef Request = CreatePostRequest(ServerUrl, AuthToken, PostPayload);
Request->OnProcessRequestComplete().BindLambda(ResponseHandler);
if(Request->ProcessRequest())
{
UE_LOG(LogSpeckle, Log, TEXT("POST Request %s to %s was sent, awaiting response"), *RequestLogName, *Request->GetURL() );
}
else
{
UE_LOG(LogSpeckle, Warning, TEXT("POST Request %s to %s failed to send"), *RequestLogName, *Request->GetURL() );
}
FAnalytics::TrackEvent(Request->GetURL(), "NodeRun", TMap<FString, FString> { {"name", __FUNCTION__}});
}
bool FClientAPI::GetResponseAsJSON(const FHttpResponsePtr Response, const FString& RequestLogName, TSharedPtr<FJsonObject>& OutObject, const TFunctionRef<void(const FString& Message)> OnErrorAction)
{
const FString JsonResponse = Response->GetContentAsString();
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonResponse);
if(!FJsonSerializer::Deserialize(Reader, OutObject))
{
const FString Message = FString::Printf(
TEXT("Recieved a response from \"%s\" for \"%s\" request, but the response failed to deserialize: %s"),
*Response->GetURL(), *RequestLogName, *JsonResponse);
OnErrorAction(Message);
return false;
}
FString Error;
if(CheckForOperationErrors(OutObject, Error))
{
OnErrorAction(Error);
return false;
}
return true;
}
FHttpRequestRef FClientAPI::CreatePostRequest(const FString& ServerUrl, const FString AuthToken, const FString& PostPayload, const FString& Encoding)
{
FHttpRequestRef Request = FHttpModule::Get().CreateRequest();
Request->SetURL(ServerUrl + "/graphql");
Request->SetVerb(TEXT("POST"));
Request->SetHeader("Accept-Encoding", Encoding);
Request->SetHeader("Content-Type", TEXT("application/json"));
if(!AuthToken.IsEmpty())
Request->SetHeader("Authorization","Bearer " + AuthToken);
Request->SetHeader("apollographql-client-name", "Unreal Engine");
Request->SetHeader("apollographql-client-version", SPECKLE_CONNECTOR_VERSION);
Request->SetContentAsString(PostPayload);
return Request;
}
bool FClientAPI::CheckRequestFailed(bool bWasSuccessful, FHttpResponsePtr Response, const FString& RequestLogName, const TFunctionRef<void(const FString& Message)> OnErrorAction)
{
//Check the request was sent
if(!bWasSuccessful)
{
FString Message = FString::Printf(TEXT("Request \"%s\" to \"%s\" was unsuccessful: %s"),
*RequestLogName, *Response->GetURL(), *Response->GetContentAsString());
OnErrorAction(Message);
return true;
}
//Check Response code
const int32 ResponseCode = Response->GetResponseCode();
if (ResponseCode != 200)
{
FString Message = FString::Printf(TEXT("Request \"%s\" to \"%s\" failed with HTTP response %d - %s"),
*RequestLogName, *Response->GetURL(), ResponseCode, *Response->GetContentAsString());
OnErrorAction(Message);
return true;
}
UE_LOG(LogSpeckle, Log, TEXT("Operation %s recieved a response from %s"), *RequestLogName, *Response->GetURL());
return false;
}
bool FClientAPI::CheckForOperationErrors(const TSharedPtr<FJsonObject> GraphQLResponse, FString& OutErrorMessage)
{
check(GraphQLResponse != nullptr);
bool WasError = false;
const TArray<TSharedPtr<FJsonValue>>* Errors;
if(GraphQLResponse->TryGetArrayField(TEXT("errors"), Errors))
{
for(const TSharedPtr<FJsonValue>& e : *Errors)
{
FString Message;
const TSharedPtr<FJsonObject>* ErrorObject;
bool HadMessage = e->TryGetObject(ErrorObject)
&& (*ErrorObject)->TryGetStringField("message", Message);
if(!HadMessage)
{
Message = "An operation error occured but had no message!\n";
UE_LOG(LogSpeckle, Warning, TEXT("%s"), *Message);
}
OutErrorMessage.Append(Message + "\n");
WasError = true;
}
}
return WasError;
}