#pragma once #include "Struct/DTFluxServerResponseStruct.h" // === IMPLÉMENTATION DES CONSTRUCTEURS === FDTFluxServerResponse::FDTFluxServerResponse() { ReceivedAt = FDateTime::Now(); ApiDataType = EDTFluxApiDataType::None; ParsingStatus = EDTFluxResponseStatus::Unset; } FDTFluxServerResponse::FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus, bool bLogErrors) { ReceivedAt = FDateTime::Now(); RawMessage = JsonMessage; ParsingStatus = InitializeFromJson(JsonMessage, bLogErrors); OutStatus = ParsingStatus; if (bLogErrors && ParsingStatus != EDTFluxResponseStatus::Success) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to create DTFluxServerResponse: %s"), *GetErrorMessage()); } } FDTFluxServerResponse FDTFluxServerResponse::CreateFromJson(const FString& JsonMessage, bool bLogErrors) { EDTFluxResponseStatus Status; FDTFluxServerResponse Response(JsonMessage, Status, bLogErrors); if (bLogErrors) { if (Status == EDTFluxResponseStatus::Success) { UE_LOG(logDTFluxNetwork, Verbose, TEXT("Successfully created DTFluxServerResponse: %s"), *Response.ToDebugString()); } else { UE_LOG(logDTFluxNetwork, Warning, TEXT("Created DTFluxServerResponse with issues: %s"), *Response.GetErrorMessage()); } } return Response; } EDTFluxResponseStatus FDTFluxServerResponse::TryParse(bool bLogErrors) { // Vérifier que le type est présent if (Type.IsEmpty()) { if (bLogErrors) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response missing 'type' field")); ApiDataType = EDTFluxApiDataType::None; ParsingStatus = EDTFluxResponseStatus::MissingData; return ParsingStatus; } } ParsingStatus = EDTFluxResponseStatus::UnknownError; // Validation supplémentaire selon le type if (ContainsDataType("race-data")) { if (bLogErrors) { UE_LOG(logDTFluxNetwork, Verbose, TEXT("Parsing race-data response")); } ApiDataType = EDTFluxApiDataType::RaceData; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } if (ContainsDataType("team-list")) { ApiDataType = EDTFluxApiDataType::TeamList; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } if (ContainsDataType("contest-ranking")) { if (ContestID == -1) { if (bLogErrors) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest-ranking missing ContestID")); } ParsingStatus = EDTFluxResponseStatus::DataError; return ParsingStatus; } ApiDataType = EDTFluxApiDataType::ContestRanking; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } else if (ContainsDataType("stage-ranking")) { if (ContestID == -1 || StageID == -1) { if (bLogErrors) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage-ranking missing ContestID or StageID")); } ParsingStatus = EDTFluxResponseStatus::DataError; return ParsingStatus; } if (SplitID != -1) { ApiDataType = EDTFluxApiDataType::SplitRanking; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } ApiDataType = EDTFluxApiDataType::StageRanking; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } if (ContainsDataType("status-update")) { ApiDataType = EDTFluxApiDataType::StatusUpdate; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } if (ContainsDataType("split-sensor")) { ApiDataType = EDTFluxApiDataType::SplitSensor; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } if (ContainsDataType("team-update")) { ApiDataType = EDTFluxApiDataType::TeamUpdate; ParsingStatus = EDTFluxResponseStatus::Success; return ParsingStatus; } return EDTFluxResponseStatus::UnknownError; } EDTFluxResponseStatus FDTFluxServerResponse::InitializeFromJson(const FString& JsonMessage, bool bLogErrors) { // Parser le JSON de base if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonMessage, this, 0, 0, false, &FailureReason)) { if (bLogErrors) { UE_LOG(logDTFluxNetwork, Error, TEXT("JSON parse error: %s\nMessage: %s"), *FailureReason.ToString(), *JsonMessage); } return EDTFluxResponseStatus::JsonParseError; } // Vérifier si c'est une erreur du serveur if (Code != -1) { if (bLogErrors) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Server error response: Code=%d, Message=%s"), Code, *Message); } return EDTFluxResponseStatus::ServerError; } return TryParse(); } FString FDTFluxServerResponse::GetErrorMessage() const { switch (ParsingStatus) { case EDTFluxResponseStatus::Success: return TEXT("No error"); case EDTFluxResponseStatus::JsonParseError: return FString::Printf(TEXT("JSON parsing failed: %s"), *FailureReason.ToString()); case EDTFluxResponseStatus::ServerError: return FString::Printf(TEXT("Server error %d: %s"), Code, *Message); case EDTFluxResponseStatus::InvalidType: return FString::Printf(TEXT("Invalid or missing response type: '%s'"), *Type); case EDTFluxResponseStatus::MissingData: return FString::Printf(TEXT("Missing required data fields for type '%s'"), *Type); case EDTFluxResponseStatus::UnknownError: default: return TEXT("Unknown error occurred during parsing"); } } FString FDTFluxServerResponse::ToDebugString() const { return FString::Printf( TEXT("DTFluxServerResponse[Status=%s, Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"), *UEnum::GetValueAsString(ParsingStatus), *Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString()); } bool FDTFluxServerResponse::ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList) { ParsingStatus = EDTFluxResponseStatus::Unset; if (!ValidateResponseType(TEXT("team-list"))) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a team-list type")); ParsingStatus = EDTFluxResponseStatus::InvalidType; return false; } TSharedPtr JsonObject; if (!ParseJsonObject(JsonObject)) { ParsingStatus = EDTFluxResponseStatus::JsonParseError; return false; } const TArray>* DataArray; if (!JsonObject->TryGetArrayField(TEXT("datas"), DataArray)) { UE_LOG(logDTFluxNetwork, Error, TEXT("No 'datas' array found in team-list response")); ParsingStatus = EDTFluxResponseStatus::MissingData; return false; } OutTeamList.Participants.Empty(); for (const TSharedPtr& Value : *DataArray) { if (Value->Type == EJson::Object) { const TSharedPtr Item = Value->AsObject(); FDTFluxParticipant Participant; if (UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant)) { OutTeamList.Participants.Add(Participant); ParsingStatus = EDTFluxResponseStatus::Success; } else { UE_LOG(logDTFluxNetwork, Warning, TEXT("Failed to parse participant from JSON")); ParsingStatus = EDTFluxResponseStatus::JsonParseError; } } } ParsingStatus = GetParsingStatus(); if (ParsingStatus == EDTFluxResponseStatus::Success && OutTeamList.Participants.Num() != 0) { UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d participants from team-list"), OutTeamList.Participants.Num()); return true; } if (OutTeamList.Participants.Num() == 0) { UE_LOG(logDTFluxNetwork, Error, TEXT("No Participant Added")); } if (ParsingStatus != EDTFluxResponseStatus::Success) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse team-list Error : %s"), *GetErrorMessage()); } return false; } bool FDTFluxServerResponse::ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate) { return ParseTeamListResponse(OutTeamUpdate); } bool FDTFluxServerResponse::ParseRaceData(FDTFluxRaceData& OutRaceData) { ParsingStatus = EDTFluxResponseStatus::Unset; if (!ValidateResponseType(TEXT("race-data"))) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a race-data type")); ParsingStatus = EDTFluxResponseStatus::InvalidType; return false; } FDTFluxRaceDataResponse RaceDataResponse; if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &RaceDataResponse)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse race-data JSON: %s"), *RawMessage); ParsingStatus = EDTFluxResponseStatus::JsonParseError; return false; } OutRaceData.Datas.Empty(); for (const auto& Contest : RaceDataResponse.Datas) { FDTFluxContest NewContest; NewContest.Name = Contest.Name; NewContest.ContestId = Contest.Id; NewContest.Date = Contest.Date; UE_LOG(logDTFluxNetwork, Verbose, TEXT("Processing Contest %d [%s] Starting at %s"), Contest.Id, *Contest.Name, *Contest.Date.ToString()); // Satges for (const auto& Stage : Contest.Stages) { FDTFluxStage NewStage; NewStage.StageId = Stage.Id; NewStage.Name = Stage.Name; // Construct full Timestamps strings FString StartTimeFString = FString::Printf(TEXT("%s %s"), *NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), *Stage.StartTime); FString EndTimeFString = FString::Printf(TEXT("%s %s"), *NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), *Stage.EndTime); FString CutOffFString = FString::Printf(TEXT("%s %s"), *NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), *Stage.CutOff); FDateTime::Parse(StartTimeFString, NewStage.StartTime); FDateTime::Parse(EndTimeFString, NewStage.EndTime); FDateTime::Parse(CutOffFString, NewStage.CutOff); NewContest.Stages.Add(NewStage); UE_LOG(logDTFluxNetwork, Verbose, TEXT("Stage %d [%s]: Start[%s], CutOff[%s], End[%s]"), Stage.Id, *Stage.Name, *NewStage.StartTime.ToString(), *NewStage.CutOff.ToString(), *NewStage.EndTime.ToString()); } // Traiter les splits for (const auto& Split : Contest.Splits) { FDTFluxSplit NewSplit; NewSplit.SplitId = Split.Id; NewSplit.Name = Split.Name; NewContest.Splits.Add(NewSplit); UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split %d [%s]"), Split.Id, *Split.Name); } // Update Contest metadata NewContest.UpdateEndTime(); NewContest.UpdateLastStageId(); OutRaceData.Datas.Add(NewContest); } UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d contests from race-data"), OutRaceData.Datas.Num()); ParsingStatus = EDTFluxResponseStatus::Success; return true; } bool FDTFluxServerResponse::ParseContestRanking(FDTFluxContestRankings& OutContestRankings) { if (!ValidateResponseType(TEXT("contest-ranking"))) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a contest-ranking type")); ParsingStatus = EDTFluxResponseStatus::InvalidType; return false; } FDTFluxContestRankingResponse ContestRankingResponse; if (!FJsonObjectConverter::JsonObjectStringToUStruct( RawMessage, &ContestRankingResponse)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse contest-ranking JSON: %s"), *RawMessage); ParsingStatus = EDTFluxResponseStatus::JsonParseError; return false; } OutContestRankings.ContestId = ContestRankingResponse.ContestID; OutContestRankings.Rankings.Empty(); for (const auto& RankingItem : ContestRankingResponse.Datas) { FDTFluxContestRanking Ranking = RankingItem; OutContestRankings.Rankings.Add(Ranking); } UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed contest ranking for Contest %d with %d entries"), OutContestRankings.ContestId, OutContestRankings.Rankings.Num()); ParsingStatus = EDTFluxResponseStatus::Success; return true; } bool FDTFluxServerResponse::ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings) { if (!ValidateResponseType(TEXT("stage-ranking"))) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a stage-ranking type")); ParsingStatus = EDTFluxResponseStatus::InvalidType; return false; } FDTFluxStageRankingResponse RankingResponse; if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &RankingResponse)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse stage-ranking JSON: %s"), *RawMessage); ParsingStatus = EDTFluxResponseStatus::JsonParseError; return false; } OutStageRankings.ContestId = ContestID; OutStageRankings.StageId = StageID; OutStageRankings.Rankings = static_cast>(RankingResponse.Datas); OutStageRankings.Initialize(); UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed stage ranking for Contest %d, Stage %d with %d entries"), OutStageRankings.ContestId, OutStageRankings.StageId, OutStageRankings.Rankings.Num()); ParsingStatus = EDTFluxResponseStatus::Success; return true; } bool FDTFluxServerResponse::ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings) { if (!ValidateResponseType(TEXT("stage-ranking")) || SplitID == -1) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a split-ranking type (stage-ranking with SplitID != -1)")); ParsingStatus = EDTFluxResponseStatus::InvalidType; return false; } FDTFluxSplitRankingResponse SplitRankingResponse; if (!FJsonObjectConverter::JsonObjectStringToUStruct< FDTFluxSplitRankingResponse>(RawMessage, &SplitRankingResponse)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-ranking JSON: %s"), *RawMessage); ParsingStatus = EDTFluxResponseStatus::JsonParseError; return false; } OutSplitRankings.ContestId = ContestID; OutSplitRankings.StageId = StageID; OutSplitRankings.SplitId = SplitID; OutSplitRankings.Rankings = static_cast>(SplitRankingResponse.Datas); UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed split ranking for Contest %d, Stage %d, Split %d with %d entries"), OutSplitRankings.ContestId, OutSplitRankings.StageId, OutSplitRankings.SplitId, OutSplitRankings.Rankings.Num()); ParsingStatus = EDTFluxResponseStatus::Success; return true; } bool FDTFluxServerResponse::ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate) { if (!ValidateResponseType(TEXT("status-update"))) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a status-update type")); ParsingStatus = EDTFluxResponseStatus::InvalidType; return false; } if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &OutStatusUpdate)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse status-update JSON: %s"), *RawMessage); ParsingStatus = EDTFluxResponseStatus::JsonParseError; return false; } UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed status update for bib %d"), OutStatusUpdate.Bib); ParsingStatus = EDTFluxResponseStatus::Success; return true; } bool FDTFluxServerResponse::ParseSplitSensorResponse(TArray& OutSplitSensorInfos) { if (!ValidateResponseType(TEXT("split-sensor"))) { UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a split-sensor type")); ParsingStatus = EDTFluxResponseStatus::InvalidType; return false; } FDTFluxSplitSensorResponse SplitSensorResponse; if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &SplitSensorResponse)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-sensor JSON: %s"), *RawMessage); ParsingStatus = EDTFluxResponseStatus::JsonParseError; return false; } OutSplitSensorInfos.Empty(); for (const auto& SplitSensorInfoResponse : SplitSensorResponse.Datas) { FDTFluxSplitSensorInfo NewSplitSensorInfo; NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib; NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID; NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID; NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID; NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time; OutSplitSensorInfos.Add(NewSplitSensorInfo); UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d"), NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId, NewSplitSensorInfo.SplitId); } UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d split sensor entries"), OutSplitSensorInfos.Num()); ParsingStatus = EDTFluxResponseStatus::Success; return true; } void FDTFluxServerResponse::ShowDebug(const bool bShouldPrintRawMessage) const { FString DebugMsg = FString::Printf( TEXT("DTFluxServerResponse[Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"), *Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString()); if (bShouldPrintRawMessage) { UE_LOG(logDTFluxNetwork, Log, TEXT("%s\nRawMessage: \"%s\""), *DebugMsg, *RawMessage); } } // === MÉTHODES PRIVÉES === bool FDTFluxServerResponse::ParseJsonObject(TSharedPtr& OutJsonObject) const { TSharedRef> Reader = TJsonReaderFactory<>::Create(RawMessage); if (!FJsonSerializer::Deserialize(Reader, OutJsonObject) || !OutJsonObject.IsValid()) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse JSON: %s"), *RawMessage); return false; } return true; } bool FDTFluxServerResponse::ValidateResponseType(const FString& ExpectedType) const { if (!IsValidResponse()) { UE_LOG(logDTFluxNetwork, Error, TEXT("Invalid server response: Code=%d, Message=%s"), Code, *Message); return false; } if (!ContainsDataType(ExpectedType)) { UE_LOG(logDTFluxNetwork, Error, TEXT("Expected type '%s' but got '%s'"), *ExpectedType, *Type); return false; } return true; }