gRPC stub generation. (#79)
Added support for generating gRPC stubs. gRPC mode is selected by adding
the option 'grpc' to the `--dart_out` argument, as in
`--dart_out=grpc:<path>`.
When gRPC mode is selected, the legacy ("generic") RPC stubs will not be
emitted.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0b1f50..4a21ebc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## Unreleased
+
+### gRPC support
+
+* Added gRPC stub generation.
+* Updated descriptor.proto from google/protobuf v3.3.0.
+
## 0.7.2 - 2017-06-12
* Added CHANGELOG.md
diff --git a/lib/code_generator.dart b/lib/code_generator.dart
index 9c9182e..ecb10e0 100644
--- a/lib/code_generator.dart
+++ b/lib/code_generator.dart
@@ -55,7 +55,7 @@
// (We may import it even if we don't generate the .pb.dart file.)
List<FileGenerator> generators = <FileGenerator>[];
for (FileDescriptorProto file in request.protoFile) {
- generators.add(new FileGenerator(file));
+ generators.add(new FileGenerator(file, options));
}
// Collect field types and importable files.
diff --git a/lib/file_generator.dart b/lib/file_generator.dart
index 6ea65d2..838ca03 100644
--- a/lib/file_generator.dart
+++ b/lib/file_generator.dart
@@ -5,6 +5,7 @@
part of protoc;
final _dartIdentifier = new RegExp(r'^\w+$');
+final _formatter = new DartFormatter();
/// Generates the Dart output files for one .proto input file.
///
@@ -90,22 +91,23 @@
}
final FileDescriptorProto descriptor;
+ final GenerationOptions options;
// The relative path used to import the .proto file, as a URI.
final Uri protoFileUri;
- final List<EnumGenerator> enumGenerators = <EnumGenerator>[];
- final List<MessageGenerator> messageGenerators = <MessageGenerator>[];
- final List<ExtensionGenerator> extensionGenerators = <ExtensionGenerator>[];
- final List<ClientApiGenerator> clientApiGenerators = <ClientApiGenerator>[];
- final List<ServiceGenerator> serviceGenerators = <ServiceGenerator>[];
+ final enumGenerators = <EnumGenerator>[];
+ final messageGenerators = <MessageGenerator>[];
+ final extensionGenerators = <ExtensionGenerator>[];
+ final clientApiGenerators = <ClientApiGenerator>[];
+ final serviceGenerators = <ServiceGenerator>[];
+ final grpcGenerators = <GrpcServiceGenerator>[];
/// True if cross-references have been resolved.
bool _linked = false;
- FileGenerator(FileDescriptorProto descriptor)
- : descriptor = descriptor,
- protoFileUri = new Uri.file(descriptor.name) {
+ FileGenerator(this.descriptor, this.options)
+ : protoFileUri = new Uri.file(descriptor.name) {
if (protoFileUri.isAbsolute) {
// protoc should never generate an import with an absolute path.
throw "FAILURE: Import with absolute path is not supported";
@@ -133,9 +135,13 @@
extensionGenerators.add(new ExtensionGenerator(extension, this));
}
for (ServiceDescriptorProto service in descriptor.service) {
- var serviceGen = new ServiceGenerator(service, this);
- serviceGenerators.add(serviceGen);
- clientApiGenerators.add(new ClientApiGenerator(serviceGen));
+ if (options.useGrpc) {
+ grpcGenerators.add(new GrpcServiceGenerator(service, this));
+ } else {
+ var serviceGen = new ServiceGenerator(service, this);
+ serviceGenerators.add(serviceGen);
+ clientApiGenerators.add(new ClientApiGenerator(serviceGen));
+ }
}
}
@@ -178,12 +184,19 @@
..content = content;
}
- return [
+ final files = [
makeFile(".pb.dart", generateMainFile(config)),
makeFile(".pbenum.dart", generateEnumFile(config)),
- makeFile(".pbserver.dart", generateServerFile(config)),
makeFile(".pbjson.dart", generateJsonFile(config)),
];
+ if (options.useGrpc) {
+ if (grpcGenerators.isNotEmpty) {
+ files.add(makeFile(".pbgrpc.dart", generateGrpcFile(config)));
+ }
+ } else {
+ files.add(makeFile(".pbserver.dart", generateServerFile(config)));
+ }
+ return files;
}
/// Returns the contents of the .pb.dart file for this .proto file.
@@ -417,6 +430,40 @@
return out.toString();
}
+ /// Returns the contents of the .pbgrpc.dart file for this .proto file.
+ String generateGrpcFile(
+ [OutputConfiguration config = const DefaultOutputConfiguration()]) {
+ if (!_linked) throw new StateError("not linked");
+ var out = new IndentingWriter();
+ _writeLibraryHeading(out, "pbgrpc");
+
+ out.println('''
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+''');
+
+ // Import .pb.dart files needed for requests and responses.
+ var imports = new Set<FileGenerator>();
+ for (var generator in grpcGenerators) {
+ generator.addImportsTo(imports);
+ }
+ for (var target in imports) {
+ _writeImport(out, config, target, ".pb.dart");
+ }
+
+ var resolvedImport =
+ config.resolveImport(protoFileUri, protoFileUri, ".pb.dart");
+ out.println("export '$resolvedImport';");
+ out.println();
+
+ for (var generator in grpcGenerators) {
+ generator.generate(out);
+ }
+
+ return _formatter.format(out.toString());
+ }
+
/// Returns the contents of the .pbjson.dart file for this .proto file.
String generateJsonFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
diff --git a/lib/grpc_generator.dart b/lib/grpc_generator.dart
new file mode 100644
index 0000000..22d2fcc
--- /dev/null
+++ b/lib/grpc_generator.dart
@@ -0,0 +1,266 @@
+// 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;
+
+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(new _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.
+
+ MessageGenerator mg = ctx.getFieldType(fqname);
+ if (mg == null) {
+ _undefinedDeps[fqname] = location;
+ return;
+ }
+ mg.checkResolved();
+ _deps[mg.fqname] = 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(jakobr): Throw more actionable error.
+ throw 'FAILURE: Unknown type reference (${fqname}) for ${location}';
+ }
+ if (fileGen.package == mg.fileGen.package || mg.fileGen.package == "") {
+ // It's either the same file, or another file with the same package.
+ // (In the second case, we import it without using "as".)
+ return mg.classname;
+ }
+ return mg.packageImportPrefix + "." + mg.classname;
+ }
+
+ void generate(IndentingWriter out) {
+ _generateClient(out);
+ out.println();
+ _generateService(out);
+ }
+
+ void _generateClient(IndentingWriter out) {
+ out.addBlock('class $_clientClassname {', '}', () {
+ out.println('final ClientChannel _channel;');
+ out.println();
+ for (final method in _methods) {
+ method.generateClientMethodDescriptor(out);
+ }
+ out.println();
+ out.println('$_clientClassname(this._channel);');
+ for (final method in _methods) {
+ method.generateClientStub(out);
+ }
+ });
+ }
+
+ void _generateService(IndentingWriter out) {
+ out.addBlock('abstract class $_serviceClassname extends Service {', '}',
+ () {
+ out.println('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);
+ }
+ });
+ }
+}
+
+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 =
+ grpcName.substring(0, 1).toLowerCase() + grpcName.substring(1);
+
+ 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 new _GrpcMethod._(
+ grpcName,
+ dartName,
+ service._fullServiceName,
+ clientStreaming,
+ serverStreaming,
+ requestType,
+ responseType,
+ argumentType,
+ clientReturnType,
+ serverReturnType);
+ }
+
+ void generateClientMethodDescriptor(IndentingWriter out) {
+ out.println(
+ 'static final _\$$_dartName = new ClientMethod<$_requestType, $_responseType>(');
+ out.println('\'/$_serviceName/$_grpcName\',');
+ out.println('($_requestType value) => value.writeToBuffer(),');
+ out.println('(List<int> value) => new $_responseType.fromBuffer(value));');
+ }
+
+ void generateClientStub(IndentingWriter out) {
+ out.println();
+ out.addBlock('$_clientReturnType $_dartName($_argumentType request) {', '}',
+ () {
+ out.println('final call = new ClientCall(_channel, _\$$_dartName);');
+ if (_clientStreaming) {
+ out.println('request.pipe(call.request);');
+ } else {
+ out.println('call.request..add(request)..close();');
+ }
+ if (_serverStreaming) {
+ out.println('return new ResponseStream(call);');
+ } else {
+ out.println('return new ResponseFuture(call);');
+ }
+ });
+ }
+
+ void generateServiceMethodRegistration(IndentingWriter out) {
+ out.println('\$addMethod(new ServiceMethod(');
+ out.println('\'$_grpcName\',');
+ out.println('$_dartName${_clientStreaming ? '' : '_Pre'},');
+ out.println('$_clientStreaming,');
+ out.println('$_serverStreaming,');
+ out.println('(List<int> value) => new $_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);');
+ }
+}
diff --git a/lib/linker.dart b/lib/linker.dart
index fa1ab9d..cf275d7 100644
--- a/lib/linker.dart
+++ b/lib/linker.dart
@@ -30,6 +30,9 @@
for (var s in f.serviceGenerators) {
s.resolve(ctx);
}
+ for (var s in f.grpcGenerators) {
+ s.resolve(ctx);
+ }
}
}
diff --git a/lib/options.dart b/lib/options.dart
index fcea308..c8fc4a6 100644
--- a/lib/options.dart
+++ b/lib/options.dart
@@ -46,7 +46,9 @@
/// Options expected by the protoc code generation compiler.
class GenerationOptions {
- GenerationOptions();
+ final bool useGrpc;
+
+ GenerationOptions({this.useGrpc = false});
}
/// A parser for a name-value pair option. Options parsed in
@@ -60,17 +62,33 @@
void parse(String name, String value, onError(String details));
}
-/// Parser used by the compiler, which supports the `field_name` option (see
-/// [FieldNameOptionParser]) and any additional option added in [parsers]. If
-/// [parsers] has a key for `field_name`, it will be ignored.
+class GrpcOptionParser implements SingleOptionParser {
+ bool grpcEnabled = false;
+
+ @override
+ void parse(String name, String value, onError(String details)) {
+ if (value != null) {
+ onError('Invalid grpc option. No value expected.');
+ return;
+ }
+ grpcEnabled = true;
+ }
+}
+
+/// Parser used by the compiler, which supports the `rpc` option (see
+/// [RpcOptionParser]) and any additional option added in [parsers]. If
+/// [parsers] has a key for `rpc`, it will be ignored.
GenerationOptions parseGenerationOptions(
CodeGeneratorRequest request, CodeGeneratorResponse response,
[Map<String, SingleOptionParser> parsers]) {
- var newParsers = <String, SingleOptionParser>{};
+ final newParsers = <String, SingleOptionParser>{};
if (parsers != null) newParsers.addAll(parsers);
+ final grpcOptionParser = new GrpcOptionParser();
+ newParsers['grpc'] = grpcOptionParser;
+
if (genericOptionsParser(request, response, newParsers)) {
- return new GenerationOptions();
+ return new GenerationOptions(useGrpc: grpcOptionParser.grpcEnabled);
}
return null;
}
diff --git a/lib/protoc.dart b/lib/protoc.dart
index 94cb333..6a1b390 100644
--- a/lib/protoc.dart
+++ b/lib/protoc.dart
@@ -3,6 +3,7 @@
import 'dart:async';
import 'dart:io';
+import 'package:dart_style/dart_style.dart';
import 'package:protobuf/mixins_meta.dart';
import 'package:protobuf/protobuf.dart';
import 'package:path/path.dart' as path;
@@ -21,6 +22,7 @@
part 'enum_generator.dart';
part 'extension_generator.dart';
part 'file_generator.dart';
+part 'grpc_generator.dart';
part 'linker.dart';
part 'message_generator.dart';
part 'options.dart';
diff --git a/pubspec.yaml b/pubspec.yaml
index b962b21..5c59ddb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -9,6 +9,7 @@
fixnum: ^0.10.5
path: ^1.0.0
protobuf: ^0.5.4
+ dart_style: ^1.0.6
dev_dependencies:
browser: any
test: ^0.12.0
diff --git a/test/client_generator_test.dart b/test/client_generator_test.dart
index 2150082..7b6cc74 100644
--- a/test/client_generator_test.dart
+++ b/test/client_generator_test.dart
@@ -30,12 +30,13 @@
}
''';
+ var options = new GenerationOptions();
var fd = buildFileDescriptor("testpkg", ["SomeRequest", "SomeReply"]);
fd.service.add(buildServiceDescriptor());
- var fg = new FileGenerator(fd);
+ var fg = new FileGenerator(fd, options);
var fd2 = buildFileDescriptor("foo.bar", ["EmptyMessage", "AnotherReply"]);
- var fg2 = new FileGenerator(fd2);
+ var fg2 = new FileGenerator(fd2, options);
link(new GenerationOptions(), [fg, fg2]);
diff --git a/test/file_generator_test.dart b/test/file_generator_test.dart
index 3ab1e20..b7d01fa 100644
--- a/test/file_generator_test.dart
+++ b/test/file_generator_test.dart
@@ -129,7 +129,7 @@
FileDescriptorProto fd = buildFileDescriptor();
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
expect(fg.generateMainFile(), expected);
});
@@ -157,7 +157,7 @@
FileDescriptorProto fd = buildFileDescriptor();
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
expect(fg.generateJsonFile(), expected);
});
@@ -220,7 +220,7 @@
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
expect(fg.generateMainFile(), expected);
expect(fg.generateEnumFile(), expectedEnum);
@@ -252,7 +252,7 @@
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
expect(fg.generateJsonFile(), expected);
});
@@ -276,7 +276,7 @@
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
var writer = new IndentingWriter();
@@ -313,7 +313,7 @@
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
var writer = new IndentingWriter();
@@ -429,7 +429,7 @@
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
var writer = new IndentingWriter();
@@ -438,6 +438,236 @@
expect(fg.generateServerFile(), expectedServer);
});
+ test('FileGenerator does not output legacy service stubs if gRPC is selected',
+ () {
+ String expectedClient = r'''
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: library_prefixes
+library test;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+import 'dart:async';
+
+import 'package:protobuf/protobuf.dart';
+
+class Empty extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Empty')
+ ..hasRequiredFields = false
+ ;
+
+ Empty() : super();
+ Empty.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r);
+ Empty.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromJson(i, r);
+ Empty clone() => new Empty()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Empty create() => new Empty();
+ static PbList<Empty> createRepeated() => new PbList<Empty>();
+ static Empty getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyEmpty();
+ return _defaultInstance;
+ }
+ static Empty _defaultInstance;
+ static void $checkItem(Empty v) {
+ if (v is !Empty) checkItemFailed(v, 'Empty');
+ }
+}
+
+class _ReadonlyEmpty extends Empty with ReadonlyMessageMixin {}
+
+''';
+
+ DescriptorProto empty = new DescriptorProto()..name = "Empty";
+
+ ServiceDescriptorProto sd = new ServiceDescriptorProto()
+ ..name = 'Test'
+ ..method.add(new MethodDescriptorProto()
+ ..name = 'Ping'
+ ..inputType = '.Empty'
+ ..outputType = '.Empty');
+
+ FileDescriptorProto fd = new FileDescriptorProto()
+ ..name = 'test'
+ ..messageType.add(empty)
+ ..service.add(sd);
+
+ var options = new GenerationOptions(useGrpc: true);
+
+ FileGenerator fg = new FileGenerator(fd, options);
+ link(options, [fg]);
+
+ var writer = new IndentingWriter();
+ fg.writeMainHeader(writer);
+ expect(fg.generateMainFile(), expectedClient);
+ });
+
+ test('FileGenerator outputs gRPC stubs if gRPC is selected', () {
+ final expectedGrpc = r'''
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: library_prefixes
+library test_pbgrpc;
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'test.pb.dart';
+export 'test.pb.dart';
+
+class TestClient {
+ final ClientChannel _channel;
+
+ static final _$unary = new ClientMethod<Input, Output>(
+ '/Test/Unary',
+ (Input value) => value.writeToBuffer(),
+ (List<int> value) => new Output.fromBuffer(value));
+ static final _$clientStreaming = new ClientMethod<Input, Output>(
+ '/Test/ClientStreaming',
+ (Input value) => value.writeToBuffer(),
+ (List<int> value) => new Output.fromBuffer(value));
+ static final _$serverStreaming = new ClientMethod<Input, Output>(
+ '/Test/ServerStreaming',
+ (Input value) => value.writeToBuffer(),
+ (List<int> value) => new Output.fromBuffer(value));
+ static final _$bidirectional = new ClientMethod<Input, Output>(
+ '/Test/Bidirectional',
+ (Input value) => value.writeToBuffer(),
+ (List<int> value) => new Output.fromBuffer(value));
+
+ TestClient(this._channel);
+
+ ResponseFuture<Output> unary(Input request) {
+ final call = new ClientCall(_channel, _$unary);
+ call.request
+ ..add(request)
+ ..close();
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<Output> clientStreaming(Stream<Input> request) {
+ final call = new ClientCall(_channel, _$clientStreaming);
+ request.pipe(call.request);
+ return new ResponseFuture(call);
+ }
+
+ ResponseStream<Output> serverStreaming(Input request) {
+ final call = new ClientCall(_channel, _$serverStreaming);
+ call.request
+ ..add(request)
+ ..close();
+ return new ResponseStream(call);
+ }
+
+ ResponseStream<Output> bidirectional(Stream<Input> request) {
+ final call = new ClientCall(_channel, _$bidirectional);
+ request.pipe(call.request);
+ return new ResponseStream(call);
+ }
+}
+
+abstract class TestServiceBase extends Service {
+ String get $name => 'Test';
+
+ TestServiceBase() {
+ $addMethod(new ServiceMethod(
+ 'Unary',
+ unary_Pre,
+ false,
+ false,
+ (List<int> value) => new Input.fromBuffer(value),
+ (Output value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod(
+ 'ClientStreaming',
+ clientStreaming,
+ true,
+ false,
+ (List<int> value) => new Input.fromBuffer(value),
+ (Output value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod(
+ 'ServerStreaming',
+ serverStreaming_Pre,
+ false,
+ true,
+ (List<int> value) => new Input.fromBuffer(value),
+ (Output value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod(
+ 'Bidirectional',
+ bidirectional,
+ true,
+ true,
+ (List<int> value) => new Input.fromBuffer(value),
+ (Output value) => value.writeToBuffer()));
+ }
+
+ Future<Output> unary_Pre(ServiceCall call, Future<Input> request) async {
+ return unary(call, await request);
+ }
+
+ Stream<Output> serverStreaming_Pre(
+ ServiceCall call, Future<Input> request) async* {
+ yield* serverStreaming(call, await request);
+ }
+
+ Future<Output> unary(ServiceCall call, Input request);
+ Future<Output> clientStreaming(ServiceCall call, Stream<Input> request);
+ Stream<Output> serverStreaming(ServiceCall call, Input request);
+ Stream<Output> bidirectional(ServiceCall call, Stream<Input> request);
+}
+''';
+
+ final input = new DescriptorProto()..name = "Input";
+ final output = new DescriptorProto()..name = "Output";
+
+ final unary = new MethodDescriptorProto()
+ ..name = 'Unary'
+ ..inputType = '.Input'
+ ..outputType = '.Output'
+ ..clientStreaming = false
+ ..serverStreaming = false;
+ final clientStreaming = new MethodDescriptorProto()
+ ..name = 'ClientStreaming'
+ ..inputType = '.Input'
+ ..outputType = '.Output'
+ ..clientStreaming = true
+ ..serverStreaming = false;
+ final serverStreaming = new MethodDescriptorProto()
+ ..name = 'ServerStreaming'
+ ..inputType = '.Input'
+ ..outputType = '.Output'
+ ..clientStreaming = false
+ ..serverStreaming = true;
+ final bidirectional = new MethodDescriptorProto()
+ ..name = 'Bidirectional'
+ ..inputType = '.Input'
+ ..outputType = '.Output'
+ ..clientStreaming = true
+ ..serverStreaming = true;
+
+ ServiceDescriptorProto sd = new ServiceDescriptorProto()
+ ..name = 'Test'
+ ..method.addAll([unary, clientStreaming, serverStreaming, bidirectional]);
+
+ FileDescriptorProto fd = new FileDescriptorProto()
+ ..name = 'test'
+ ..messageType.addAll([input, output])
+ ..service.add(sd);
+
+ var options = new GenerationOptions(useGrpc: true);
+
+ FileGenerator fg = new FileGenerator(fd, options);
+ link(options, [fg]);
+
+ var writer = new IndentingWriter();
+ fg.writeMainHeader(writer);
+ expect(fg.generateGrpcFile(), expectedGrpc);
+ });
+
test('FileGenerator generates imports for .pb.dart files', () {
// NOTE: Below > 80 cols because it is matching generated code > 80 cols.
String expected = r'''
@@ -614,8 +844,9 @@
var response = new CodeGeneratorResponse();
var options = parseGenerationOptions(request, response);
- FileGenerator fg = new FileGenerator(fd);
- link(options, [fg, new FileGenerator(fd1), new FileGenerator(fd2)]);
+ FileGenerator fg = new FileGenerator(fd, options);
+ link(options,
+ [fg, new FileGenerator(fd1, options), new FileGenerator(fd2, options)]);
expect(fg.generateMainFile(), expected);
expect(fg.generateJsonFile(), expectedJson);
});
diff --git a/test/message_generator_test.dart b/test/message_generator_test.dart
index 80bf551..2b3c13a 100755
--- a/test/message_generator_test.dart
+++ b/test/message_generator_test.dart
@@ -127,7 +127,7 @@
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
- FileGenerator fg = new FileGenerator(fd);
+ FileGenerator fg = new FileGenerator(fd, options);
MessageGenerator mg = new MessageGenerator(md, fg, {}, null);
var ctx = new GenerationContext(options);
diff --git a/test/service_generator_test.dart b/test/service_generator_test.dart
index b917228..fe5c82f 100644
--- a/test/service_generator_test.dart
+++ b/test/service_generator_test.dart
@@ -40,12 +40,13 @@
''';
+ var options = new GenerationOptions();
var fd = buildFileDescriptor("testpkg", ["SomeRequest", "SomeReply"]);
fd.service.add(buildServiceDescriptor());
- var fg = new FileGenerator(fd);
+ var fg = new FileGenerator(fd, options);
var fd2 = buildFileDescriptor("foo.bar", ["EmptyMessage", "AnotherReply"]);
- var fg2 = new FileGenerator(fd2);
+ var fg2 = new FileGenerator(fd2, options);
link(new GenerationOptions(), [fg, fg2]);