-
Notifications
You must be signed in to change notification settings - Fork 730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introducing delegate if there is GraphQL errors #770
Introducing delegate if there is GraphQL errors #770
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this idea! Would love to see some tests on it please 😃
Will create tests when we can mock. Depending on #767 |
beaa094
to
ca2e22d
Compare
completionHandler: @escaping (_ result: Result<GraphQLResponse<Operation>, Error>) -> Void) throws { | ||
guard let delegate = self.delegate as? HTTPNetworkTransportGraphQLErrorDelegate, | ||
let graphQLErrors = response.parseErrorsOnlyFast(), | ||
!graphQLErrors.isEmpty else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is more of a note to self than to you: I need to add my readability isNotEmpty
boolean extension to this code 🙃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #872
ca2e22d
to
e7ab4dc
Compare
@designatednerd I rebased and added two tests. One with error and one with none. |
@designatednerd I rebased and added two tests. One with error and one with none. I also noticed tests are not executed randomly. |
@kimdv can you merge in and/or rebase off |
7136765
to
2e8d94a
Compare
@designatednerd done! |
🎉 Nothing! 🎉 Mergin' |
how do you know if failure after retrying? |
That's why we explicitly recommend against just calling If you're not looking at the actual errors that have come in, you could easily wind up in an infinite retry loop. |
I'm thinking about case where server say "token invalid", I update token, and server say "still invalid". Correct thing to do is to give up, but I don't know that I already tried to update token |
It sounds a bit more like a bug in your server if a just-updated token is being rejected as invalid, but you could theoretically add some tracking of what the last (n) errors received were, and if some aspect of them (status code, message, etc) is the same, then stop retrying. |
maybe it's me, but I like to code defensively 🤷♂ |
That's fair, but I think the "how do I handle situation x" stuff is more of an application-level decision, so we're letting developers handle that as defensively as they'd like to. |
Hi! final class GraphQLClient {
private var retryHandlers = [(Bool) -> Void]()
private let isolationQueue = DispatchQueue(label: "GraphQLClient.isolationQueue",
qos: .userInitiated,
attributes: [],
autoreleaseFrequency: .workItem,
target: nil)
} extension GraphQLClient: HTTPNetworkTransportGraphQLErrorDelegate {
func networkTransport(_ networkTransport: HTTPNetworkTransport, receivedGraphQLErrors errors: [GraphQLError], retryHandler: @escaping (Bool) -> Void) {
let errors = errors.map { OurGraphQLError(error: $0) }
guard error.contains(.authTokenInvalid) else {
retryHandler(false)
return
}
let isTokenExchangeInProgress = scheduleForExecution(retryHandler: retryHandler)
if isTokenExchangeInProgress { return }
exchangeRefreshToken { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success:
strongSelf.executeRetryHandlers(retry: true)
case .failure(let error):
strongSelf.executeRetryHandlers(retry: false)
}
}
}
private func scheduleForExecution(retryHandler: @escaping (Bool) -> Void ) -> Bool {
var isTokenExchangeInProgress = true
isolationQueue.sync {
isTokenExchangeInProgress = !retryHandlers.isEmpty
retryHandlers.append(retryHandler)
}
return isTokenExchangeInProgress
}
private func executeRetryHandlers(retry: Bool) {
isolationQueue.async { [weak self] in
guard let strongSelf = self else { return }
for handler in strongSelf.retryHandlers {
handler(retry)
}
strongSelf.retryHandlers = []
}
}
} In this way we can cancel if refresh token exchange fails. |
The idea of this delegate is based on
It adds ability for better error handling on the clients when there is Graphql errors
Etc when a token is expired.
The diffrent from this delegate and
HTTPNetworkTransportRetryDelegate
is that some errors might come from the GraphQL API and not from middleware etc.