1/* 2 * 3 * Copyright 2015 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19#import "ProtoRPC.h" 20 21#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 22#import <Protobuf/GPBProtocolBuffers.h> 23#else 24#import <GPBProtocolBuffers.h> 25#endif 26#import <RxLibrary/GRXWriteable.h> 27#import <RxLibrary/GRXWriter+Transformations.h> 28 29static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) { 30 NSDictionary *info = @{ 31 NSLocalizedDescriptionKey : @"Unable to parse response from the server", 32 NSLocalizedRecoverySuggestionErrorKey : 33 @"If this RPC is idempotent, retry " 34 @"with exponential backoff. Otherwise, query the server status before " 35 @"retrying.", 36 NSUnderlyingErrorKey : parsingError, 37 @"Expected class" : expectedClass, 38 @"Received value" : proto, 39 }; 40 // TODO(jcanizales): Use kGRPCErrorDomain and GRPCErrorCodeInternal when they're public. 41 return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info]; 42} 43 44#pragma clang diagnostic push 45#pragma clang diagnostic ignored "-Wdeprecated-implementations" 46@implementation ProtoRPC { 47#pragma clang diagnostic pop 48 id<GRXWriteable> _responseWriteable; 49} 50 51#pragma clang diagnostic push 52#pragma clang diagnostic ignored "-Wobjc-designated-initializers" 53- (instancetype)initWithHost:(NSString *)host 54 path:(NSString *)path 55 requestsWriter:(GRXWriter *)requestsWriter { 56 [NSException raise:NSInvalidArgumentException 57 format:@"Please use ProtoRPC's designated initializer instead."]; 58 return nil; 59} 60#pragma clang diagnostic pop 61 62// Designated initializer 63- (instancetype)initWithHost:(NSString *)host 64 method:(GRPCProtoMethod *)method 65 requestsWriter:(GRXWriter *)requestsWriter 66 responseClass:(Class)responseClass 67 responsesWriteable:(id<GRXWriteable>)responsesWriteable { 68 // Because we can't tell the type system to constrain the class, we need to check at runtime: 69 if (![responseClass respondsToSelector:@selector(parseFromData:error:)]) { 70 [NSException raise:NSInvalidArgumentException 71 format:@"A protobuf class to parse the responses must be provided."]; 72 } 73 // A writer that serializes the proto messages to send. 74 GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) { 75 if (![proto isKindOfClass:GPBMessage.class]) { 76 [NSException raise:NSInvalidArgumentException 77 format:@"Request must be a proto message: %@", proto]; 78 } 79 return [proto data]; 80 }]; 81 if ((self = [super initWithHost:host path:method.HTTPPath requestsWriter:bytesWriter])) { 82 __weak ProtoRPC *weakSelf = self; 83 84 // A writeable that parses the proto messages received. 85 _responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { 86 // TODO(jcanizales): This is done in the main thread, and needs to happen in another thread. 87 NSError *error = nil; 88 id parsed = [responseClass parseFromData:value error:&error]; 89 if (parsed) { 90 [responsesWriteable writeValue:parsed]; 91 } else { 92 [weakSelf finishWithError:ErrorForBadProto(value, responseClass, error)]; 93 } 94 } 95 completionHandler:^(NSError *errorOrNil) { 96 [responsesWriteable writesFinishedWithError:errorOrNil]; 97 }]; 98 } 99 return self; 100} 101 102- (void)start { 103 [self startWithWriteable:_responseWriteable]; 104} 105 106- (void)startWithWriteable:(id<GRXWriteable>)writeable { 107 [super startWithWriteable:writeable]; 108 // Break retain cycles. 109 _responseWriteable = nil; 110} 111@end 112 113@implementation GRPCProtoCall 114 115@end 116