// Copyright Epic Games, Inc. All Rights Reserved. #include "ReviewComments.h" #include "UObject/Class.h" #include "Serialization/JsonSerializer.h" namespace { FString LowerCamelCase(const FString& In) { FString Result = In; if (!Result.IsEmpty()) { Result[0] = tolower(Result[0]); } return Result; } bool TryGetJsonObjectField(FReviewTopic* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } *OutVal = FReviewTopic::FromString(Value->AsString()); return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const FReviewTopic& Val, const FString& Key) { JsonObject->SetStringField(Key, Val.ToString()); } bool TryGetJsonObjectField(int32* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } *OutVal = (int32)Value->AsNumber(); return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const int32& Val, const FString& Key) { JsonObject->SetNumberField(Key, Val); } bool TryGetJsonObjectField(FString* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } *OutVal = Value->AsString(); return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const FString& Val, const FString& Key) { JsonObject->SetStringField(Key, Val); } bool TryGetJsonObjectField(FReviewCommentContext* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } // for some reason the API stores null-contexts as empty arrays. if (Value->Type == EJson::Array) { checkSlow(Value->AsArray().IsEmpty()); return false; } *OutVal = FReviewCommentContext::FromJson(Value->AsObject()); return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const FReviewCommentContext& Val, const FString& Key) { JsonObject->SetObjectField(Key, Val.ToJsonObject()); } bool TryGetJsonObjectField(TSet* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } for (const TSharedPtr& Item : Value->AsArray()) { OutVal->Add(Item->AsString()); } return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const TSet& Val, const FString& Key) { TArray> Array; for (const FString& Element : Val) { Array.Add(MakeShared(Element)); } JsonObject->SetArrayField(Key, Array); } bool TryGetJsonObjectField(TArray* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } for (const TSharedPtr& Item : Value->AsArray()) { OutVal->Add(Item->AsString()); } return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const TArray& Val, const FString& Key) { TArray> Array; for (const FString& Element : Val) { Array.Add(MakeShared(Element)); } JsonObject->SetArrayField(Key, Array); } bool TryGetJsonObjectField(EReviewCommentTaskState* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } *OutVal = (EReviewCommentTaskState)StaticEnum()->GetValueByNameString(Value->AsString()); return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const EReviewCommentTaskState& Val, const FString& Key) { const FString EnumString = StaticEnum()->GetValueAsString(Val); int32 ChopIndex = INDEX_NONE; if (EnumString.FindLastChar(':', ChopIndex)) { JsonObject->SetStringField(Key, LowerCamelCase(EnumString.RightChop(ChopIndex + 1))); } } bool TryGetJsonObjectField(FDateTime* OutVal, const TSharedPtr& JsonObject, const FString& Key) { if (const TSharedPtr &Value = JsonObject->TryGetField(Key)) { if (Value->IsNull()) { return false; } *OutVal = FDateTime::FromUnixTimestamp((int64)Value->AsNumber()); return true; } return false; } void TrySetJsonObjectField(TSharedPtr JsonObject, const FDateTime& Val, const FString& Key) { JsonObject->SetNumberField(Key, Val.ToUnixTimestamp()); } template bool TryGetJsonObjectField(TOptional* OutVal, const TSharedPtr& JsonObject, const FString& Key) { OptionalType Val; if (TryGetJsonObjectField(&Val, JsonObject, Key)) { *OutVal = Val; return true; } return false; } template void TrySetJsonObjectField(TSharedPtr JsonObject, const TOptional& Val, const FString& Key) { if (Val.IsSet()) { TrySetJsonObjectField(JsonObject, Val.GetValue(), Key); } } #define TRY_SET_FROM_JSON(OwningObject, Key, JsonObject) TryGetJsonObjectField(&(OwningObject).Key, JsonObject, LowerCamelCase(#Key)) #define TRY_SET_FROM_JSON_NAMED(OwningObject, Key, JsonObject, Name) TryGetJsonObjectField(&(OwningObject).Key, JsonObject, LowerCamelCase(Name)) #define SET_JSON_FIELD(JsonObject, OwningObject, Key) TrySetJsonObjectField(JsonObject, (OwningObject).Key, LowerCamelCase(#Key)) #define SET_JSON_FIELD_NAMED(JsonObject, OwningObject, Key, Name) TrySetJsonObjectField(JsonObject, (OwningObject).Key, LowerCamelCase(Name)) } FString FReviewCommentContext::ToJson() const { FString Result; const TSharedRef> Writer = TJsonWriterFactory<>::Create(&Result); FJsonSerializer::Serialize(ToJsonObject().ToSharedRef(), Writer); return Result; } TSharedPtr FReviewCommentContext::ToJsonObject() const { TSharedPtr Result = MakeShared(); SET_JSON_FIELD(Result, *this, File); SET_JSON_FIELD(Result, *this, Version); SET_JSON_FIELD(Result, *this, Content); SET_JSON_FIELD(Result, *this, LeftLine); SET_JSON_FIELD(Result, *this, RightLine); SET_JSON_FIELD_NAMED(Result, *this, ReplyTo, TEXT("comment")); return Result; } FReviewCommentContext FReviewCommentContext::FromJson(const FString& Json) { const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Json); TSharedPtr JsonValue; if (FJsonSerializer::Deserialize(JsonReader, JsonValue)) { const TSharedPtr< FJsonObject > JsonObject = JsonValue->AsObject(); return FromJson(JsonObject); } return {}; } FReviewCommentContext FReviewCommentContext::FromJson(const TSharedPtr& JsonObject) { FReviewCommentContext Result = {}; if (!JsonObject.IsValid()) { return Result; } TRY_SET_FROM_JSON(Result, File, JsonObject); TRY_SET_FROM_JSON(Result, Version, JsonObject); TRY_SET_FROM_JSON(Result, Content, JsonObject); TRY_SET_FROM_JSON(Result, LeftLine, JsonObject); TRY_SET_FROM_JSON(Result, RightLine, JsonObject); TRY_SET_FROM_JSON_NAMED(Result, ReplyTo, JsonObject, TEXT("comment")); return Result; } FString FReviewTopic::ToString() const { switch(TopicType) { case EReviewTopicType::Review: return FString::Format(TEXT("reviews/{0}"), {ChangelistNum}); case EReviewTopicType::Change: return FString::Format(TEXT("changes/{0}"), {ChangelistNum}); } return {}; } FReviewTopic FReviewTopic::FromString(const FString& ReviewTopic) { FReviewTopic Result; FString TopicTypeString; ReviewTopic.Split("/", &TopicTypeString, &Result.ChangelistNum); if (TopicTypeString == TEXT("reviews")) { Result.TopicType = EReviewTopicType::Review; } else if (TopicTypeString == TEXT("changes")) { Result.TopicType = EReviewTopicType::Change; } else { Result.TopicType = EReviewTopicType::Unrecognised; } return Result; } FString FReviewComment::ToJson() const { FString Result; const TSharedRef> Writer = TJsonWriterFactory<>::Create(&Result); FJsonSerializer::Serialize(ToJsonObject().ToSharedRef(), Writer); return Result; } TSharedPtr FReviewComment::ToJsonObject() const { TSharedPtr Result = MakeShared(); SET_JSON_FIELD(Result, *this, Topic); SET_JSON_FIELD(Result, *this, Body); SET_JSON_FIELD(Result, *this, Context); // don't include context if it doesn't have any fields const TMap>& ContextValues = Result->GetObjectField(TEXT("Context"))->Values; if (ContextValues.IsEmpty()) { Result->RemoveField(TEXT("Context")); } SET_JSON_FIELD(Result, *this, Likes); SET_JSON_FIELD(Result, *this, ReadBy); SET_JSON_FIELD(Result, *this, TaskState); SET_JSON_FIELD(Result, *this, User); SET_JSON_FIELD_NAMED(Result, *this, CommentID, TEXT("id")); SET_JSON_FIELD_NAMED(Result, *this, CreatedTime, TEXT("time")); SET_JSON_FIELD_NAMED(Result, *this, EditedTime, TEXT("edited")); SET_JSON_FIELD_NAMED(Result, *this, UpdatedTime, TEXT("updated")); if (bIsClosed) { TrySetJsonObjectField(Result, TArray{TEXT("closed")}, TEXT("flags")); } return Result; } FReviewComment FReviewComment::FromJson(const FString& Json) { const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Json); TSharedPtr JsonValue; if (FJsonSerializer::Deserialize(JsonReader, JsonValue)) { const TSharedPtr< FJsonObject > JsonObject = JsonValue->AsObject(); return FromJson(JsonObject); } return {}; } FReviewComment FReviewComment::FromJson(const TSharedPtr& JsonObject) { FReviewComment Result = {}; if (!JsonObject.IsValid()) { return Result; } TRY_SET_FROM_JSON(Result, Topic, JsonObject); TRY_SET_FROM_JSON(Result, Body, JsonObject); TRY_SET_FROM_JSON(Result, Context, JsonObject); TRY_SET_FROM_JSON(Result, Likes, JsonObject); TRY_SET_FROM_JSON(Result, ReadBy, JsonObject); TRY_SET_FROM_JSON(Result, TaskState, JsonObject); TRY_SET_FROM_JSON(Result, User, JsonObject); TRY_SET_FROM_JSON_NAMED(Result, CommentID, JsonObject, TEXT("id")); TRY_SET_FROM_JSON_NAMED(Result, CreatedTime, JsonObject, TEXT("time")); TRY_SET_FROM_JSON_NAMED(Result, EditedTime, JsonObject, TEXT("edited")); TRY_SET_FROM_JSON_NAMED(Result, UpdatedTime, JsonObject, TEXT("updated")); // set flags Result.bIsClosed = false; if (const TSharedPtr &FlagsField = JsonObject->TryGetField(TEXT("flags"))) { if (!FlagsField->IsNull()) { for (const TSharedPtr& Item : FlagsField->AsArray()) { FString Flag = Item->AsString(); if (Flag == TEXT("closed")) { Result.bIsClosed = true; } else { // swarm documentation doesn't mention any flags except 'closed'. if this is hit, let jordan.hoffmann know ensure(false); } } } } return Result; }