// // V2vRepository.swift // SodaLive // // Created by klaus on 2/9/26. // import Foundation import Combine import Moya import CombineMoya protocol V2VRepository { func join(request: V2VJoinRequest) -> AnyPublisher func leave(agentId: String) -> AnyPublisher } enum V2VRepositoryError: Error { case network(message: String) case decoding case business(message: String) var userMessage: String { switch self { case .network(let message): return message case .decoding: return I18n.Common.commonError case .business(let message): return message } } } final class V2VRepositoryImpl: V2VRepository { private let api: MoyaProvider init(api: MoyaProvider = MoyaProvider()) { self.api = api } func join(request: V2VJoinRequest) -> AnyPublisher { api.requestPublisher(.join(request: request)) .tryMap { response in try Self.validateStatusCode(response) do { let decoded = try JSONDecoder().decode(V2VJoinResponse.self, from: response.data) guard !decoded.agentId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { throw V2VRepositoryError.decoding } return decoded.agentId } catch let error as V2VRepositoryError { throw error } catch { throw V2VRepositoryError.decoding } } .mapError { Self.mapError($0) } .eraseToAnyPublisher() } func leave(agentId: String) -> AnyPublisher { api.requestPublisher(.leave(agentId: agentId)) .tryMap { response in try Self.validateStatusCode(response) if !response.data.isEmpty { if let text = String(data: response.data, encoding: .utf8) { DEBUG_LOG("[V2V] leave response: \(text)") } _ = try? JSONDecoder().decode(V2VLeaveResponse.self, from: response.data) } return () } .mapError { Self.mapError($0) } .eraseToAnyPublisher() } private static func validateStatusCode(_ response: Response) throws { guard (200..<300).contains(response.statusCode) else { throw V2VRepositoryError.business(message: parseBusinessMessage(from: response.data) ?? I18n.Common.commonError) } } private static func mapError(_ error: Error) -> V2VRepositoryError { if let mapped = error as? V2VRepositoryError { return mapped } guard let moyaError = error as? MoyaError else { return .network(message: I18n.Common.commonError) } switch moyaError { case .statusCode(let response): if let message = parseBusinessMessage(from: response.data) { return .business(message: message) } return .business(message: I18n.Common.commonError) case .objectMapping, .jsonMapping, .encodableMapping, .stringMapping, .imageMapping: return .decoding default: return .network(message: I18n.Common.commonError) } } private static func parseBusinessMessage(from data: Data) -> String? { if let decoded = try? JSONDecoder().decode(V2VErrorResponse.self, from: data), let message = decoded.message?.trimmingCharacters(in: .whitespacesAndNewlines), !message.isEmpty { return message } if let decoded = try? JSONDecoder().decode(ApiResponseWithoutData.self, from: data), let message = decoded.message?.trimmingCharacters(in: .whitespacesAndNewlines), !message.isEmpty { return message } return nil } } private struct V2VErrorResponse: Decodable { let message: String? }