billp / TermiNetwork

🌏 A zero-dependency networking solution for building modern and secure iOS, watchOS, macOS and tvOS applications.
https://billp.github.io/TermiNetwork
MIT License
99 stars 8 forks source link

Issue with interceptors and catching errors. #54

Closed petyots closed 1 year ago

petyots commented 1 year ago

Hi, It seems there is an issue for which I can't find any workaround. I have UnauthorizedInterceptor which refreshes the access token when needed which is working fine.


import Foundation
import TermiNetwork

final class UnauthorizedInterceptor: InterceptorProtocol {

    private var authService = TokenService.shared

    func requestFinished(responseData data: Data?,
                         error: TNError?,
                         request: Request,
                         proceed: @escaping (InterceptionAction) -> Void) {
        switch error {
            case .notSuccess(let statusCode, _):
                if statusCode == 401 {
                    // Login and get a new token.
                    Client<AuthRepository>()
                        .request(for: .refreshToken(refreshToken: authService.token!.refreshToken, expiredAccessToken: authService.token!.accessToken))
                        .success(responseType: AccessToken.self) { response in
                            try? self.authService.saveToken(accessToken: response)

                            let authorizationValue = String(format: "Bearer %@", response.accessToken)

                            // Update the global header in configuration which is inherited by all requests.
                            Environment.current.configuration?.headers?["Authorization"] = authorizationValue

                            // Update current request's header.
                            request.headers?["Authorization"] = authorizationValue

                            // Finally retry the original request.
                            proceed(.retry(delay: 0.2))
                        }
                } else {
                    // Continue if the retry limit is reached
                    proceed(.continue)
                }
            default:
                proceed(.continue)
        }
    }
}

BUT when the request which caused the token to be refresh also fails let's say with 422 in my case the Data is empty this is only happening when we are trying to catch the error in the request where 422 is happening


func save() async {
        let valid = formManager.triggerValidation()
        if !valid {
            return 
        }

        isLoading = true
        defer {
            isLoading = false
        }

        if var user = TokenService.shared.currentUser {
            do {
                user.firstName = firstName
                user.lastName = lastName
                user.username = username

                let _ = try await Client<UserRepository>()
                    .request(for: .updateUser(user: user))
                    .async(as: String.self)
            } catch let error {
                switch error as? TNError {
                    case .notSuccess(let statusCode, let data):
                        if statusCode == 401 {
                            let errorModel2 = try? data.deserializeJSONData() as [String: String]
                            print(String(describing: errorModel2))
                        }
                        if statusCode == 422 {
                            do {
                                let errorModel = try data.deserializeJSONData() as ApiErrorResponse
                                print(String(describing: errorModel))
                            } catch {
                                print(String(describing: data))
                                print(String(describing: error))
                            }
                        }
                    default:
                        print("Error \(error.localizedDescription)")
                }
            }
        }
    }

So when the interceptor is not involved everything is ok the errorModel is populated but when the interceptor kicks in the data is empty. Overall I think the request is throwing the error instead of passing it thru the interceptor that's why we can't catch the next error after the first(401) from the interceptor

billp commented 1 year ago

Hello, thanks for the issue. As I can see from your code, your refresh token request only has a success callback and not a failure one. Have you tried to add a failure callback in which you can call the proceed(.continue) closure? Then you can catch the error from the caller. Let me know if this helps.

petyots commented 1 year ago

Issue is resolved for now by doing some changes on the backend. Will reopen if the solution of yours is needed and it does not work.

P.S Thanks for the great tool!!!