Home | History | Annotate | Download | only in ProtoRPC
      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 
     29 static 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