I'm working with a REST service that responds with indeterminate types. In particular, it can respond with a String
, a correct payload (T: Decodable
) or an error payload (PayloadError
).
In order to cope with this type of requirement I've ended up with the following solution:
do {
let decoder = JSONDecoder()
let either = try decoder.decode(Either<T, PayloadError>.self, from: data)
switch either {
case .left(let value):
completion(.success(value))
case .right(let payloadError):
completion(.failure(.generic)))
}
} catch {
if let value = String(data: data, encoding: .utf8) as? T {
completion(.success(value))
} else {
completion(.failure(.generic))
}
}
where Either
is defined as:
enum Either<T, U> {
case left(T)
case right(U)
}
extension Either: Decodable where T: Decodable, U: Decodable {
init(from decoder: Decoder) throws {
if let value = try? T(from: decoder) {
self = .left(value)
} else if let value = try? U(from: decoder) {
self = .right(value)
} else {
let context = DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Cannot decode \(T.self) or \(U.self)"
)
throw DecodingError.dataCorrupted(context)
}
}
}
Do you think the code looks good or should I use a more elegant solution?
Update 01.11.2020
These are the possible use cases along side with concrete responses.
Use Case 1
The client calls API method /authenticate
. The server can responds with an authentication token of type String
or a payload error of type PayloadError
(if the server responds with unauthorized, for example). The JSON used to decode PayloadError
is something like:
{
code: 1,
message: "Error message here"
}
PayloadError
is defined as:
struct PayloadError: Decodable {
let code: Int
let message: String
}
Use Case 2
The client calls any other method. For example, it can call /user
that gives back the info for the current user (the auth token is passed along). A struct User: Decodable
is decoded from the returned JSON. If there is an error, the server replies with a PayloadError
.
Based on the above use cases, the server can respond with:
String
T: Decodable
(likeUser
)PayloadError
What I don't like is the code in catch
since its intent is not clear.
T
andPayloadError
types? – I wonder howString(data: data, encoding: .utf8) as? T
works ifT
is anything butString
. \$\endgroup\$.generic
is from your custom error type? Perhaps you can add the signature of the completion function (including the used types), just to avoid misunderstandings. – As far as I can see, there is a general problem: If a string is expected, then any response which is not a well-formed PayloadError will be interpreted as a string. \$\endgroup\$