blob: b7b3784547afaf00b56742c3aca496be8e22d4da [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.
part of '../protoc.dart';
class GrpcServiceGenerator {
final ServiceDescriptorProto _descriptor;
/// The generator of the .pb.dart file that will contain this service.
final FileGenerator fileGen;
final int _serviceIndex;
/// The message types needed directly by this service.
///
/// The key is the fully qualified name.
/// Populated by [resolve].
final Map<String, MessageGenerator> _deps = {};
/// Maps each undefined type to a string describing its location.
///
/// Populated by [resolve].
final Map<String, String> _undefinedDeps = {};
/// Fully-qualified gRPC service name.
late final String _fullServiceName;
/// Dart class name for client stub.
late final String _clientClassname;
/// Dart class name for server stub.
late final String _serviceClassname;
/// List of gRPC methods.
final List<_GrpcMethod> _methods = [];
late final List<int> _serviceDescriptorPath = [
...fileGen.fieldPath,
ClientApiGenerator.fileDescriptorServiceTag,
_serviceIndex,
];
GrpcServiceGenerator(this._descriptor, this.fileGen, this._serviceIndex) {
final name = _descriptor.name;
final package = fileGen.package;
if (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 (final 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 (final 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) {
final mg = _deps[fqname];
if (mg == null) {
final 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) {
final commentBlock = fileGen.commentBlock(_serviceDescriptorPath);
if (commentBlock != null) {
out.println(commentBlock);
}
if (_descriptor.options.deprecated) {
out.println(
"@$coreImportPrefix.Deprecated('This service is deprecated')");
}
out.println("@$protobufImportPrefix.GrpcServiceName('$_fullServiceName')");
out.addBlock('class $_clientClassname extends $_client {', '}', () {
// Look for and generate default_host info.
final defaultHost = _descriptor.options.defaultHost;
if (defaultHost != null) {
out.println('/// The hostname for this service.');
out.println("static const $_string defaultHost = '$defaultHost';");
out.println();
}
// Look for and generate oauth_scopes info.
final oauthScopes = _descriptor.options.oauthScopes;
if (oauthScopes != null) {
out.println('/// OAuth scopes needed for the client.');
out.println('static const $_list<$_string> oauthScopes = [');
for (final scope in oauthScopes.split(',')) {
out.println(" '$scope',");
}
out.println('];');
out.println();
}
for (final method in _methods) {
method.generateClientMethodDescriptor(out);
}
out.println();
out.println(
'$_clientClassname(super.channel, {super.options, super.interceptors});');
for (var i = 0; i < _methods.length; i++) {
_methods[i].generateClientStub(out, this, i);
}
});
}
void _generateService(IndentingWriter out) {
out.println("@$protobufImportPrefix.GrpcServiceName('$_fullServiceName')");
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 _client = '$grpcImportPrefix.Client';
static final String _service = '$grpcImportPrefix.Service';
static final String _list = '$coreImportPrefix.List';
static final String _string = '$coreImportPrefix.String';
}
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;
final bool _deprecated;
_GrpcMethod._(
this._grpcName,
this._dartName,
this._serviceName,
this._clientStreaming,
this._serverStreaming,
this._requestType,
this._responseType,
this._argumentType,
this._clientReturnType,
this._serverReturnType,
this._deprecated);
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>';
final deprecated = method.options.deprecated;
return _GrpcMethod._(
grpcName,
dartName,
service._fullServiceName,
clientStreaming,
serverStreaming,
requestType,
responseType,
argumentType,
clientReturnType,
serverReturnType,
deprecated);
}
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));');
}
List<int> _methodDescriptorPath(GrpcServiceGenerator generator, int index) {
return [
...generator._serviceDescriptorPath,
ClientApiGenerator.serviceDescriptorMethodTag,
index,
];
}
void generateClientStub(IndentingWriter out,
GrpcServiceGenerator serviceGenerator, int methodIndex) {
out.println();
final commentBlock = serviceGenerator.fileGen
.commentBlock(_methodDescriptorPath(serviceGenerator, methodIndex));
if (commentBlock != null) {
out.println(commentBlock);
}
if (_deprecated) {
out.println(
'@$coreImportPrefix.Deprecated(\'This method is deprecated\')');
}
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';
}
extension on ServiceOptions {
String? get defaultHost => getExtension(Client.defaultHost) as String?;
String? get oauthScopes => getExtension(Client.oauthScopes) as String?;
}