blob: 73fe67eb244b2ed839e8caf340271d4a8c1a4cd2 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart=2.11
part of '../protoc.dart';
class GrpcServiceGenerator {
final ServiceDescriptorProto _descriptor;
/// The generator of the .pb.dart file that will contain this service.
final FileGenerator fileGen;
/// The message types needed directly by this service.
///
/// The key is the fully qualified name.
/// Populated by [resolve].
final _deps = <String, MessageGenerator>{};
/// Maps each undefined type to a string describing its location.
///
/// Populated by [resolve].
final _undefinedDeps = <String, String>{};
/// Fully-qualified gRPC service name.
String _fullServiceName;
/// Dart class name for client stub.
String _clientClassname;
/// Dart class name for server stub.
String _serviceClassname;
/// List of gRPC methods.
final _methods = <_GrpcMethod>[];
GrpcServiceGenerator(this._descriptor, this.fileGen) {
final name = _descriptor.name;
final package = fileGen.package;
if (package != null && package.isNotEmpty) {
_fullServiceName = '$package.$name';
} else {
_fullServiceName = name;
}
// avoid: ClientClient
_clientClassname = name.endsWith('Client') ? name : name + 'Client';
// avoid: ServiceServiceBase
_serviceClassname =
name.endsWith('Service') ? name + 'Base' : name + 'ServiceBase';
}
/// Finds all message types used by this service.
///
/// Puts the types found in [_deps]. If a type name can't be resolved, puts it
/// in [_undefinedDeps].
/// Precondition: messages have been registered and resolved.
void resolve(GenerationContext ctx) {
for (var method in _descriptor.method) {
_methods.add(_GrpcMethod(this, ctx, method));
}
}
/// Adds a dependency on the given message type.
///
/// If the type name can't be resolved, adds it to [_undefinedDeps].
void _addDependency(GenerationContext ctx, String fqname, String location) {
if (_deps.containsKey(fqname)) return; // Already added.
final mg = ctx.getFieldType(fqname) as MessageGenerator;
if (mg == null) {
_undefinedDeps[fqname] = location;
return;
}
mg.checkResolved();
_deps[mg.dottedName] = mg;
}
/// Adds dependencies of [generate] to [imports].
///
/// For each .pb.dart file that the generated code needs to import,
/// add its generator.
void addImportsTo(Set<FileGenerator> imports) {
for (var mg in _deps.values) {
imports.add(mg.fileGen);
}
}
/// Returns the Dart class name to use for a message type.
///
/// Throws an exception if it can't be resolved.
String _getDartClassName(String fqname) {
var mg = _deps[fqname];
if (mg == null) {
var location = _undefinedDeps[fqname];
// TODO(nichite): Throw more actionable error.
throw 'FAILURE: Unknown type reference ($fqname) for $location';
}
return mg.fileImportPrefix + '.' + mg.classname;
}
void generate(IndentingWriter out) {
_generateClient(out);
out.println();
_generateService(out);
}
void _generateClient(IndentingWriter out) {
out.addBlock('class $_clientClassname extends $_client {', '}', () {
for (final method in _methods) {
method.generateClientMethodDescriptor(out);
}
out.println();
out.println('$_clientClassname($_clientChannel channel,');
out.println(' {$_callOptions? options,');
out.println(
' $coreImportPrefix.Iterable<$_interceptor>? interceptors})');
out.println(' : super(channel, options: options,');
out.println(' interceptors: interceptors);');
for (final method in _methods) {
method.generateClientStub(out);
}
});
}
void _generateService(IndentingWriter out) {
out.addBlock('abstract class $_serviceClassname extends $_service {', '}',
() {
out.println(
'$coreImportPrefix.String get \$name => \'$_fullServiceName\';');
out.println();
out.addBlock('$_serviceClassname() {', '}', () {
for (final method in _methods) {
method.generateServiceMethodRegistration(out);
}
});
out.println();
for (final method in _methods) {
method.generateServiceMethodPreamble(out);
}
for (final method in _methods) {
method.generateServiceMethodStub(out);
}
});
}
static final String _callOptions = '$grpcImportPrefix.CallOptions';
static final String _interceptor = '$grpcImportPrefix.ClientInterceptor';
static final String _client = '$grpcImportPrefix.Client';
static final String _clientChannel = '$grpcImportPrefix.ClientChannel';
static final String _service = '$grpcImportPrefix.Service';
}
class _GrpcMethod {
final String _grpcName;
final String _dartName;
final String _serviceName;
final bool _clientStreaming;
final bool _serverStreaming;
final String _requestType;
final String _responseType;
final String _argumentType;
final String _clientReturnType;
final String _serverReturnType;
_GrpcMethod._(
this._grpcName,
this._dartName,
this._serviceName,
this._clientStreaming,
this._serverStreaming,
this._requestType,
this._responseType,
this._argumentType,
this._clientReturnType,
this._serverReturnType);
factory _GrpcMethod(GrpcServiceGenerator service, GenerationContext ctx,
MethodDescriptorProto method) {
final grpcName = method.name;
final dartName = lowerCaseFirstLetter(grpcName);
final clientStreaming = method.clientStreaming;
final serverStreaming = method.serverStreaming;
service._addDependency(ctx, method.inputType, 'input type of $grpcName');
service._addDependency(ctx, method.outputType, 'output type of $grpcName');
final requestType = service._getDartClassName(method.inputType);
final responseType = service._getDartClassName(method.outputType);
final argumentType =
clientStreaming ? '$_stream<$requestType>' : requestType;
final clientReturnType = serverStreaming
? '$_responseStream<$responseType>'
: '$_responseFuture<$responseType>';
final serverReturnType =
serverStreaming ? '$_stream<$responseType>' : '$_future<$responseType>';
return _GrpcMethod._(
grpcName,
dartName,
service._fullServiceName,
clientStreaming,
serverStreaming,
requestType,
responseType,
argumentType,
clientReturnType,
serverReturnType);
}
void generateClientMethodDescriptor(IndentingWriter out) {
out.println(
'static final _\$$_dartName = $_clientMethod<$_requestType, $_responseType>(');
out.println(' \'/$_serviceName/$_grpcName\',');
out.println(' ($_requestType value) => value.writeToBuffer(),');
out.println(
' ($coreImportPrefix.List<$coreImportPrefix.int> value) => $_responseType.fromBuffer(value));');
}
void generateClientStub(IndentingWriter out) {
out.println();
out.addBlock(
'$_clientReturnType $_dartName($_argumentType request, {${GrpcServiceGenerator._callOptions}? options}) {',
'}', () {
if (_clientStreaming && _serverStreaming) {
out.println(
'return \$createStreamingCall(_\$$_dartName, request, options: options);');
} else if (_clientStreaming && !_serverStreaming) {
out.println(
'return \$createStreamingCall(_\$$_dartName, request, options: options).single;');
} else if (!_clientStreaming && _serverStreaming) {
out.println(
'return \$createStreamingCall(_\$$_dartName, $_stream.fromIterable([request]), options: options);');
} else {
out.println(
'return \$createUnaryCall(_\$$_dartName, request, options: options);');
}
});
}
void generateServiceMethodRegistration(IndentingWriter out) {
out.println('\$addMethod($_serviceMethod<$_requestType, $_responseType>(');
out.println(' \'$_grpcName\',');
out.println(' $_dartName${_clientStreaming ? '' : '_Pre'},');
out.println(' $_clientStreaming,');
out.println(' $_serverStreaming,');
out.println(
' ($coreImportPrefix.List<$coreImportPrefix.int> value) => $_requestType.fromBuffer(value),');
out.println(' ($_responseType value) => value.writeToBuffer()));');
}
void generateServiceMethodPreamble(IndentingWriter out) {
if (_clientStreaming) return;
out.addBlock(
'$_serverReturnType ${_dartName}_Pre($_serviceCall call, $_future<$_requestType> request) async${_serverStreaming ? '*' : ''} {',
'}', () {
if (_serverStreaming) {
out.println('yield* $_dartName(call, await request);');
} else {
out.println('return $_dartName(call, await request);');
}
});
out.println();
}
void generateServiceMethodStub(IndentingWriter out) {
out.println(
'$_serverReturnType $_dartName($_serviceCall call, $_argumentType request);');
}
static final String _serviceCall = '$grpcImportPrefix.ServiceCall';
static final String _serviceMethod = '$grpcImportPrefix.ServiceMethod';
static final String _clientMethod = '$grpcImportPrefix.ClientMethod';
static final String _future = '$asyncImportPrefix.Future';
static final String _stream = '$asyncImportPrefix.Stream';
static final String _responseFuture = '$grpcImportPrefix.ResponseFuture';
static final String _responseStream = '$grpcImportPrefix.ResponseStream';
}