Skip to content
This repository has been archived by the owner on Jan 26, 2021. It is now read-only.

Commit

Permalink
Initial support for generating client and server stubs for Dart.
Browse files Browse the repository at this point in the history
  • Loading branch information
wibling committed Jun 25, 2015
1 parent 8195898 commit f33e6e8
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 12 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
PLUGIN_SRC = \
prepend.dart \
bin/protoc_plugin.dart \
lib/client_generator.dart \
lib/code_generator.dart \
lib/enum_generator.dart \
lib/exceptions.dart \
Expand All @@ -12,6 +13,7 @@ PLUGIN_SRC = \
lib/output_config.dart \
lib/protobuf_field.dart \
lib/protoc.dart \
lib/service_generator.dart \
lib/src/descriptor.pb.dart \
lib/src/plugin.pb.dart \
lib/writer.dart
Expand All @@ -36,6 +38,7 @@ TEST_PROTO_LIST = \
package1 \
package2 \
package3 \
service \
toplevel_import \
toplevel
TEST_PROTO_DIR=$(OUTPUT_DIR)/protos
Expand Down
56 changes: 56 additions & 0 deletions lib/client_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2015, 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 ClientApiGenerator extends ProtobufContainer {
final String classname;
final String fqname;

final ProtobufContainer _parent;
final GenerationContext _context;
final ServiceDescriptorProto _descriptor;

ClientApiGenerator(ServiceDescriptorProto descriptor,
ProtobufContainer parent, this._context)
: _descriptor = descriptor,
_parent = parent,
classname = descriptor.name,
fqname = (parent == null || parent.fqname == null)
? descriptor.name
: (parent.fqname == '.'
? '.${descriptor.name}'
: '${parent.fqname}.${descriptor.name}') {
_context.register(this);
}

String get package => _parent.package;

String _shortType(String typename) {
return typename.substring(typename.lastIndexOf('.')+1);
}

void generate(IndentingWriter out) {
out.addBlock('class ${classname}Api {', '}', () {
out.println('RpcClient _client;');
out.println('${classname}Api(this._client);');
out.println();
for (MethodDescriptorProto m in _descriptor.method) {
// lowercase first letter in method name.
var methodName =
m.name.substring(0,1).toLowerCase() + m.name.substring(1);
out.addBlock('Future<${_shortType(m.outputType)}> $methodName('
'ClientContext ctx, ${_shortType(m.inputType)} request) '
'async {', '}', () {
out.println('var emptyResponse = new ${_shortType(m.outputType)}();');
out.println('var result = await _client.invoke(ctx, '
'\'${_descriptor.name}\', \'${m.name}\', '
'request, emptyResponse);');
out.println('return result;');
});
}
});
out.println();
}
}
22 changes: 20 additions & 2 deletions lib/file_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class FileGenerator extends ProtobufContainer {
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>[];

FileGenerator(this._fileDescriptor, this._parent, this._context) {
_context.register(this);
Expand All @@ -46,6 +48,10 @@ class FileGenerator extends ProtobufContainer {
extensionGenerators.add(
new ExtensionGenerator(extension, this, _context));
}
for (ServiceDescriptorProto service in _fileDescriptor.service) {
serviceGenerators.add(new ServiceGenerator(service, this, _context));
clientApiGenerators.add(new ClientApiGenerator(service, this, _context));
}
}

String get package => _fileDescriptor.package;
Expand Down Expand Up @@ -90,12 +96,17 @@ class FileGenerator extends ProtobufContainer {

String libraryName = _generateLibraryName(filePath);

// Print header and imports. We only add the dart:async import if there
// are services in the FileDescriptorProto.
out.println(
'///\n'
'// Generated code. Do not modify.\n'
'///\n'
'library $libraryName;\n'
'\n'
'library $libraryName;\n');
if (_fileDescriptor.service.isNotEmpty) {
out.println("import 'dart:async';\n");
}
out.println(
"import 'package:fixnum/fixnum.dart';\n"
"import 'package:protobuf/protobuf.dart';"
);
Expand Down Expand Up @@ -158,6 +169,13 @@ class FileGenerator extends ProtobufContainer {
out.println('}');
});
}

for (ClientApiGenerator c in clientApiGenerators) {
c.generate(out);
}
for (ServiceGenerator s in serviceGenerators) {
s.generate(out);
}
}

/// Returns a map from import names to the Dart symbols to be imported.
Expand Down
8 changes: 6 additions & 2 deletions lib/indenting_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ class IndentingWriter implements Writer {

IndentingWriter(this._indentSequence, this._writer);

void addBlock(String start, String end, void body()) {
void addBlock(String start, String end, void body(), {endWithNewline: true}) {
println(start);
var oldIndent = _currentIndent;
_currentIndent = '$_currentIndent$_indentSequence';
body();
_currentIndent = oldIndent;
println(end);
if (endWithNewline) {
println(end);
} else {
print(end);
}
}

void print(String stringToPrint) {
Expand Down
2 changes: 2 additions & 0 deletions lib/protoc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'src/descriptor.pb.dart';
import 'src/plugin.pb.dart';
import 'src/dart_options.pb.dart';

part 'client_generator.dart';
part 'code_generator.dart';
part 'enum_generator.dart';
part 'exceptions.dart';
Expand All @@ -21,4 +22,5 @@ part 'message_generator.dart';
part 'options.dart';
part 'output_config.dart';
part 'protobuf_field.dart';
part 'service_generator.dart';
part 'writer.dart';
95 changes: 95 additions & 0 deletions lib/service_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2015, 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 ServiceGenerator extends ProtobufContainer {
final String classname;
final String fqname;

final ProtobufContainer _parent;
final GenerationContext _context;
final ServiceDescriptorProto _descriptor;
final List<MethodDescriptorProto> _methodDescriptors;

ServiceGenerator(ServiceDescriptorProto descriptor, ProtobufContainer parent,
this._context)
: _descriptor = descriptor,
_parent = parent,
classname = descriptor.name,
fqname = _qualifiedName(descriptor, parent),
_methodDescriptors = descriptor.method {
_context.register(this);
}

static String _qualifiedName(ServiceDescriptorProto descriptor,
ProtobufContainer parent) {
if (parent == null || parent.fqname == null) {
return descriptor.name;
} else if (parent.fqname == '.') {
return '.${descriptor.name}';
} else {
return '${parent.fqname}.${descriptor.name}';
}
}

static String _serviceClassName(descriptor) {
if (descriptor.name.endsWith("Service")) {
return descriptor.name + "Base"; // avoid: ServiceServiceBase
} else {
return descriptor.name + "ServiceBase";
}
}

String get package => _parent.package;

String _shortType(String typename) {
return typename.substring(typename.lastIndexOf('.') + 1);
}

String _methodName(String name) =>
name.substring(0,1).toLowerCase() + name.substring(1);

void generate(IndentingWriter out) {
out.addBlock(
'abstract class ${_serviceClassName(_descriptor)} extends '
'GeneratedService {',
'}', () {
for (MethodDescriptorProto m in _methodDescriptors) {
var methodName = _methodName(m.name);
out.println('Future<${_shortType(m.outputType)}> $methodName('
'ServerContext ctx, ${_shortType(m.inputType)} request);');
}
out.println();

out.addBlock(
'GeneratedMessage createRequest(String method) {', '}', () {
out.addBlock("switch (method) {", "}", () {
for (MethodDescriptorProto m in _methodDescriptors) {
out.println(
"case '${m.name}': return new ${_shortType(m.inputType)}();");
}
out.println("default: "
"throw new ArgumentError('Unknown method: \$method');");
});
});
out.println();

out.addBlock(
'Future<GeneratedMessage> handleCall(ServerContext ctx, '
'String method, GeneratedMessage request) async {', '}', () {
out.addBlock("switch (method) {", "}", () {
for (MethodDescriptorProto m in _methodDescriptors) {
var methodName = _methodName(m.name);
out.println(
"case '${m.name}': return await $methodName(ctx, request);");
}
out.println("default: "
"throw new ArgumentError('Unknown method: \$method');");
});
});
});
out.println();
}
}
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: protoc_plugin
version: 0.3.9
version: 0.3.10
author: Dart Team <[email protected]>
description: Protoc compiler plugin to generate Dart code
homepage: https://github.com/dart-lang/dart-protoc-plugin
environment:
sdk: '>=1.0.0 <2.0.0'
dependencies:
protobuf: '>=0.3.9 <0.4.0'
protobuf: '>=0.3.10 <0.4.0'
path: '>=1.0.0 <2.0.0'
dev_dependencies:
unittest: '>=0.9.0 <0.11.0'
18 changes: 12 additions & 6 deletions test/all_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,42 @@

library protoc_plugin_all_tests;

import 'client_generator_test.dart' as cgt;
import 'enum_generator_test.dart' as egt;
import 'file_generator_test.dart' as fgt;
import 'generated_message_test.dart' as gmt;
import 'hash_code_test.dart' as hct;
import 'indenting_writer_test.dart' as iwt;
import 'json_test.dart' as jt;
import 'map_test.dart' as map_test;
import 'message_generator_test.dart' as mgt;
import 'file_generator_test.dart' as fgt;
import 'message_test.dart' as mt;
import 'protoc_options_test.dart' as pot;
import 'reserved_names_test.dart' as rnt;
import 'service_test.dart' as st;
import 'service_generator_test.dart' as sgt;
import 'unknown_field_set_test.dart' as ufst;
import 'validate_fail_test.dart' as vft;
import 'wire_format_test.dart' as wft;
import 'reserved_names_test.dart' as rnt;
import 'hash_code_test.dart' as hct;
import 'package:unittest/compact_vm_config.dart';

void main() {
useCompactVMConfiguration();
cgt.main();
egt.main();
fgt.main();
gmt.main();
hct.main();
iwt.main();
jt.main();
map_test.main();
mgt.main();
fgt.main();
mt.main();
pot.main();
rnt.main();
st.main();
sgt.main();
ufst.main();
vft.main();
wft.main();
rnt.main();
hct.main();
}
63 changes: 63 additions & 0 deletions test/client_generator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env dart
// Copyright (c) 2015, 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.

library client_generator_test;

import 'package:protoc_plugin/src/descriptor.pb.dart';
import 'package:protoc_plugin/src/plugin.pb.dart';
import 'package:protoc_plugin/protoc.dart';
import 'package:unittest/unittest.dart';

ServiceDescriptorProto buildServiceDescriptor() {
ServiceDescriptorProto sd = new ServiceDescriptorProto()
..name = 'Test'
..method.addAll([
new MethodDescriptorProto()
..name = 'AMethod'
..inputType = 'SomeRequest'
..outputType = 'SomeReply',
new MethodDescriptorProto()
..name = 'AnotherMethod'
..inputType = '.foo.bar.EmptyMessage'
..outputType = '.foo.bar.AnotherReply',
]);
return sd;
}

void main() {
test('testClientGenerator', () {
// NOTE: Below > 80 cols because it is matching generated code > 80 cols.
String expected = r'''
class TestApi {
RpcClient _client;
TestApi(this._client);
Future<SomeReply> aMethod(ClientContext ctx, SomeRequest request) async {
var emptyResponse = new SomeReply();
var result = await _client.invoke(ctx, 'Test', 'AMethod', request, emptyResponse);
return result;
}
Future<AnotherReply> anotherMethod(ClientContext ctx, EmptyMessage request) async {
var emptyResponse = new AnotherReply();
var result = await _client.invoke(ctx, 'Test', 'AnotherMethod', request, emptyResponse);
return result;
}
}
''';
var options = parseGenerationOptions(
new CodeGeneratorRequest(), new CodeGeneratorResponse());
var context =
new GenerationContext(options, new DefaultOutputConfiguration());
var fd = new FileDescriptorProto();
var fg = new FileGenerator(fd, null, context);
ServiceDescriptorProto sd = buildServiceDescriptor();
MemoryWriter buffer = new MemoryWriter();
IndentingWriter writer = new IndentingWriter(' ', buffer);
ClientApiGenerator cag = new ClientApiGenerator(sd, fg, context);
cag.generate(writer);
expect(buffer.toString(), expected);
});
}
Loading

0 comments on commit f33e6e8

Please sign in to comment.