Skip to content

Commit

Permalink
Use Swift's built-in Result type when available (#467)
Browse files Browse the repository at this point in the history
* Updates Result.swift to use Swift's built-in Result type on Swift 5.0+

* Fix Result deprecation warnings internally

* Kickstart CI

Co-authored-by: Rita Zerrizuela <zeta@widcket.com>
  • Loading branch information
ejensen and Widcket authored Apr 21, 2021
1 parent a6e7507 commit 408a9bf
Show file tree
Hide file tree
Showing 13 changed files with 84 additions and 53 deletions.
4 changes: 2 additions & 2 deletions Auth0/AuthenticationServicesSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ final class AuthenticationServicesSession: SessionTransaction {
guard $1 == nil, let callbackURL = $0 else {
let authError = $1 ?? WebAuthError.unknownError
if case ASWebAuthenticationSessionError.canceledLogin = authError {
self?.callback(.failure(error: WebAuthError.userCancelled))
self?.callback(.failure(WebAuthError.userCancelled))
} else {
self?.callback(.failure(error: authError))
self?.callback(.failure(authError))
}
return TransactionStore.shared.clear()
}
Expand Down
6 changes: 3 additions & 3 deletions Auth0/BaseAuthTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,20 @@ class BaseAuthTransaction: NSObject, AuthTransaction {
}

func cancel() {
self.callback(Result.failure(error: WebAuthError.userCancelled))
self.callback(Result.failure(WebAuthError.userCancelled))
}

func handleUrl(_ url: URL) -> Bool {
self.logger?.trace(url: url, source: "iOS Safari")
guard url.absoluteString.lowercased().hasPrefix(self.redirectURL.absoluteString.lowercased()) else { return false }
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
self.callback(.failure(error: AuthenticationError(string: url.absoluteString, statusCode: 200)))
self.callback(.failure(AuthenticationError(string: url.absoluteString, statusCode: 200)))
return false
}
let items = self.handler.values(fromComponents: components)
guard has(state: self.state, inItems: items) else { return false }
if items["error"] != nil {
self.callback(.failure(error: AuthenticationError(info: items, statusCode: 0)))
self.callback(.failure(AuthenticationError(info: items, statusCode: 0)))
} else {
self.handler.credentials(from: items, callback: self.callback)
}
Expand Down
8 changes: 4 additions & 4 deletions Auth0/BaseWebAuth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ class BaseWebAuth: WebAuthenticatable {

func start(_ callback: @escaping (Result<Credentials>) -> Void) {
guard let redirectURL = self.redirectURL else {
return callback(Result.failure(error: WebAuthError.noBundleIdentifierFound))
return callback(Result.failure(WebAuthError.noBundleIdentifierFound))
}
if self.responseType.contains(.idToken) {
guard self.nonce != nil else { return callback(Result.failure(error: WebAuthError.noNonceProvided)) }
guard self.nonce != nil else { return callback(Result.failure(WebAuthError.noNonceProvided)) }
}
let handler = self.handler(redirectURL)
let state = self.parameters["state"] ?? generateDefaultState()
Expand All @@ -165,7 +165,7 @@ class BaseWebAuth: WebAuthenticatable {
guard let queryItems = URLComponents(url: invitationURL, resolvingAgainstBaseURL: false)?.queryItems,
let organizationId = queryItems.first(where: { $0.name == "organization" })?.value,
let invitationId = queryItems.first(where: { $0.name == "invitation" })?.value else {
return callback(.failure(error: WebAuthError.unknownError)) // TODO: On the next major, create a new error case
return callback(.failure(WebAuthError.unknownError)) // TODO: On the next major, create a new error case
}
organization = organizationId
invitation = invitationId
Expand Down Expand Up @@ -205,7 +205,7 @@ class BaseWebAuth: WebAuthenticatable {
}
#endif
// TODO: On the next major add a new case to WebAuthError
callback(.failure(error: WebAuthError.unknownError))
callback(.failure(WebAuthError.unknownError))
return nil
}

Expand Down
30 changes: 15 additions & 15 deletions Auth0/Handlers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import Foundation
func plainJson(from response: Response<AuthenticationError>, callback: Request<[String: Any], AuthenticationError>.Callback) {
do {
if let dictionary = try response.result() as? [String: Any] {
callback(.success(result: dictionary))
callback(.success(dictionary))
} else {
callback(.failure(error: AuthenticationError(string: string(response.data))))
callback(.failure(AuthenticationError(string: string(response.data))))
}

} catch let error {
callback(.failure(error: error))
callback(.failure(error))
}
}

Expand All @@ -41,26 +41,26 @@ func codable<T: Codable>(from response: Response<AuthenticationError>, callback:
let data = try JSONSerialization.data(withJSONObject: dictionary)
let decoder = JSONDecoder()
let decodedObject = try decoder.decode(T.self, from: data)
callback(.success(result: decodedObject))
callback(.success(decodedObject))
} else {
callback(.failure(error: AuthenticationError(string: string(response.data))))
callback(.failure(AuthenticationError(string: string(response.data))))
}

} catch let error {
callback(.failure(error: error))
callback(.failure(error))
}
}

func authenticationObject<T: JSONObjectPayload>(from response: Response<AuthenticationError>, callback: Request<T, AuthenticationError>.Callback) {
do {
if let dictionary = try response.result() as? [String: Any], let object = T(json: dictionary) {
callback(.success(result: object))
callback(.success(object))
} else {
callback(.failure(error: AuthenticationError(string: string(response.data))))
callback(.failure(AuthenticationError(string: string(response.data))))
}

} catch let error {
callback(.failure(error: error))
callback(.failure(error))
}
}

Expand All @@ -69,23 +69,23 @@ func databaseUser(from response: Response<AuthenticationError>, callback: Reques
if let dictionary = try response.result() as? [String: Any], let email = dictionary["email"] as? String {
let username = dictionary["username"] as? String
let verified = dictionary["email_verified"] as? Bool ?? false
callback(.success(result: (email: email, username: username, verified: verified)))
callback(.success((email: email, username: username, verified: verified)))
} else {
callback(.failure(error: AuthenticationError(string: string(response.data))))
callback(.failure(AuthenticationError(string: string(response.data))))
}

} catch let error {
callback(.failure(error: error))
callback(.failure(error))
}
}

func noBody(from response: Response<AuthenticationError>, callback: Request<Void, AuthenticationError>.Callback) {
do {
_ = try response.result()
callback(.success(result: ()))
callback(.success(()))
} catch let error as Auth0Error where error.code == emptyBodyError {
callback(.success(result: ()))
callback(.success(()))
} catch let error {
callback(.failure(error: error))
callback(.failure(error))
}
}
12 changes: 6 additions & 6 deletions Auth0/Management.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,24 @@ struct Management: Trackable, Loggable {
func managementObject(response: Response<ManagementError>, callback: Request<ManagementObject, ManagementError>.Callback) {
do {
if let dictionary = try response.result() as? ManagementObject {
callback(.success(result: dictionary))
callback(.success(dictionary))
} else {
callback(.failure(error: ManagementError(string: string(response.data))))
callback(.failure(ManagementError(string: string(response.data))))
}
} catch let error {
callback(.failure(error: error))
callback(.failure(error))
}
}

func managementObjects(response: Response<ManagementError>, callback: Request<[ManagementObject], ManagementError>.Callback) {
do {
if let list = try response.result() as? [ManagementObject] {
callback(.success(result: list))
callback(.success(list))
} else {
callback(.failure(error: ManagementError(string: string(response.data))))
callback(.failure(ManagementError(string: string(response.data))))
}
} catch let error {
callback(.failure(error: error))
callback(.failure(error))
}
}

Expand Down
6 changes: 3 additions & 3 deletions Auth0/MobileWebAuth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ final class MobileWebAuth: BaseWebAuth, WebAuth {
} else {
DispatchQueue.main.async {
guard let presenting = controller?.presentingViewController else {
return callback(Result.failure(error: WebAuthError.cannotDismissWebAuthController))
return callback(Result.failure(WebAuthError.cannotDismissWebAuthController))
}
presenting.dismiss(animated: true) {
callback(result)
Expand Down Expand Up @@ -285,9 +285,9 @@ final class SafariServicesSession: SessionTransaction {
guard $1 == nil, let callbackURL = $0 else {
let authError = $1 ?? WebAuthError.unknownError
if case SFAuthenticationError.canceledLogin = authError {
self.callback(.failure(error: WebAuthError.userCancelled))
self.callback(.failure(WebAuthError.userCancelled))
} else {
self.callback(.failure(error: authError))
self.callback(.failure(authError))
}
return TransactionStore.shared.clear()
}
Expand Down
4 changes: 2 additions & 2 deletions Auth0/NativeAuth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public protocol NativeAuthTransaction: AuthTransaction {

```
let credetials = NativeAuthCredentials(token: "{IdP Token}", extras: [:])
let result = Auth0.Result.success(result: credentials)
let result = Auth0.Result.success(credentials)
```
- parameter callback: callback with the IdP credentials on success or the cause of the error.
*/
Expand Down Expand Up @@ -121,7 +121,7 @@ public extension NativeAuthTransaction {
self.authentication.loginSocial(token: credentials.token, connection: self.connection, scope: self.scope, parameters: parameters)
.start(callback)
case .failure(let error):
callback(.failure(error: error))
callback(.failure(error))
}
}
}
Expand Down
18 changes: 9 additions & 9 deletions Auth0/OAuth2Grant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ struct ImplicitGrant: OAuth2Grant {
nonce: self.defaults["nonce"],
organization: self.organization)
validateFrontChannelIDToken(idToken: values["id_token"], for: responseType, with: validatorContext) { error in
if let error = error { return callback(.failure(error: error)) }
if let error = error { return callback(.failure(error)) }
guard !responseType.contains(.token) || values["access_token"] != nil else {
return callback(.failure(error: WebAuthError.missingAccessToken))
return callback(.failure(WebAuthError.missingAccessToken))
}
callback(.success(result: Credentials(json: values as [String: Any])))
callback(.success(Credentials(json: values as [String: Any])))
}
}

Expand Down Expand Up @@ -152,7 +152,7 @@ struct PKCE: OAuth2Grant {
func credentials(from values: [String: String], callback: @escaping (Result<Credentials>) -> Void) {
guard let code = values["code"] else {
let string = "No code found in parameters \(values)"
return callback(.failure(error: AuthenticationError(string: string)))
return callback(.failure(AuthenticationError(string: string)))
}
let idToken = values["id_token"]
let responseType = self.responseType
Expand All @@ -168,20 +168,20 @@ struct PKCE: OAuth2Grant {
nonce: self.defaults["nonce"],
organization: self.organization)
validateFrontChannelIDToken(idToken: idToken, for: responseType, with: validatorContext) { error in
if let error = error { return callback(.failure(error: error)) }
if let error = error { return callback(.failure(error)) }
authentication
.tokenExchange(withCode: code, codeVerifier: verifier, redirectURI: redirectUrlString)
.start { result in
switch result {
case .failure(let error as AuthenticationError) where error.description == "Unauthorized":
// Special case for PKCE when the correct method for token endpoint authentication is not set (it should be None)
let webAuthError = WebAuthError.pkceNotAllowed("Unable to complete authentication with PKCE. PKCE support can be enabled by setting Application Type to 'Native' and Token Endpoint Authentication Method to 'None' for this app at 'https://manage.auth0.com/#/applications/\(clientId)/settings'.")
return callback(.failure(error: webAuthError))
case .failure(let error): return callback(.failure(error: error))
return callback(.failure(webAuthError))
case .failure(let error): return callback(.failure(error))
case .success(let credentials):
guard isFrontChannelIdTokenExpected else {
return validate(idToken: credentials.idToken, with: validatorContext) { error in
if let error = error { return callback(.failure(error: error)) }
if let error = error { return callback(.failure(error)) }
callback(result)
}
}
Expand All @@ -191,7 +191,7 @@ struct PKCE: OAuth2Grant {
refreshToken: credentials.refreshToken,
expiresIn: credentials.expiresIn,
scope: credentials.scope)
return callback(.success(result: newCredentials))
return callback(.success(newCredentials))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Auth0/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public struct ConcatRequest<F, S, E: Auth0Error>: Requestable {
first.start { result in
switch result {
case .failure(let cause):
callback(.failure(error: cause))
callback(.failure(cause))
case .success:
second.start(callback)
}
Expand Down
34 changes: 32 additions & 2 deletions Auth0/Result.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,43 @@

import Foundation

#if swift(<5.0)
/**
Result object for Auth0 APIs requests

- Success: request completed successfuly with it's response body
- Failure: request failed with a specific error
*/
public enum Result<T> {
case success(result: T)
case failure(error: Error)
case success(T)
case failure(Error)
}

// Shims for older interface with named parameters
extension Result {
@available(*, deprecated, renamed: "success(_:)")
public static func success(result: T) -> Self {
return .success(result)
}

@available(*, deprecated, renamed: "failure(_:)")
public static func failure(error: Error) -> Self {
return .failure(error)
}
}
#else
public typealias Result<T> = Swift.Result<T, Error>

// Shims for older interface with named parameters
extension Result {
@available(*, deprecated, renamed: "success(_:)")
public static func success(result: Success) -> Self {
return .success(result)
}

@available(*, deprecated, renamed: "failure(_:)")
public static func failure(error: Failure) -> Self {
return .failure(error)
}
}
#endif
8 changes: 4 additions & 4 deletions Auth0Tests/NativeAuthSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class MockNativeAuthTransaction: NativeAuthTransaction {
}

func cancel() {
self.delayed(.failure(error: WebAuthError.userCancelled))
self.delayed(.failure(WebAuthError.userCancelled))
self.delayed = { _ in }
}

Expand All @@ -82,7 +82,7 @@ class MockNativeAuthTransaction: NativeAuthTransaction {

/// Test Hooks
var onNativeAuth: () -> Result<NativeAuthCredentials> = {
return .success(result: NativeAuthCredentials(token: FacebookToken, extras: [:]))
return .success(NativeAuthCredentials(token: FacebookToken, extras: [:]))
}
}

Expand Down Expand Up @@ -162,7 +162,7 @@ class NativeAuthSpec: QuickSpec {

it("should yield error on native auth failure") {
nativeTransaction.onNativeAuth = {
return .failure(error: WebAuthError.missingAccessToken)
return .failure(WebAuthError.missingAccessToken)
}
waitUntil(timeout: Timeout) { done in
nativeTransaction.start { result in
Expand All @@ -181,7 +181,7 @@ class NativeAuthSpec: QuickSpec {

it("should yield auth error on invalid native access token") {
nativeTransaction.onNativeAuth = {
return .success(result: NativeAuthCredentials(token: InvalidFacebookToken, extras: [:]))
return .success(NativeAuthCredentials(token: InvalidFacebookToken, extras: [:]))
}
waitUntil(timeout: Timeout) { done in
nativeTransaction.start { result in
Expand Down
4 changes: 2 additions & 2 deletions Auth0Tests/WebAuthSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -425,13 +425,13 @@ class WebAuthSpec: QuickSpec {

it("should fail if controller is not presented") {
let callback = newWebAuth().newSafari(DomainURL, callback: { result = $0 }).1
callback(.success(result: Credentials(json: ["access_token": "at", "token_type": "bearer"])))
callback(.success(Credentials(json: ["access_token": "at", "token_type": "bearer"])))
expect(result).toEventually(beFailure())
}

it("should fail if user dismissed safari viewcontroller") {
let callback = newWebAuth().newSafari(DomainURL, callback: { result = $0 }).1
callback(.failure(error: WebAuthError.userCancelled))
callback(.failure(WebAuthError.userCancelled))
expect(result).toEventually(beFailure())
}

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Swift toolkit that lets you communicate efficiently with many of the [Auth0 API](https://auth0.com/docs/api/info) functions and enables you to seamlessly integrate the Auth0 login.

## Important Notices

[Behaviour changes in iOS 13](https://github.com/auth0/Auth0.swift/pull/297) related to Web Authentication require that developers using Xcode 11 with this library **must** compile using Swift 5.x. This *should* be the default setting applied when updating, unless it has been manually set. However, we recommend checking that this value is set correctly.

## Table of Contents
Expand Down

0 comments on commit 408a9bf

Please sign in to comment.