From e6e1c3326974ccc68e95730caab550ae92eb43e2 Mon Sep 17 00:00:00 2001 From: Agam Agarwal Date: Mon, 12 May 2025 16:09:24 +0530 Subject: [PATCH 1/4] Add fromDart() and toDart() methods to convert between $core.Duration and proto Duration. --- protoc_plugin/lib/src/base_type.dart | 10 ++-- protoc_plugin/lib/src/file_generator.dart | 3 +- protoc_plugin/lib/src/message_generator.dart | 2 +- protoc_plugin/lib/src/protobuf_field.dart | 8 ++-- protoc_plugin/lib/src/shared.dart | 1 + protoc_plugin/lib/src/well_known_types.dart | 24 ++++++++-- protoc_plugin/test/duration_test.dart | 48 ++++++++++++++++++++ 7 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 protoc_plugin/test/duration_test.dart diff --git a/protoc_plugin/lib/src/base_type.dart b/protoc_plugin/lib/src/base_type.dart index c49810812..10bd42d63 100644 --- a/protoc_plugin/lib/src/base_type.dart +++ b/protoc_plugin/lib/src/base_type.dart @@ -89,19 +89,19 @@ class BaseType { 'SF3', '$coreImportPrefix.int', r'$_setSignedInt32', null); case FieldDescriptorProto_Type.TYPE_INT64: return const BaseType._raw(FieldDescriptorProto_Type.TYPE_INT64, '6', - '$_fixnumImportPrefix.Int64', r'$_setInt64', null); + '$fixnumImportPrefix.Int64', r'$_setInt64', null); case FieldDescriptorProto_Type.TYPE_UINT64: return const BaseType._raw(FieldDescriptorProto_Type.TYPE_UINT64, 'U6', - '$_fixnumImportPrefix.Int64', r'$_setInt64', null); + '$fixnumImportPrefix.Int64', r'$_setInt64', null); case FieldDescriptorProto_Type.TYPE_SINT64: return const BaseType._raw(FieldDescriptorProto_Type.TYPE_SINT64, 'S6', - '$_fixnumImportPrefix.Int64', r'$_setInt64', null); + '$fixnumImportPrefix.Int64', r'$_setInt64', null); case FieldDescriptorProto_Type.TYPE_FIXED64: return const BaseType._raw(FieldDescriptorProto_Type.TYPE_FIXED64, 'F6', - '$_fixnumImportPrefix.Int64', r'$_setInt64', null); + '$fixnumImportPrefix.Int64', r'$_setInt64', null); case FieldDescriptorProto_Type.TYPE_SFIXED64: return const BaseType._raw(FieldDescriptorProto_Type.TYPE_SFIXED64, - 'SF6', '$_fixnumImportPrefix.Int64', r'$_setInt64', null); + 'SF6', '$fixnumImportPrefix.Int64', r'$_setInt64', null); case FieldDescriptorProto_Type.TYPE_STRING: return const BaseType._raw(FieldDescriptorProto_Type.TYPE_STRING, 'S', '$coreImportPrefix.String', r'$_setString', null); diff --git a/protoc_plugin/lib/src/file_generator.dart b/protoc_plugin/lib/src/file_generator.dart index 220a325ab..0eb7a3564 100644 --- a/protoc_plugin/lib/src/file_generator.dart +++ b/protoc_plugin/lib/src/file_generator.dart @@ -12,7 +12,6 @@ const String _convertImportPrefix = r'$convert'; const String _convertImportUrl = 'dart:convert'; const String _coreImportUrl = 'dart:core'; -const String _fixnumImportPrefix = r'$fixnum'; const String _grpcImportUrl = 'package:grpc/service_api.dart'; const String _protobufImportUrl = 'package:protobuf/protobuf.dart'; @@ -318,7 +317,7 @@ class FileGenerator extends ProtobufContainer { if (_needsFixnumImport) { importWriter.addImport('package:fixnum/fixnum.dart', - prefix: _fixnumImportPrefix); + prefix: fixnumImportPrefix); } if (_needsProtobufImport) { diff --git a/protoc_plugin/lib/src/message_generator.dart b/protoc_plugin/lib/src/message_generator.dart index f05d16de3..87465592a 100644 --- a/protoc_plugin/lib/src/message_generator.dart +++ b/protoc_plugin/lib/src/message_generator.dart @@ -680,7 +680,7 @@ class MessageGenerator extends ProtobufContainer { } return '\$_getI($index, $defaultExpr)'; } - if (fieldType == '$_fixnumImportPrefix.Int64' && defaultExpr == 'null') { + if (fieldType == '$fixnumImportPrefix.Int64' && defaultExpr == 'null') { return '\$_getI64($index)'; } if (defaultExpr == 'null') { diff --git a/protoc_plugin/lib/src/protobuf_field.dart b/protoc_plugin/lib/src/protobuf_field.dart index 7906cbec8..5ea7ec0e2 100644 --- a/protoc_plugin/lib/src/protobuf_field.dart +++ b/protoc_plugin/lib/src/protobuf_field.dart @@ -111,7 +111,7 @@ class ProtobufField { /// Whether this field uses the Int64 from the fixnum package. bool get needsFixnumImport => - baseType.unprefixed == '$_fixnumImportPrefix.Int64'; + baseType.unprefixed == '$fixnumImportPrefix.Int64'; /// Whether this field is a map field definition: /// `map map_field = N`. @@ -302,8 +302,8 @@ class ProtobufField { break; } } else { - if (makeDefault == '$_fixnumImportPrefix.Int64.ZERO' && - type == '$_fixnumImportPrefix.Int64' && + if (makeDefault == '$fixnumImportPrefix.Int64.ZERO' && + type == '$fixnumImportPrefix.Int64' && typeConstant == '$protobufImportPrefix.PbFieldType.O6') { invocation = 'aInt64'; } else { @@ -388,7 +388,7 @@ class ProtobufField { case FieldDescriptorProto_Type.TYPE_SFIXED64: var value = '0'; if (descriptor.hasDefaultValue()) value = descriptor.defaultValue; - if (value == '0') return '$_fixnumImportPrefix.Int64.ZERO'; + if (value == '0') return '$fixnumImportPrefix.Int64.ZERO'; return "$protobufImportPrefix.parseLongInt('$value')"; case FieldDescriptorProto_Type.TYPE_STRING: return _getDefaultAsStringExpr(null); diff --git a/protoc_plugin/lib/src/shared.dart b/protoc_plugin/lib/src/shared.dart index 7ecdb01ef..446f12d41 100644 --- a/protoc_plugin/lib/src/shared.dart +++ b/protoc_plugin/lib/src/shared.dart @@ -12,6 +12,7 @@ const protobufImportPrefix = r'$pb'; const asyncImportPrefix = r'$async'; const coreImportPrefix = r'$core'; const grpcImportPrefix = r'$grpc'; +const fixnumImportPrefix = r'$fixnum'; const mixinImportPrefix = r'$mixin'; extension FileDescriptorProtoExt on FileGenerator { diff --git a/protoc_plugin/lib/src/well_known_types.dart b/protoc_plugin/lib/src/well_known_types.dart index 24e360b07..ff6a31937 100644 --- a/protoc_plugin/lib/src/well_known_types.dart +++ b/protoc_plugin/lib/src/well_known_types.dart @@ -12,9 +12,9 @@ const _wellKnownImportPath = const _wellKnownMixins = { 'google.protobuf.Any': PbMixin('AnyMixin', - importFrom: _wellKnownImportPath, - injectedHelpers: [ - ''' + importFrom: _wellKnownImportPath, + injectedHelpers: [ + ''' /// Creates a new [Any] encoding [message]. /// /// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is @@ -45,6 +45,24 @@ static Timestamp fromDateTime($coreImportPrefix.DateTime dateTime) { 'google.protobuf.Duration': PbMixin( 'DurationMixin', importFrom: _wellKnownImportPath, + injectedHelpers: [ + ''' +/// Converts the [Duration] to [$coreImportPrefix.Duration]. +/// +/// This is a lossy conversion, as [$coreImportPrefix.Duration] is limited to [int] +/// microseconds and also does not support nanosecond precision. +$coreImportPrefix.Duration toDart() => + $coreImportPrefix.Duration( + seconds: seconds.toInt(), + microseconds: nanos ~/ 1000, + ); + +/// Creates a new instance from [$coreImportPrefix.Duration]. +static Duration fromDart($coreImportPrefix.Duration duration) => Duration() + ..seconds = $fixnumImportPrefix.Int64(duration.inSeconds) + ..nanos = (duration.inMicroseconds % $coreImportPrefix.Duration.microsecondsPerSecond) * 1000; +''', + ], hasProto3JsonHelpers: true, ), 'google.protobuf.Struct': PbMixin( diff --git a/protoc_plugin/test/duration_test.dart b/protoc_plugin/test/duration_test.dart new file mode 100644 index 000000000..bb365eea6 --- /dev/null +++ b/protoc_plugin/test/duration_test.dart @@ -0,0 +1,48 @@ +// Copyright (c) 2025, 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 'package:fixnum/fixnum.dart'; +import 'package:protoc_plugin/test/protos/google/protobuf/duration.pb.dart' + as pb; +import 'package:test/test.dart'; + +void main() { + test('core duration -> proto duration', () { + final coreDuration = Duration( + days: 1, + hours: 2, + minutes: 3, + seconds: 4, + milliseconds: 5, + microseconds: 6, + ); + + final protoDuration = pb.Duration.fromDart(coreDuration); + + expect(protoDuration.seconds, Int64(86400 + 3600 * 2 + 60 * 3 + 4)); + expect(protoDuration.nanos, 5006000); + }); + + test('core duration -> proto duration -> core duration', () { + final coreDuration = Duration( + days: 1, + hours: 2, + minutes: 3, + seconds: 4, + milliseconds: 5, + microseconds: 6, + ); + + expect(pb.Duration.fromDart(coreDuration).toDart(), coreDuration); + }); + + test('proto duration -> core duration -> proto duration', () { + final protoDuration = + pb.Duration() + ..seconds = Int64(987654321) + ..nanos = 999999000; + + expect(pb.Duration.fromDart(protoDuration.toDart()), protoDuration); + }); +} From dc1101072ab451ffffe3d0e262bcf65bf4aed853 Mon Sep 17 00:00:00 2001 From: Agam Agarwal Date: Mon, 12 May 2025 16:30:50 +0530 Subject: [PATCH 2/4] Update Changelog for #986 --- protoc_plugin/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md index ed3665388..b47f38c0a 100644 --- a/protoc_plugin/CHANGELOG.md +++ b/protoc_plugin/CHANGELOG.md @@ -3,9 +3,12 @@ * Fix factory argument types for protobuf `Map` fields. ([#975]) * Fix import order changes when files are passed in different order to `protoc`. ([#952]) +* Add fromDart() and toDart() methods to convert between core Duration and proto + Duration ([#986]) [#975]: https://github.com/google/protobuf.dart/issues/975 [#952]: https://github.com/google/protobuf.dart/issues/952 +[#986]: https://github.com/google/protobuf.dart/issues/986 ## 22.0.1 From d404901a0896c925fcbab59fdcd5ce118b6519ac Mon Sep 17 00:00:00 2001 From: Agam Agarwal Date: Mon, 12 May 2025 17:55:13 +0530 Subject: [PATCH 3/4] Fix import for output duration.pb.dart --- protoc_plugin/test/duration_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protoc_plugin/test/duration_test.dart b/protoc_plugin/test/duration_test.dart index bb365eea6..772765a7d 100644 --- a/protoc_plugin/test/duration_test.dart +++ b/protoc_plugin/test/duration_test.dart @@ -3,10 +3,10 @@ // BSD-style license that can be found in the LICENSE file. import 'package:fixnum/fixnum.dart'; -import 'package:protoc_plugin/test/protos/google/protobuf/duration.pb.dart' - as pb; import 'package:test/test.dart'; +import '../out/protos/google/protobuf/duration.pb.dart' as pb; + void main() { test('core duration -> proto duration', () { final coreDuration = Duration( From 85c1122fa968d48499d2650de4125638e5f72972 Mon Sep 17 00:00:00 2001 From: Agam Agarwal Date: Mon, 12 May 2025 17:59:37 +0530 Subject: [PATCH 4/4] Ran dart format --- protoc_plugin/lib/src/well_known_types.dart | 6 +++--- protoc_plugin/test/duration_test.dart | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/protoc_plugin/lib/src/well_known_types.dart b/protoc_plugin/lib/src/well_known_types.dart index ff6a31937..f9ee9090b 100644 --- a/protoc_plugin/lib/src/well_known_types.dart +++ b/protoc_plugin/lib/src/well_known_types.dart @@ -12,9 +12,9 @@ const _wellKnownImportPath = const _wellKnownMixins = { 'google.protobuf.Any': PbMixin('AnyMixin', - importFrom: _wellKnownImportPath, - injectedHelpers: [ - ''' + importFrom: _wellKnownImportPath, + injectedHelpers: [ + ''' /// Creates a new [Any] encoding [message]. /// /// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is diff --git a/protoc_plugin/test/duration_test.dart b/protoc_plugin/test/duration_test.dart index 772765a7d..8085dfdbd 100644 --- a/protoc_plugin/test/duration_test.dart +++ b/protoc_plugin/test/duration_test.dart @@ -38,10 +38,9 @@ void main() { }); test('proto duration -> core duration -> proto duration', () { - final protoDuration = - pb.Duration() - ..seconds = Int64(987654321) - ..nanos = 999999000; + final protoDuration = pb.Duration() + ..seconds = Int64(987654321) + ..nanos = 999999000; expect(pb.Duration.fromDart(protoDuration.toDart()), protoDuration); });