Permit unknown enum values when ignoreUnknownFields=true. (#354)
This change allows for the backend to add additional values to an enum without parsing failures occurring on the client.
This is consistent with Java behavior.
diff --git a/protobuf/lib/src/protobuf/generated_message.dart b/protobuf/lib/src/protobuf/generated_message.dart
index 13f4dfb..a22d8c4 100644
--- a/protobuf/lib/src/protobuf/generated_message.dart
+++ b/protobuf/lib/src/protobuf/generated_message.dart
@@ -227,8 +227,8 @@
/// Well-known types and their special JSON encoding are supported.
///
/// If [ignoreUnknownFields] is `false` (the default) an
- /// [FormatException] is be thrown if an unknown field name
- /// is encountered. Otherwise the unknown field is ignored.
+ /// [FormatException] is be thrown if an unknown field or enum name
+ /// is encountered. Otherwise the unknown field or enum 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
diff --git a/protobuf/lib/src/protobuf/proto3_json.dart b/protobuf/lib/src/protobuf/proto3_json.dart
index f0a03b2..f76f065 100644
--- a/protobuf/lib/src/protobuf/proto3_json.dart
+++ b/protobuf/lib/src/protobuf/proto3_json.dart
@@ -202,11 +202,14 @@
orElse: () => null)
: fieldInfo.enumValues
.firstWhere((e) => e.name == value, orElse: () => null);
- if (result != null) return result;
+ if ((result != null) || ignoreUnknownFields) return result;
throw context.parseException('Unknown enum value', value);
} else if (value is int) {
return fieldInfo.valueOf(value) ??
- (throw context.parseException('Unknown enum value', value));
+ (ignoreUnknownFields
+ ? null
+ : (throw context.parseException(
+ 'Unknown enum value', value)));
}
throw context.parseException(
'Expected enum as a string or integer', value);
diff --git a/protobuf/test/json_test.dart b/protobuf/test/json_test.dart
index ce2f792..c94c304 100644
--- a/protobuf/test/json_test.dart
+++ b/protobuf/test/json_test.dart
@@ -4,10 +4,11 @@
library json_test;
import 'dart:convert';
+
import 'package:fixnum/fixnum.dart' show Int64;
import 'package:test/test.dart';
-import 'mock_util.dart' show T;
+import 'mock_util.dart' show T, mockEnumValues;
void main() {
var example = T()
@@ -15,6 +16,54 @@
..str = 'hello'
..int32s.addAll(<int>[1, 2, 3]);
+ test('testProto3JsonEnum', () {
+ // No enum value specified.
+ expect(example.hasEnm, isFalse);
+ // Defaults to first when it doesn't exist.
+ expect(example.enm, equals(mockEnumValues.first));
+ expect((example..mergeFromProto3Json({'enm': 'a'})).enm.name, equals('a'));
+ // Now it's explicitly set after merging.
+ expect(example.hasEnm, isTrue);
+
+ expect((example..mergeFromProto3Json({'enm': 'b'})).enm.name, equals('b'));
+ // "c" is not a legal enum value.
+ expect(
+ () => example..mergeFromProto3Json({'enm': 'c'}),
+ throwsA(allOf(isFormatException,
+ predicate((e) => e.message.contains('Unknown enum value')))));
+ // `example` hasn't changed.
+ expect(example.hasEnm, isTrue);
+ expect(example.enm.name, equals('b'));
+
+ // "c" is not a legal enum value, but we are ignoring unknown fields, so
+ // default behavior is to unset `enm`, returning the default value "a"
+ expect(
+ (example..mergeFromProto3Json({'enm': 'c'}, ignoreUnknownFields: true))
+ .enm
+ .name,
+ equals('a'));
+ expect(example.hasEnm, isFalse);
+
+ // Same for index values...
+ expect((example..mergeFromProto3Json({'enm': 2})).enm.name, 'b');
+ expect(
+ () => example..mergeFromProto3Json({'enm': 3}),
+ throwsA(allOf(isFormatException,
+ predicate((e) => e.message.contains('Unknown enum value')))));
+ // `example` hasn't changed.
+ expect(example.hasEnm, isTrue);
+ expect(example.enm.name, equals('b'));
+
+ // "c" is not a legal enum value, but we are ignoring unknown fields, so
+ // default behavior is to unset `enm`, returning the default value "a"
+ expect(
+ (example..mergeFromProto3Json({'enm': 3}, ignoreUnknownFields: true))
+ .enm
+ .name,
+ equals('a'));
+ expect(example.hasEnm, isFalse);
+ });
+
test('testWriteToJson', () {
var json = example.writeToJson();
checkJsonMap(jsonDecode(json));
diff --git a/protobuf/test/map_mixin_test.dart b/protobuf/test/map_mixin_test.dart
index ec60cbb..e37529d 100644
--- a/protobuf/test/map_mixin_test.dart
+++ b/protobuf/test/map_mixin_test.dart
@@ -33,7 +33,7 @@
expect(r.isEmpty, false);
expect(r.isNotEmpty, true);
- expect(r.keys, ['val', 'str', 'child', 'int32s', 'int64']);
+ expect(r.keys, ['val', 'str', 'child', 'int32s', 'int64', 'enm']);
expect(r['val'], 42);
expect(r['str'], '');
@@ -42,11 +42,12 @@
expect(r['int32s'], []);
var v = r.values;
- expect(v.length, 5);
+ expect(v.length, 6);
expect(v.first, 42);
expect(v.toList()[1], '');
expect(v.toList()[3].toString(), '[]');
- expect(v.last, 0);
+ expect(v.toList()[4], 0);
+ expect(v.toList()[5].name, 'a');
});
test('operator []= sets record fields', () {
diff --git a/protobuf/test/mock_util.dart b/protobuf/test/mock_util.dart
index 2153dfc..565a5bf 100644
--- a/protobuf/test/mock_util.dart
+++ b/protobuf/test/mock_util.dart
@@ -6,15 +6,27 @@
import 'package:fixnum/fixnum.dart' show Int64;
import 'package:protobuf/protobuf.dart'
- show GeneratedMessage, BuilderInfo, CreateBuilderFunc, PbFieldType;
+ show
+ BuilderInfo,
+ CreateBuilderFunc,
+ GeneratedMessage,
+ PbFieldType,
+ ProtobufEnum;
+final mockEnumValues = [ProtobufEnum(1, 'a'), ProtobufEnum(2, 'b')];
BuilderInfo mockInfo(String className, CreateBuilderFunc create) {
return BuilderInfo(className)
..a(1, 'val', PbFieldType.O3, defaultOrMaker: 42)
..a(2, 'str', PbFieldType.OS)
..a(3, 'child', PbFieldType.OM, defaultOrMaker: create, subBuilder: create)
..p<int>(4, 'int32s', PbFieldType.P3)
- ..a(5, 'int64', PbFieldType.O6);
+ ..a(5, 'int64', PbFieldType.O6)
+ // 6 is reserved for extensions in other tests.
+ ..e(7, 'enm', PbFieldType.OE,
+ defaultOrMaker: mockEnumValues.first,
+ valueOf: (i) =>
+ mockEnumValues.firstWhere((e) => e.value == i, orElse: () => null),
+ enumValues: mockEnumValues);
}
/// A minimal protobuf implementation for testing.
@@ -37,6 +49,9 @@
Int64 get int64 => $_get(4, Int64(0));
set int64(x) => setField(5, x);
+ ProtobufEnum get enm => $_getN(5);
+ bool get hasEnm => $_has(5);
+
@override
GeneratedMessage clone() {
var create = info_.byName['child'].subBuilder;