Как расшифровать тело ошибки в Alamofire 5?

Я пытаюсь перенести свой проект с Alamofire 4.9 на 5.3, и у меня возникают проблемы с обработкой ошибок. Я бы хотел использовать Decodableнасколько это возможно, но мои конечные точки API возвращают одну структуру JSON, когда все идет хорошо, и другую структуру JSON, когда возникает ошибка, одинаковая для всех ошибок на всех конечных точках. Соответствующие Codable в моем коде ApiError.

Я хотел бы создать настраиваемый сериализатор ответов, который может дать мне Result<T, ApiError> вместо значения по умолчанию Result<T, AFError>. Я нашел эту статью, которая, кажется, объясняет общий процесс, но код в ней не компилируется.

Как мне создать такой кастом ResponseSerializer?

2 ответа

Решение

В итоге я заставил его работать со следующим ResponseSerializer:

struct APIError: Error, Decodable {
    let message: String
    let code: String
    let args: [String]
}

final class TwoDecodableResponseSerializer<T: Decodable>: ResponseSerializer {
    
    lazy var decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        return decoder
    }()

    private lazy var successSerializer = DecodableResponseSerializer<T>(decoder: decoder)
    private lazy var errorSerializer = DecodableResponseSerializer<APIError>(decoder: decoder)

    public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Result<T, APIError> {

        guard error == nil else { return .failure(APIError(message: "Unknown error", code: "unknown", args: [])) }

        guard let response = response else { return .failure(APIError(message: "Empty response", code: "empty_response", args: [])) }

        do {
            if response.statusCode < 200 || response.statusCode >= 300 {
                let result = try errorSerializer.serialize(request: request, response: response, data: data, error: nil)
                return .failure(result)
            } else {
                let result = try successSerializer.serialize(request: request, response: response, data: data, error: nil)
                return .success(result)
            }
        } catch(let err) {
            return .failure(APIError(message: "Could not serialize body", code: "unserializable_body", args: [String(data: data!, encoding: .utf8)!, err.localizedDescription]))
        }

    }

}

extension DataRequest {
    @discardableResult func responseTwoDecodable<T: Decodable>(queue: DispatchQueue = DispatchQueue.global(qos: .userInitiated), of t: T.Type, completionHandler: @escaping (Result<T, APIError>) -> Void) -> Self {
        return response(queue: .main, responseSerializer: TwoDecodableResponseSerializer<T>()) { response in
            switch response.result {
            case .success(let result):
                completionHandler(result)
            case .failure(let error):
                completionHandler(.failure(APIError(message: "Other error", code: "other", args: [error.localizedDescription])))
            }
        }
    }
}

И с этим я могу вызвать свой API так:

AF.request(request).validate().responseTwoDecodable(of: [Item].self) { response in
            switch response {
            case .success(let items):
                completion(.success(items))
            case .failure(let error): //error is an APIError
                log.error("Error while loading items: \(String(describing: error))")
                completion(.failure(.couldNotLoad(underlyingError: error)))
            }
        }

Я просто считаю, что любой код состояния за пределами диапазона 200-299 соответствует ошибке.

ResponseSerializers имеют одно требование. В основном вы можете просто скопировать существующие сериализаторы. Например, если вы хотите проанализировать CSV (без проверки ответа):

struct CommaDelimitedSerializer: ResponseSerializer {
    func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> [String] {
        // Call the existing StringResponseSerializer to get many behaviors automatically.
        let string = try StringResponseSerializer().serialize(request: request, 
                                                              response: response, 
                                                              data: data, 
                                                              error: error)
        
        return Array(string.split(separator: ","))
    }
}

Вы можете прочитать больше в документации Alamofire.

Другие вопросы по тегам