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 @@ class CodeGenerator extends ProtobufContainer { // (We may import it even if we don't generate the .pb.dart file.) List generators = []; 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 @@ class FileGenerator extends ProtobufContainer { } final FileDescriptorProto descriptor; + final GenerationOptions options; // The relative path used to import the .proto file, as a URI. final Uri protoFileUri; - final List enumGenerators = []; - final List messageGenerators = []; - final List extensionGenerators = []; - final List clientApiGenerators = []; - final List serviceGenerators = []; + final enumGenerators = []; + final messageGenerators = []; + final extensionGenerators = []; + final clientApiGenerators = []; + final serviceGenerators = []; + final grpcGenerators = []; /// 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 @@ class FileGenerator extends ProtobufContainer { 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 @@ class FileGenerator extends ProtobufContainer { ..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 @@ import 'package:protobuf/protobuf.dart'; 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(); + 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 = {}; + + /// Maps each undefined type to a string describing its location. + /// + /// Populated by [resolve]. + final _undefinedDeps = {}; + + /// 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 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 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 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 @@ void link(GenerationOptions options, Iterable files) { 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 @@ bool genericOptionsParser(CodeGeneratorRequest request, /// 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 @@ abstract class SingleOptionParser { 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 parsers]) { - var newParsers = {}; + final newParsers = {}; 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 @@ library protoc; 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 'code_generator.dart'; 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 @@ dependencies: 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 @@ class TestApi { } '''; + 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 @@ class _ReadonlyPhoneNumber extends PhoneNumber with ReadonlyMessageMixin {} 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 @@ const PhoneNumber$json = const { 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 @@ class PhoneType extends ProtobufEnum { 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 @@ const PhoneType$json = const { 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 @@ import 'package:protobuf/protobuf.dart'; 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 @@ import 'package:protobuf/protobuf.dart'; 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 @@ abstract class TestServiceBase extends GeneratedService { 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 @@ abstract class TestServiceBase extends GeneratedService { 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 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 createRepeated() => new PbList(); + 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( + '/Test/Unary', + (Input value) => value.writeToBuffer(), + (List value) => new Output.fromBuffer(value)); + static final _$clientStreaming = new ClientMethod( + '/Test/ClientStreaming', + (Input value) => value.writeToBuffer(), + (List value) => new Output.fromBuffer(value)); + static final _$serverStreaming = new ClientMethod( + '/Test/ServerStreaming', + (Input value) => value.writeToBuffer(), + (List value) => new Output.fromBuffer(value)); + static final _$bidirectional = new ClientMethod( + '/Test/Bidirectional', + (Input value) => value.writeToBuffer(), + (List value) => new Output.fromBuffer(value)); + + TestClient(this._channel); + + ResponseFuture unary(Input request) { + final call = new ClientCall(_channel, _$unary); + call.request + ..add(request) + ..close(); + return new ResponseFuture(call); + } + + ResponseFuture clientStreaming(Stream request) { + final call = new ClientCall(_channel, _$clientStreaming); + request.pipe(call.request); + return new ResponseFuture(call); + } + + ResponseStream serverStreaming(Input request) { + final call = new ClientCall(_channel, _$serverStreaming); + call.request + ..add(request) + ..close(); + return new ResponseStream(call); + } + + ResponseStream bidirectional(Stream 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 value) => new Input.fromBuffer(value), + (Output value) => value.writeToBuffer())); + $addMethod(new ServiceMethod( + 'ClientStreaming', + clientStreaming, + true, + false, + (List value) => new Input.fromBuffer(value), + (Output value) => value.writeToBuffer())); + $addMethod(new ServiceMethod( + 'ServerStreaming', + serverStreaming_Pre, + false, + true, + (List value) => new Input.fromBuffer(value), + (Output value) => value.writeToBuffer())); + $addMethod(new ServiceMethod( + 'Bidirectional', + bidirectional, + true, + true, + (List value) => new Input.fromBuffer(value), + (Output value) => value.writeToBuffer())); + } + + Future unary_Pre(ServiceCall call, Future request) async { + return unary(call, await request); + } + + Stream serverStreaming_Pre( + ServiceCall call, Future request) async* { + yield* serverStreaming(call, await request); + } + + Future unary(ServiceCall call, Input request); + Future clientStreaming(ServiceCall call, Stream request); + Stream serverStreaming(ServiceCall call, Input request); + Stream bidirectional(ServiceCall call, Stream 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 @@ const M$json = const { 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 @@ class _ReadonlyPhoneNumber extends PhoneNumber with ReadonlyMessageMixin {} 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 @@ abstract class TestServiceBase extends GeneratedService { '''; + 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]);