| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- #import "BTGraphQLHTTP.h"
- #import "Braintree-Version.h"
- #if __has_include(<Braintree/BraintreeCore.h>)
- #import <Braintree/BTURLUtils.h>
- #import <Braintree/BTHTTPErrors.h>
- #import <Braintree/BTJSON.h>
- #else
- #import <BraintreeCore/BTURLUtils.h>
- #import <BraintreeCore/BTHTTPErrors.h>
- #import <BraintreeCore/BTJSON.h>
- #endif
- @interface BTGraphQLHTTP ()
- @property (nonatomic, copy) NSString *tokenizationKey;
- @property (nonatomic, copy) NSString *authorizationFingerprint;
- @end
- @implementation BTGraphQLHTTP
- static NSString *BraintreeVersion = @"2018-03-06";
- #pragma mark - Overrides
- - (void)GET:(__unused NSString *)aPath completion:(__unused void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [NSException raise:@"" format:@"GET is unsupported"];
- }
- - (void)GET:(__unused NSString *)aPath parameters:(__unused NSDictionary *)parameters completion:(__unused void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [NSException raise:@"" format:@"GET is unsupported"];
- }
- - (void)POST:(__unused NSString *)aPath completion:(void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [self httpRequest:@"POST" parameters:nil completion:completionBlock];
- }
- - (void)POST:(__unused NSString *)aPath parameters:(NSDictionary *)parameters completion:(void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [self httpRequest:@"POST" parameters:parameters completion:completionBlock];
- }
- - (void)PUT:(__unused NSString *)aPath completion:(__unused void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [NSException raise:@"" format:@"PUT is unsupported"];
- }
- - (void)PUT:(__unused NSString *)aPath parameters:(__unused NSDictionary *)parameters completion:(__unused void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [NSException raise:@"" format:@"PUT is unsupported"];
- }
- - (void)DELETE:(__unused NSString *)aPath completion:(__unused void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [NSException raise:@"" format:@"DELETE is unsupported"];
- }
- - (void)DELETE:(__unused NSString *)aPath parameters:(__unused NSDictionary *)parameters completion:(__unused void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock {
- [NSException raise:@"" format:@"DELETE is unsupported"];
- }
- - (void)handleRequestCompletion:(NSData *)data
- response:(NSURLResponse *)response
- error:(NSError *)error
- completionBlock:(void (^)(BTJSON * _Nonnull, NSHTTPURLResponse * _Nonnull, NSError * _Nonnull))completionBlock
- {
- // Network error
- if (error) {
- [self callCompletionBlock:completionBlock body:nil response:(NSHTTPURLResponse *)response error:error];
- return;
- }
- if (data == nil) {
- NSError *error = [[NSError alloc] initWithDomain:BTHTTPErrorDomain
- code:BTHTTPErrorCodeUnknown
- userInfo:@{NSLocalizedDescriptionKey: @"An unexpected error occurred with the HTTP request."}];
- [self callCompletionBlock:completionBlock body:nil response:(NSHTTPURLResponse *)response error:error];
- return;
- }
- NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
- BTJSON *body = [[BTJSON alloc] initWithValue:json];
- // Success case
- if ([body asDictionary] && ![body[@"errors"] asArray]) {
- [self callCompletionBlock:completionBlock body:body response:(NSHTTPURLResponse *)response error:nil];
- return;
- }
- BTJSON *errorJSON = body[@"errors"][0];
- NSString *errorType = [errorJSON[@"extensions"][@"errorType"] asString];
- NSInteger statusCode = 0;
- BTHTTPErrorCode errorCode = BTHTTPErrorCodeUnknown;
- NSMutableDictionary *errorBody = [NSMutableDictionary new];
- if ([errorType isEqualToString:@"user_error"]) {
- statusCode = 422;
- errorCode = BTHTTPErrorCodeClientError;
- errorBody[@"error"] = @{@"message": @"Input is invalid"};
- NSMutableArray *errors = [NSMutableArray new];
- NSUInteger errorCount = [body[@"errors"] asArray].count;
- for (NSUInteger i = 0; i < errorCount; i++) {
- BTJSON *error = body[@"errors"][i];
- NSArray *inputPath = [error[@"extensions"][@"inputPath"] asStringArray];
- // Defensive programming
- if (!inputPath) {
- continue;
- }
- [self addErrorForInputPath:[inputPath subarrayWithRange:NSMakeRange(1, inputPath.count - 1)]
- withGraphQLError:[error asDictionary]
- toArray:errors];
- }
- if (errors.count > 0) {
- errorBody[@"fieldErrors"] = [errors copy];
- }
- } else if ([errorType isEqualToString:@"developer_error"]) {
- statusCode = 403;
- errorCode = BTHTTPErrorCodeClientError;
- if ([errorJSON[@"message"] asString]) {
- errorBody[@"error"] = @{@"message": [errorJSON[@"message"] asString]};
- }
- } else {
- statusCode = 500;
- errorCode = BTHTTPErrorCodeServerError;
- errorBody[@"error"] = @{@"message": @"An unexpected error occurred"};
- }
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
- NSHTTPURLResponse *nestedErrorResponse = [[NSHTTPURLResponse alloc] initWithURL:response.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:httpResponse.allHeaderFields];
- // Create errors
- NSError *returnedError = [[NSError alloc] initWithDomain:BTHTTPErrorDomain
- code:errorCode
- userInfo:@{
- BTHTTPURLResponseKey: nestedErrorResponse,
- BTHTTPJSONResponseBodyKey: [[BTJSON alloc] initWithValue:[errorBody copy]]
- }];
- [self callCompletionBlock:completionBlock body:[[BTJSON alloc] initWithValue:[errorBody copy]] response:(NSHTTPURLResponse *)response error:returnedError];
- }
- #pragma mark - Private methods
- - (void)httpRequest:(NSString *)method
- parameters:(NSDictionary *)parameters
- completion:(void(^)(BTJSON *body, NSHTTPURLResponse *response, NSError *error))completionBlock
- {
- if (!self.baseURL || [self.baseURL.absoluteString isEqualToString:@""]) {
- NSMutableDictionary *errorUserInfo = [NSMutableDictionary new];
- if (method) errorUserInfo[@"method"] = method;
- if (parameters) errorUserInfo[@"parameters"] = parameters;
- completionBlock(nil, nil, [NSError errorWithDomain:BTHTTPErrorDomain code:BTHTTPErrorCodeMissingBaseURL userInfo:errorUserInfo]);
- return;
- }
- NSURLComponents *components = [NSURLComponents componentsWithString:self.baseURL.absoluteString];
- NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:@{
- @"User-Agent": [self userAgentString],
- @"Braintree-Version": BraintreeVersion
- }];
- headers[@"Authorization"] = [NSString stringWithFormat:@"Bearer %@", self.authorizationFingerprint ?: self.tokenizationKey];
- parameters = parameters ? [NSMutableDictionary dictionaryWithDictionary:parameters] : [NSMutableDictionary new];
- NSMutableURLRequest *request;
- headers[@"Content-Type"] = @"application/json; charset=utf-8";
- NSError *jsonSerializationError;
- NSData *bodyData = [NSJSONSerialization dataWithJSONObject:parameters
- options:0
- error:&jsonSerializationError];
- if (jsonSerializationError) {
- completionBlock(nil, nil, jsonSerializationError);
- return;
- }
- request = [NSMutableURLRequest requestWithURL:components.URL];
- [request setHTTPBody:bodyData];
- [request setAllHTTPHeaderFields:headers];
- [request setHTTPMethod:method];
- // Perform the actual request
- NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
- [self handleRequestCompletion:data response:response error:error completionBlock:completionBlock];
- }];
- [task resume];
- }
- /// Walks through the input path recursively and adds field errors to a mutable array
- - (void)addErrorForInputPath:(NSArray <NSString *> *)inputPath withGraphQLError:(NSDictionary *)errorJSON toArray:(NSMutableArray <NSDictionary *> *)errors {
- NSString *field = [inputPath firstObject];
- if (inputPath.count == 1) {
- NSMutableDictionary *errorsBody = [NSMutableDictionary new];
- [errorsBody setObject:field forKey:@"field"];
- [errorsBody setObject:errorJSON[@"message"] forKey:@"message"];
- if (errorJSON[@"extensions"][@"legacyCode"]) { // prevent crash if missing from GraphQL response
- [errorsBody setObject:errorJSON[@"extensions"][@"legacyCode"] forKey:@"code"];
- }
- [errors addObject:errorsBody];
- return;
- }
- // Find nested error that matches the field
- NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(field == %@)", field];
- NSDictionary *nestedFieldError = [[errors filteredArrayUsingPredicate:predicate] firstObject];
- // If the nested error hasn't been created yet, add one
- if (!nestedFieldError) {
- nestedFieldError = @{
- @"field": field,
- @"fieldErrors": [NSMutableArray new]
- };
- [errors addObject:nestedFieldError];
- }
- [self addErrorForInputPath:[inputPath subarrayWithRange:NSMakeRange(1, inputPath.count - 1)]
- withGraphQLError:errorJSON
- toArray:nestedFieldError[@"fieldErrors"]];
- }
- @end
|