So, I'm writing an application that performs calls to an external service. In trying to model this with Effect, I've found plenty of examples on how to do this if the call can never fail. But I don't want to provide a reasonable default in the case where the user's login credentials were refused by the service; I think that's a really good case for a failure result.
Given that what I think I want is an Effect<AuthToken, Error>, how do I make this work?
Effect.task {
do {
let authToken = try await possiblyFailingAPICall(credentials)
return authToken // the success case, obvs.
} catch {
print("Login failed.") // what do I return here?
}
}
I should clarify this a bit. In simplifying the example, I think I erased some important stuff. So, here's the longer, actual code:
enum APIError: Error, Equatable {
case malformedHostURL(String),
operationFailed(String),
dataCorrupted(String),
missingInformation(String)
}
struct AuthClient {
enum WebAPIError: Error, Equatable {
case identityTokenMissing
case unableToDecodeIdentityToken
case unableToEncodeJSONData
case unableToDecodeJSONData
case unauthorized
case invalidResponse
case httpError(statusCode: Int)
}
struct SIWAAuthRequestBody: Encodable, CustomStringConvertible {
let name: String?
let email: String?
let appleIdentityToken: String
var description: String {
"name: \(name ?? "nil"), email: \(email ?? "nil"), appleIdentityToken: \(appleIdentityToken)"
}
}
var signInWithApple: (String?, String?, Data?) -> Effect<AuthContent, Error>
}
extension AuthClient {
static let serviceHost = "http://localhost:8080"
static let live = Self(
signInWithApple: { name, email, identityToken in
Effect.task {
guard let identityToken = identityToken else { throw WebAPIError.identityTokenMissing }
guard let identityTokenString = String(data: identityToken, encoding: .utf8)
else { throw WebAPIError.unableToDecodeIdentityToken }
let body = SIWAAuthRequestBody(name: name,
email: email,
appleIdentityToken: identityTokenString)
guard let jsonBody = try? JSONEncoder().encode(body) else { throw WebAPIError.unableToEncodeJSONData }
guard let url = URL(string: "\(serviceHost)/v1/siwa/auth") else { throw APIError.malformedHostURL(serviceHost) }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonBody
do {
let authContent: AuthContent = try await session.decode(for: request, dateDecodingStrategy: .iso8601)
return authContent
} catch {
throw APIError.operationFailed(error.localizedDescription)
}
}
.setFailureType(to: Error.self)
.eraseToEffect()
}
)
}
The compiler complains, saying, "invalid conversion from throwing function of type '@Sendable () async throws -> AuthContent' to non-throwing function type '@Sendable () async -> AuthContent'"
I'm not entirely sure what that means. Does it mean that I need to refactor the error types so that the closure can only throw a single type? It reads as though the closure isn't allowed to throw anything. Or something.
Yeah @victor is correct, the .setFailureType(to:) method is only defined when Failure == Never, and your effect does have a failure. So removing that should fix things.