proto3 json support (#274)
diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md
index fba6f37..aee700b 100644
--- a/protobuf/CHANGELOG.md
+++ b/protobuf/CHANGELOG.md
@@ -1,7 +1,14 @@
## 0.14.0
+* Support for proto3 json (json with field names as keys)
+ - encoding and decoding.
+ - Support for well-known types.
+ - Use `GeneratedMessage.toProto3Json()` to encode and `GeneratedMessage.mergeFromProto3Json(json)`
+ to decode.
+
* `FieldInfo` objects have a new getter `.protoName` that gives the non-camel-case name of the field
as in the `.proto`-file.
+
* **Breaking**: The field-adder methods on `BuilderInfo` now takes only named optional arguments.
To migrate, update `protoc_plugin` to version 18.0.0 or higher.
* The field-adder methods on `BuilderInfo` all take a new argument `protoName`.
diff --git a/protobuf/lib/meta.dart b/protobuf/lib/meta.dart
index 920ac6e..66af3a0 100644
--- a/protobuf/lib/meta.dart
+++ b/protobuf/lib/meta.dart
@@ -43,6 +43,7 @@
'mergeFromJson',
'mergeFromJsonMap',
'mergeFromMessage',
+ 'mergeFromProto3Json',
'mergeUnknownFields',
'noSuchMethod',
'runtimeType',
@@ -50,6 +51,7 @@
'setField',
'toBuilder',
'toDebugString',
+ 'toProto3Json',
'toString',
'unknownFields',
'writeToBuffer',
diff --git a/protobuf/lib/protobuf.dart b/protobuf/lib/protobuf.dart
index 905e43d..4f2332f 100644
--- a/protobuf/lib/protobuf.dart
+++ b/protobuf/lib/protobuf.dart
@@ -13,6 +13,10 @@
import 'package:fixnum/fixnum.dart' show Int64;
+import 'src/protobuf/json_parsing_context.dart';
+import 'src/protobuf/type_registry.dart';
+export 'src/protobuf/type_registry.dart' show TypeRegistry;
+
part 'src/protobuf/coded_buffer.dart';
part 'src/protobuf/coded_buffer_reader.dart';
part 'src/protobuf/coded_buffer_writer.dart';
@@ -32,6 +36,7 @@
part 'src/protobuf/pb_list.dart';
part 'src/protobuf/pb_map.dart';
part 'src/protobuf/protobuf_enum.dart';
+part 'src/protobuf/proto3_json.dart';
part 'src/protobuf/readonly_message.dart';
part 'src/protobuf/rpc_client.dart';
part 'src/protobuf/unknown_field_set.dart';
diff --git a/protobuf/lib/src/protobuf/builder_info.dart b/protobuf/lib/src/protobuf/builder_info.dart
index 98bb04e..c779210 100644
--- a/protobuf/lib/src/protobuf/builder_info.dart
+++ b/protobuf/lib/src/protobuf/builder_info.dart
@@ -18,7 +18,18 @@
bool hasRequiredFields = true;
List<FieldInfo> _sortedByTag;
- BuilderInfo(String messageName, {PackageName package = const PackageName('')})
+ // For well-known types.
+ final Object Function(GeneratedMessage message, TypeRegistry typeRegistry)
+ toProto3Json;
+ final Function(GeneratedMessage targetMessage, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) fromProto3Json;
+ final CreateBuilderFunc createEmptyInstance;
+
+ BuilderInfo(String messageName,
+ {PackageName package = const PackageName(''),
+ this.createEmptyInstance,
+ this.toProto3Json,
+ this.fromProto3Json})
: qualifiedMessageName = "${package.prefix}$messageName";
void add<T>(
@@ -42,12 +53,17 @@
_addField(fieldInfo);
}
- void addMapField<K, V>(int tagNumber, String name, int keyFieldType,
- int valueFieldType, BuilderInfo mapEntryBuilderInfo,
+ void addMapField<K, V>(
+ int tagNumber,
+ String name,
+ int keyFieldType,
+ int valueFieldType,
+ BuilderInfo mapEntryBuilderInfo,
+ CreateBuilderFunc valueCreator,
{String protoName}) {
var index = byIndex.length;
_addField(MapFieldInfo<K, V>(name, tagNumber, index, PbFieldType.M,
- keyFieldType, valueFieldType, mapEntryBuilderInfo,
+ keyFieldType, valueFieldType, mapEntryBuilderInfo, valueCreator,
protoName: protoName));
}
@@ -165,8 +181,8 @@
..add(PbMap._valueFieldNumber, 'value', valueFieldType, null,
valueCreator, valueOf, enumValues);
- addMapField<K, V>(
- tagNumber, name, keyFieldType, valueFieldType, mapEntryBuilderInfo,
+ addMapField<K, V>(tagNumber, name, keyFieldType, valueFieldType,
+ mapEntryBuilderInfo, valueCreator,
protoName: protoName);
}
diff --git a/protobuf/lib/src/protobuf/field_info.dart b/protobuf/lib/src/protobuf/field_info.dart
index 65ec162..91fe8cd 100644
--- a/protobuf/lib/src/protobuf/field_info.dart
+++ b/protobuf/lib/src/protobuf/field_info.dart
@@ -8,6 +8,9 @@
class FieldInfo<T> {
FrozenPbList<T> _emptyList;
+ /// Name of this field as the `json_name` reported by protoc.
+ ///
+ /// This will typically be in camel case.
final String name;
/// The name of this field as written in the proto-definition.
@@ -188,15 +191,25 @@
}
class MapFieldInfo<K, V> extends FieldInfo<PbMap<K, V>> {
- int keyFieldType;
- int valueFieldType;
- CreateBuilderFunc valueCreator;
+ final int keyFieldType;
+ final int valueFieldType;
- // BuilderInfo used when creating a field set for a map field.
+ /// Creates a new empty instance of the value type.
+ ///
+ /// `null` if the value type is not a Message type.
+ final CreateBuilderFunc valueCreator;
+
final BuilderInfo _mapEntryBuilderInfo;
- MapFieldInfo(String name, int tagNumber, int index, int type,
- this.keyFieldType, this.valueFieldType, this._mapEntryBuilderInfo,
+ MapFieldInfo(
+ String name,
+ int tagNumber,
+ int index,
+ int type,
+ this.keyFieldType,
+ this.valueFieldType,
+ this._mapEntryBuilderInfo,
+ this.valueCreator,
{String protoName})
: super(name, tagNumber, index, type,
defaultOrMaker: () =>
@@ -208,6 +221,9 @@
assert(!_isEnum(type) || valueOf != null);
}
+ FieldInfo get valueFieldInfo =>
+ _mapEntryBuilderInfo.fieldInfo[PbMap._valueFieldNumber];
+
Map<K, V> _ensureMapField(_FieldSet fs) {
return fs._ensureMapField<K, V>(this);
}
diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart
index 97071c6..bce29d8 100644
--- a/protobuf/lib/src/protobuf/field_set.dart
+++ b/protobuf/lib/src/protobuf/field_set.dart
@@ -206,6 +206,7 @@
Map<K, V> _getDefaultMap<K, V>(MapFieldInfo<K, V> fi) {
assert(fi.isMapField);
+
if (_isReadOnly)
return PbMap<K, V>.unmodifiable(PbMap<K, V>(
fi.keyFieldType, fi.valueFieldType, fi._mapEntryBuilderInfo));
diff --git a/protobuf/lib/src/protobuf/generated_message.dart b/protobuf/lib/src/protobuf/generated_message.dart
index 24d1d1f..fa1f0ea 100644
--- a/protobuf/lib/src/protobuf/generated_message.dart
+++ b/protobuf/lib/src/protobuf/generated_message.dart
@@ -199,10 +199,58 @@
/// literals; values with a 64-bit integer datatype (regardless of their
/// actual runtime value) are represented as strings. Enumerated values are
/// represented as their integer value.
+ ///
+ /// For the proto3 JSON format use: [toProto3JSON].
String writeToJson() => jsonEncode(writeToJsonMap());
+ /// Returns an Object representing Proto3 JSON serialization of [this].
+ ///
+ /// The key for each field is be the camel-cased name of the field.
+ ///
+ /// Well-known types and their special JSON encoding are supported.
+ /// If a well-known type cannot be encoded (eg. a `google.protobuf.Timestamp`
+ /// with negative `nanoseconds`) an error is thrown.
+ ///
+ /// Extensions and unknown fields are not encoded.
+ ///
+ /// The [typeRegistry] is be used for encoding `Any` messages. If an `Any`
+ /// message encoding a type not in [typeRegistry] is encountered, an
+ /// error is thrown.
+ Object toProto3Json(
+ {TypeRegistry typeRegistry = const TypeRegistry.empty()}) =>
+ _writeToProto3Json(_fieldSet, typeRegistry);
+
+ /// Merges field values from [json], a JSON object using proto3 encoding.
+ ///
+ /// Well-known types and their special JSON encoding are supported.
+ /// Except `FieldMask`.
+ ///
+ /// If [ignoreUnknownFields] is `false` (the default) an
+ /// [FormatException] is be thrown if an unknown field name
+ /// is encountered. Otherwise the unknown field is ignored.
+ ///
+ /// If [supportNamesWithUnderscores] is `true` (the default) field names in
+ /// the JSON can be represented as either camel-case JSON-names or names with
+ /// underscores.
+ /// If `false` only the JSON names are supported.
+ ///
+ /// The [typeRegistry] is be used for decoding `Any` messages. If an `Any`
+ /// message encoding a type not in [typeRegistry] is encountered, a
+ /// [FormatException] is thrown.
+ ///
+ /// If the JSON is otherwise not formatted correctly (a String where a
+ /// number was expected etc.) a [FormatException] is thrown.
+ void mergeFromProto3Json(Object json,
+ {TypeRegistry typeRegistry = const TypeRegistry.empty(),
+ bool ignoreUnknownFields = false,
+ bool supportNamesWithUnderscores = true}) =>
+ _mergeFromProto3Json(json, _fieldSet, typeRegistry, ignoreUnknownFields,
+ supportNamesWithUnderscores);
+
/// Merges field values from [data], a JSON object, encoded as described by
/// [GeneratedMessage.writeToJson].
+ ///
+ /// For the proto3 JSON format use: [mergeFromProto3JSON].
void mergeFromJson(String data,
[ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY]) {
/// Disable lazy creation of Dart objects for a dart2js speedup.
diff --git a/protobuf/lib/src/protobuf/json_parsing_context.dart b/protobuf/lib/src/protobuf/json_parsing_context.dart
new file mode 100644
index 0000000..ad4e139
--- /dev/null
+++ b/protobuf/lib/src/protobuf/json_parsing_context.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, 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.
+
+class JsonParsingContext {
+ // A list of indices into maps and lists pointing to the current root.
+ final List<String> _path = <String>[];
+ final bool ignoreUnknownFields;
+ final bool supportNamesWithUnderscores;
+ JsonParsingContext(
+ this.ignoreUnknownFields, this.supportNamesWithUnderscores);
+
+ void addMapIndex(String index) {
+ _path.add(index);
+ }
+
+ void addListIndex(int index) {
+ _path.add(index.toString());
+ }
+
+ void popIndex() {
+ _path.removeLast();
+ }
+
+ /// Returns a FormatException indicating the indices to the current [path].
+ Exception parseException(String message, Object source) {
+ String formattedPath = _path.map((s) => '[\"$s\"]').join();
+ return FormatException(
+ 'Protobuf JSON decoding failed at: root$formattedPath. $message',
+ source);
+ }
+}
diff --git a/protobuf/lib/src/protobuf/mixins/well_known.dart b/protobuf/lib/src/protobuf/mixins/well_known.dart
index 6f8c647..06fd629 100644
--- a/protobuf/lib/src/protobuf/mixins/well_known.dart
+++ b/protobuf/lib/src/protobuf/mixins/well_known.dart
@@ -1,8 +1,16 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:convert';
+
import 'package:fixnum/fixnum.dart';
+import '../json_parsing_context.dart';
import '../../../protobuf.dart';
+import '../type_registry.dart';
-abstract class AnyMixin {
+abstract class AnyMixin implements GeneratedMessage {
String get typeUrl;
set typeUrl(String value);
List<int> get value;
@@ -40,9 +48,99 @@
target.value = message.writeToBuffer();
target.typeUrl = '${typeUrlPrefix}/${message.info_.qualifiedMessageName}';
}
+
+ // From google/protobuf/any.proto:
+ // JSON
+ // ====
+ // The JSON representation of an `Any` value uses the regular
+ // representation of the deserialized, embedded message, with an
+ // additional field `@type` which contains the type URL. Example:
+ //
+ // package google.profile;
+ // message Person {
+ // string first_name = 1;
+ // string last_name = 2;
+ // }
+ //
+ // {
+ // "@type": "type.googleapis.com/google.profile.Person",
+ // "firstName": <string>,
+ // "lastName": <string>
+ // }
+ //
+ // If the embedded message type is well-known and has a custom JSON
+ // representation, that representation will be embedded adding a field
+ // `value` which holds the custom JSON in addition to the `@type`
+ // field. Example (for message [google.protobuf.Duration][]):
+ //
+ // {
+ // "@type": "type.googleapis.com/google.protobuf.Duration",
+ // "value": "1.212s"
+ // }
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ AnyMixin any = message as AnyMixin;
+ BuilderInfo info = typeRegistry.lookup(_typeNameFromUrl(any.typeUrl));
+ if (info == null)
+ throw ArgumentError(
+ 'The type of the Any message (${any.typeUrl}) is not in the given typeRegistry.');
+ GeneratedMessage unpacked = info.createEmptyInstance()
+ ..mergeFromBuffer(any.value);
+ Object proto3Json = unpacked.toProto3Json();
+ if (info.toProto3Json == null) {
+ Map<String, dynamic> map = proto3Json as Map<String, dynamic>;
+ map['@type'] = any.typeUrl;
+ return map;
+ } else {
+ return {'@type': any.typeUrl, 'value': proto3Json};
+ }
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is! Map<String, dynamic>) {
+ throw context.parseException(
+ 'Expected Any message encoded as {@type,...},', json);
+ }
+ final object = json as Map<String, dynamic>;
+ final typeUrl = object['@type'];
+
+ if (typeUrl is String) {
+ AnyMixin any = message as AnyMixin;
+ BuilderInfo info = typeRegistry.lookup(_typeNameFromUrl(typeUrl));
+ if (info == null) {
+ throw context.parseException(
+ 'Decoding Any of type ${typeUrl} not in TypeRegistry $typeRegistry',
+ json);
+ }
+
+ Object subJson = info.fromProto3Json == null
+ // TODO(sigurdm): avoid cloning [object] here.
+ ? (Map<String, dynamic>.from(object)..remove('@type'))
+ : object['value'];
+ // TODO(sigurdm): We lose [context.path].
+ GeneratedMessage packedMessage = info.createEmptyInstance()
+ ..mergeFromProto3Json(subJson,
+ typeRegistry: typeRegistry,
+ supportNamesWithUnderscores: context.supportNamesWithUnderscores,
+ ignoreUnknownFields: context.ignoreUnknownFields);
+
+ any.value = packedMessage.writeToBuffer();
+ any.typeUrl = typeUrl;
+ } else {
+ throw context.parseException('Expected a string', json);
+ }
+ }
+}
+
+String _typeNameFromUrl(String typeUrl) {
+ int index = typeUrl.lastIndexOf('/');
+ return index < 0 ? '' : typeUrl.substring(index + 1);
}
abstract class TimestampMixin {
+ static final RegExp finalGroupsOfThreeZeroes = RegExp(r'(?:000)*$');
+
Int64 get seconds;
set seconds(Int64 value);
@@ -65,4 +163,575 @@
target.seconds = Int64(micros ~/ Duration.microsecondsPerSecond);
target.nanos = (micros % Duration.microsecondsPerSecond).toInt() * 1000;
}
+
+ static String _twoDigits(int n) {
+ if (n >= 10) return "${n}";
+ return "0${n}";
+ }
+
+ static final DateTime _minTimestamp = DateTime.utc(1);
+ static final DateTime _maxTimestamp = DateTime.utc(9999, 13, 31, 23, 59, 59);
+
+ // From google/protobuf/timestamp.proto:
+ // # JSON Mapping
+ //
+ // In JSON format, the Timestamp type is encoded as a string in the
+ // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+ // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+ // where {year} is always expressed using four digits while {month}, {day},
+ // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+ // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+ // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+ // is required. A proto3 JSON serializer should always use UTC (as indicated by
+ // "Z") when printing the Timestamp type and a proto3 JSON parser should be
+ // able to accept both UTC and other timezones (as indicated by an offset).
+ //
+ // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+ // 01:30 UTC on January 15, 2017.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ TimestampMixin timestamp = message as TimestampMixin;
+ DateTime dateTime = timestamp.toDateTime();
+
+ if (timestamp.nanos < 0) {
+ throw ArgumentError(
+ 'Timestamp with negative `nanos`: ${timestamp.nanos}');
+ }
+ if (timestamp.nanos > 999999999) {
+ throw ArgumentError(
+ 'Timestamp with `nanos` out of range: ${timestamp.nanos}');
+ }
+ if (dateTime.isBefore(_minTimestamp) || dateTime.isAfter(_maxTimestamp)) {
+ throw ArgumentError('Timestamp Must be from 0001-01-01T00:00:00Z to '
+ '9999-12-31T23:59:59Z inclusive. Was: ${dateTime.toIso8601String()}');
+ }
+
+ // Because [DateTime] doesn't have nano-second precision, we cannot use
+ // dateTime.toIso8601String().
+ String y = '${dateTime.year}'.padLeft(4, '0');
+ String m = _twoDigits(dateTime.month);
+ String d = _twoDigits(dateTime.day);
+ String h = _twoDigits(dateTime.hour);
+ String min = _twoDigits(dateTime.minute);
+ String sec = _twoDigits(dateTime.second);
+ String secFrac = "";
+ if (timestamp.nanos > 0) {
+ secFrac = "." +
+ timestamp.nanos
+ .toString()
+ .padLeft(9, "0")
+ .replaceFirst(finalGroupsOfThreeZeroes, '');
+ }
+ return "$y-$m-${d}T$h:$min:$sec${secFrac}Z";
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is String) {
+ String jsonWithoutFracSec = json;
+ int nanos = 0;
+ Match fracSecsMatch = RegExp(r'\.(\d+)').firstMatch(json);
+ if (fracSecsMatch != null) {
+ String fracSecs = fracSecsMatch[1];
+ if (fracSecs.length > 9) {
+ throw context.parseException(
+ 'Timestamp can have at most than 9 decimal digits', json);
+ }
+ nanos = int.parse(fracSecs.padRight(9, '0'));
+ jsonWithoutFracSec =
+ json.replaceRange(fracSecsMatch.start, fracSecsMatch.end, '');
+ }
+ DateTime dateTimeWithoutFractionalSeconds =
+ DateTime.tryParse(jsonWithoutFracSec) ??
+ (throw context.parseException(
+ 'Timestamp not well formatted. ', json));
+
+ TimestampMixin timestamp = message as TimestampMixin;
+ setFromDateTime(timestamp, dateTimeWithoutFractionalSeconds);
+ timestamp.nanos = nanos;
+ } else {
+ throw context.parseException(
+ 'Expected timestamp represented as String', json);
+ }
+ }
+}
+
+abstract class DurationMixin {
+ Int64 get seconds;
+ set seconds(Int64 value);
+
+ int get nanos;
+ set nanos(int value);
+
+ static final RegExp finalZeroes = RegExp(r'0+$');
+
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ DurationMixin duration = message as DurationMixin;
+ String secFrac = duration.nanos
+ // nanos and seconds should always have the same sign.
+ .abs()
+ .toString()
+ .padLeft(9, '0')
+ .replaceFirst(finalZeroes, '');
+ String secPart = secFrac == '' ? '' : '.$secFrac';
+ return "${duration.seconds}${secPart}s";
+ }
+
+ static final RegExp durationPattern = RegExp(r'(-?\d*)(?:\.(\d*))?s$');
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ DurationMixin duration = message as DurationMixin;
+ if (json is String) {
+ Match match = durationPattern.matchAsPrefix(json);
+ if (match == null) {
+ throw context.parseException(
+ 'Expected a String of the form `<seconds>.<nanos>s`', json);
+ } else {
+ String secondsString = match[1];
+ Int64 seconds =
+ secondsString == '' ? Int64.ZERO : Int64.parseInt(secondsString);
+ duration.seconds = seconds;
+ int nanos = int.parse((match[2] ?? '').padRight(9, '0'));
+ duration.nanos = seconds < 0 ? -nanos : nanos;
+ }
+ } else {
+ throw context.parseException(
+ 'Expected a String of the form `<seconds>.<nanos>s`', json);
+ }
+ }
+}
+
+abstract class StructMixin implements GeneratedMessage {
+ Map<String, ValueMixin> get fields;
+ static const _fieldsFieldTagNumber = 1;
+
+ // From google/protobuf/struct.proto:
+ // The JSON representation for `Struct` is JSON object.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ StructMixin struct = message as StructMixin;
+ return struct.fields.map((key, value) =>
+ MapEntry(key, ValueMixin.toProto3JsonHelper(value, typeRegistry)));
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is Map) {
+ // Check for emptiness to avoid setting `.fields` if there are no
+ // values.
+ if (json.isNotEmpty) {
+ Map<String, ValueMixin> fields = (message as StructMixin).fields;
+ GeneratedMessage Function() valueCreator =
+ (message.info_.fieldInfo[_fieldsFieldTagNumber] as MapFieldInfo)
+ .valueCreator;
+
+ json.forEach((key, value) {
+ if (key is! String) {
+ throw context.parseException('Expected String key', json);
+ }
+ ValueMixin v = valueCreator();
+ context.addMapIndex(key);
+ ValueMixin.fromProto3JsonHelper(v, value, typeRegistry, context);
+ context.popIndex();
+ fields[key] = v;
+ });
+ }
+ } else {
+ throw context.parseException(
+ 'Expected a JSON object literal (map)', json);
+ }
+ }
+}
+
+abstract class ValueMixin implements GeneratedMessage {
+ bool hasNullValue();
+ ProtobufEnum get nullValue;
+ set nullValue(covariant ProtobufEnum value);
+ bool hasNumberValue();
+ double get numberValue;
+ set numberValue(double v);
+ bool hasStringValue();
+ String get stringValue;
+ set stringValue(String v);
+ bool hasBoolValue();
+ bool get boolValue;
+ set boolValue(bool v);
+ bool hasStructValue();
+ StructMixin get structValue;
+ set structValue(covariant StructMixin v);
+ bool hasListValue();
+ ListValueMixin get listValue;
+ set listValue(covariant ListValueMixin v);
+
+ // From google/protobuf/struct.proto:
+ // The JSON representation for `Value` is JSON value
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ ValueMixin value = message as ValueMixin;
+ // This would ideally be a switch, but we cannot import the enum we are
+ // switching over.
+ if (value.hasNullValue()) {
+ return null;
+ } else if (value.hasNumberValue()) {
+ return value.numberValue;
+ } else if (value.hasStringValue()) {
+ return value.stringValue;
+ } else if (value.hasBoolValue()) {
+ return value.boolValue;
+ } else if (value.hasStructValue()) {
+ return StructMixin.toProto3JsonHelper(value.structValue, typeRegistry);
+ } else if (value.hasListValue()) {
+ return ListValueMixin.toProto3JsonHelper(value.listValue, typeRegistry);
+ } else {
+ throw ArgumentError('Serializing google.protobuf.Value with no value');
+ }
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ ValueMixin value = message as ValueMixin;
+ if (json == null) {
+ // Rely on the getter retrieving the default to provide an instance.
+ value.nullValue = value.nullValue;
+ } else if (json is num) {
+ value.numberValue = json.toDouble();
+ } else if (json is String) {
+ value.stringValue = json;
+ } else if (json is bool) {
+ value.boolValue = json;
+ } else if (json is Map) {
+ // Clone because the default instance is frozen.
+ StructMixin structValue = value.structValue.clone();
+ StructMixin.fromProto3JsonHelper(
+ structValue, json, typeRegistry, context);
+ value.structValue = structValue;
+ } else if (json is List) {
+ // Clone because the default instance is frozen.
+ ListValueMixin listValue = value.listValue.clone();
+ ListValueMixin.fromProto3JsonHelper(
+ listValue, json, typeRegistry, context);
+ value.listValue = listValue;
+ } else {
+ throw context.parseException(
+ 'Expected a json-value (Map, List, String, number, bool or null)',
+ json);
+ }
+ }
+}
+
+abstract class ListValueMixin implements GeneratedMessage {
+ List<ValueMixin> get values;
+
+ // From google/protobuf/struct.proto:
+ // The JSON representation for `ListValue` is JSON array.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ ListValueMixin list = message as ListValueMixin;
+ return list.values
+ .map((value) => ValueMixin.toProto3JsonHelper(value, typeRegistry))
+ .toList();
+ }
+
+ static const _valueFieldTagNumber = 1;
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ ListValueMixin list = message as ListValueMixin;
+ if (json is List) {
+ CreateBuilderFunc subBuilder =
+ message.info_.subBuilder(_valueFieldTagNumber);
+ for (int i = 0; i < json.length; i++) {
+ Object element = json[i];
+ ValueMixin v = subBuilder();
+ context.addListIndex(i);
+ ValueMixin.fromProto3JsonHelper(v, element, typeRegistry, context);
+ context.popIndex();
+ list.values.add(v);
+ }
+ } else {
+ throw context.parseException('Expected a json-List', json);
+ }
+ }
+}
+
+abstract class FieldMaskMixin {
+ List<String> get paths;
+
+ // From google/protobuf/field_mask.proto:
+ // # JSON Encoding of Field Masks
+ //
+ // In JSON, a field mask is encoded as a single string where paths are
+ // separated by a comma. Fields name in each path are converted
+ // to/from lower-camel naming conventions.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ FieldMaskMixin fieldMask = message as FieldMaskMixin;
+ for (String path in fieldMask.paths) {
+ if (path.indexOf(RegExp('[A-Z]|_[^a-z]')) != -1) {
+ throw ArgumentError(
+ 'Bad fieldmask $path. Does not round-trip to json.');
+ }
+ }
+ return fieldMask.paths.map(_toCamelCase).join(',');
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is String) {
+ if (json.indexOf('_') != -1) {
+ throw context.parseException(
+ 'Invalid Character `_` in FieldMask', json);
+ }
+ if (json == '') {
+ // The empty string splits to a single value. So this is a special case.
+ return;
+ }
+ (message as FieldMaskMixin).paths
+ ..addAll(json.split(',').map(_fromCamelCase));
+ } else {
+ throw context.parseException(
+ 'Expected String formatted as FieldMask', json);
+ }
+ }
+
+ static String _toCamelCase(String name) {
+ return name.replaceAllMapped(
+ RegExp('_([a-z])'), (Match m) => '${m.group(1).toUpperCase()}');
+ }
+
+ static String _fromCamelCase(String name) {
+ return name.replaceAllMapped(
+ RegExp('[A-Z]'), (Match m) => '_${m.group(0).toLowerCase()}');
+ }
+}
+
+abstract class DoubleValueMixin {
+ double get value;
+ set value(double value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `DoubleValue` is JSON number.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as DoubleValueMixin).value;
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is num) {
+ (message as DoubleValueMixin).value = json.toDouble();
+ } else if (json is String) {
+ (message as DoubleValueMixin).value = double.tryParse(json) ??
+ (throw context.parseException(
+ 'Expected string to encode a double', json));
+ } else {
+ throw context.parseException(
+ 'Expected a double as a String or number', json);
+ }
+ }
+}
+
+abstract class FloatValueMixin {
+ double get value;
+ set value(double value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `FloatValue` is JSON number.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as FloatValueMixin).value;
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is num) {
+ (message as FloatValueMixin).value = json.toDouble();
+ } else if (json is String) {
+ (message as FloatValueMixin).value = double.tryParse(json) ??
+ (throw context.parseException(
+ 'Expected a float as a String or number', json));
+ } else {
+ throw context.parseException(
+ 'Expected a float as a String or number', json);
+ }
+ }
+}
+
+abstract class Int64ValueMixin {
+ Int64 get value;
+ set value(Int64 value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `Int64Value` is JSON string.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as Int64ValueMixin).value.toString();
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is int) {
+ (message as Int64ValueMixin).value = Int64(json);
+ } else if (json is String) {
+ try {
+ (message as Int64ValueMixin).value = Int64.parseInt(json);
+ } on FormatException {
+ throw context.parseException('Expected string to encode integer', json);
+ }
+ } else {
+ throw context.parseException(
+ 'Expected an integer encoded as a String or number', json);
+ }
+ }
+}
+
+abstract class UInt64ValueMixin {
+ Int64 get value;
+ set value(Int64 value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `UInt64Value` is JSON string.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as UInt64ValueMixin).value.toStringUnsigned();
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is int) {
+ (message as UInt64ValueMixin).value = Int64(json);
+ } else if (json is String) {
+ try {
+ (message as UInt64ValueMixin).value = Int64.parseInt(json);
+ } on FormatException {
+ throw context.parseException(
+ 'Expected string to encode unsigned integer', json);
+ }
+ } else {
+ throw context.parseException(
+ 'Expected an unsigned integer as a String or integer', json);
+ }
+ }
+}
+
+abstract class Int32ValueMixin {
+ int get value;
+ set value(int value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `Int32Value` is JSON number.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as Int32ValueMixin).value;
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is int) {
+ (message as Int32ValueMixin).value = json;
+ } else if (json is String) {
+ (message as Int32ValueMixin).value = int.tryParse(json) ??
+ (throw context.parseException(
+ 'Expected string to encode integer', json));
+ } else {
+ throw context.parseException(
+ 'Expected an integer encoded as a String or number', json);
+ }
+ }
+}
+
+abstract class UInt32ValueMixin {
+ int get value;
+ set value(int value);
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as UInt32ValueMixin).value;
+ }
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `UInt32Value` is JSON number.
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is int) {
+ (message as UInt32ValueMixin).value = json;
+ } else if (json is String) {
+ (message as UInt32ValueMixin).value = int.tryParse(json) ??
+ (throw context.parseException(
+ 'Expected String to encode an integer', json));
+ } else {
+ throw context.parseException(
+ 'Expected an unsigned integer as a String or integer', json);
+ }
+ }
+}
+
+abstract class BoolValueMixin {
+ bool get value;
+ set value(bool value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `BoolValue` is JSON `true` and `false`
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as BoolValueMixin).value;
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is bool) {
+ (message as BoolValueMixin).value = json;
+ } else {
+ throw context.parseException('Expected a bool', json);
+ }
+ }
+}
+
+abstract class StringValueMixin {
+ String get value;
+ set value(String value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `StringValue` is JSON string.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return (message as StringValueMixin).value;
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is String) {
+ (message as StringValueMixin).value = json;
+ } else {
+ throw context.parseException('Expected a String', json);
+ }
+ }
+}
+
+abstract class BytesValueMixin {
+ List<int> get value;
+ set value(List<int> value);
+
+ // From google/protobuf/wrappers.proto:
+ // The JSON representation for `BytesValue` is JSON string.
+ static Object toProto3JsonHelper(
+ GeneratedMessage message, TypeRegistry typeRegistry) {
+ return base64.encode((message as BytesValueMixin).value);
+ }
+
+ static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+ TypeRegistry typeRegistry, JsonParsingContext context) {
+ if (json is String) {
+ try {
+ (message as BytesValueMixin).value = base64.decode(json);
+ } on FormatException {
+ throw context.parseException(
+ 'Expected bytes encoded as base64 String', json);
+ }
+ } else {
+ throw context.parseException(
+ 'Expected bytes encoded as base64 String', json);
+ }
+ }
}
diff --git a/protobuf/lib/src/protobuf/proto3_json.dart b/protobuf/lib/src/protobuf/proto3_json.dart
new file mode 100644
index 0000000..d3bae84
--- /dev/null
+++ b/protobuf/lib/src/protobuf/proto3_json.dart
@@ -0,0 +1,402 @@
+// Copyright (c) 2019, 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 protobuf;
+
+Object _writeToProto3Json(_FieldSet fs, TypeRegistry typeRegistry) {
+ String convertToMapKey(dynamic key, int keyType) {
+ int baseType = PbFieldType._baseType(keyType);
+
+ assert(!_isRepeated(keyType));
+
+ switch (baseType) {
+ case PbFieldType._BOOL_BIT:
+ return key ? 'true' : 'false';
+ case PbFieldType._STRING_BIT:
+ return key;
+ case PbFieldType._UINT64_BIT:
+ return (key as Int64).toStringUnsigned();
+ case PbFieldType._INT32_BIT:
+ case PbFieldType._SINT32_BIT:
+ case PbFieldType._UINT32_BIT:
+ case PbFieldType._FIXED32_BIT:
+ case PbFieldType._SFIXED32_BIT:
+ case PbFieldType._INT64_BIT:
+ case PbFieldType._SINT64_BIT:
+ case PbFieldType._SFIXED64_BIT:
+ case PbFieldType._FIXED64_BIT:
+ return key.toString();
+ default:
+ throw StateError('Not a valid key type $keyType');
+ }
+ }
+
+ Object valueToProto3Json(dynamic fieldValue, int fieldType) {
+ if (fieldValue == null) return null;
+
+ if (_isGroupOrMessage(fieldType)) {
+ return _writeToProto3Json(
+ (fieldValue as GeneratedMessage)._fieldSet, typeRegistry);
+ } else if (_isEnum(fieldType)) {
+ return (fieldValue as ProtobufEnum).name;
+ } else {
+ int baseType = PbFieldType._baseType(fieldType);
+ switch (baseType) {
+ case PbFieldType._BOOL_BIT:
+ return fieldValue ? true : false;
+ case PbFieldType._STRING_BIT:
+ return fieldValue;
+ case PbFieldType._INT32_BIT:
+ case PbFieldType._SINT32_BIT:
+ case PbFieldType._UINT32_BIT:
+ case PbFieldType._FIXED32_BIT:
+ case PbFieldType._SFIXED32_BIT:
+ return fieldValue;
+ case PbFieldType._INT64_BIT:
+ case PbFieldType._SINT64_BIT:
+ case PbFieldType._SFIXED64_BIT:
+ case PbFieldType._FIXED64_BIT:
+ return fieldValue.toString();
+ case PbFieldType._FLOAT_BIT:
+ case PbFieldType._DOUBLE_BIT:
+ double value = fieldValue;
+ if (value.isNaN) return 'NaN';
+ if (value.isInfinite) {
+ if (value.isNegative) {
+ return '-Infinity';
+ } else {
+ return 'Infinity';
+ }
+ }
+ return value;
+ case PbFieldType._UINT64_BIT:
+ return (fieldValue as Int64).toStringUnsigned();
+ case PbFieldType._BYTES_BIT:
+ return base64Encode(fieldValue);
+ default:
+ throw StateError(
+ 'Invariant violation: unexpected value type $fieldType');
+ }
+ }
+ }
+
+ if (fs._meta.toProto3Json != null) {
+ return fs._meta.toProto3Json(fs._message, typeRegistry);
+ }
+
+ Map<String, dynamic> result = <String, dynamic>{};
+ for (FieldInfo fieldInfo in fs._infosSortedByTag) {
+ var value = fs._values[fieldInfo.index];
+ if (value == null || (value is List && value.isEmpty)) {
+ continue; // It's missing, repeated, or an empty byte array.
+ }
+ dynamic jsonValue;
+ if (fieldInfo.isMapField) {
+ jsonValue = (value as PbMap).map((key, entryValue) {
+ MapFieldInfo mapEntryInfo = fieldInfo as MapFieldInfo;
+ return MapEntry(convertToMapKey(key, mapEntryInfo.keyFieldType),
+ valueToProto3Json(entryValue, mapEntryInfo.valueFieldType));
+ });
+ } else if (fieldInfo.isRepeated) {
+ jsonValue = (value as PbList)
+ .map((element) => valueToProto3Json(element, fieldInfo.type))
+ .toList();
+ } else {
+ jsonValue = valueToProto3Json(value, fieldInfo.type);
+ }
+ result[fieldInfo.name] = jsonValue;
+ }
+ // Extensions and unknown fields are not encoded by proto3 JSON.
+ return result;
+}
+
+void _mergeFromProto3Json(
+ Object json,
+ _FieldSet fieldSet,
+ TypeRegistry typeRegistry,
+ bool ignoreUnknownFields,
+ bool supportNamesWithUnderscores) {
+ JsonParsingContext context =
+ JsonParsingContext(ignoreUnknownFields, supportNamesWithUnderscores);
+
+ void recursionHelper(Object json, _FieldSet fieldSet) {
+ int tryParse32Bit(String s) {
+ return int.tryParse(s) ??
+ (throw context.parseException('expected integer', s));
+ }
+
+ int check32BitSigned(int n) {
+ if (n < -2147483648 || n > 2147483647) {
+ throw context.parseException('expected 32 bit unsigned integer', n);
+ }
+ return n;
+ }
+
+ int check32BitUnsigned(int n) {
+ if (n < 0 || n > 0xFFFFFFFF) {
+ throw context.parseException('expected 32 bit unsigned integer', n);
+ }
+ return n;
+ }
+
+ Int64 tryParse64Bit(String s) {
+ Int64 result;
+ try {
+ result = Int64.parseInt(s);
+ } on FormatException {
+ throw context.parseException('expected integer', json);
+ }
+ return result;
+ }
+
+ Object convertProto3JsonValue(Object value, FieldInfo fieldInfo) {
+ if (value == null) {
+ return fieldInfo.makeDefault();
+ }
+ int fieldType = fieldInfo.type;
+ switch (PbFieldType._baseType(fieldType)) {
+ case PbFieldType._BOOL_BIT:
+ if (value is bool) {
+ return value;
+ }
+ throw context.parseException('Expected bool value', json);
+ case PbFieldType._BYTES_BIT:
+ if (value is String) {
+ Uint8List result;
+ try {
+ result = base64Decode(value);
+ } on FormatException {
+ throw context.parseException(
+ 'Expected bytes encoded as base64 String', json);
+ }
+ return result;
+ }
+ throw context.parseException(
+ 'Expected bytes encoded as base64 String', value);
+ case PbFieldType._STRING_BIT:
+ if (value is String) {
+ return value;
+ }
+ throw context.parseException('Expected String value', value);
+ case PbFieldType._FLOAT_BIT:
+ case PbFieldType._DOUBLE_BIT:
+ if (value is double) {
+ return value;
+ } else if (value is num) {
+ return value.toDouble();
+ } else if (value is String) {
+ return double.tryParse(value) ??
+ (throw context.parseException(
+ 'Expected String to encode a double', value));
+ }
+ throw context.parseException(
+ 'Expected a double represented as a String or number', value);
+ case PbFieldType._ENUM_BIT:
+ if (value is String) {
+ // TODO(sigurdm): Do we want to avoid linear search here? Measure...
+ return fieldInfo.enumValues.firstWhere((e) => e.name == value,
+ orElse: () =>
+ throw context.parseException('Unknown enum value', value));
+ } else if (value is int) {
+ return fieldInfo.valueOf(value) ??
+ (throw context.parseException('Unknown enum value', value));
+ }
+ throw context.parseException(
+ 'Expected enum as a string or integer', value);
+ case PbFieldType._UINT32_BIT:
+ int result;
+ if (value is int) {
+ result = value;
+ } else if (value is String) {
+ result = tryParse32Bit(value);
+ } else {
+ throw context.parseException(
+ 'Expected int or stringified int', value);
+ }
+ return check32BitUnsigned(result);
+ case PbFieldType._INT32_BIT:
+ case PbFieldType._SINT32_BIT:
+ case PbFieldType._UINT32_BIT:
+ case PbFieldType._FIXED32_BIT:
+ case PbFieldType._SFIXED32_BIT:
+ int result;
+ if (value is int) {
+ result = value;
+ } else if (value is String) {
+ result = tryParse32Bit(value);
+ } else {
+ throw context.parseException(
+ 'Expected int or stringified int', value);
+ }
+ check32BitSigned(result);
+ return result;
+ case PbFieldType._UINT64_BIT:
+ Int64 result;
+ if (value is int) {
+ result = Int64(value);
+ } else if (value is String) {
+ result = tryParse64Bit(value);
+ } else {
+ throw context.parseException(
+ 'Expected int or stringified int', value);
+ }
+ return result;
+ case PbFieldType._INT64_BIT:
+ case PbFieldType._SINT64_BIT:
+ case PbFieldType._FIXED64_BIT:
+ case PbFieldType._SFIXED64_BIT:
+ if (value is int) return Int64(value);
+ if (value is String) {
+ Int64 result;
+ try {
+ result = Int64.parseInt(value);
+ } on FormatException {
+ throw context.parseException(
+ 'Expected int or stringified int', value);
+ }
+ return result;
+ }
+ throw context.parseException(
+ 'Expected int or stringified int', value);
+ case PbFieldType._GROUP_BIT:
+ case PbFieldType._MESSAGE_BIT:
+ GeneratedMessage subMessage = fieldInfo.subBuilder();
+ recursionHelper(value, subMessage._fieldSet);
+ return subMessage;
+ default:
+ throw StateError('Unknown type $fieldType');
+ }
+ }
+
+ Object decodeMapKey(String key, int fieldType) {
+ switch (PbFieldType._baseType(fieldType)) {
+ case PbFieldType._BOOL_BIT:
+ switch (key) {
+ case 'true':
+ return true;
+ case 'false':
+ return false;
+ default:
+ throw context.parseException(
+ 'Wrong boolean key, should be one of ("true", "false")', key);
+ }
+ // ignore: dead_code
+ throw StateError('(Should have been) unreachable statement');
+ case PbFieldType._STRING_BIT:
+ return key;
+ case PbFieldType._UINT64_BIT:
+ // TODO(sigurdm): We do not throw on negative values here.
+ // That would probably require going via bignum.
+ return tryParse64Bit(key);
+ case PbFieldType._INT64_BIT:
+ case PbFieldType._SINT64_BIT:
+ case PbFieldType._SFIXED64_BIT:
+ case PbFieldType._FIXED64_BIT:
+ return tryParse64Bit(key);
+ case PbFieldType._INT32_BIT:
+ case PbFieldType._SINT32_BIT:
+ case PbFieldType._FIXED32_BIT:
+ case PbFieldType._SFIXED32_BIT:
+ return check32BitSigned(tryParse32Bit(key));
+ case PbFieldType._UINT32_BIT:
+ return check32BitUnsigned(tryParse32Bit(key));
+ default:
+ throw StateError('Not a valid key type $fieldType');
+ }
+ }
+
+ if (json == null) {
+ // `null` represents the default value. Do nothing more.
+ return;
+ }
+
+ BuilderInfo info = fieldSet._meta;
+
+ final wellKnownConverter = info.fromProto3Json;
+ if (wellKnownConverter != null) {
+ wellKnownConverter(fieldSet._message, json, typeRegistry, context);
+ } else {
+ if (json is Map) {
+ Map<String, FieldInfo> byName = info.byName;
+
+ json.forEach((key, value) {
+ if (key is! String) {
+ throw context.parseException('Key was not a String', key);
+ }
+ context.addMapIndex(key);
+
+ FieldInfo fieldInfo = byName[key];
+ if (fieldInfo == null && supportNamesWithUnderscores) {
+ // We don't optimize for field names with underscores, instead do a
+ // linear search for the index.
+ fieldInfo = byName.values.firstWhere(
+ (FieldInfo info) => info.protoName == key,
+ orElse: () => null);
+ }
+ if (fieldInfo == null) {
+ if (ignoreUnknownFields) {
+ return;
+ } else {
+ throw context.parseException('Unknown field name \'$key\'', key);
+ }
+ }
+
+ if (_isMapField(fieldInfo.type)) {
+ if (value is Map) {
+ MapFieldInfo mapFieldInfo = fieldInfo;
+ Map fieldValues = fieldSet._ensureMapField(fieldInfo);
+ value.forEach((subKey, subValue) {
+ if (subKey is! String) {
+ throw context.parseException('Expected a String key', subKey);
+ }
+ context.addMapIndex(subKey);
+ final result = fieldValues[
+ decodeMapKey(subKey, mapFieldInfo.keyFieldType)] =
+ convertProto3JsonValue(
+ subValue, mapFieldInfo.valueFieldInfo);
+ context.popIndex();
+ return result;
+ });
+ } else {
+ throw context.parseException('Expected a map', value);
+ }
+ } else if (_isRepeated(fieldInfo.type)) {
+ if (value == null) {
+ // `null` is accepted as the empty list [].
+ fieldSet._ensureRepeatedField(fieldInfo);
+ } else if (value is List) {
+ List values = fieldSet._ensureRepeatedField(fieldInfo);
+ for (int i = 0; i < value.length; i++) {
+ final entry = value[i];
+ context.addListIndex(i);
+ values.add(convertProto3JsonValue(entry, fieldInfo));
+ context.popIndex();
+ }
+ } else {
+ throw context.parseException('Expected a list', value);
+ }
+ } else if (_isGroupOrMessage(fieldInfo.type)) {
+ // TODO(sigurdm) consider a cleaner separation between parsing and merging.
+ GeneratedMessage parsedSubMessage =
+ convertProto3JsonValue(value, fieldInfo);
+ GeneratedMessage original = fieldSet._values[fieldInfo.index];
+ if (original == null) {
+ fieldSet._values[fieldInfo.index] = parsedSubMessage;
+ } else {
+ original.mergeFromMessage(parsedSubMessage);
+ }
+ } else {
+ fieldSet._setFieldUnchecked(
+ fieldInfo, convertProto3JsonValue(value, fieldInfo));
+ }
+ context.popIndex();
+ });
+ } else {
+ throw context.parseException('Expected JSON object', json);
+ }
+ }
+ }
+
+ recursionHelper(json, fieldSet);
+}
diff --git a/protobuf/lib/src/protobuf/type_registry.dart b/protobuf/lib/src/protobuf/type_registry.dart
new file mode 100644
index 0000000..8d73952
--- /dev/null
+++ b/protobuf/lib/src/protobuf/type_registry.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, 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.
+
+import '../../protobuf.dart';
+
+/// A TypeRegistry is used to resolve Any messages in the proto3 JSON conversion.
+///
+/// You must provide a TypeRegistry containing all message types used in
+/// Any message fields, or the JSON conversion will fail because data
+/// in Any message fields is unrecognizable. You don't need to supply a
+/// TypeRegistry if you don't use Any message fields.
+class TypeRegistry {
+ final Map<String, BuilderInfo> _mapping;
+
+ /// Constructs a new TypeRegistry recognizing the given types of messages.
+ ///
+ /// You can use an empty message of the given type to represent the type. Eg:
+ ///
+ /// ```dart
+ /// TypeRegistry([Foo(), Bar()]);
+ /// ```
+ TypeRegistry(Iterable<GeneratedMessage> types)
+ : _mapping = Map.fromEntries(types.map((message) =>
+ MapEntry(message.info_.qualifiedMessageName, message.info_)));
+
+ const TypeRegistry.empty() : _mapping = const {};
+
+ BuilderInfo lookup(String qualifiedName) {
+ return _mapping[qualifiedName];
+ }
+}
diff --git a/protoc_plugin/Makefile b/protoc_plugin/Makefile
index 5c42682..5338686 100644
--- a/protoc_plugin/Makefile
+++ b/protoc_plugin/Makefile
@@ -12,10 +12,19 @@
TEST_PROTO_LIST = \
_leading_underscores \
google/protobuf/any \
+ google/protobuf/api \
+ google/protobuf/duration \
+ google/protobuf/empty \
+ google/protobuf/field_mask \
+ google/protobuf/source_context \
+ google/protobuf/struct \
google/protobuf/timestamp \
+ google/protobuf/type \
google/protobuf/unittest_import \
google/protobuf/unittest_optimize_for \
+ google/protobuf/unittest_well_known_types \
google/protobuf/unittest \
+ google/protobuf/wrappers \
dart_name \
enum_extension \
extend_unittest \
diff --git a/protoc_plugin/lib/message_generator.dart b/protoc_plugin/lib/message_generator.dart
index e09566b..1c76f4f 100644
--- a/protoc_plugin/lib/message_generator.dart
+++ b/protoc_plugin/lib/message_generator.dart
@@ -291,6 +291,10 @@
String packageClause = package == ''
? ''
: ', package: const $_protobufImportPrefix.PackageName(\'$package\')';
+ String proto3JsonClause = (mixin?.hasProto3JsonHelpers ?? false)
+ ? ', toProto3Json: $_mixinImportPrefix.${mixin.name}.toProto3JsonHelper, '
+ 'fromProto3Json: $_mixinImportPrefix.${mixin.name}.fromProto3JsonHelper'
+ : '';
out.addAnnotatedBlock(
'class ${classname} extends $_protobufImportPrefix.GeneratedMessage${mixinClause} {',
'}', [
@@ -312,7 +316,9 @@
}
out.addBlock(
'static final $_protobufImportPrefix.BuilderInfo _i = '
- '$_protobufImportPrefix.BuilderInfo(\'${messageName}\'$packageClause)',
+ '$_protobufImportPrefix.BuilderInfo(\'${messageName}\'$packageClause'
+ ', createEmptyInstance: create'
+ '$proto3JsonClause)',
';', () {
for (int oneof = 0; oneof < _oneofFields.length; oneof++) {
List<int> tags =
diff --git a/protoc_plugin/lib/mixins.dart b/protoc_plugin/lib/mixins.dart
index 06b9dc6..151e351 100644
--- a/protoc_plugin/lib/mixins.dart
+++ b/protoc_plugin/lib/mixins.dart
@@ -34,13 +34,23 @@
/// May be null if the mixin doesn't reserve any new names.
final List<String> reservedNames;
- /// Code to inject into the class using the mixin.
+ /// Code to inject into the class using the mixin.
///
/// Typically used for static helpers since you cannot mix in static members.
final List<String> injectedHelpers;
- const PbMixin(this.name,
- {this.importFrom, this.parent, this.reservedNames, this.injectedHelpers});
+ /// If `True` the mixin should have static methods for converting to and from
+ /// proto3 Json.
+ final bool hasProto3JsonHelpers;
+
+ const PbMixin(
+ this.name, {
+ this.importFrom,
+ this.parent,
+ this.reservedNames,
+ this.injectedHelpers,
+ this.hasProto3JsonHelpers = false,
+ });
/// Returns the mixin and its ancestors, in the order they should be applied.
Iterable<PbMixin> findMixinsToApply() {
diff --git a/protoc_plugin/lib/well_known_types.dart b/protoc_plugin/lib/well_known_types.dart
index 9ac1829..787f6b5 100644
--- a/protoc_plugin/lib/well_known_types.dart
+++ b/protoc_plugin/lib/well_known_types.dart
@@ -7,9 +7,12 @@
PbMixin wellKnownMixinForFullName(String qualifiedName) =>
_wellKnownMixins[qualifiedName];
+const _wellKnownImportPath =
+ 'package:protobuf/src/protobuf/mixins/well_known.dart';
+
const _wellKnownMixins = {
'google.protobuf.Any': PbMixin('AnyMixin',
- importFrom: 'package:protobuf/src/protobuf/mixins/well_known.dart',
+ importFrom: _wellKnownImportPath,
injectedHelpers: [
'''
/// Creates a new [Any] encoding [message].
@@ -23,9 +26,10 @@
typeUrlPrefix: typeUrlPrefix);
return result;
}'''
- ]),
+ ],
+ hasProto3JsonHelpers: true),
'google.protobuf.Timestamp': PbMixin('TimestampMixin',
- importFrom: 'package:protobuf/src/protobuf/mixins/well_known.dart',
+ importFrom: _wellKnownImportPath,
injectedHelpers: [
'''
/// Creates a new instance from [dateTime].
@@ -36,5 +40,76 @@
$_mixinImportPrefix.TimestampMixin.setFromDateTime(result, dateTime);
return result;
}'''
- ]),
+ ],
+ hasProto3JsonHelpers: true),
+ 'google.protobuf.Duration': PbMixin(
+ 'DurationMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.Struct': PbMixin(
+ 'StructMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.Value': PbMixin(
+ 'ValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.ListValue': PbMixin(
+ 'ListValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.DoubleValue': PbMixin(
+ 'DoubleValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.FloatValue': PbMixin(
+ 'FloatValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.Int64Value': PbMixin(
+ 'Int64ValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.UInt64Value': PbMixin(
+ 'UInt64ValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.Int32Value': PbMixin(
+ 'Int32ValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.UInt32Value': PbMixin(
+ 'UInt32ValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.BoolValue': PbMixin(
+ 'BoolValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.StringValue': PbMixin(
+ 'StringValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.BytesValue': PbMixin(
+ 'BytesValueMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ ),
+ 'google.protobuf.FieldMask': PbMixin(
+ 'FieldMaskMixin',
+ importFrom: _wellKnownImportPath,
+ hasProto3JsonHelpers: true,
+ )
};
diff --git a/protoc_plugin/test/goldens/grpc_service.pb b/protoc_plugin/test/goldens/grpc_service.pb
index 9954f5a..d46c0b1 100644
--- a/protoc_plugin/test/goldens/grpc_service.pb
+++ b/protoc_plugin/test/goldens/grpc_service.pb
@@ -10,7 +10,7 @@
import 'package:protobuf/protobuf.dart' as $pb;
class Empty extends $pb.GeneratedMessage {
- static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty')
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty', createEmptyInstance: create)
..hasRequiredFields = false
;
diff --git a/protoc_plugin/test/goldens/imports.pb b/protoc_plugin/test/goldens/imports.pb
index 27b8265..98fd25c 100644
--- a/protoc_plugin/test/goldens/imports.pb
+++ b/protoc_plugin/test/goldens/imports.pb
@@ -13,7 +13,7 @@
import 'package2.pb.dart' as $2;
class M extends $pb.GeneratedMessage {
- static final $pb.BuilderInfo _i = $pb.BuilderInfo('M')
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('M', createEmptyInstance: create)
..a<M>(1, 'm', $pb.PbFieldType.OM, defaultOrMaker: M.getDefault, subBuilder: M.create)
..a<$1.M>(2, 'm1', $pb.PbFieldType.OM, defaultOrMaker: $1.M.getDefault, subBuilder: $1.M.create)
..a<$2.M>(3, 'm2', $pb.PbFieldType.OM, defaultOrMaker: $2.M.getDefault, subBuilder: $2.M.create)
diff --git a/protoc_plugin/test/goldens/messageGenerator b/protoc_plugin/test/goldens/messageGenerator
index 7a80061..abc7f36 100644
--- a/protoc_plugin/test/goldens/messageGenerator
+++ b/protoc_plugin/test/goldens/messageGenerator
@@ -1,5 +1,5 @@
class PhoneNumber extends $pb.GeneratedMessage {
- static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber')
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber', createEmptyInstance: create)
..aQS(1, 'number')
..e<PhoneNumber_PhoneType>(2, 'type', $pb.PbFieldType.OE, defaultOrMaker: PhoneNumber_PhoneType.MOBILE, valueOf: PhoneNumber_PhoneType.valueOf, enumValues: PhoneNumber_PhoneType.values)
..a<$core.String>(3, 'name', $pb.PbFieldType.OS, defaultOrMaker: '\$')
diff --git a/protoc_plugin/test/goldens/messageGenerator.meta b/protoc_plugin/test/goldens/messageGenerator.meta
index 3590367..03d3aaa 100644
--- a/protoc_plugin/test/goldens/messageGenerator.meta
+++ b/protoc_plugin/test/goldens/messageGenerator.meta
@@ -9,8 +9,8 @@
path: 4
path: 0
sourceFile:
- begin: 443
- end: 454
+ begin: 472
+ end: 483
}
annotation: {
path: 4
@@ -18,8 +18,8 @@
path: 2
path: 1
sourceFile:
- begin: 1371
- end: 1377
+ begin: 1400
+ end: 1406
}
annotation: {
path: 4
@@ -27,8 +27,8 @@
path: 2
path: 1
sourceFile:
- begin: 1402
- end: 1408
+ begin: 1431
+ end: 1437
}
annotation: {
path: 4
@@ -36,8 +36,8 @@
path: 2
path: 1
sourceFile:
- begin: 1461
- end: 1470
+ begin: 1490
+ end: 1499
}
annotation: {
path: 4
@@ -45,8 +45,8 @@
path: 2
path: 1
sourceFile:
- begin: 1493
- end: 1504
+ begin: 1522
+ end: 1533
}
annotation: {
path: 4
@@ -54,8 +54,8 @@
path: 2
path: 0
sourceFile:
- begin: 1554
- end: 1558
+ begin: 1583
+ end: 1587
}
annotation: {
path: 4
@@ -63,8 +63,8 @@
path: 2
path: 0
sourceFile:
- begin: 1579
- end: 1583
+ begin: 1608
+ end: 1612
}
annotation: {
path: 4
@@ -72,8 +72,8 @@
path: 2
path: 0
sourceFile:
- begin: 1642
- end: 1649
+ begin: 1671
+ end: 1678
}
annotation: {
path: 4
@@ -81,8 +81,8 @@
path: 2
path: 0
sourceFile:
- begin: 1672
- end: 1681
+ begin: 1701
+ end: 1710
}
annotation: {
path: 4
@@ -90,8 +90,8 @@
path: 2
path: 2
sourceFile:
- begin: 1722
- end: 1726
+ begin: 1751
+ end: 1755
}
annotation: {
path: 4
@@ -99,8 +99,8 @@
path: 2
path: 2
sourceFile:
- begin: 1753
- end: 1757
+ begin: 1782
+ end: 1786
}
annotation: {
path: 4
@@ -108,8 +108,8 @@
path: 2
path: 2
sourceFile:
- begin: 1810
- end: 1817
+ begin: 1839
+ end: 1846
}
annotation: {
path: 4
@@ -117,8 +117,8 @@
path: 2
path: 2
sourceFile:
- begin: 1840
- end: 1849
+ begin: 1869
+ end: 1878
}
annotation: {
path: 4
@@ -126,8 +126,8 @@
path: 2
path: 3
sourceFile:
- begin: 1939
- end: 1954
+ begin: 1968
+ end: 1983
}
annotation: {
path: 4
@@ -135,8 +135,8 @@
path: 2
path: 3
sourceFile:
- begin: 2028
- end: 2043
+ begin: 2057
+ end: 2072
}
annotation: {
path: 4
@@ -144,8 +144,8 @@
path: 2
path: 3
sourceFile:
- begin: 2145
- end: 2163
+ begin: 2174
+ end: 2192
}
annotation: {
path: 4
@@ -153,6 +153,6 @@
path: 2
path: 3
sourceFile:
- begin: 2235
- end: 2255
+ begin: 2264
+ end: 2284
}
diff --git a/protoc_plugin/test/goldens/oneMessage.pb b/protoc_plugin/test/goldens/oneMessage.pb
index fe417ad..9886c38 100644
--- a/protoc_plugin/test/goldens/oneMessage.pb
+++ b/protoc_plugin/test/goldens/oneMessage.pb
@@ -10,7 +10,7 @@
import 'package:protobuf/protobuf.dart' as $pb;
class PhoneNumber extends $pb.GeneratedMessage {
- static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber')
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber', createEmptyInstance: create)
..aQS(1, 'number')
..a<$core.int>(2, 'type', $pb.PbFieldType.O3)
..a<$core.String>(3, 'name', $pb.PbFieldType.OS, defaultOrMaker: '\$')
diff --git a/protoc_plugin/test/goldens/oneMessage.pb.meta b/protoc_plugin/test/goldens/oneMessage.pb.meta
index daba01c..4e80e02 100644
--- a/protoc_plugin/test/goldens/oneMessage.pb.meta
+++ b/protoc_plugin/test/goldens/oneMessage.pb.meta
@@ -9,8 +9,8 @@
path: 4
path: 0
sourceFile: test
- begin: 564
- end: 575
+ begin: 593
+ end: 604
}
annotation: {
path: 4
@@ -18,8 +18,8 @@
path: 2
path: 0
sourceFile: test
- begin: 1492
- end: 1498
+ begin: 1521
+ end: 1527
}
annotation: {
path: 4
@@ -27,8 +27,8 @@
path: 2
path: 0
sourceFile: test
- begin: 1523
- end: 1529
+ begin: 1552
+ end: 1558
}
annotation: {
path: 4
@@ -36,8 +36,8 @@
path: 2
path: 0
sourceFile: test
- begin: 1582
- end: 1591
+ begin: 1611
+ end: 1620
}
annotation: {
path: 4
@@ -45,8 +45,8 @@
path: 2
path: 0
sourceFile: test
- begin: 1614
- end: 1625
+ begin: 1643
+ end: 1654
}
annotation: {
path: 4
@@ -54,8 +54,8 @@
path: 2
path: 1
sourceFile: test
- begin: 1663
- end: 1667
+ begin: 1692
+ end: 1696
}
annotation: {
path: 4
@@ -63,8 +63,8 @@
path: 2
path: 1
sourceFile: test
- begin: 1690
- end: 1694
+ begin: 1719
+ end: 1723
}
annotation: {
path: 4
@@ -72,8 +72,8 @@
path: 2
path: 1
sourceFile: test
- begin: 1749
- end: 1756
+ begin: 1778
+ end: 1785
}
annotation: {
path: 4
@@ -81,8 +81,8 @@
path: 2
path: 1
sourceFile: test
- begin: 1779
- end: 1788
+ begin: 1808
+ end: 1817
}
annotation: {
path: 4
@@ -90,8 +90,8 @@
path: 2
path: 2
sourceFile: test
- begin: 1829
- end: 1833
+ begin: 1858
+ end: 1862
}
annotation: {
path: 4
@@ -99,8 +99,8 @@
path: 2
path: 2
sourceFile: test
- begin: 1860
- end: 1864
+ begin: 1889
+ end: 1893
}
annotation: {
path: 4
@@ -108,8 +108,8 @@
path: 2
path: 2
sourceFile: test
- begin: 1917
- end: 1924
+ begin: 1946
+ end: 1953
}
annotation: {
path: 4
@@ -117,6 +117,6 @@
path: 2
path: 2
sourceFile: test
- begin: 1947
- end: 1956
+ begin: 1976
+ end: 1985
}
diff --git a/protoc_plugin/test/goldens/service.pb b/protoc_plugin/test/goldens/service.pb
index e719f50..fdaabe3 100644
--- a/protoc_plugin/test/goldens/service.pb
+++ b/protoc_plugin/test/goldens/service.pb
@@ -11,7 +11,7 @@
import 'package:protobuf/protobuf.dart' as $pb;
class Empty extends $pb.GeneratedMessage {
- static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty')
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty', createEmptyInstance: create)
..hasRequiredFields = false
;
diff --git a/protoc_plugin/test/proto3_json_test.dart b/protoc_plugin/test/proto3_json_test.dart
new file mode 100644
index 0000000..d9c1fa8
--- /dev/null
+++ b/protoc_plugin/test/proto3_json_test.dart
@@ -0,0 +1,981 @@
+#!/usr/bin/env dart
+// Copyright (c) 2019, 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.
+
+import 'dart:core' hide Duration;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:protobuf/protobuf.dart';
+import 'package:test/test.dart';
+
+import '../out/protos/google/protobuf/any.pb.dart';
+import '../out/protos/google/protobuf/duration.pb.dart';
+import '../out/protos/google/protobuf/empty.pb.dart';
+import '../out/protos/google/protobuf/field_mask.pb.dart';
+import '../out/protos/google/protobuf/struct.pb.dart';
+import '../out/protos/google/protobuf/timestamp.pb.dart';
+import '../out/protos/google/protobuf/unittest.pb.dart';
+
+import '../out/protos/google/protobuf/unittest_well_known_types.pb.dart';
+import '../out/protos/google/protobuf/wrappers.pb.dart';
+import '../out/protos/map_field.pb.dart';
+import 'test_util.dart';
+
+final testAllTypesJson = {
+ 'optionalInt32': 101,
+ 'optionalInt64': '102',
+ 'optionalUint32': 103,
+ 'optionalUint64': '104',
+ 'optionalSint32': 105,
+ 'optionalSint64': '106',
+ 'optionalFixed32': 107,
+ 'optionalFixed64': '108',
+ 'optionalSfixed32': 109,
+ 'optionalSfixed64': '110',
+ 'optionalFloat': 111.0,
+ 'optionalDouble': 112.0,
+ 'optionalBool': true,
+ 'optionalString': '115',
+ 'optionalBytes': 'MTE2',
+ 'optionalgroup': {'a': 117},
+ 'optionalNestedMessage': {'bb': 118},
+ 'optionalForeignMessage': {'c': 119},
+ 'optionalImportMessage': {'d': 120},
+ 'optionalNestedEnum': 'BAZ',
+ 'optionalForeignEnum': 'FOREIGN_BAZ',
+ 'optionalImportEnum': 'IMPORT_BAZ',
+ 'optionalStringPiece': '124',
+ 'optionalCord': '125',
+ 'repeatedInt32': [201, 301],
+ 'repeatedInt64': ['202', '302'],
+ 'repeatedUint32': [203, 303],
+ 'repeatedUint64': ['204', '304'],
+ 'repeatedSint32': [205, 305],
+ 'repeatedSint64': ['206', '306'],
+ 'repeatedFixed32': [207, 307],
+ 'repeatedFixed64': ['208', '308'],
+ 'repeatedSfixed32': [209, 309],
+ 'repeatedSfixed64': ['210', '310'],
+ 'repeatedFloat': [211.0, 311.0],
+ 'repeatedDouble': [212.0, 312.0],
+ 'repeatedBool': [true, false],
+ 'repeatedString': ['215', '315'],
+ 'repeatedBytes': ['MjE2', 'MzE2'],
+ 'repeatedgroup': [
+ {'a': 217},
+ {'a': 317}
+ ],
+ 'repeatedNestedMessage': [
+ {'bb': 218},
+ {'bb': 318}
+ ],
+ 'repeatedForeignMessage': [
+ {'c': 219},
+ {'c': 319}
+ ],
+ 'repeatedImportMessage': [
+ {'d': 220},
+ {'d': 320}
+ ],
+ 'repeatedNestedEnum': ['BAR', 'BAZ'],
+ 'repeatedForeignEnum': ['FOREIGN_BAR', 'FOREIGN_BAZ'],
+ 'repeatedImportEnum': ['IMPORT_BAR', 'IMPORT_BAZ'],
+ 'repeatedStringPiece': ['224', '324'],
+ 'repeatedCord': ['225', '325'],
+ 'defaultInt32': 401,
+ 'defaultInt64': '402',
+ 'defaultUint32': 403,
+ 'defaultUint64': '404',
+ 'defaultSint32': 405,
+ 'defaultSint64': '406',
+ 'defaultFixed32': 407,
+ 'defaultFixed64': '408',
+ 'defaultSfixed32': 409,
+ 'defaultSfixed64': '410',
+ 'defaultFloat': 411.0,
+ 'defaultDouble': 412.0,
+ 'defaultBool': false,
+ 'defaultString': '415',
+ 'defaultBytes': 'NDE2',
+ 'defaultNestedEnum': 'FOO',
+ 'defaultForeignEnum': 'FOREIGN_FOO',
+ 'defaultImportEnum': 'IMPORT_FOO',
+ 'defaultStringPiece': '424',
+ 'defaultCord': '425'
+};
+
+void main() {
+ group('encode', () {
+ test('testOutput', () {
+ expect(getAllSet().toProto3Json(), testAllTypesJson);
+ });
+
+ test('testUnsignedOutput', () {
+ TestAllTypes message = TestAllTypes();
+ // These values are selected because they are large enough to set the sign bit.
+ message.optionalUint64 = Int64.parseHex('f0000000ffff0000');
+ message.optionalFixed64 = Int64.parseHex('f0000000ffff0001');
+
+ expect(message.toProto3Json(), {
+ 'optionalUint64': '17293822573397606400',
+ 'optionalFixed64': '-1152921500311945215'
+ });
+ });
+
+ test('doubles', () {
+ void testValue(double value, Object expected) {
+ TestAllTypes message = TestAllTypes()
+ ..defaultFloat = value
+ ..defaultDouble = value;
+ expect(
+ (message.toProto3Json() as Map)['defaultDouble'], equals(expected));
+ }
+
+ testValue(-0.0, -0.0);
+ testValue(0.0, 0);
+ testValue(1.0, 1);
+ testValue(-1.0, -1);
+ testValue(double.nan, 'NaN');
+ testValue(double.infinity, 'Infinity');
+ testValue(double.negativeInfinity, '-Infinity');
+ });
+
+ test('map value', () {
+ TestMap message = TestMap()
+ ..int32ToInt32Field[32] = 32
+ ..int32ToStringField[0] = 'foo'
+ ..int32ToStringField[1] = 'bar'
+ ..int32ToBytesField[-1] = [1, 2, 3]
+ ..int32ToEnumField[1] = TestMap_EnumValue.BAZ
+ ..int32ToMessageField[21] = (TestMap_MessageValue()
+ ..value = 2
+ ..secondValue = 3)
+ ..stringToInt32Field['key'] = -1
+ ..uint32ToInt32Field[0] = 0
+ ..int64ToInt32Field[Int64.ZERO] = 0
+ ..int64ToInt32Field[Int64.ONE] = 1
+ ..int64ToInt32Field[-Int64.ONE] = -1
+ ..int64ToInt32Field[Int64.MIN_VALUE] = -2
+ ..int64ToInt32Field[Int64.MAX_VALUE] = 2
+ ..uint64ToInt32Field[Int64.MIN_VALUE] = -2;
+ expect(message.toProto3Json(), {
+ 'int32ToInt32Field': {'32': 32},
+ 'int32ToStringField': {'0': 'foo', '1': 'bar'},
+ 'int32ToBytesField': {'-1': 'AQID'},
+ 'int32ToEnumField': {'1': 'BAZ'},
+ 'int32ToMessageField': {
+ '21': {'value': 2, 'secondValue': 3}
+ },
+ 'stringToInt32Field': {'key': -1},
+ 'uint32ToInt32Field': {'0': 0},
+ 'int64ToInt32Field': {
+ '0': 0,
+ '1': 1,
+ '-1': -1,
+ '-9223372036854775808': -2,
+ '9223372036854775807': 2
+ },
+ 'uint64ToInt32Field': {'9223372036854775808': -2},
+ });
+ });
+
+ test('Timestamp', () {
+ expect(
+ Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17, 40))
+ .toProto3Json(),
+ '1969-07-20T20:17:40Z');
+ expect(Timestamp.fromDateTime(DateTime.utc(9)).toProto3Json(),
+ '0009-01-01T00:00:00Z');
+ expect(Timestamp.fromDateTime(DateTime.utc(420)).toProto3Json(),
+ '0420-01-01T00:00:00Z');
+ expect(() => Timestamp.fromDateTime(DateTime.utc(42001)).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ expect(() => Timestamp.fromDateTime(DateTime.utc(-1)).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ expect(
+ Timestamp.fromDateTime(DateTime.utc(9999, 12, 31, 23, 59, 59))
+ .toProto3Json(),
+ '9999-12-31T23:59:59Z');
+
+ expect((Timestamp()..nanos = 1).toProto3Json(),
+ '1970-01-01T00:00:00.000000001Z');
+ expect((Timestamp()..nanos = 8200000).toProto3Json(),
+ '1970-01-01T00:00:00.008200Z');
+ expect(() => (Timestamp()..nanos = -8200000).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ expect(() => (Timestamp()..nanos = -8200000).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ expect(() => (Timestamp()..nanos = 1000000000).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ });
+
+ test('Duration', () {
+ expect(
+ (Duration()
+ ..seconds = Int64(0)
+ ..nanos = 0)
+ .toProto3Json(),
+ '0s');
+ expect(
+ (Duration()
+ ..seconds = Int64(10)
+ ..nanos = 0)
+ .toProto3Json(),
+ '10s');
+ expect(
+ (Duration()
+ ..seconds = Int64(10)
+ ..nanos = 1)
+ .toProto3Json(),
+ '10.000000001s');
+ expect(
+ (Duration()
+ ..seconds = Int64(10)
+ ..nanos = 10)
+ .toProto3Json(),
+ '10.00000001s');
+ expect(
+ (Duration()
+ ..seconds = -Int64(1)
+ ..nanos = -99000)
+ .toProto3Json(),
+ '-1.000099s');
+ });
+
+ test('Any', () {
+ expect(
+ Any.pack(TestAllTypes()..optionalFixed64 = Int64(100))
+ .toProto3Json(typeRegistry: TypeRegistry([TestAllTypes()])),
+ {
+ '@type': 'type.googleapis.com/protobuf_unittest.TestAllTypes',
+ 'optionalFixed64': '100'
+ });
+ expect(
+ Any.pack(Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17)))
+ .toProto3Json(typeRegistry: TypeRegistry([Timestamp()])),
+ {
+ '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+ 'value': '1969-07-20T20:17:00Z'
+ });
+ expect(
+ () => Any.pack(Timestamp.fromDateTime(DateTime(1969, 7, 20, 20, 17)))
+ .toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ });
+
+ test('struct', () {
+ final s = Struct()
+ ..fields['null'] = (Value()..nullValue = NullValue.NULL_VALUE)
+ ..fields['number'] = (Value()..numberValue = 22.3)
+ ..fields['string'] = (Value()..stringValue = 'foo')
+ ..fields['bool'] = (Value()..boolValue = false)
+ ..fields['struct'] = (Value()
+ ..structValue =
+ (Struct()..fields['a'] = (Value()..numberValue = 0.0)))
+ ..fields['list'] = (Value()
+ ..listValue = (ListValue()
+ ..values.addAll([
+ Value()..structValue = Struct(),
+ Value()..listValue = ListValue(),
+ Value()..stringValue = 'why'
+ ])));
+ expect(s.toProto3Json(), {
+ 'null': null,
+ 'number': 22.3,
+ 'string': 'foo',
+ 'bool': false,
+ 'struct': {'a': 0},
+ 'list': [{}, [], 'why']
+ });
+ expect(
+ () => Value().toProto3Json(), throwsA(TypeMatcher<ArgumentError>()));
+ });
+
+ test('empty', () {
+ expect(Empty().toProto3Json(), {});
+ });
+
+ test('wrapper types', () {
+ final t = TestWellKnownTypes()
+ ..doubleField = (DoubleValue()..value = 10.01)
+ ..floatField = (FloatValue()..value = 3.0)
+ ..int64Field = (Int64Value()..value = Int64.MIN_VALUE)
+ ..uint64Field = (UInt64Value()..value = Int64.MIN_VALUE)
+ ..int32Field = (Int32Value()..value = 101)
+ ..uint32Field = (UInt32Value()..value = 102)
+ ..boolField = (BoolValue()..value = false)
+ ..stringField = (StringValue()..value = 'Pop')
+ ..bytesField = (BytesValue()..value = [8, 9, 10]);
+ expect(t.toProto3Json(), {
+ 'doubleField': 10.01,
+ 'floatField': 3,
+ 'int64Field': '-9223372036854775808',
+ 'uint64Field': '9223372036854775808',
+ 'int32Field': 101,
+ 'uint32Field': 102,
+ 'boolField': false,
+ 'stringField': 'Pop',
+ 'bytesField': 'CAkK',
+ });
+ });
+
+ test('field mask', () {
+ expect(FieldMask().toProto3Json(), '');
+ expect((FieldMask()..paths.addAll(['foo_bar_baz'])).toProto3Json(),
+ 'fooBarBaz');
+ expect((FieldMask()..paths.addAll(['foo_bar', 'zop'])).toProto3Json(),
+ 'fooBar,zop');
+ expect(() => (FieldMask()..paths.add('foo_3_bar')).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ expect(() => (FieldMask()..paths.add('foo__bar')).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ expect(() => (FieldMask()..paths.add('fooBar')).toProto3Json(),
+ throwsA(TypeMatcher<ArgumentError>()));
+ });
+ });
+
+ group('decode', () {
+ parseFailure(List<String> expectedPath) => throwsA(predicate((e) {
+ if (e is FormatException) {
+ final pathExpression =
+ RegExp(r'root(\["[^"]*"]*\])*').firstMatch(e.message)[0];
+ final actualPath = RegExp(r'\["([^"]*)"\]')
+ .allMatches(pathExpression)
+ .map((match) => match[1])
+ .toList();
+ if (actualPath.length != expectedPath.length) return false;
+ for (int i = 0; i < actualPath.length; i++) {
+ if (actualPath[i] != expectedPath[i]) return false;
+ }
+ return true;
+ }
+ return false;
+ }));
+
+ test('Nulls', () {
+ final decoded = TestAllTypes()
+ ..mergeFromProto3Json({'defaultString': null});
+ expect(decoded, TestAllTypes()..defaultString = 'hello');
+ });
+ test('decode TestAllTypes', () {
+ final decoded = TestAllTypes()..mergeFromProto3Json(testAllTypesJson);
+ expect(decoded, getAllSet());
+ });
+ test('Type expectations', () {
+ expect(
+ () => TestAllTypes()..mergeFromProto3Json({1: 1}), parseFailure([]));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalBool': 1}),
+ parseFailure(['optionalBool']));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalBytes': 1}),
+ parseFailure(['optionalBytes']));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalBytes': '()'}),
+ parseFailure(['optionalBytes']));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt32': '()'}),
+ parseFailure(['optionalInt32']));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt32': 20.4}),
+ parseFailure(['optionalInt32']));
+ expect(TestAllTypes()..mergeFromProto3Json({'optionalInt32': '28'}),
+ TestAllTypes()..optionalInt32 = 28);
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt64': '()'}),
+ parseFailure(['optionalInt64']));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt64': 20.4}),
+ parseFailure(['optionalInt64']));
+ expect(TestAllTypes()..mergeFromProto3Json({'optionalInt64': '28'}),
+ TestAllTypes()..optionalInt64 = Int64(28));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalDouble': 'a'}),
+ parseFailure(['optionalDouble']));
+ expect(TestAllTypes()..mergeFromProto3Json({'optionalDouble': 28}),
+ TestAllTypes()..optionalDouble = 28.0);
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalDouble': 'a'}),
+ parseFailure(['optionalDouble']));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalString': 11}),
+ parseFailure(['optionalString']));
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({'optionalEnum': 'wrongValue'}),
+ parseFailure(['optionalEnum']));
+ expect(() => TestAllTypes()..mergeFromProto3Json({'optionalEnum': []}),
+ parseFailure(['optionalEnum']));
+ expect(
+ () =>
+ TestAllTypes()..mergeFromProto3Json({'optionalNestedEnum': 100}),
+ parseFailure(['optionalNestedEnum']));
+ expect(
+ TestAllTypes()..mergeFromProto3Json({'optionalNestedEnum': 1}),
+ TestAllTypes()
+ ..optionalNestedEnum = TestAllTypes_NestedEnum.valueOf(1));
+ expect(TestAllTypes()..mergeFromProto3Json({'repeatedBool': null}),
+ TestAllTypes());
+ expect(() => TestAllTypes()..mergeFromProto3Json({'repeatedBool': 100}),
+ parseFailure(['repeatedBool']));
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'repeatedBool': [true, false, 1]
+ }),
+ parseFailure(['repeatedBool', '2']));
+ expect(() => TestAllTypes()..mergeFromProto3Json(Object()),
+ parseFailure([]));
+ });
+
+ test('merging behavior', () {
+ final t = TestAllTypes()
+ ..optionalForeignMessage = ForeignMessage()
+ ..repeatedForeignMessage.add(ForeignMessage()..c = 1);
+ final f = t.optionalForeignMessage;
+ expect(
+ t
+ ..mergeFromProto3Json({
+ 'repeatedForeignMessage': [
+ {'c': 2}
+ ],
+ 'optionalForeignMessage': {'c': 2}
+ }),
+ TestAllTypes()
+ ..optionalForeignMessage = (ForeignMessage()..c = 2)
+ ..repeatedForeignMessage
+ .addAll([ForeignMessage()..c = 1, ForeignMessage()..c = 2]));
+ expect(f, ForeignMessage()..c = 2);
+ });
+
+ test('names_with_underscores', () {
+ expect(
+ TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optional_foreign_message': {'c': 1}
+ }),
+ TestAllTypes()..optionalForeignMessage = (ForeignMessage()..c = 1));
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optional_foreign_message': {'c': 1}
+ }, supportNamesWithUnderscores: false),
+ parseFailure(['optional_foreign_message']));
+ });
+
+ test('map value', () {
+ TestMap expected = TestMap()
+ ..int32ToInt32Field[32] = 32
+ ..int32ToStringField[0] = 'foo'
+ ..int32ToStringField[1] = 'bar'
+ ..int32ToBytesField[-1] = [1, 2, 3]
+ ..int32ToEnumField[1] = TestMap_EnumValue.BAZ
+ ..int32ToMessageField[21] = (TestMap_MessageValue()
+ ..value = 2
+ ..secondValue = 3)
+ ..stringToInt32Field['key'] = -1
+ ..uint32ToInt32Field[0] = 0
+ ..int64ToInt32Field[Int64.ZERO] = 0
+ ..int64ToInt32Field[Int64.ONE] = 1
+ ..int64ToInt32Field[-Int64.ONE] = -1
+ ..int64ToInt32Field[Int64.MIN_VALUE] = -2
+ ..int64ToInt32Field[Int64.MAX_VALUE] = 2
+ ..uint64ToInt32Field[Int64.MIN_VALUE] = -2;
+ expect(
+ TestMap()
+ ..mergeFromProto3Json({
+ 'int32ToInt32Field': {'32': 32},
+ 'int32ToStringField': {'0': 'foo', '1': 'bar'},
+ 'int32ToBytesField': {'-1': 'AQID'},
+ 'int32ToEnumField': {'1': 'BAZ'},
+ 'int32ToMessageField': {
+ '21': {'value': 2, 'secondValue': 3}
+ },
+ 'stringToInt32Field': {'key': -1},
+ 'uint32ToInt32Field': {'0': 0},
+ 'int64ToInt32Field': {
+ '0': 0,
+ '1': 1,
+ '-1': -1,
+ '-9223372036854775808': -2,
+ '9223372036854775807': 2
+ },
+ 'uint64ToInt32Field': {'9223372036854775808': -2},
+ }),
+ expected);
+ expect(() => TestMap()..mergeFromProto3Json([]), parseFailure([]));
+ expect(
+ () => TestMap()
+ ..mergeFromProto3Json({
+ 'int32ToInt32Field': {'32': 'a'}
+ }),
+ parseFailure(['int32ToInt32Field', '32']));
+ expect(
+ () => TestMap()
+ ..mergeFromProto3Json({
+ 'int32ToInt32Field': {'2147483648': 1}
+ }),
+ parseFailure(['int32ToInt32Field', '2147483648']));
+ expect(
+ () => TestMap()
+ ..mergeFromProto3Json({
+ 'uint32ToInt32Field': {'-32': 21}
+ }),
+ parseFailure(['uint32ToInt32Field', '-32']));
+ expect(
+ () => TestMap()
+ ..mergeFromProto3Json({
+ 'uint32ToInt32Field': {'4294967296': 21}
+ }),
+ parseFailure(['uint32ToInt32Field', '4294967296']));
+ expect(
+ TestMap()
+ ..mergeFromProto3Json({
+ 'int32ToInt32Field': <dynamic, dynamic>{'2': 21}
+ }),
+ TestMap()..int32ToInt32Field[2] = 21);
+ });
+ test('ints', () {
+ expect(
+ (TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalUint64': '17293822573397606400',
+ }))
+ .optionalUint64,
+ Int64.parseHex('f0000000ffff0000'));
+
+ // TODO(sigurdm): This should throw.
+ expect(
+ TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalUint64': '-1',
+ }),
+ TestAllTypes()
+ ..optionalUint64 =
+ Int64.fromBytes([255, 255, 255, 255, 255, 255, 255, 255]));
+
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalUint32': '-1',
+ }),
+ parseFailure(['optionalUint32']));
+
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalUint32': -1,
+ }),
+ parseFailure(['optionalUint32']));
+
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalUint32': 0xFFFFFFFF + 1,
+ }),
+ parseFailure(['optionalUint32']));
+
+ expect(
+ TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalInt32': '2147483647',
+ }),
+ TestAllTypes()..optionalInt32 = 2147483647);
+
+ expect(
+ TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalInt32': '-2147483648',
+ }),
+ TestAllTypes()..optionalInt32 = -2147483648);
+
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalInt32': 2147483647 + 1,
+ }),
+ parseFailure(['optionalInt32']));
+
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalInt32': (2147483647 + 1).toString(),
+ }),
+ parseFailure(['optionalInt32']));
+
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'optionalInt32': -2147483648 - 1,
+ }),
+ parseFailure(['optionalInt32']));
+ });
+
+ test('unknown fields', () {
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'myOwnInventedField': 'blahblahblah',
+ }),
+ throwsA(const TypeMatcher<FormatException>()));
+ expect(
+ () => TestAllTypes()
+ ..mergeFromProto3Json({
+ 'myOwnInventedField': 'blahblahblah',
+ }, ignoreUnknownFields: false),
+ throwsA(const TypeMatcher<FormatException>()));
+ final t = TestAllTypes()
+ ..mergeFromProto3Json({
+ 'myOwnInventedField': 'blahblahblah',
+ }, ignoreUnknownFields: true);
+ expect(t, TestAllTypes());
+ expect(t.unknownFields.isEmpty, isTrue);
+ });
+ test('Any', () {
+ final m1 = Any()
+ ..mergeFromProto3Json({
+ '@type': 'type.googleapis.com/protobuf_unittest.TestAllTypes',
+ 'optionalFixed64': '100'
+ }, typeRegistry: TypeRegistry([TestAllTypes()]));
+
+ expect(m1.unpackInto(TestAllTypes()).optionalFixed64, Int64(100));
+
+ final m2 = Any()
+ ..mergeFromProto3Json({
+ '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+ 'value': '1969-07-20T19:17:00Z'
+ }, typeRegistry: TypeRegistry([Timestamp()]));
+
+ expect(m2.unpackInto(Timestamp()).toDateTime().millisecondsSinceEpoch,
+ DateTime.utc(1969, 7, 20, 19, 17).millisecondsSinceEpoch);
+
+ expect(
+ () => Any()
+ ..mergeFromProto3Json({
+ '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+ 'value': '1969-07-20T19:17:00Z'
+ }),
+ parseFailure([]));
+
+ expect(
+ () => Any()
+ ..mergeFromProto3Json(
+ {'@type': 11, 'value': '1969-07-20T19:17:00Z'}),
+ parseFailure([]));
+
+ final m3 = Any()
+ ..mergeFromProto3Json({
+ '@type': 'type.googleapis.com/google.protobuf.Any',
+ 'value': {
+ '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+ 'value': '1969-07-20T19:17:00Z'
+ }
+ }, typeRegistry: TypeRegistry([Timestamp(), Any()]));
+
+ expect(
+ m3
+ .unpackInto(Any())
+ .unpackInto(Timestamp())
+ .toDateTime()
+ .millisecondsSinceEpoch,
+ DateTime.utc(1969, 7, 20, 19, 17).millisecondsSinceEpoch);
+
+ // TODO(sigurdm): We would ideally like the error path to be
+ // ['value', 'value'].
+ expect(
+ () => Any()
+ ..mergeFromProto3Json({
+ '@type': 'type.googleapis.com/google.protobuf.Any',
+ 'value': {
+ '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+ 'value': '1969'
+ }
+ }, typeRegistry: TypeRegistry([Timestamp(), Any()])),
+ parseFailure([]));
+
+ expect(() => Any()..mergeFromProto3Json('@type'), parseFailure([]));
+
+ expect(() => Any()..mergeFromProto3Json(11), parseFailure([]));
+
+ expect(() => Any()..mergeFromProto3Json(['@type']), parseFailure([]));
+
+ expect(
+ () => Any()
+ ..mergeFromProto3Json({
+ '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+ 'value': '1969-07-20T19:17:00Z'
+ }),
+ parseFailure([]));
+ });
+
+ test('Duration', () {
+ expect(
+ Duration()..mergeFromProto3Json('0s'),
+ Duration()
+ ..seconds = Int64(0)
+ ..nanos = 0);
+ expect(
+ Duration()..mergeFromProto3Json('10s'),
+ Duration()
+ ..seconds = Int64(10)
+ ..nanos = 0);
+ expect(
+ Duration()..mergeFromProto3Json('10.000000001s'),
+ Duration()
+ ..seconds = Int64(10)
+ ..nanos = 1);
+ expect(
+ Duration()..mergeFromProto3Json('10.00000001s'),
+ Duration()
+ ..seconds = Int64(10)
+ ..nanos = 10);
+ expect(
+ Duration()..mergeFromProto3Json('-1.000099s'),
+ Duration()
+ ..seconds = -Int64(1)
+ ..nanos = -99000);
+
+ expect(
+ Duration()..mergeFromProto3Json('0.s'),
+ Duration()
+ ..seconds = Int64(0)
+ ..nanos = 0);
+ expect(
+ Duration()..mergeFromProto3Json('.0s'),
+ Duration()
+ ..seconds = Int64(0)
+ ..nanos = 0);
+ expect(
+ Duration()..mergeFromProto3Json('.5s'),
+ Duration()
+ ..seconds = Int64(0)
+ ..nanos = 500000000);
+ expect(
+ Duration()..mergeFromProto3Json('5.s'),
+ Duration()
+ ..seconds = Int64(5)
+ ..nanos = 0);
+ expect(
+ Duration()..mergeFromProto3Json('.s'),
+ Duration()
+ ..seconds = Int64(0)
+ ..nanos = 0);
+ expect(() => Duration()..mergeFromProto3Json('0.5'), parseFailure([]));
+ expect(() => Duration()..mergeFromProto3Json(100), parseFailure([]));
+ });
+
+ test('Timestamp', () {
+ expect(Timestamp()..mergeFromProto3Json('1969-07-20T20:17:00Z'),
+ Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17)));
+ expect(
+ Timestamp()..mergeFromProto3Json('1970-01-01T00:00:00.000000001Z'),
+ Timestamp()
+ ..seconds = Int64(0)
+ ..nanos = 1);
+ expect(Timestamp()..mergeFromProto3Json('1970-01-01T00:00:00.008200Z'),
+ Timestamp.fromDateTime(DateTime.utc(1970))..nanos = 8200000);
+ expect(
+ Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00-04:00'),
+ Timestamp()
+ ..seconds = Int64(82200)
+ ..nanos = 0);
+ expect(
+ Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00+04:00'),
+ Timestamp()
+ ..seconds = Int64(53400)
+ ..nanos = 0);
+ expect(
+ Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00.0001+04:00'),
+ Timestamp()
+ ..seconds = Int64(53400)
+ ..nanos = 100000);
+ expect(
+ () => Timestamp()
+ ..mergeFromProto3Json('1970-01-01T00:00:00.0000000001Z'),
+ parseFailure([]));
+ expect(() => Timestamp()..mergeFromProto3Json(1970), parseFailure([]));
+ expect(() => Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00.'),
+ parseFailure([]));
+ });
+
+ test('wrapper types', () {
+ expect(
+ TestWellKnownTypes()
+ ..mergeFromProto3Json({
+ 'doubleField': 10.01,
+ 'floatField': 3,
+ 'int64Field': '-9223372036854775808',
+ 'uint64Field': '9223372036854775808',
+ 'int32Field': 101,
+ 'uint32Field': 102,
+ 'boolField': false,
+ 'stringField': 'Pop',
+ 'bytesField': 'CAkK',
+ }),
+ TestWellKnownTypes()
+ ..doubleField = (DoubleValue()..value = 10.01)
+ ..floatField = (FloatValue()..value = 3.0)
+ ..int64Field = (Int64Value()..value = Int64.MIN_VALUE)
+ ..uint64Field = (UInt64Value()..value = Int64.MIN_VALUE)
+ ..int32Field = (Int32Value()..value = 101)
+ ..uint32Field = (UInt32Value()..value = 102)
+ ..boolField = (BoolValue()..value = false)
+ ..stringField = (StringValue()..value = 'Pop')
+ ..bytesField = (BytesValue()..value = [8, 9, 10]));
+
+ expect(
+ TestWellKnownTypes()
+ ..mergeFromProto3Json({
+ 'doubleField': '10.01',
+ 'floatField': '3',
+ 'int64Field': -854775808,
+ 'uint64Field': 854775808,
+ 'int32Field': '101',
+ 'uint32Field': '102',
+ 'boolField': false,
+ }),
+ TestWellKnownTypes()
+ ..doubleField = (DoubleValue()..value = 10.01)
+ ..floatField = (FloatValue()..value = 3.0)
+ ..int64Field = (Int64Value()..value = Int64(-854775808))
+ ..uint64Field = (UInt64Value()..value = Int64(854775808))
+ ..int32Field = (Int32Value()..value = 101)
+ ..uint32Field = (UInt32Value()..value = 102)
+ ..boolField = (BoolValue()..value = false),
+ reason: 'alternative representations should be accepted');
+
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'doubleField': 'a'}),
+ parseFailure(['doubleField']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'doubleField': {}}),
+ parseFailure(['doubleField']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'floatField': 'a'}),
+ parseFailure(['floatField']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'floatField': {}}),
+ parseFailure(['floatField']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'int64Field': 'a'}),
+ parseFailure(['int64Field']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'int64Field': {}}),
+ parseFailure(['int64Field']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'int64Field': 10.4}),
+ parseFailure(['int64Field']));
+
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'uint64Field': 'a'}),
+ parseFailure(['uint64Field']));
+ expect(
+ () =>
+ TestWellKnownTypes()..mergeFromProto3Json({'uint64Field': 10.4}),
+ parseFailure(['uint64Field']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'int32Field': 'a'}),
+ parseFailure(['int32Field']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'int32Field': 10.4}),
+ parseFailure(['int32Field']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'uint32Field': 'a'}),
+ parseFailure(['uint32Field']));
+ expect(
+ () =>
+ TestWellKnownTypes()..mergeFromProto3Json({'uint32Field': 10.4}),
+ parseFailure(['uint32Field']));
+ expect(
+ () =>
+ TestWellKnownTypes()..mergeFromProto3Json({'boolField': 'false'}),
+ parseFailure(['boolField']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'stringField': 22}),
+ parseFailure(['stringField']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'bytesField': 22}),
+ parseFailure(['bytesField']));
+ expect(
+ () => TestWellKnownTypes()..mergeFromProto3Json({'bytesField': '()'}),
+ parseFailure(['bytesField']));
+
+ expect(
+ TestWellKnownTypes()
+ ..mergeFromProto3Json({
+ 'doubleField': null,
+ 'floatField': null,
+ 'int64Field': null,
+ 'uint64Field': null,
+ 'int32Field': null,
+ 'uint32Field': null,
+ 'boolField': null,
+ 'stringField': null,
+ 'bytesField': null,
+ }),
+ TestWellKnownTypes()
+ ..doubleField = DoubleValue()
+ ..floatField = FloatValue()
+ ..int64Field = Int64Value()
+ ..uint64Field = UInt64Value()
+ ..int32Field = Int32Value()
+ ..uint32Field = UInt32Value()
+ ..boolField = BoolValue()
+ ..stringField = StringValue()
+ ..bytesField = BytesValue(),
+ reason: 'having fields of wrapper types set to null will return an '
+ 'empty wrapper (with unset .value field)');
+ });
+
+ test('struct', () {
+ final f = {
+ 'null': null,
+ 'number': 22.3,
+ 'string': 'foo',
+ 'bool': false,
+ 'struct': {'a': 0},
+ 'list': [{}, [], 'why']
+ };
+
+ final s = Struct()
+ ..fields['null'] = (Value()..nullValue = NullValue.NULL_VALUE)
+ ..fields['number'] = (Value()..numberValue = 22.3)
+ ..fields['string'] = (Value()..stringValue = 'foo')
+ ..fields['bool'] = (Value()..boolValue = false)
+ ..fields['struct'] = (Value()
+ ..structValue =
+ (Struct()..fields['a'] = (Value()..numberValue = 0.0)))
+ ..fields['list'] = (Value()
+ ..listValue = (ListValue()
+ ..values.addAll([
+ Value()..structValue = Struct(),
+ Value()..listValue = ListValue(),
+ Value()..stringValue = 'why'
+ ])));
+ expect(Struct()..mergeFromProto3Json(f), s);
+
+ expect(Struct()..mergeFromProto3Json(<dynamic, dynamic>{'a': 12}),
+ (Struct()..fields['a'] = (Value()..numberValue = 12.0)),
+ reason: 'Allow key type to be `dynamic`');
+
+ expect(() => Struct()..mergeFromProto3Json({1: 2}), parseFailure([]),
+ reason: 'Non-string key in JSON object literal');
+
+ expect(() => Struct()..mergeFromProto3Json([]), parseFailure([]),
+ reason: 'Non object literal');
+
+ expect(() => Struct()..mergeFromProto3Json([]), parseFailure([]),
+ reason: 'Non-string key in JSON object literal');
+
+ expect(() => Value()..mergeFromProto3Json(Object()), parseFailure([]),
+ reason: 'Non JSON value');
+
+ expect(() => ListValue()..mergeFromProto3Json({}), parseFailure([]),
+ reason: 'Non-list');
+ });
+
+ test('field mask', () {
+ expect(
+ TestWellKnownTypes()
+ ..mergeFromProto3Json({'fieldMaskField': 'foo,barBaz'}),
+ TestWellKnownTypes()
+ ..fieldMaskField = (FieldMask()..paths.addAll(['foo', 'bar_baz'])));
+ expect(() => FieldMask()..mergeFromProto3Json('foo,bar_bar'),
+ parseFailure([]));
+
+ expect(FieldMask()..mergeFromProto3Json(''), FieldMask());
+ expect(() => FieldMask()..mergeFromProto3Json(12), parseFailure([]));
+ });
+ });
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/api.proto b/protoc_plugin/test/protos/google/protobuf/api.proto
new file mode 100644
index 0000000..f37ee2f
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/api.proto
@@ -0,0 +1,210 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/source_context.proto";
+import "google/protobuf/type.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "ApiProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/api;api";
+
+// Api is a light-weight descriptor for an API Interface.
+//
+// Interfaces are also described as "protocol buffer services" in some contexts,
+// such as by the "service" keyword in a .proto file, but they are different
+// from API Services, which represent a concrete implementation of an interface
+// as opposed to simply a description of methods and bindings. They are also
+// sometimes simply referred to as "APIs" in other contexts, such as the name of
+// this message itself. See https://cloud.google.com/apis/design/glossary for
+// detailed terminology.
+message Api {
+
+ // The fully qualified name of this interface, including package name
+ // followed by the interface's simple name.
+ string name = 1;
+
+ // The methods of this interface, in unspecified order.
+ repeated Method methods = 2;
+
+ // Any metadata attached to the interface.
+ repeated Option options = 3;
+
+ // A version string for this interface. If specified, must have the form
+ // `major-version.minor-version`, as in `1.10`. If the minor version is
+ // omitted, it defaults to zero. If the entire version field is empty, the
+ // major version is derived from the package name, as outlined below. If the
+ // field is not empty, the version in the package name will be verified to be
+ // consistent with what is provided here.
+ //
+ // The versioning schema uses [semantic
+ // versioning](http://semver.org) where the major version number
+ // indicates a breaking change and the minor version an additive,
+ // non-breaking change. Both version numbers are signals to users
+ // what to expect from different versions, and should be carefully
+ // chosen based on the product plan.
+ //
+ // The major version is also reflected in the package name of the
+ // interface, which must end in `v<major-version>`, as in
+ // `google.feature.v1`. For major versions 0 and 1, the suffix can
+ // be omitted. Zero major versions must only be used for
+ // experimental, non-GA interfaces.
+ //
+ //
+ string version = 4;
+
+ // Source context for the protocol buffer service represented by this
+ // message.
+ SourceContext source_context = 5;
+
+ // Included interfaces. See [Mixin][].
+ repeated Mixin mixins = 6;
+
+ // The source syntax of the service.
+ Syntax syntax = 7;
+}
+
+// Method represents a method of an API interface.
+message Method {
+
+ // The simple name of this method.
+ string name = 1;
+
+ // A URL of the input message type.
+ string request_type_url = 2;
+
+ // If true, the request is streamed.
+ bool request_streaming = 3;
+
+ // The URL of the output message type.
+ string response_type_url = 4;
+
+ // If true, the response is streamed.
+ bool response_streaming = 5;
+
+ // Any metadata attached to the method.
+ repeated Option options = 6;
+
+ // The source syntax of this method.
+ Syntax syntax = 7;
+}
+
+// Declares an API Interface to be included in this interface. The including
+// interface must redeclare all the methods from the included interface, but
+// documentation and options are inherited as follows:
+//
+// - If after comment and whitespace stripping, the documentation
+// string of the redeclared method is empty, it will be inherited
+// from the original method.
+//
+// - Each annotation belonging to the service config (http,
+// visibility) which is not set in the redeclared method will be
+// inherited.
+//
+// - If an http annotation is inherited, the path pattern will be
+// modified as follows. Any version prefix will be replaced by the
+// version of the including interface plus the [root][] path if
+// specified.
+//
+// Example of a simple mixin:
+//
+// package google.acl.v1;
+// service AccessControl {
+// // Get the underlying ACL object.
+// rpc GetAcl(GetAclRequest) returns (Acl) {
+// option (google.api.http).get = "/v1/{resource=**}:getAcl";
+// }
+// }
+//
+// package google.storage.v2;
+// service Storage {
+// rpc GetAcl(GetAclRequest) returns (Acl);
+//
+// // Get a data record.
+// rpc GetData(GetDataRequest) returns (Data) {
+// option (google.api.http).get = "/v2/{resource=**}";
+// }
+// }
+//
+// Example of a mixin configuration:
+//
+// apis:
+// - name: google.storage.v2.Storage
+// mixins:
+// - name: google.acl.v1.AccessControl
+//
+// The mixin construct implies that all methods in `AccessControl` are
+// also declared with same name and request/response types in
+// `Storage`. A documentation generator or annotation processor will
+// see the effective `Storage.GetAcl` method after inherting
+// documentation and annotations as follows:
+//
+// service Storage {
+// // Get the underlying ACL object.
+// rpc GetAcl(GetAclRequest) returns (Acl) {
+// option (google.api.http).get = "/v2/{resource=**}:getAcl";
+// }
+// ...
+// }
+//
+// Note how the version in the path pattern changed from `v1` to `v2`.
+//
+// If the `root` field in the mixin is specified, it should be a
+// relative path under which inherited HTTP paths are placed. Example:
+//
+// apis:
+// - name: google.storage.v2.Storage
+// mixins:
+// - name: google.acl.v1.AccessControl
+// root: acls
+//
+// This implies the following inherited HTTP annotation:
+//
+// service Storage {
+// // Get the underlying ACL object.
+// rpc GetAcl(GetAclRequest) returns (Acl) {
+// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl";
+// }
+// ...
+// }
+message Mixin {
+ // The fully qualified name of the interface which is included.
+ string name = 1;
+
+ // If non-empty specifies a path under which inherited HTTP paths
+ // are rooted.
+ string root = 2;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/duration.proto b/protoc_plugin/test/protos/google/protobuf/duration.proto
new file mode 100644
index 0000000..975fce4
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/duration.proto
@@ -0,0 +1,117 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/duration";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DurationProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Duration represents a signed, fixed-length span of time represented
+// as a count of seconds and fractions of seconds at nanosecond
+// resolution. It is independent of any calendar and concepts like "day"
+// or "month". It is related to Timestamp in that the difference between
+// two Timestamp values is a Duration and it can be added or subtracted
+// from a Timestamp. Range is approximately +-10,000 years.
+//
+// # Examples
+//
+// Example 1: Compute Duration from two Timestamps in pseudo code.
+//
+// Timestamp start = ...;
+// Timestamp end = ...;
+// Duration duration = ...;
+//
+// duration.seconds = end.seconds - start.seconds;
+// duration.nanos = end.nanos - start.nanos;
+//
+// if (duration.seconds < 0 && duration.nanos > 0) {
+// duration.seconds += 1;
+// duration.nanos -= 1000000000;
+// } else if (durations.seconds > 0 && duration.nanos < 0) {
+// duration.seconds -= 1;
+// duration.nanos += 1000000000;
+// }
+//
+// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
+//
+// Timestamp start = ...;
+// Duration duration = ...;
+// Timestamp end = ...;
+//
+// end.seconds = start.seconds + duration.seconds;
+// end.nanos = start.nanos + duration.nanos;
+//
+// if (end.nanos < 0) {
+// end.seconds -= 1;
+// end.nanos += 1000000000;
+// } else if (end.nanos >= 1000000000) {
+// end.seconds += 1;
+// end.nanos -= 1000000000;
+// }
+//
+// Example 3: Compute Duration from datetime.timedelta in Python.
+//
+// td = datetime.timedelta(days=3, minutes=10)
+// duration = Duration()
+// duration.FromTimedelta(td)
+//
+// # JSON Mapping
+//
+// In JSON format, the Duration type is encoded as a string rather than an
+// object, where the string ends in the suffix "s" (indicating seconds) and
+// is preceded by the number of seconds, with nanoseconds expressed as
+// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
+// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
+// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
+// microsecond should be expressed in JSON format as "3.000001s".
+//
+//
+message Duration {
+
+ // Signed seconds of the span of time. Must be from -315,576,000,000
+ // to +315,576,000,000 inclusive. Note: these bounds are computed from:
+ // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
+ int64 seconds = 1;
+
+ // Signed fractions of a second at nanosecond resolution of the span
+ // of time. Durations less than one second are represented with a 0
+ // `seconds` field and a positive or negative `nanos` field. For durations
+ // of one second or more, a non-zero value for the `nanos` field must be
+ // of the same sign as the `seconds` field. Must be from -999,999,999
+ // to +999,999,999 inclusive.
+ int32 nanos = 2;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/empty.proto b/protoc_plugin/test/protos/google/protobuf/empty.proto
new file mode 100644
index 0000000..03cacd2
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/empty.proto
@@ -0,0 +1,52 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "github.com/golang/protobuf/ptypes/empty";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "EmptyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option cc_enable_arenas = true;
+
+// A generic empty message that you can re-use to avoid defining duplicated
+// empty messages in your APIs. A typical example is to use it as the request
+// or the response type of an API method. For instance:
+//
+// service Foo {
+// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
+// }
+//
+// The JSON representation for `Empty` is empty JSON object `{}`.
+message Empty {}
diff --git a/protoc_plugin/test/protos/google/protobuf/field_mask.proto b/protoc_plugin/test/protos/google/protobuf/field_mask.proto
new file mode 100644
index 0000000..76e09f3
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/field_mask.proto
@@ -0,0 +1,252 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "FieldMaskProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/field_mask;field_mask";
+
+// `FieldMask` represents a set of symbolic field paths, for example:
+//
+// paths: "f.a"
+// paths: "f.b.d"
+//
+// Here `f` represents a field in some root message, `a` and `b`
+// fields in the message found in `f`, and `d` a field found in the
+// message in `f.b`.
+//
+// Field masks are used to specify a subset of fields that should be
+// returned by a get operation or modified by an update operation.
+// Field masks also have a custom JSON encoding (see below).
+//
+// # Field Masks in Projections
+//
+// When used in the context of a projection, a response message or
+// sub-message is filtered by the API to only contain those fields as
+// specified in the mask. For example, if the mask in the previous
+// example is applied to a response message as follows:
+//
+// f {
+// a : 22
+// b {
+// d : 1
+// x : 2
+// }
+// y : 13
+// }
+// z: 8
+//
+// The result will not contain specific values for fields x,y and z
+// (their value will be set to the default, and omitted in proto text
+// output):
+//
+//
+// f {
+// a : 22
+// b {
+// d : 1
+// }
+// }
+//
+// A repeated field is not allowed except at the last position of a
+// paths string.
+//
+// If a FieldMask object is not present in a get operation, the
+// operation applies to all fields (as if a FieldMask of all fields
+// had been specified).
+//
+// Note that a field mask does not necessarily apply to the
+// top-level response message. In case of a REST get operation, the
+// field mask applies directly to the response, but in case of a REST
+// list operation, the mask instead applies to each individual message
+// in the returned resource list. In case of a REST custom method,
+// other definitions may be used. Where the mask applies will be
+// clearly documented together with its declaration in the API. In
+// any case, the effect on the returned resource/resources is required
+// behavior for APIs.
+//
+// # Field Masks in Update Operations
+//
+// A field mask in update operations specifies which fields of the
+// targeted resource are going to be updated. The API is required
+// to only change the values of the fields as specified in the mask
+// and leave the others untouched. If a resource is passed in to
+// describe the updated values, the API ignores the values of all
+// fields not covered by the mask.
+//
+// If a repeated field is specified for an update operation, the existing
+// repeated values in the target resource will be overwritten by the new values.
+// Note that a repeated field is only allowed in the last position of a `paths`
+// string.
+//
+// If a sub-message is specified in the last position of the field mask for an
+// update operation, then the existing sub-message in the target resource is
+// overwritten. Given the target message:
+//
+// f {
+// b {
+// d : 1
+// x : 2
+// }
+// c : 1
+// }
+//
+// And an update message:
+//
+// f {
+// b {
+// d : 10
+// }
+// }
+//
+// then if the field mask is:
+//
+// paths: "f.b"
+//
+// then the result will be:
+//
+// f {
+// b {
+// d : 10
+// }
+// c : 1
+// }
+//
+// However, if the update mask was:
+//
+// paths: "f.b.d"
+//
+// then the result would be:
+//
+// f {
+// b {
+// d : 10
+// x : 2
+// }
+// c : 1
+// }
+//
+// In order to reset a field's value to the default, the field must
+// be in the mask and set to the default value in the provided resource.
+// Hence, in order to reset all fields of a resource, provide a default
+// instance of the resource and set all fields in the mask, or do
+// not provide a mask as described below.
+//
+// If a field mask is not present on update, the operation applies to
+// all fields (as if a field mask of all fields has been specified).
+// Note that in the presence of schema evolution, this may mean that
+// fields the client does not know and has therefore not filled into
+// the request will be reset to their default. If this is unwanted
+// behavior, a specific service may require a client to always specify
+// a field mask, producing an error if not.
+//
+// As with get operations, the location of the resource which
+// describes the updated values in the request message depends on the
+// operation kind. In any case, the effect of the field mask is
+// required to be honored by the API.
+//
+// ## Considerations for HTTP REST
+//
+// The HTTP kind of an update operation which uses a field mask must
+// be set to PATCH instead of PUT in order to satisfy HTTP semantics
+// (PUT must only be used for full updates).
+//
+// # JSON Encoding of Field Masks
+//
+// In JSON, a field mask is encoded as a single string where paths are
+// separated by a comma. Fields name in each path are converted
+// to/from lower-camel naming conventions.
+//
+// As an example, consider the following message declarations:
+//
+// message Profile {
+// User user = 1;
+// Photo photo = 2;
+// }
+// message User {
+// string display_name = 1;
+// string address = 2;
+// }
+//
+// In proto a field mask for `Profile` may look as such:
+//
+// mask {
+// paths: "user.display_name"
+// paths: "photo"
+// }
+//
+// In JSON, the same mask is represented as below:
+//
+// {
+// mask: "user.displayName,photo"
+// }
+//
+// # Field Masks and Oneof Fields
+//
+// Field masks treat fields in oneofs just as regular fields. Consider the
+// following message:
+//
+// message SampleMessage {
+// oneof test_oneof {
+// string name = 4;
+// SubMessage sub_message = 9;
+// }
+// }
+//
+// The field mask can be:
+//
+// mask {
+// paths: "name"
+// }
+//
+// Or:
+//
+// mask {
+// paths: "sub_message"
+// }
+//
+// Note that oneof type names ("test_oneof" in this case) cannot be used in
+// paths.
+//
+// ## Field Mask Verification
+//
+// The implementation of any API method which has a FieldMask type field in the
+// request should verify the included field paths, and return an
+// `INVALID_ARGUMENT` error if any path is duplicated or unmappable.
+message FieldMask {
+ // The set of field mask paths.
+ repeated string paths = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/source_context.proto b/protoc_plugin/test/protos/google/protobuf/source_context.proto
new file mode 100644
index 0000000..f3b2c96
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/source_context.proto
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "SourceContextProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/source_context;source_context";
+
+// `SourceContext` represents information about the source of a
+// protobuf element, like the file in which it is defined.
+message SourceContext {
+ // The path-qualified name of the .proto file that contained the associated
+ // protobuf element. For example: `"google/protobuf/source_context.proto"`.
+ string file_name = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/struct.proto b/protoc_plugin/test/protos/google/protobuf/struct.proto
new file mode 100644
index 0000000..7d7808e
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/struct.proto
@@ -0,0 +1,96 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/struct;structpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "StructProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+
+// `Struct` represents a structured data value, consisting of fields
+// which map to dynamically typed values. In some languages, `Struct`
+// might be supported by a native representation. For example, in
+// scripting languages like JS a struct is represented as an
+// object. The details of that representation are described together
+// with the proto support for the language.
+//
+// The JSON representation for `Struct` is JSON object.
+message Struct {
+ // Unordered map of dynamically typed values.
+ map<string, Value> fields = 1;
+}
+
+// `Value` represents a dynamically typed value which can be either
+// null, a number, a string, a boolean, a recursive struct value, or a
+// list of values. A producer of value is expected to set one of that
+// variants, absence of any variant indicates an error.
+//
+// The JSON representation for `Value` is JSON value.
+message Value {
+ // The kind of value.
+ oneof kind {
+ // Represents a null value.
+ NullValue null_value = 1;
+ // Represents a double value.
+ double number_value = 2;
+ // Represents a string value.
+ string string_value = 3;
+ // Represents a boolean value.
+ bool bool_value = 4;
+ // Represents a structured value.
+ Struct struct_value = 5;
+ // Represents a repeated `Value`.
+ ListValue list_value = 6;
+ }
+}
+
+// `NullValue` is a singleton enumeration to represent the null value for the
+// `Value` type union.
+//
+// The JSON representation for `NullValue` is JSON `null`.
+enum NullValue {
+ // Null value.
+ NULL_VALUE = 0;
+}
+
+// `ListValue` is a wrapper around a repeated field of values.
+//
+// The JSON representation for `ListValue` is JSON array.
+message ListValue {
+ // Repeated field of dynamically typed values.
+ repeated Value values = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/type.proto b/protoc_plugin/test/protos/google/protobuf/type.proto
new file mode 100644
index 0000000..624c15e
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/type.proto
@@ -0,0 +1,187 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/any.proto";
+import "google/protobuf/source_context.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TypeProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/ptype;ptype";
+
+// A protocol buffer message type.
+message Type {
+ // The fully qualified message name.
+ string name = 1;
+ // The list of fields.
+ repeated Field fields = 2;
+ // The list of types appearing in `oneof` definitions in this type.
+ repeated string oneofs = 3;
+ // The protocol buffer options.
+ repeated Option options = 4;
+ // The source context.
+ SourceContext source_context = 5;
+ // The source syntax.
+ Syntax syntax = 6;
+}
+
+// A single field of a message type.
+message Field {
+ // Basic field types.
+ enum Kind {
+ // Field type unknown.
+ TYPE_UNKNOWN = 0;
+ // Field type double.
+ TYPE_DOUBLE = 1;
+ // Field type float.
+ TYPE_FLOAT = 2;
+ // Field type int64.
+ TYPE_INT64 = 3;
+ // Field type uint64.
+ TYPE_UINT64 = 4;
+ // Field type int32.
+ TYPE_INT32 = 5;
+ // Field type fixed64.
+ TYPE_FIXED64 = 6;
+ // Field type fixed32.
+ TYPE_FIXED32 = 7;
+ // Field type bool.
+ TYPE_BOOL = 8;
+ // Field type string.
+ TYPE_STRING = 9;
+ // Field type group. Proto2 syntax only, and deprecated.
+ TYPE_GROUP = 10;
+ // Field type message.
+ TYPE_MESSAGE = 11;
+ // Field type bytes.
+ TYPE_BYTES = 12;
+ // Field type uint32.
+ TYPE_UINT32 = 13;
+ // Field type enum.
+ TYPE_ENUM = 14;
+ // Field type sfixed32.
+ TYPE_SFIXED32 = 15;
+ // Field type sfixed64.
+ TYPE_SFIXED64 = 16;
+ // Field type sint32.
+ TYPE_SINT32 = 17;
+ // Field type sint64.
+ TYPE_SINT64 = 18;
+ };
+
+ // Whether a field is optional, required, or repeated.
+ enum Cardinality {
+ // For fields with unknown cardinality.
+ CARDINALITY_UNKNOWN = 0;
+ // For optional fields.
+ CARDINALITY_OPTIONAL = 1;
+ // For required fields. Proto2 syntax only.
+ CARDINALITY_REQUIRED = 2;
+ // For repeated fields.
+ CARDINALITY_REPEATED = 3;
+ };
+
+ // The field type.
+ Kind kind = 1;
+ // The field cardinality.
+ Cardinality cardinality = 2;
+ // The field number.
+ int32 number = 3;
+ // The field name.
+ string name = 4;
+ // The field type URL, without the scheme, for message or enumeration
+ // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`.
+ string type_url = 6;
+ // The index of the field type in `Type.oneofs`, for message or enumeration
+ // types. The first type has index 1; zero means the type is not in the list.
+ int32 oneof_index = 7;
+ // Whether to use alternative packed wire representation.
+ bool packed = 8;
+ // The protocol buffer options.
+ repeated Option options = 9;
+ // The field JSON name.
+ string json_name = 10;
+ // The string value of the default value of this field. Proto2 syntax only.
+ string default_value = 11;
+}
+
+// Enum type definition.
+message Enum {
+ // Enum type name.
+ string name = 1;
+ // Enum value definitions.
+ repeated EnumValue enumvalue = 2;
+ // Protocol buffer options.
+ repeated Option options = 3;
+ // The source context.
+ SourceContext source_context = 4;
+ // The source syntax.
+ Syntax syntax = 5;
+}
+
+// Enum value definition.
+message EnumValue {
+ // Enum value name.
+ string name = 1;
+ // Enum value number.
+ int32 number = 2;
+ // Protocol buffer options.
+ repeated Option options = 3;
+}
+
+// A protocol buffer option, which can be attached to a message, field,
+// enumeration, etc.
+message Option {
+ // The option's name. For protobuf built-in options (options defined in
+ // descriptor.proto), this is the short name. For example, `"map_entry"`.
+ // For custom options, it should be the fully-qualified name. For example,
+ // `"google.api.http"`.
+ string name = 1;
+ // The option's value packed in an Any message. If the value is a primitive,
+ // the corresponding wrapper type defined in google/protobuf/wrappers.proto
+ // should be used. If the value is an enum, it should be stored as an int32
+ // value using the google.protobuf.Int32Value type.
+ Any value = 2;
+}
+
+// The syntax in which a protocol buffer element is defined.
+enum Syntax {
+ // Syntax `proto2`.
+ SYNTAX_PROTO2 = 0;
+ // Syntax `proto3`.
+ SYNTAX_PROTO3 = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/unittest_well_known_types.proto b/protoc_plugin/test/protos/google/protobuf/unittest_well_known_types.proto
new file mode 100644
index 0000000..c907524
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/unittest_well_known_types.proto
@@ -0,0 +1,114 @@
+syntax = "proto3";
+
+package protobuf_unittest;
+
+option csharp_namespace = "Google.Protobuf.TestProtos";
+option java_multiple_files = true;
+option java_package = "com.google.protobuf.test";
+
+import "google/protobuf/any.proto";
+import "google/protobuf/api.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/source_context.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/type.proto";
+import "google/protobuf/wrappers.proto";
+
+// Test that we can include all well-known types.
+// Each wrapper type is included separately, as languages
+// map handle different wrappers in different ways.
+message TestWellKnownTypes {
+ google.protobuf.Any any_field = 1;
+ google.protobuf.Api api_field = 2;
+ google.protobuf.Duration duration_field = 3;
+ google.protobuf.Empty empty_field = 4;
+ google.protobuf.FieldMask field_mask_field = 5;
+ google.protobuf.SourceContext source_context_field = 6;
+ google.protobuf.Struct struct_field = 7;
+ google.protobuf.Timestamp timestamp_field = 8;
+ google.protobuf.Type type_field = 9;
+ google.protobuf.DoubleValue double_field = 10;
+ google.protobuf.FloatValue float_field = 11;
+ google.protobuf.Int64Value int64_field = 12;
+ google.protobuf.UInt64Value uint64_field = 13;
+ google.protobuf.Int32Value int32_field = 14;
+ google.protobuf.UInt32Value uint32_field = 15;
+ google.protobuf.BoolValue bool_field = 16;
+ google.protobuf.StringValue string_field = 17;
+ google.protobuf.BytesValue bytes_field = 18;
+ // Part of struct, but useful to be able to test separately
+ google.protobuf.Value value_field = 19;
+}
+
+// A repeated field for each well-known type.
+message RepeatedWellKnownTypes {
+ repeated google.protobuf.Any any_field = 1;
+ repeated google.protobuf.Api api_field = 2;
+ repeated google.protobuf.Duration duration_field = 3;
+ repeated google.protobuf.Empty empty_field = 4;
+ repeated google.protobuf.FieldMask field_mask_field = 5;
+ repeated google.protobuf.SourceContext source_context_field = 6;
+ repeated google.protobuf.Struct struct_field = 7;
+ repeated google.protobuf.Timestamp timestamp_field = 8;
+ repeated google.protobuf.Type type_field = 9;
+ // These don't actually make a lot of sense, but they're not prohibited...
+ repeated google.protobuf.DoubleValue double_field = 10;
+ repeated google.protobuf.FloatValue float_field = 11;
+ repeated google.protobuf.Int64Value int64_field = 12;
+ repeated google.protobuf.UInt64Value uint64_field = 13;
+ repeated google.protobuf.Int32Value int32_field = 14;
+ repeated google.protobuf.UInt32Value uint32_field = 15;
+ repeated google.protobuf.BoolValue bool_field = 16;
+ repeated google.protobuf.StringValue string_field = 17;
+ repeated google.protobuf.BytesValue bytes_field = 18;
+}
+
+message OneofWellKnownTypes {
+ oneof oneof_field {
+ google.protobuf.Any any_field = 1;
+ google.protobuf.Api api_field = 2;
+ google.protobuf.Duration duration_field = 3;
+ google.protobuf.Empty empty_field = 4;
+ google.protobuf.FieldMask field_mask_field = 5;
+ google.protobuf.SourceContext source_context_field = 6;
+ google.protobuf.Struct struct_field = 7;
+ google.protobuf.Timestamp timestamp_field = 8;
+ google.protobuf.Type type_field = 9;
+ google.protobuf.DoubleValue double_field = 10;
+ google.protobuf.FloatValue float_field = 11;
+ google.protobuf.Int64Value int64_field = 12;
+ google.protobuf.UInt64Value uint64_field = 13;
+ google.protobuf.Int32Value int32_field = 14;
+ google.protobuf.UInt32Value uint32_field = 15;
+ google.protobuf.BoolValue bool_field = 16;
+ google.protobuf.StringValue string_field = 17;
+ google.protobuf.BytesValue bytes_field = 18;
+ }
+}
+
+// A map field for each well-known type. We only
+// need to worry about the value part of the map being the
+// well-known types, as messages can't be map keys.
+message MapWellKnownTypes {
+ map<int32,google.protobuf.Any> any_field = 1;
+ map<int32,google.protobuf.Api> api_field = 2;
+ map<int32,google.protobuf.Duration> duration_field = 3;
+ map<int32,google.protobuf.Empty> empty_field = 4;
+ map<int32,google.protobuf.FieldMask> field_mask_field = 5;
+ map<int32,google.protobuf.SourceContext> source_context_field = 6;
+ map<int32,google.protobuf.Struct> struct_field = 7;
+ map<int32,google.protobuf.Timestamp> timestamp_field = 8;
+ map<int32,google.protobuf.Type> type_field = 9;
+ map<int32,google.protobuf.DoubleValue> double_field = 10;
+ map<int32,google.protobuf.FloatValue> float_field = 11;
+ map<int32,google.protobuf.Int64Value> int64_field = 12;
+ map<int32,google.protobuf.UInt64Value> uint64_field = 13;
+ map<int32,google.protobuf.Int32Value> int32_field = 14;
+ map<int32,google.protobuf.UInt32Value> uint32_field = 15;
+ map<int32,google.protobuf.BoolValue> bool_field = 16;
+ map<int32,google.protobuf.StringValue> string_field = 17;
+ map<int32,google.protobuf.BytesValue> bytes_field = 18;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/wrappers.proto b/protoc_plugin/test/protos/google/protobuf/wrappers.proto
new file mode 100644
index 0000000..0194763
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/wrappers.proto
@@ -0,0 +1,118 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Wrappers for primitive (non-message) types. These types are useful
+// for embedding primitives in the `google.protobuf.Any` type and for places
+// where we need to distinguish between the absence of a primitive
+// typed field and its default value.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/wrappers";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "WrappersProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// Wrapper message for `double`.
+//
+// The JSON representation for `DoubleValue` is JSON number.
+message DoubleValue {
+ // The double value.
+ double value = 1;
+}
+
+// Wrapper message for `float`.
+//
+// The JSON representation for `FloatValue` is JSON number.
+message FloatValue {
+ // The float value.
+ float value = 1;
+}
+
+// Wrapper message for `int64`.
+//
+// The JSON representation for `Int64Value` is JSON string.
+message Int64Value {
+ // The int64 value.
+ int64 value = 1;
+}
+
+// Wrapper message for `uint64`.
+//
+// The JSON representation for `UInt64Value` is JSON string.
+message UInt64Value {
+ // The uint64 value.
+ uint64 value = 1;
+}
+
+// Wrapper message for `int32`.
+//
+// The JSON representation for `Int32Value` is JSON number.
+message Int32Value {
+ // The int32 value.
+ int32 value = 1;
+}
+
+// Wrapper message for `uint32`.
+//
+// The JSON representation for `UInt32Value` is JSON number.
+message UInt32Value {
+ // The uint32 value.
+ uint32 value = 1;
+}
+
+// Wrapper message for `bool`.
+//
+// The JSON representation for `BoolValue` is JSON `true` and `false`.
+message BoolValue {
+ // The bool value.
+ bool value = 1;
+}
+
+// Wrapper message for `string`.
+//
+// The JSON representation for `StringValue` is JSON string.
+message StringValue {
+ // The string value.
+ string value = 1;
+}
+
+// Wrapper message for `bytes`.
+//
+// The JSON representation for `BytesValue` is JSON string.
+message BytesValue {
+ // The bytes value.
+ bytes value = 1;
+}
diff --git a/protoc_plugin/test/protos/map_field.proto b/protoc_plugin/test/protos/map_field.proto
index db2a606..0017d72 100644
--- a/protoc_plugin/test/protos/map_field.proto
+++ b/protoc_plugin/test/protos/map_field.proto
@@ -27,6 +27,7 @@
map<string, int32> string_to_int32_field = 6;
map<uint32, int32> uint32_to_int32_field = 7;
map<int64, int32> int64_to_int32_field = 8;
+ map<uint64, int32> uint64_to_int32_field = 9;
}
message Inner {