diff --git a/README.md b/README.md index 46b3b0a8..671029fb 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ Key features: * Blueprintable FJsonValue wrapper - **full Json features made for blueprints!** * Both bindable events and **latent functions** are provided to control the asynchronous requests -Check the [Wiki](https://github.com/ufna/VaRest/wiki) tab for plugin usage examples and installation notes. +Check the [Wiki](https://hiazma.atlassian.net/wiki/display/VAR) for plugin usage examples and installation notes. -Current version: **1.1 R 13** (UE 4.11) +Current version: **1.1 R 14** (UE 4.11-4.12) ![SCREENSHOT](SCREENSHOT.jpg) diff --git a/Source/VaRestEditorPlugin/Classes/VaRest_BreakJson.h b/Source/VaRestEditorPlugin/Classes/VaRest_BreakJson.h index 13847c05..d2fdb657 100644 --- a/Source/VaRestEditorPlugin/Classes/VaRest_BreakJson.h +++ b/Source/VaRestEditorPlugin/Classes/VaRest_BreakJson.h @@ -23,13 +23,13 @@ struct FVaRest_NamedType { GENERATED_USTRUCT_BODY(); - UPROPERTY(EditAnywhere) + UPROPERTY(EditAnywhere, Category = NamedType) FString Name; - UPROPERTY(EditAnywhere) + UPROPERTY(EditAnywhere, Category = NamedType) EVaRest_JsonType Type; - UPROPERTY(EditAnywhere) + UPROPERTY(EditAnywhere, Category = NamedType) bool bIsArray; }; diff --git a/Source/VaRestPlugin/Classes/Json/VaRestJsonObject.h b/Source/VaRestPlugin/Classes/VaRestJsonObject.h similarity index 99% rename from Source/VaRestPlugin/Classes/Json/VaRestJsonObject.h rename to Source/VaRestPlugin/Classes/VaRestJsonObject.h index 2ad0a0f4..e074c7e6 100644 --- a/Source/VaRestPlugin/Classes/Json/VaRestJsonObject.h +++ b/Source/VaRestPlugin/Classes/VaRestJsonObject.h @@ -37,7 +37,7 @@ class VARESTPLUGIN_API UVaRestJsonObject : public UObject UFUNCTION(BlueprintCallable, Category = "VaRest|Json") FString EncodeJson() const; - /** Serialize Json to string (signel string without line breaks) */ + /** Serialize Json to string (single string without line breaks) */ UFUNCTION(BlueprintCallable, Category = "VaRest|Json") FString EncodeJsonToSingleString() const; diff --git a/Source/VaRestPlugin/Classes/Json/VaRestJsonValue.h b/Source/VaRestPlugin/Classes/VaRestJsonValue.h similarity index 100% rename from Source/VaRestPlugin/Classes/Json/VaRestJsonValue.h rename to Source/VaRestPlugin/Classes/VaRestJsonValue.h diff --git a/Source/VaRestPlugin/Classes/VaRestLibrary.h b/Source/VaRestPlugin/Classes/VaRestLibrary.h new file mode 100644 index 00000000..2d3153c3 --- /dev/null +++ b/Source/VaRestPlugin/Classes/VaRestLibrary.h @@ -0,0 +1,91 @@ +// Copyright 2016 Vladimir Alyamkin. All Rights Reserved. + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "VaRestTypes.h" +#include "VaRestLibrary.generated.h" + +class UVaRestRequestJSON; +class UVaRestJsonObject; + +DECLARE_DYNAMIC_DELEGATE_OneParam(FVaRestCallDelegate, UVaRestRequestJSON*, Request); + +USTRUCT() +struct FVaRestCallResponse +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + UVaRestRequestJSON* Request; + + UPROPERTY() + UObject* WorldContextObject; + + UPROPERTY() + FVaRestCallDelegate Callback; + + FDelegateHandle CompleteDelegateHandle; + FDelegateHandle FailDelegateHandle; + + FVaRestCallResponse() + : Request(nullptr) + , WorldContextObject(nullptr) + { + } + +}; + +/** + * Usefull tools for REST communications + */ +UCLASS() +class UVaRestLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + + + ////////////////////////////////////////////////////////////////////////// + // Helpers + +public: + /** Applies percent-encoding to text */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") + static FString PercentEncode(const FString& Source); + + /** + * Encodes a FString into a Base64 string + * + * @param Source The string data to convert + * @return A string that encodes the binary data in a way that can be safely transmitted via various Internet protocols + */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (DisplayName = "Base64 Encode")) + static FString Base64Encode(const FString& Source); + + /** + * Decodes a Base64 string into a FString + * + * @param Source The stringified data to convert + * @param Dest The out buffer that will be filled with the decoded data + * @return True if the buffer was decoded, false if it failed to decode + */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (DisplayName = "Base64 Decode")) + static bool Base64Decode(const FString& Source, FString& Dest); + + + ////////////////////////////////////////////////////////////////////////// + // Easy URL processing + +public: + /** Easy way to process http requests */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (WorldContext = "WorldContextObject")) + static void CallURL(UObject* WorldContextObject, const FString& URL, ERequestVerb Verb, ERequestContentType ContentType, UVaRestJsonObject* VaRestJson, const FVaRestCallDelegate& Callback); + + /** Called when URL is processed (one for both success/unsuccess events)*/ + static void OnCallComplete(UVaRestRequestJSON* Request); + +private: + static TMap RequestMap; + +}; diff --git a/Source/VaRestPlugin/Classes/Json/VaRestRequestJSON.h b/Source/VaRestPlugin/Classes/VaRestRequestJSON.h similarity index 79% rename from Source/VaRestPlugin/Classes/Json/VaRestRequestJSON.h rename to Source/VaRestPlugin/Classes/VaRestRequestJSON.h index faf5047a..f789f82d 100644 --- a/Source/VaRestPlugin/Classes/Json/VaRestRequestJSON.h +++ b/Source/VaRestPlugin/Classes/VaRestRequestJSON.h @@ -2,6 +2,12 @@ #pragma once +#include "Delegate.h" +#include "Http.h" +#include "Map.h" +#include "Json.h" + +#include "VaRestTypes.h" #include "VaRestRequestJSON.generated.h" /** @@ -57,41 +63,26 @@ template class FVaRestLatentAction : public FPendingLatentAction const int32 OutputLink; const FWeakObjectPtr CallbackTarget; T &Result; - }; -/** Verb (GET, PUT, POST) used by the request */ -UENUM(BlueprintType) -namespace ERequestVerb +template void FVaRestLatentAction::Cancel() { - enum Type + UObject *Obj = Request.Get(); + if (Obj != nullptr) { - GET, - POST, - PUT, - DEL UMETA(DisplayName="DELETE"), - /** Set CUSTOM verb by SetCustomVerb() function */ - CUSTOM - }; + ((UVaRestRequestJSON*)Obj)->Cancel(); + } } -/** Content type (json, urlencoded, etc.) used by the request */ -UENUM(BlueprintType) -namespace ERequestContentType -{ - enum Type - { - x_www_form_urlencoded_url UMETA(DisplayName = "x-www-form-urlencoded (URL)"), - x_www_form_urlencoded_body UMETA(DisplayName = "x-www-form-urlencoded (Request Body)"), - json, - binary - }; -} /** Generate a delegates for callback events */ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestComplete, class UVaRestRequestJSON*, Request); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestFail, class UVaRestRequestJSON*, Request); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnStaticRequestComplete, class UVaRestRequestJSON*); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnStaticRequestFail, class UVaRestRequestJSON*); + + /** * General helper class http requests via blueprints */ @@ -110,11 +101,11 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject /** Creates new request with defined verb and content type */ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Request", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Request") - static UVaRestRequestJSON* ConstructRequestExt(UObject* WorldContextObject, ERequestVerb::Type Verb, ERequestContentType::Type ContentType); + static UVaRestRequestJSON* ConstructRequestExt(UObject* WorldContextObject, ERequestVerb Verb, ERequestContentType ContentType); /** Set verb to the request */ UFUNCTION(BlueprintCallable, Category = "VaRest|Request") - void SetVerb(ERequestVerb::Type Verb); + void SetVerb(ERequestVerb Verb); /** Set custom verb to the request */ UFUNCTION(BlueprintCallable, Category = "VaRest|Request") @@ -123,7 +114,7 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject /** Set content type to the request. If you're using the x-www-form-urlencoded, * params/constaints should be defined as key=ValueString pairs from Json data */ UFUNCTION(BlueprintCallable, Category = "VaRest|Request") - void SetContentType(ERequestContentType::Type ContentType); + void SetContentType(ERequestContentType ContentType); /** Set content type of the request for binary post data */ UFUNCTION(BlueprintCallable, Category = "VaRest|Request") @@ -137,10 +128,6 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject UFUNCTION(BlueprintCallable, Category = "VaRest|Request") void SetHeader(const FString& HeaderName, const FString& HeaderValue); - /** Applies percent-encoding to text */ - UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") - static FString PercentEncode(const FString& Text); - ////////////////////////////////////////////////////////////////////////// // Destruction and reset @@ -183,9 +170,17 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject /////////////////////////////////////////////////////////////////////////// - // Response data access + // Request/response data access + + /** Get url of http request */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Request") + FString GetURL(); - /** Get the responce code of the last query */ + /** Get status of http request */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Request") + ERequestStatus GetStatus(); + + /** Get the response code of the last query */ UFUNCTION(BlueprintCallable, Category = "VaRest|Response") int32 GetResponseCode(); @@ -210,7 +205,7 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject virtual void ApplyURL(const FString& Url, UVaRestJsonObject *&Result, UObject* WorldContextObject, struct FLatentActionInfo LatentInfo); /** Apply current internal setup to request and process it */ - void ProcessRequest(TSharedRef HttpRequest); + void ProcessRequest(); ////////////////////////////////////////////////////////////////////////// @@ -228,6 +223,37 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject /** Event occured when the request wasn't successfull */ UPROPERTY(BlueprintAssignable, Category = "VaRest|Event") FOnRequestFail OnRequestFail; + + /** Event occured when the request has been completed */ + FOnStaticRequestComplete OnStaticRequestComplete; + + /** Event occured when the request wasn't successfull */ + FOnStaticRequestFail OnStaticRequestFail; + + + ////////////////////////////////////////////////////////////////////////// + // Tags + +public: + /** Add tag to this request */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") + void AddTag(FName Tag); + + /** + * Remove tag from this request + * + * @return Number of removed elements + */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") + int32 RemoveTag(FName Tag); + + /** See if this request contains the supplied tag */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") + bool HasTag(FName Tag) const; + +protected: + /** Array of tags that can be used for grouping and categorizing */ + TArray Tags; ////////////////////////////////////////////////////////////////////////// @@ -244,7 +270,7 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject protected: /** Latent action helper */ - FVaRestLatentAction *ContinueAction; + FVaRestLatentAction* ContinueAction; /** Internal request data stored as JSON */ UPROPERTY() @@ -261,10 +287,10 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject UVaRestJsonObject* ResponseJsonObj; /** Verb for making request (GET,POST,etc) */ - ERequestVerb::Type RequestVerb; + ERequestVerb RequestVerb; /** Content type to be applied for request */ - ERequestContentType::Type RequestContentType; + ERequestContentType RequestContentType; /** Mapping of header section to values. Used to generate final header string for request */ TMap RequestHeaders; @@ -278,4 +304,7 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject /** Custom verb that will be used with RequestContentType == CUSTOM */ FString CustomVerb; + /** Request we're currently processing */ + TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); + }; diff --git a/Source/VaRestPlugin/Classes/VaRestTypes.h b/Source/VaRestPlugin/Classes/VaRestTypes.h new file mode 100644 index 00000000..0ba037e2 --- /dev/null +++ b/Source/VaRestPlugin/Classes/VaRestTypes.h @@ -0,0 +1,41 @@ +// Copyright 2016 Vladimir Alyamkin. All Rights Reserved. + +#pragma once + +/** Verb (GET, PUT, POST) used by the request */ +UENUM(BlueprintType) +enum class ERequestVerb : uint8 +{ + GET, + POST, + PUT, + DEL UMETA(DisplayName = "DELETE"), + /** Set CUSTOM verb by SetCustomVerb() function */ + CUSTOM +}; + +/** Content type (json, urlencoded, etc.) used by the request */ +UENUM(BlueprintType) +enum class ERequestContentType : uint8 +{ + x_www_form_urlencoded_url UMETA(DisplayName = "x-www-form-urlencoded (URL)"), + x_www_form_urlencoded_body UMETA(DisplayName = "x-www-form-urlencoded (Request Body)"), + json, + binary +}; + +/** Enumerates the current state of an Http request */ +UENUM(BlueprintType) +enum class ERequestStatus : uint8 +{ + /** Has not been started via ProcessRequest() */ + NotStarted, + /** Currently being ticked and processed */ + Processing, + /** Finished but failed */ + Failed, + /** Failed because it was unable to connect (safe to retry) */ + Failed_ConnectionError, + /** Finished and was successful */ + Succeeded +}; diff --git a/Source/VaRestPlugin/Private/Json/VaRestJsonObject.cpp b/Source/VaRestPlugin/Private/VaRestJsonObject.cpp similarity index 74% rename from Source/VaRestPlugin/Private/Json/VaRestJsonObject.cpp rename to Source/VaRestPlugin/Private/VaRestJsonObject.cpp index 5972c3aa..44dd0e59 100644 --- a/Source/VaRestPlugin/Private/Json/VaRestJsonObject.cpp +++ b/Source/VaRestPlugin/Private/VaRestJsonObject.cpp @@ -103,7 +103,7 @@ TArray UVaRestJsonObject::GetFieldNames() bool UVaRestJsonObject::HasField(const FString& FieldName) const { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return false; } @@ -113,7 +113,7 @@ bool UVaRestJsonObject::HasField(const FString& FieldName) const void UVaRestJsonObject::RemoveField(const FString& FieldName) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -123,22 +123,26 @@ void UVaRestJsonObject::RemoveField(const FString& FieldName) UVaRestJsonValue* UVaRestJsonObject::GetField(const FString& FieldName) const { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { - return NULL; + return nullptr; } TSharedPtr NewVal = JsonObj->TryGetField(FieldName); + if (NewVal.IsValid()) + { + UVaRestJsonValue* NewValue = NewObject(); + NewValue->SetRootValue(NewVal); - UVaRestJsonValue* NewValue = NewObject(); - NewValue->SetRootValue(NewVal); - - return NewValue; + return NewValue; + } + + return nullptr; } void UVaRestJsonObject::SetField(const FString& FieldName, UVaRestJsonValue* JsonValue) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -152,8 +156,9 @@ void UVaRestJsonObject::SetField(const FString& FieldName, UVaRestJsonValue* Jso float UVaRestJsonObject::GetNumberField(const FString& FieldName) const { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Number"), *FieldName); return 0.0f; } @@ -162,7 +167,7 @@ float UVaRestJsonObject::GetNumberField(const FString& FieldName) const void UVaRestJsonObject::SetNumberField(const FString& FieldName, float Number) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -172,17 +177,18 @@ void UVaRestJsonObject::SetNumberField(const FString& FieldName, float Number) FString UVaRestJsonObject::GetStringField(const FString& FieldName) const { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type String"), *FieldName); return TEXT(""); } - + return JsonObj->GetStringField(FieldName); } void UVaRestJsonObject::SetStringField(const FString& FieldName, const FString& StringValue) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -192,8 +198,9 @@ void UVaRestJsonObject::SetStringField(const FString& FieldName, const FString& bool UVaRestJsonObject::GetBoolField(const FString& FieldName) const { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Boolean"), *FieldName); return false; } @@ -202,7 +209,7 @@ bool UVaRestJsonObject::GetBoolField(const FString& FieldName) const void UVaRestJsonObject::SetBoolField(const FString& FieldName, bool InValue) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -212,8 +219,13 @@ void UVaRestJsonObject::SetBoolField(const FString& FieldName, bool InValue) TArray UVaRestJsonObject::GetArrayField(const FString& FieldName) { + if (!JsonObj->HasTypedField(FieldName)) + { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); + } + TArray OutArray; - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return OutArray; } @@ -232,7 +244,7 @@ TArray UVaRestJsonObject::GetArrayField(const FString& FieldN void UVaRestJsonObject::SetArrayField(const FString& FieldName, const TArray& InArray) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -298,9 +310,10 @@ void UVaRestJsonObject::MergeJsonObject(UVaRestJsonObject* InJsonObject, bool Ov UVaRestJsonObject* UVaRestJsonObject::GetObjectField(const FString& FieldName) const { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) { - return NULL; + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Object"), *FieldName); + return nullptr; } TSharedPtr JsonObjField = JsonObj->GetObjectField(FieldName); @@ -313,7 +326,7 @@ UVaRestJsonObject* UVaRestJsonObject::GetObjectField(const FString& FieldName) c void UVaRestJsonObject::SetObjectField(const FString& FieldName, UVaRestJsonObject* JsonObject) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -327,9 +340,13 @@ void UVaRestJsonObject::SetObjectField(const FString& FieldName, UVaRestJsonObje TArray UVaRestJsonObject::GetNumberArrayField(const FString& FieldName) { - TArray NumberArray; + if (!JsonObj->HasTypedField(FieldName)) + { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); + } - if (!JsonObj.IsValid()) + TArray NumberArray; + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return NumberArray; } @@ -337,6 +354,12 @@ TArray UVaRestJsonObject::GetNumberArrayField(const FString& FieldName) TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName); for (TArray >::TConstIterator It(JsonArrayValues); It; ++It) { + auto Value = (*It).Get(); + if (Value->Type != EJson::Number) + { + UE_LOG(LogVaRest, Error, TEXT("Not Number element in array with field name %s"), *FieldName); + } + NumberArray.Add((*It)->AsNumber()); } @@ -345,7 +368,7 @@ TArray UVaRestJsonObject::GetNumberArrayField(const FString& FieldName) void UVaRestJsonObject::SetNumberArrayField(const FString& FieldName, const TArray& NumberArray) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } @@ -362,9 +385,13 @@ void UVaRestJsonObject::SetNumberArrayField(const FString& FieldName, const TArr TArray UVaRestJsonObject::GetStringArrayField(const FString& FieldName) { - TArray StringArray; + if (!JsonObj->HasTypedField(FieldName)) + { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); + } - if (!JsonObj.IsValid()) + TArray StringArray; + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return StringArray; } @@ -372,6 +399,12 @@ TArray UVaRestJsonObject::GetStringArrayField(const FString& FieldName) TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName); for (TArray >::TConstIterator It(JsonArrayValues); It; ++It) { + auto Value = (*It).Get(); + if (Value->Type != EJson::String) + { + UE_LOG(LogVaRest, Error, TEXT("Not String element in array with field name %s"), *FieldName); + } + StringArray.Add((*It)->AsString()); } @@ -380,13 +413,12 @@ TArray UVaRestJsonObject::GetStringArrayField(const FString& FieldName) void UVaRestJsonObject::SetStringArrayField(const FString& FieldName, const TArray& StringArray) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } TArray< TSharedPtr > EntriesArray; - for (auto String : StringArray) { EntriesArray.Add(MakeShareable(new FJsonValueString(String))); @@ -397,9 +429,13 @@ void UVaRestJsonObject::SetStringArrayField(const FString& FieldName, const TArr TArray UVaRestJsonObject::GetBoolArrayField(const FString& FieldName) { - TArray BoolArray; + if (!JsonObj->HasTypedField(FieldName)) + { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); + } - if (!JsonObj.IsValid()) + TArray BoolArray; + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return BoolArray; } @@ -407,6 +443,12 @@ TArray UVaRestJsonObject::GetBoolArrayField(const FString& FieldName) TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName); for (TArray >::TConstIterator It(JsonArrayValues); It; ++It) { + auto Value = (*It).Get(); + if (Value->Type != EJson::Boolean) + { + UE_LOG(LogVaRest, Error, TEXT("Not Boolean element in array with field name %s"), *FieldName); + } + BoolArray.Add((*It)->AsBool()); } @@ -415,13 +457,12 @@ TArray UVaRestJsonObject::GetBoolArrayField(const FString& FieldName) void UVaRestJsonObject::SetBoolArrayField(const FString& FieldName, const TArray& BoolArray) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } TArray< TSharedPtr > EntriesArray; - for (auto Boolean : BoolArray) { EntriesArray.Add(MakeShareable(new FJsonValueBoolean(Boolean))); @@ -432,9 +473,13 @@ void UVaRestJsonObject::SetBoolArrayField(const FString& FieldName, const TArray TArray UVaRestJsonObject::GetObjectArrayField(const FString& FieldName) { - TArray OutArray; + if (!JsonObj->HasTypedField(FieldName)) + { + UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); + } - if (!JsonObj.IsValid()) + TArray OutArray; + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return OutArray; } @@ -442,6 +487,11 @@ TArray UVaRestJsonObject::GetObjectArrayField(const FString& TArray< TSharedPtr > ValArray = JsonObj->GetArrayField(FieldName); for (auto Value : ValArray) { + if (Value->Type != EJson::Object) + { + UE_LOG(LogVaRest, Error, TEXT("Not Object element in array with field name %s"), *FieldName); + } + TSharedPtr NewObj = Value->AsObject(); UVaRestJsonObject* NewJson = NewObject(); @@ -455,13 +505,12 @@ TArray UVaRestJsonObject::GetObjectArrayField(const FString& void UVaRestJsonObject::SetObjectArrayField(const FString& FieldName, const TArray& ObjectArray) { - if (!JsonObj.IsValid()) + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { return; } TArray< TSharedPtr > EntriesArray; - for (auto Value : ObjectArray) { EntriesArray.Add(MakeShareable(new FJsonValueObject(Value->GetRootObject()))); diff --git a/Source/VaRestPlugin/Private/Json/VaRestJsonValue.cpp b/Source/VaRestPlugin/Private/VaRestJsonValue.cpp similarity index 99% rename from Source/VaRestPlugin/Private/Json/VaRestJsonValue.cpp rename to Source/VaRestPlugin/Private/VaRestJsonValue.cpp index ba6aa96a..fd767f9f 100644 --- a/Source/VaRestPlugin/Private/Json/VaRestJsonValue.cpp +++ b/Source/VaRestPlugin/Private/VaRestJsonValue.cpp @@ -229,7 +229,7 @@ UVaRestJsonObject* UVaRestJsonValue::AsObject() if (!JsonVal.IsValid()) { ErrorMessage(TEXT("Object")); - return NULL; + return nullptr; } TSharedPtr NewObj = JsonVal->AsObject(); diff --git a/Source/VaRestPlugin/Private/VaRestLibrary.cpp b/Source/VaRestPlugin/Private/VaRestLibrary.cpp new file mode 100644 index 00000000..238a3535 --- /dev/null +++ b/Source/VaRestPlugin/Private/VaRestLibrary.cpp @@ -0,0 +1,107 @@ +// Copyright 2016 Vladimir Alyamkin. All Rights Reserved. + +#include "VaRestPluginPrivatePCH.h" +#include "Base64.h" + +////////////////////////////////////////////////////////////////////////// +// Helpers + +FString UVaRestLibrary::PercentEncode(const FString& Source) +{ + FString OutText = Source; + + OutText = OutText.Replace(TEXT(" "), TEXT("%20")); + OutText = OutText.Replace(TEXT("!"), TEXT("%21")); + OutText = OutText.Replace(TEXT("\""), TEXT("%22")); + OutText = OutText.Replace(TEXT("#"), TEXT("%23")); + OutText = OutText.Replace(TEXT("$"), TEXT("%24")); + OutText = OutText.Replace(TEXT("&"), TEXT("%26")); + OutText = OutText.Replace(TEXT("'"), TEXT("%27")); + OutText = OutText.Replace(TEXT("("), TEXT("%28")); + OutText = OutText.Replace(TEXT(")"), TEXT("%29")); + OutText = OutText.Replace(TEXT("*"), TEXT("%2A")); + OutText = OutText.Replace(TEXT("+"), TEXT("%2B")); + OutText = OutText.Replace(TEXT(","), TEXT("%2C")); + OutText = OutText.Replace(TEXT("/"), TEXT("%2F")); + OutText = OutText.Replace(TEXT(":"), TEXT("%3A")); + OutText = OutText.Replace(TEXT(";"), TEXT("%3B")); + OutText = OutText.Replace(TEXT("="), TEXT("%3D")); + OutText = OutText.Replace(TEXT("?"), TEXT("%3F")); + OutText = OutText.Replace(TEXT("@"), TEXT("%40")); + OutText = OutText.Replace(TEXT("["), TEXT("%5B")); + OutText = OutText.Replace(TEXT("]"), TEXT("%5D")); + OutText = OutText.Replace(TEXT("{"), TEXT("%7B")); + OutText = OutText.Replace(TEXT("}"), TEXT("%7D")); + + return OutText; +} + +FString UVaRestLibrary::Base64Encode(const FString& Source) +{ + return FBase64::Encode(Source); +} + +bool UVaRestLibrary::Base64Decode(const FString& Source, FString& Dest) +{ + return FBase64::Decode(Source, Dest); +} + + +////////////////////////////////////////////////////////////////////////// +// Easy URL processing + +TMap UVaRestLibrary::RequestMap; + +void UVaRestLibrary::CallURL(UObject* WorldContextObject, const FString& URL, ERequestVerb Verb, ERequestContentType ContentType, UVaRestJsonObject* VaRestJson, const FVaRestCallDelegate& Callback) +{ + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject); + if (World == nullptr) + { + UE_LOG(LogVaRest, Error, TEXT("UVaRestLibrary: Wrong world context")) + return; + } + + // Check we have valid data json + if (VaRestJson == nullptr) + { + VaRestJson = UVaRestJsonObject::ConstructJsonObject(WorldContextObject); + } + + UVaRestRequestJSON* Request = NewObject(); + + Request->SetVerb(Verb); + Request->SetContentType(ContentType); + Request->SetRequestObject(VaRestJson); + + FVaRestCallResponse Response; + Response.Request = Request; + Response.WorldContextObject = WorldContextObject; + Response.Callback = Callback; + + Response.CompleteDelegateHandle = Request->OnStaticRequestComplete.AddStatic(&UVaRestLibrary::OnCallComplete); + Response.FailDelegateHandle = Request->OnStaticRequestFail.AddStatic(&UVaRestLibrary::OnCallComplete); + + RequestMap.Add(Request, Response); + + Request->ResetResponseData(); + Request->ProcessURL(URL); +} + +void UVaRestLibrary::OnCallComplete(UVaRestRequestJSON* Request) +{ + if (!RequestMap.Contains(Request)) + { + return; + } + + FVaRestCallResponse* Response = RequestMap.Find(Request); + + Request->OnStaticRequestComplete.Remove(Response->CompleteDelegateHandle); + Request->OnStaticRequestFail.Remove(Response->FailDelegateHandle); + + Response->Callback.ExecuteIfBound(Request); + + Response->WorldContextObject = nullptr; + Response->Request = nullptr; + RequestMap.Remove(Request); +} diff --git a/Source/VaRestPlugin/Private/Json/VaRestRequestJSON.cpp b/Source/VaRestPlugin/Private/VaRestRequestJSON.cpp similarity index 77% rename from Source/VaRestPlugin/Private/Json/VaRestRequestJSON.cpp rename to Source/VaRestPlugin/Private/VaRestRequestJSON.cpp index bbb57866..9437c847 100644 --- a/Source/VaRestPlugin/Private/Json/VaRestRequestJSON.cpp +++ b/Source/VaRestPlugin/Private/VaRestRequestJSON.cpp @@ -3,15 +3,6 @@ #include "VaRestPluginPrivatePCH.h" #include "CoreMisc.h" -template void FVaRestLatentAction::Cancel() -{ - UObject *Obj = Request.Get(); - if (Obj != nullptr) - { - ((UVaRestRequestJSON*)Obj)->Cancel(); - } -} - UVaRestRequestJSON::UVaRestRequestJSON(const class FObjectInitializer& PCIP) : Super(PCIP), BinaryContentType(TEXT("application/octet-stream")) @@ -29,8 +20,8 @@ UVaRestRequestJSON* UVaRestRequestJSON::ConstructRequest(UObject* WorldContextOb UVaRestRequestJSON* UVaRestRequestJSON::ConstructRequestExt( UObject* WorldContextObject, - ERequestVerb::Type Verb, - ERequestContentType::Type ContentType) + ERequestVerb Verb, + ERequestContentType ContentType) { UVaRestRequestJSON* Request = ConstructRequest(WorldContextObject); @@ -40,7 +31,7 @@ UVaRestRequestJSON* UVaRestRequestJSON::ConstructRequestExt( return Request; } -void UVaRestRequestJSON::SetVerb(ERequestVerb::Type Verb) +void UVaRestRequestJSON::SetVerb(ERequestVerb Verb) { RequestVerb = Verb; } @@ -50,7 +41,7 @@ void UVaRestRequestJSON::SetCustomVerb(FString Verb) CustomVerb = Verb; } -void UVaRestRequestJSON::SetContentType(ERequestContentType::Type ContentType) +void UVaRestRequestJSON::SetContentType(ERequestContentType ContentType) { RequestContentType = ContentType; } @@ -65,42 +56,11 @@ void UVaRestRequestJSON::SetBinaryRequestContent(const TArray &Bytes) RequestBytes = Bytes; } - void UVaRestRequestJSON::SetHeader(const FString& HeaderName, const FString& HeaderValue) { RequestHeaders.Add(HeaderName, HeaderValue); } -FString UVaRestRequestJSON::PercentEncode(const FString& Text) -{ - FString OutText = Text; - - OutText = OutText.Replace(TEXT(" "), TEXT("%20")); - OutText = OutText.Replace(TEXT("!"), TEXT("%21")); - OutText = OutText.Replace(TEXT("\""), TEXT("%22")); - OutText = OutText.Replace(TEXT("#"), TEXT("%23")); - OutText = OutText.Replace(TEXT("$"), TEXT("%24")); - OutText = OutText.Replace(TEXT("&"), TEXT("%26")); - OutText = OutText.Replace(TEXT("'"), TEXT("%27")); - OutText = OutText.Replace(TEXT("("), TEXT("%28")); - OutText = OutText.Replace(TEXT(")"), TEXT("%29")); - OutText = OutText.Replace(TEXT("*"), TEXT("%2A")); - OutText = OutText.Replace(TEXT("+"), TEXT("%2B")); - OutText = OutText.Replace(TEXT(","), TEXT("%2C")); - OutText = OutText.Replace(TEXT("/"), TEXT("%2F")); - OutText = OutText.Replace(TEXT(":"), TEXT("%3A")); - OutText = OutText.Replace(TEXT(";"), TEXT("%3B")); - OutText = OutText.Replace(TEXT("="), TEXT("%3D")); - OutText = OutText.Replace(TEXT("?"), TEXT("%3F")); - OutText = OutText.Replace(TEXT("@"), TEXT("%40")); - OutText = OutText.Replace(TEXT("["), TEXT("%5B")); - OutText = OutText.Replace(TEXT("]"), TEXT("%5D")); - OutText = OutText.Replace(TEXT("{"), TEXT("%7B")); - OutText = OutText.Replace(TEXT("}"), TEXT("%7D")); - - return OutText; -} - ////////////////////////////////////////////////////////////////////////// // Destruction and reset @@ -113,7 +73,7 @@ void UVaRestRequestJSON::ResetData() void UVaRestRequestJSON::ResetRequestData() { - if (RequestJsonObj != NULL) + if (RequestJsonObj != nullptr) { RequestJsonObj->Reset(); } @@ -121,11 +81,13 @@ void UVaRestRequestJSON::ResetRequestData() { RequestJsonObj = NewObject(); } + + HttpRequest = FHttpModule::Get().CreateRequest(); } void UVaRestRequestJSON::ResetResponseData() { - if (ResponseJsonObj != NULL) + if (ResponseJsonObj != nullptr) { ResponseJsonObj->Reset(); } @@ -175,6 +137,16 @@ void UVaRestRequestJSON::SetResponseObject(UVaRestJsonObject* JsonObject) /////////////////////////////////////////////////////////////////////////// // Response data access +FString UVaRestRequestJSON::GetURL() +{ + return HttpRequest->GetURL(); +} + +ERequestStatus UVaRestRequestJSON::GetStatus() +{ + return ERequestStatus((uint8)HttpRequest->GetStatus()); +} + int32 UVaRestRequestJSON::GetResponseCode() { return ResponseCode; @@ -185,7 +157,7 @@ FString UVaRestRequestJSON::GetResponseHeader(const FString HeaderName) FString Result; FString* Header = ResponseHeaders.Find(HeaderName); - if (Header != NULL) + if (Header != nullptr) { Result = *Header; } @@ -209,15 +181,13 @@ TArray UVaRestRequestJSON::GetAllResponseHeaders() void UVaRestRequestJSON::ProcessURL(const FString& Url) { - TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); HttpRequest->SetURL(Url); - ProcessRequest(HttpRequest); + ProcessRequest(); } void UVaRestRequestJSON::ApplyURL(const FString& Url, UVaRestJsonObject *&Result, UObject* WorldContextObject, FLatentActionInfo LatentInfo) { - TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); HttpRequest->SetURL(Url); // Prepare latent action @@ -226,7 +196,7 @@ void UVaRestRequestJSON::ApplyURL(const FString& Url, UVaRestJsonObject *&Result FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); FVaRestLatentAction *Kont = LatentActionManager.FindExistingAction>(LatentInfo.CallbackTarget, LatentInfo.UUID); - if (Kont != NULL) + if (Kont != nullptr) { Kont->Cancel(); LatentActionManager.RemoveActionsForObject(LatentInfo.CallbackTarget); @@ -235,10 +205,10 @@ void UVaRestRequestJSON::ApplyURL(const FString& Url, UVaRestJsonObject *&Result LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, ContinueAction = new FVaRestLatentAction(this, Result, LatentInfo)); } - ProcessRequest(HttpRequest); + ProcessRequest(); } -void UVaRestRequestJSON::ProcessRequest(TSharedRef HttpRequest) +void UVaRestRequestJSON::ProcessRequest() { // Set verb switch (RequestVerb) @@ -286,7 +256,7 @@ void UVaRestRequestJSON::ProcessRequest(TSharedRef HttpRequest) if (!Key.IsEmpty() && !Value.IsEmpty()) { UrlParams += ParamIdx == 0 ? "?" : "&"; - UrlParams += UVaRestRequestJSON::PercentEncode(Key) + "=" + UVaRestRequestJSON::PercentEncode(Value); + UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value); } ParamIdx++; @@ -295,6 +265,8 @@ void UVaRestRequestJSON::ProcessRequest(TSharedRef HttpRequest) // Apply params HttpRequest->SetURL(HttpRequest->GetURL() + UrlParams); + UE_LOG(LogVaRest, Log, TEXT("Request (urlencoded): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams); + break; } case ERequestContentType::x_www_form_urlencoded_body: @@ -313,7 +285,7 @@ void UVaRestRequestJSON::ProcessRequest(TSharedRef HttpRequest) if (!Key.IsEmpty() && !Value.IsEmpty()) { UrlParams += ParamIdx == 0 ? "" : "&"; - UrlParams += UVaRestRequestJSON::PercentEncode(Key) + "=" + UVaRestRequestJSON::PercentEncode(Value); + UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value); } ParamIdx++; @@ -322,6 +294,8 @@ void UVaRestRequestJSON::ProcessRequest(TSharedRef HttpRequest) // Apply params HttpRequest->SetContentAsString(UrlParams); + UE_LOG(LogVaRest, Log, TEXT("Request (url body): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams); + break; } case ERequestContentType::binary: @@ -329,6 +303,8 @@ void UVaRestRequestJSON::ProcessRequest(TSharedRef HttpRequest) HttpRequest->SetHeader(TEXT("Content-Type"), BinaryContentType); HttpRequest->SetContent(RequestBytes); + UE_LOG(LogVaRest, Log, TEXT("Request (binary): %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL()); + break; } case ERequestContentType::json: @@ -343,6 +319,8 @@ void UVaRestRequestJSON::ProcessRequest(TSharedRef HttpRequest) // Set Json content HttpRequest->SetContentAsString(OutputString); + UE_LOG(LogVaRest, Log, TEXT("Request (json): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *OutputString); + break; } @@ -372,13 +350,17 @@ void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttp // Be sure that we have no data from previous response ResetResponseData(); + // Save response code first as int32 + ResponseCode = Response->GetResponseCode(); + // Check we have result to process futher if (!bWasSuccessful) { - UE_LOG(LogVaRest, Error, TEXT("Request failed: %s"), *Request->GetURL()); + UE_LOG(LogVaRest, Error, TEXT("Request failed (%d): %s"), ResponseCode, *Request->GetURL()); // Broadcast the result event OnRequestFail.Broadcast(this); + OnStaticRequestFail.Broadcast(this); return; } @@ -386,11 +368,8 @@ void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttp // Save response data as a string ResponseContent = Response->GetContentAsString(); - // Save response code as int32 - ResponseCode = Response->GetResponseCode(); - // Log response state - UE_LOG(LogVaRest, Log, TEXT("Response (%d): %s"), Response->GetResponseCode(), *Response->GetContentAsString()); + UE_LOG(LogVaRest, Log, TEXT("Response (%d): %s"), ResponseCode, *ResponseContent); // Process response headers TArray Headers = Response->GetAllHeaders(); @@ -424,6 +403,7 @@ void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttp // Broadcast the result event OnRequestComplete.Broadcast(this); + OnStaticRequestComplete.Broadcast(this); // Finish the latent action if (ContinueAction) @@ -434,3 +414,25 @@ void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttp K->Call(ResponseJsonObj); } } + + +////////////////////////////////////////////////////////////////////////// +// Tags + +void UVaRestRequestJSON::AddTag(FName Tag) +{ + if (Tag != NAME_None) + { + Tags.AddUnique(Tag); + } +} + +int32 UVaRestRequestJSON::RemoveTag(FName Tag) +{ + return Tags.Remove(Tag); +} + +bool UVaRestRequestJSON::HasTag(FName Tag) const +{ + return (Tag != NAME_None) && Tags.Contains(Tag); +} diff --git a/VaRestPlugin.uplugin b/VaRestPlugin.uplugin index bce225b7..a3e1b0f1 100644 --- a/VaRestPlugin.uplugin +++ b/VaRestPlugin.uplugin @@ -2,8 +2,8 @@ "FileVersion" : 3, "FriendlyName" : "VaRest", - "Version" : 13, - "VersionName" : "1.1-r13", + "Version" : 14, + "VersionName" : "1.1-r14", "CreatedBy" : "Vladimir Alyamkin", "CreatedByURL" : "http://alyamkin.com", "EngineVersion" : "4.11.0", @@ -20,7 +20,7 @@ }, { "Name": "VaRestEditorPlugin", - "Type": "Editor" + "Type": "Developer" } ] } \ No newline at end of file