Add method for retrieving extensions from unknown fields (#256)
diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md
index a151f52..bff643b 100644
--- a/protobuf/CHANGELOG.md
+++ b/protobuf/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.13.13
+
+* `Added `ExtensionRegistry.reparseMessage()` for decoding extensions from unknown fields after the initial
+ decoding.
+
## 0.13.12
* `BuilderInfo.add` now ignores fields with tag number 0.
diff --git a/protobuf/lib/src/protobuf/extension_registry.dart b/protobuf/lib/src/protobuf/extension_registry.dart
index 243d609..0892c2d 100644
--- a/protobuf/lib/src/protobuf/extension_registry.dart
+++ b/protobuf/lib/src/protobuf/extension_registry.dart
@@ -33,13 +33,91 @@
}
return null;
}
+
+ /// Returns a shallow copy of [message], with all extensions in [this] parsed
+ /// from the unknown fields of [message].
+ ///
+ /// Extensions already present in [message] will be preserved.
+ ///
+ /// If [message] is frozen, the result will be as well.
+ ///
+ /// Throws an [InvalidProtocolBufferException] if the parsed extensions are
+ /// malformed.
+ ///
+ /// Using this method to retrieve extensions is more expensive overall than
+ /// using an [ExtensionRegistry] with all the needed extensions when doing
+ /// [GeneratedMessage.fromBuffer].
+ ///
+ /// Example:
+ ///
+ /// `foo.proto`
+ /// ```proto
+ /// syntax = "proto2";
+ ///
+ /// message Foo {
+ /// extensions 1 to max;
+ /// }
+ ///
+ /// extend Foo {
+ /// optional string val1 = 1;
+ /// optional string val2 = 2;
+ /// }
+ /// ```
+ /// `main.dart`
+ /// ```
+ /// import 'package:protobuf/protobuf.dart';
+ /// import 'package:test/test.dart';
+ /// import 'src/generated/sample.pb.dart';
+ ///
+ /// void main() {
+ /// ExtensionRegistry r1 = ExtensionRegistry()..add(Sample.val1);
+ /// ExtensionRegistry r2 = ExtensionRegistry()..add(Sample.val2);
+ /// Foo original = Foo()..setExtension(Sample.val1, 'a')..setExtension(Sample.val2, 'b');
+ /// Foo withUnknownFields = Foo.fromBuffer(original.writeToBuffer());
+ /// Foo reparsed1 = r1.reparseMessage(withUnknownFields);
+ /// Foo reparsed2 = r2.reparseMessage(reparsed1);
+ /// expect(withUnknownFields.hasExtension(Sample.val1), isFalse);
+ /// expect(withUnknownFields.hasExtension(Sample.val2), isFalse);
+ /// expect(reparsed1.hasExtension(Sample.val1), isTrue);
+ /// expect(reparsed1.hasExtension(Sample.val2), isFalse);
+ /// expect(reparsed2.hasExtension(Sample.val1), isTrue);
+ /// expect(reparsed2.hasExtension(Sample.val2), isTrue);
+ /// }
+ /// ```
+ T reparseMessage<T extends GeneratedMessage>(T message) =>
+ _reparseMessage(message, this);
+}
+
+T _reparseMessage<T extends GeneratedMessage>(
+ T message, ExtensionRegistry extensionRegistry) {
+ T result = message.createEmptyInstance();
+
+ result._fieldSet._shallowCopyValues(message._fieldSet);
+ UnknownFieldSet resultUnknownFields = result._fieldSet._unknownFields;
+ if (resultUnknownFields != null) {
+ CodedBufferWriter codedBufferWriter = CodedBufferWriter();
+ extensionRegistry._extensions[message.info_.qualifiedMessageName]
+ ?.forEach((tagNumber, extension) {
+ final UnknownFieldSetField unknownField =
+ resultUnknownFields._fields[tagNumber];
+ if (unknownField != null) {
+ unknownField.writeTo(tagNumber, codedBufferWriter);
+ }
+ resultUnknownFields._fields.remove(tagNumber);
+ });
+
+ result.mergeFromBuffer(codedBufferWriter.toBuffer(), extensionRegistry);
+ }
+ if (message._fieldSet._isReadOnly) {
+ result.freeze();
+ }
+ return result;
}
class _EmptyExtensionRegistry implements ExtensionRegistry {
const _EmptyExtensionRegistry();
- // Needed to quiet missing member warning.
- get _extensions => null;
+ get _extensions => const <String, Map<int, Extension>>{};
void add(Extension extension) {
throw UnsupportedError('Immutable ExtensionRegistry');
@@ -50,4 +128,7 @@
}
Extension getExtension(String messageName, int tagNumber) => null;
+
+ T reparseMessage<T extends GeneratedMessage>(T message) =>
+ _reparseMessage(message, this);
}
diff --git a/protobuf/pubspec.yaml b/protobuf/pubspec.yaml
index 20e8044..9e8b5b0 100644
--- a/protobuf/pubspec.yaml
+++ b/protobuf/pubspec.yaml
@@ -1,5 +1,5 @@
name: protobuf
-version: 0.13.12
+version: 0.13.13
author: Dart Team <misc@dartlang.org>
description: >
Runtime library for protocol buffers support.
diff --git a/protoc_plugin/Makefile b/protoc_plugin/Makefile
index 1b5ee10..a12c57f 100644
--- a/protoc_plugin/Makefile
+++ b/protoc_plugin/Makefile
@@ -18,6 +18,7 @@
google/protobuf/unittest \
dart_name \
enum_extension \
+ extend_unittest \
ExtensionEnumNameConflict \
ExtensionNameConflict \
foo \
diff --git a/protoc_plugin/test/extension_test.dart b/protoc_plugin/test/extension_test.dart
index 8423ebf..a5c14bb 100644
--- a/protoc_plugin/test/extension_test.dart
+++ b/protoc_plugin/test/extension_test.dart
@@ -10,6 +10,7 @@
import '../out/protos/google/protobuf/unittest.pb.dart';
import '../out/protos/enum_extension.pb.dart';
+import '../out/protos/extend_unittest.pb.dart';
import '../out/protos/nested_extension.pb.dart';
import '../out/protos/non_nested_extension.pb.dart';
import '../out/protos/ExtensionNameConflict.pb.dart';
@@ -23,6 +24,17 @@
return true;
}));
+final withExtensions = TestAllExtensions()
+ ..setExtension(
+ Unittest.optionalForeignMessageExtension, ForeignMessage()..c = 3)
+ ..setExtension(Unittest.defaultStringExtension, 'bar')
+ ..getExtension(Unittest.repeatedBytesExtension).add('pop'.codeUnits)
+ ..setExtension(
+ Extend_unittest.outer,
+ Outer()
+ ..inner = (Inner()..value = 'abc')
+ ..setExtension(Extend_unittest.extensionInner, Inner()..value = 'def'));
+
void main() {
test('can set all extension types', () {
TestAllExtensions message = TestAllExtensions();
@@ -232,4 +244,146 @@
expect(decodedWithExtension == withExtension, false);
expect(decodedWithExtension == withExtension, false);
});
+
+ test(
+ 'ExtensionRegistry.reparseMessage will preserve already registered extensions',
+ () {
+ ExtensionRegistry r1 = ExtensionRegistry();
+ Unittest.registerAllExtensions(r1);
+
+ ExtensionRegistry r2 = ExtensionRegistry();
+ Extend_unittest.registerAllExtensions(r2);
+ final withUnknownFields =
+ TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+ final reparsedR1 = r1.reparseMessage(withUnknownFields);
+
+ expect(
+ reparsedR1.getExtension(Unittest.optionalForeignMessageExtension).c, 3);
+
+ expect(
+ r2
+ .reparseMessage(reparsedR1)
+ .getExtension(Unittest.optionalForeignMessageExtension)
+ .c,
+ 3);
+ });
+
+ test(
+ 'ExtensionRegistry.reparseMessage reparses extensions that were not in the original registry',
+ () {
+ ExtensionRegistry r = ExtensionRegistry();
+ Unittest.registerAllExtensions(r);
+ Extend_unittest.registerAllExtensions(r);
+
+ final withUnknownFields =
+ TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+ final reparsed = r.reparseMessage(withUnknownFields);
+
+ expect(withUnknownFields.getExtension(Unittest.defaultStringExtension),
+ 'hello');
+
+ expect(reparsed.getExtension(Unittest.defaultStringExtension), 'bar');
+
+ expect(
+ reparsed.unknownFields
+ .getField(Unittest.defaultStringExtension.tagNumber),
+ null,
+ reason:
+ 'ExtensionRegistry.reparseMessage does not leave reparsed fields in unknownFields');
+ expect(reparsed.getExtension(Unittest.repeatedBytesExtension),
+ ['pop'.codeUnits]);
+
+ expect(
+ reparsed.getExtension(Unittest.optionalForeignMessageExtension).c, 3);
+
+ expect(reparsed.getExtension(Extend_unittest.outer).inner.value, 'abc');
+
+ final onlyOuter = ExtensionRegistry()..add(Extend_unittest.outer);
+ final onlyOuterReparsed = onlyOuter.reparseMessage(withUnknownFields);
+ expect(
+ onlyOuterReparsed
+ .getExtension(Extend_unittest.outer)
+ .hasExtension(Extend_unittest.extensionInner),
+ isFalse);
+ expect(
+ onlyOuter
+ .reparseMessage(withUnknownFields)
+ .getExtension(Extend_unittest.outer)
+ .getExtension(Extend_unittest.extensionInner)
+ .value,
+ '');
+
+ expect(
+ reparsed
+ .getExtension(Extend_unittest.outer)
+ .hasExtension(Extend_unittest.extensionInner),
+ isTrue);
+ expect(
+ reparsed
+ .getExtension(Extend_unittest.outer)
+ .getExtension(Extend_unittest.extensionInner)
+ .value,
+ 'def');
+ });
+
+ test('ExtensionRegistry.reparseMessage does not update the original', () {
+ ExtensionRegistry r = ExtensionRegistry();
+ Unittest.registerAllExtensions(r);
+ Extend_unittest.registerAllExtensions(r);
+
+ final withUnknownFields =
+ TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+
+ final reparsedWithEmpty =
+ ExtensionRegistry().reparseMessage(withUnknownFields);
+ expect(reparsedWithEmpty, isNot(same(withUnknownFields)));
+
+ final reparsed = r.reparseMessage(withUnknownFields);
+
+ List<String> strings =
+ withUnknownFields.getExtension(Unittest.repeatedStringExtension);
+ expect(strings, []);
+ strings.add('pop2');
+ expect(reparsed.getExtension(Unittest.repeatedStringExtension), []);
+ });
+
+ test('ExtensionRegistry.reparseMessage will throw on malformed buffers', () {
+ final ExtensionRegistry r = ExtensionRegistry();
+ Unittest.registerAllExtensions(r);
+ final ExtensionRegistry r2 = ExtensionRegistry();
+
+ Extend_unittest.registerAllExtensions(r2);
+
+ // The message encoded in this buffer has an encoding error in the
+ // Extend_unittest.outer extension field.
+ final withMalformedExtensionEncoding = TestAllExtensions.fromBuffer([
+ 154, 1, 2, 8, 3, 210, //
+ 4, 3, 98, 97, 114, 194, 6, 14, 10, 5, 10, 4,
+ 97, 98, 99, 18, 5, 10, 3, 100, 101, 102
+ ]);
+ expect(
+ r
+ .reparseMessage(withMalformedExtensionEncoding)
+ .getExtension(Unittest.defaultStringExtension),
+ 'bar',
+ reason:
+ 'this succeeds because it does not decode Extend_unittest.outer');
+ expect(() => r2.reparseMessage(withMalformedExtensionEncoding),
+ throwsA(const TypeMatcher<InvalidProtocolBufferException>()));
+ });
+
+ test('ExtensionRegistry.reparseMessage preserves frozenness', () {
+ final ExtensionRegistry r = ExtensionRegistry();
+ Unittest.registerAllExtensions(r);
+
+ final withUnknownFields =
+ TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+ withUnknownFields.freeze();
+
+ expect(
+ () => r
+ .reparseMessage(withUnknownFields)
+ .setExtension(Unittest.defaultStringExtension, 'blah'),
+ throwsA(TypeMatcher<UnsupportedError>()));
+ });
}
diff --git a/protoc_plugin/test/protos/extend_unittest.proto b/protoc_plugin/test/protos/extend_unittest.proto
new file mode 100644
index 0000000..cdbdaee
--- /dev/null
+++ b/protoc_plugin/test/protos/extend_unittest.proto
@@ -0,0 +1,24 @@
+// 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.
+
+syntax = "proto2";
+
+import "google/protobuf/unittest.proto";
+
+message Outer {
+ optional Inner inner = 1;
+ extensions 2 to max;
+}
+
+extend Outer {
+ optional Inner extension_inner = 2;
+}
+
+message Inner {
+ optional string value = 1;
+}
+
+extend protobuf_unittest.TestAllExtensions {
+ optional Outer outer = 104;
+}