1

I have a function to talk to my REST server as follows

func send<T: Decodable>(_ request: HTTPSClient.Request) async throws -> T {
    do {
        let (data, status): (Data, HTTPSClient.StatusCode) = try await request.send()
        if status.responseType != .success { // success means 2xx
            throw try JSONDecoder().decode(CustomError.self, from: data)
        }
        return try JSONDecoder().decode(T.self, from: data)
    } catch {
        // some error handling here
    }
}

And is called as follows


public struct API1Response: Codable {
    // some fields
}

...

let response: API1Response = try await self.send(httpsRequest)

Now I have a special use case where the response needs to be JSON decoded into different structs based on the HTTP response status code (2xx). For example, if the response code is 200 OK, it needs to be decoded into struct APIOKResponse. If the response code is 202 Accepted, it needs to be decoded into struct APIAcceptedResponse and so on.

I want to write a similar function as above which can support multiple response types

I have written the below function, it does not throw any compilation errors

func send<T: Decodable>(_ request: HTTPSClient.Request, _ types: [HTTPSClient.StatusCode: T.Type]) async throws -> T {
    do {
        let (data, status): (Data, HTTPSClient.StatusCode) = try await request.send()
        if status.responseType != .success { // success means 2xx
            throw try JSONDecoder().decode(CustomError.self, from: data)
        }
        guard let t = types[status] else {
            throw ClientError.unknownResponse
        }
        return try JSONDecoder().decode(t.self, from: data)
    } catch {
        // some error handling here
    }
}

I don't understand how to call this though. Tried below

    
struct APIAcceptedResponse: Codable {
    // some fields
}
    
struct APIOKResponse: Codable {
    // some fields
}

...

let response = try await self.send(httpsRequest, [.accepted: APIAcceptedResponse, .ok: APIOKResponse])

// and

let response = try await self.send(httpsRequest, [.accepted: APIAcceptedResponse.self, .ok: APIOKResponse.self])

But in both cases it shows error

Cannot convert value of type 'APIAcceptedResponse.Type' to expected dictionary value type 'APIOKResponse.Type'
  • Is there something I am doing wrong in the send function itself?
  • If not, how to call it?
  • Is this is something can be achieved even?
Suyash Medhavi
  • 1,135
  • 6
  • 18
  • Are you sure your Rest server's response is changing when status code is 200 or 202? – Omer Tekbiyik Nov 09 '22 at 10:33
  • @OmerTekbiyik yes I am sure – Suyash Medhavi Nov 09 '22 at 10:34
  • Personnally, I'd use a `Result< APIAcceptedResponse, APIError>` for the returned value where `APIError` is a Error enum associated value which can holds a `CustomError`, `ClientError`, or basic error. It doesn't fix your issue, but that's an alternative; – Larme Nov 09 '22 at 11:11
  • This is not how generics work. `T` is of a certain type. You can´t have an array of multiple different types. – burnsi Nov 09 '22 at 11:16
  • @Larme It is not an issue of returning success and error. Issue is depending on HTTP status code, there can be 2 TYPES of success reponses – Suyash Medhavi Nov 09 '22 at 11:16

1 Answers1

0

This cannot be solved with generics. T has to be of a certain type. So you cannot provide an array of multiple different types the function could choose from. Also this type has to be known at compile time. You can´t choose it depending on your response.

As an alternative you could use protocols.

A simple example:

protocol ApiResponse: Decodable{
    // common properties of all responses
}

struct ApiOkResponse: Codable, ApiResponse{
    
}

struct ApiErrorResponse: Codable, ApiResponse{
    
}

func getType(_ statusCode: Int) -> ApiResponse.Type{
    if statusCode == 200{
        return ApiOkResponse.self
    } else {
        return ApiErrorResponse.self
    }
}

func send() throws -> ApiResponse{
    let data = Data()
    let responseStatusCode = 200
    let type = getType(responseStatusCode)
    return try JSONDecoder().decode(type, from: data)
}
burnsi
  • 6,194
  • 13
  • 17
  • 27