Swift Generics - Pass multiple types from caller

64 views Asked by At

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?
1

There are 1 answers

0
burnsi On

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)
}