Skip to content

Commit 8b4f44c

Browse files
authored
fix(dart): Make it so that BYTES_TYPE fields are not nullable (#3122)
Previously, all `BYTES_TYPE` fields were nullable and assigned null by default because they are of type `Uint8List` and the `Uint8List` constructor is non-const, which means that they can't be used as a default argument. This change makes them non-nullable (unless they are optional). For example: ```diff final class ComplianceData extends ProtoMessage { static const String fullyQualifiedName = 'google.showcase.v1beta1.ComplianceData'; final bool fBool; - final Uint8List? fBytes; + final Uint8List fBytes; final ComplianceData_LifeKingdom fKingdom; ComplianceData({ this.fBool = false, - this.fBytes, + Uint8List? fBytes, this.fKingdom = ComplianceData_LifeKingdom.$default, }) : super(fullyQualifiedName); }) : fBytes = fBytes ?? Uint8List(0), super(fullyQualifiedName); factory ComplianceData.fromJson(Map<String, dynamic> json) => ComplianceData( fBool: json['fBool'] ?? false, - fBytes: decodeBytes(json['fBytes']), + fBytes: decodeBytes(json['fBytes']) ?? Uint8List(0), if (fKingdom.isNotDefault) 'fKingdom': fKingdom.toJson(), ); Object toJson() => { if (fBool.isNotDefault) 'fBool': fBool, - if (fBytes != null) 'fBytes': encodeBytes(fBytes), + if (fBytes.isNotDefault) 'fBytes': encodeBytes(fBytes), if (fKingdom.isNotDefault) 'fKingdom': fKingdom.toJson(), }; ```
1 parent 403ea7a commit 8b4f44c

File tree

3 files changed

+72
-73
lines changed

3 files changed

+72
-73
lines changed

internal/sidekick/dart/annotate.go

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,27 @@ var omitGeneration = map[string]string{
3434
".google.protobuf.Value": "",
3535
}
3636

37+
var defaultValues = map[api.Typez]struct {
38+
Value string
39+
IsConst bool
40+
}{
41+
api.BOOL_TYPE: {"false", true},
42+
api.BYTES_TYPE: {"Uint8List(0)", false},
43+
api.DOUBLE_TYPE: {"0", true},
44+
api.FIXED32_TYPE: {"0", true},
45+
api.FIXED64_TYPE: {"0", true},
46+
api.FLOAT_TYPE: {"0", true},
47+
api.INT32_TYPE: {"0", true},
48+
api.INT64_TYPE: {"0", true},
49+
api.SFIXED32_TYPE: {"0", true},
50+
api.SFIXED64_TYPE: {"0", true},
51+
api.SINT32_TYPE: {"0", true},
52+
api.SINT64_TYPE: {"0", true},
53+
api.STRING_TYPE: {"''", true},
54+
api.UINT32_TYPE: {"0", true},
55+
api.UINT64_TYPE: {"0", true},
56+
}
57+
3758
type modelAnnotations struct {
3859
Parent *api.API
3960
// The Dart package name (e.g. google_cloud_secretmanager).
@@ -163,9 +184,12 @@ type fieldAnnotation struct {
163184
Required bool
164185
Nullable bool
165186
FieldBehaviorRequired bool
166-
DefaultValue string
167-
FromJson string
168-
ToJson string
187+
// The default value for the string, e.g. "0" for an integer type.
188+
DefaultValue string
189+
// Whether the default value is constant or not, e.g. "0" is constant but "Uint8List(0)" is not.
190+
ConstDefault bool
191+
FromJson string
192+
ToJson string
169193
}
170194

171195
type enumAnnotation struct {
@@ -743,33 +767,11 @@ func (annotate *annotateModel) annotateField(field *api.Field) {
743767
}
744768
}
745769

746-
// We interpret proto implicit presence as non-nullable for Dart.
747-
required := implicitPresence
748-
749-
// We can't make 'bytes' required, as UInt8List in Dart can't be const.
750-
if field.Typez == api.BYTES_TYPE {
751-
required = false
752-
}
753-
754770
// Calculate the default field value.
755-
defaultValues := map[api.Typez]string{
756-
api.BOOL_TYPE: "false",
757-
api.DOUBLE_TYPE: "0",
758-
api.FIXED32_TYPE: "0",
759-
api.FIXED64_TYPE: "0",
760-
api.FLOAT_TYPE: "0",
761-
api.INT32_TYPE: "0",
762-
api.INT64_TYPE: "0",
763-
api.SFIXED32_TYPE: "0",
764-
api.SFIXED64_TYPE: "0",
765-
api.SINT32_TYPE: "0",
766-
api.SINT64_TYPE: "0",
767-
api.STRING_TYPE: "''",
768-
api.UINT32_TYPE: "0",
769-
api.UINT64_TYPE: "0",
770-
}
771771
defaultValue := ""
772-
if required {
772+
constDefault := true
773+
fieldRequired := slices.Contains(field.Behavior, api.FIELD_BEHAVIOR_REQUIRED)
774+
if implicitPresence && !fieldRequired {
773775
switch {
774776
case field.Repeated:
775777
defaultValue = "const []"
@@ -781,22 +783,22 @@ func (annotate *annotateModel) annotateField(field *api.Field) {
781783
typeName := annotate.resolveEnumName(annotate.state.EnumByID[field.TypezID])
782784
defaultValue = fmt.Sprintf("%s.$default", typeName)
783785
default:
784-
defaultValue = defaultValues[field.Typez]
786+
defaultValue = defaultValues[field.Typez].Value
787+
constDefault = defaultValues[field.Typez].IsConst
785788
}
786789
}
787-
788790
state := annotate.state
789-
790791
field.Codec = &fieldAnnotation{
791792
Name: fieldName(field),
792793
Type: annotate.fieldType(field),
793794
DocLines: formatDocComments(field.Documentation, state),
794-
Required: required,
795-
Nullable: !required,
796-
FieldBehaviorRequired: slices.Contains(field.Behavior, api.FIELD_BEHAVIOR_REQUIRED),
795+
Required: implicitPresence,
796+
Nullable: !implicitPresence,
797+
FieldBehaviorRequired: fieldRequired,
797798
DefaultValue: defaultValue,
798-
FromJson: annotate.createFromJsonLine(field, state, required),
799-
ToJson: createToJsonLine(field, state, required),
799+
FromJson: annotate.createFromJsonLine(field, state, implicitPresence),
800+
ToJson: createToJsonLine(field, state, implicitPresence),
801+
ConstDefault: constDefault,
800802
}
801803
}
802804

@@ -817,24 +819,7 @@ func (annotate *annotateModel) createFromJsonLine(field *api.Field, state *api.A
817819
typeName := annotate.resolveEnumName(annotate.state.EnumByID[field.TypezID])
818820
bang = fmt.Sprintf(" ?? %s.$default", typeName)
819821
default:
820-
defaultValues := map[api.Typez]string{
821-
api.BOOL_TYPE: "false",
822-
api.BYTES_TYPE: "Uint8List()",
823-
api.DOUBLE_TYPE: "0",
824-
api.FIXED32_TYPE: "0",
825-
api.FIXED64_TYPE: "0",
826-
api.FLOAT_TYPE: "0",
827-
api.INT32_TYPE: "0",
828-
api.INT64_TYPE: "0",
829-
api.SFIXED32_TYPE: "0",
830-
api.SFIXED64_TYPE: "0",
831-
api.SINT32_TYPE: "0",
832-
api.SINT64_TYPE: "0",
833-
api.STRING_TYPE: "''",
834-
api.UINT32_TYPE: "0",
835-
api.UINT64_TYPE: "0",
836-
}
837-
bang = fmt.Sprintf(" ?? %s", defaultValues[field.Typez])
822+
bang = fmt.Sprintf(" ?? %s", defaultValues[field.Typez].Value)
838823
}
839824
}
840825

internal/sidekick/dart/annotate_test.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,9 @@ func TestBuildQueryLines(t *testing.T) {
519519
{
520520
&api.Field{Name: "bool", JSONName: "bool", Typez: api.BOOL_TYPE},
521521
[]string{"if (result.bool$ case final $1 when $1.isNotDefault) 'bool': '${$1}'"},
522+
}, {
523+
&api.Field{Name: "bytes", JSONName: "bytes", Typez: api.BYTES_TYPE},
524+
[]string{"if (result.bytes case final $1 when $1.isNotDefault) 'bytes': encodeBytes($1)!"},
522525
}, {
523526
&api.Field{Name: "int32", JSONName: "int32", Typez: api.INT32_TYPE},
524527
[]string{"if (result.int32 case final $1 when $1.isNotDefault) 'int32': '${$1}'"},
@@ -549,6 +552,9 @@ func TestBuildQueryLines(t *testing.T) {
549552
{
550553
&api.Field{Name: "bool_opt", JSONName: "bool", Typez: api.BOOL_TYPE, Optional: true},
551554
[]string{"if (result.boolOpt case final $1?) 'bool': '${$1}'"},
555+
}, {
556+
&api.Field{Name: "bytes_opt", JSONName: "bytes", Typez: api.BYTES_TYPE, Optional: true},
557+
[]string{"if (result.bytesOpt case final $1?) 'bytes': encodeBytes($1)!"},
552558
}, {
553559
&api.Field{Name: "int32_opt", JSONName: "int32", Typez: api.INT32_TYPE, Optional: true},
554560
[]string{"if (result.int32Opt case final $1?) 'int32': '${$1}'"},
@@ -585,6 +591,9 @@ func TestBuildQueryLines(t *testing.T) {
585591
{
586592
&api.Field{Name: "boolList", JSONName: "boolList", Typez: api.BOOL_TYPE, Repeated: true},
587593
[]string{"if (result.boolList case final $1 when $1.isNotDefault) 'boolList': $1.map((e) => '$e')"},
594+
}, {
595+
&api.Field{Name: "bytesList", JSONName: "bytesList", Typez: api.BYTES_TYPE, Repeated: true},
596+
[]string{"if (result.bytesList case final $1 when $1.isNotDefault) 'bytesList': $1.map((e) => encodeBytes(e)!)"},
588597
}, {
589598
&api.Field{Name: "int32List", JSONName: "int32List", Typez: api.INT32_TYPE, Repeated: true},
590599
[]string{"if (result.int32List case final $1 when $1.isNotDefault) 'int32List': $1.map((e) => '$e')"},
@@ -604,15 +613,6 @@ func TestBuildQueryLines(t *testing.T) {
604613
&api.Field{Name: "int32List_opt", JSONName: "int32List", Typez: api.INT32_TYPE, Repeated: true, Optional: true},
605614
[]string{"if (result.int32ListOpt case final $1 when $1.isNotDefault) 'int32List': $1.map((e) => '$e')"},
606615
},
607-
608-
// bytes, repeated bytes
609-
{
610-
&api.Field{Name: "bytes", JSONName: "bytes", Typez: api.BYTES_TYPE},
611-
[]string{"if (result.bytes case final $1?) 'bytes': encodeBytes($1)!"},
612-
}, {
613-
&api.Field{Name: "bytesList", JSONName: "bytesList", Typez: api.BYTES_TYPE, Repeated: true},
614-
[]string{"if (result.bytesList case final $1?) 'bytesList': $1.map((e) => encodeBytes(e)!)"},
615-
},
616616
} {
617617
t.Run(test.field.Name, func(t *testing.T) {
618618
message := &api.Message{
@@ -853,6 +853,9 @@ func TestCreateFromJsonLine(t *testing.T) {
853853
{
854854
&api.Field{Name: "bool", JSONName: "bool", Typez: api.BOOL_TYPE},
855855
"json['bool'] ?? false",
856+
}, {
857+
&api.Field{Name: "bytes", JSONName: "bytes", Typez: api.BYTES_TYPE},
858+
"decodeBytes(json['bytes']) ?? Uint8List(0)",
856859
}, {
857860
&api.Field{Name: "int32", JSONName: "int32", Typez: api.INT32_TYPE},
858861
"json['int32'] ?? 0",
@@ -868,6 +871,9 @@ func TestCreateFromJsonLine(t *testing.T) {
868871
{
869872
&api.Field{Name: "bool_opt", JSONName: "bool", Typez: api.BOOL_TYPE, Optional: true},
870873
"json['bool']",
874+
}, {
875+
&api.Field{Name: "bytes_opt", JSONName: "bytes", Typez: api.BYTES_TYPE, Optional: true},
876+
"decodeBytes(json['bytes'])",
871877
}, {
872878
&api.Field{Name: "int32_opt", JSONName: "int32", Typez: api.INT32_TYPE, Optional: true},
873879
"json['int32']",
@@ -886,6 +892,9 @@ func TestCreateFromJsonLine(t *testing.T) {
886892
{
887893
&api.Field{Name: "boolList", JSONName: "boolList", Typez: api.BOOL_TYPE, Repeated: true},
888894
"decodeList(json['boolList']) ?? []",
895+
}, {
896+
&api.Field{Name: "bytesList", JSONName: "bytesList", Typez: api.BYTES_TYPE, Repeated: true},
897+
"decodeListBytes(json['bytesList']) ?? []",
889898
}, {
890899
&api.Field{Name: "int32List", JSONName: "int32List", Typez: api.INT32_TYPE, Repeated: true},
891900
"decodeList(json['int32List']) ?? []",
@@ -900,15 +909,6 @@ func TestCreateFromJsonLine(t *testing.T) {
900909
"decodeList(json['int32List']) ?? []",
901910
},
902911

903-
// bytes, repeated bytes
904-
{
905-
&api.Field{Name: "bytes", JSONName: "bytes", Typez: api.BYTES_TYPE},
906-
"decodeBytes(json['bytes'])",
907-
}, {
908-
&api.Field{Name: "bytesList", JSONName: "bytesList", Typez: api.BYTES_TYPE, Repeated: true},
909-
"decodeListBytes(json['bytesList'])",
910-
},
911-
912912
// enums
913913
{
914914
&api.Field{Name: "message", JSONName: "message", Typez: api.ENUM_TYPE, TypezID: enumState.ID},

internal/sidekick/dart/templates/lib/message.mustache

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,24 @@ final class {{Codec.Name}} extends {{Codec.Model.Codec.ProtoPrefix}}ProtoMessage
3030
required this.{{Codec.Name}},
3131
{{/Codec.FieldBehaviorRequired}}
3232
{{^Codec.FieldBehaviorRequired}}
33+
{{! If the default value is `const` (e.g. `0`) then we can use it as a
34+
default value. Otherwise (e.g. `Uint8List(0)`), we need to make the type
35+
nullable and assign the default value in the initializer list. }}
36+
{{#Codec.ConstDefault}}
3337
this.{{Codec.Name}}{{#Codec.DefaultValue}} = {{{Codec.DefaultValue}}}{{/Codec.DefaultValue}},
38+
{{/Codec.ConstDefault}}
39+
{{^Codec.ConstDefault}}
40+
{{{Codec.Type}}}? {{Codec.Name}},
41+
{{/Codec.ConstDefault}}
3442
{{/Codec.FieldBehaviorRequired}}
3543
{{/Fields}}
36-
}{{/Codec.HasFields}}) : super(fullyQualifiedName){{Codec.ConstructorBody}}
44+
}{{/Codec.HasFields}}) :
45+
{{#Fields}}
46+
{{^Codec.ConstDefault}}
47+
{{Codec.Name}} = {{Codec.Name}} ?? {{{Codec.DefaultValue}}},
48+
{{/Codec.ConstDefault}}
49+
{{/Fields}}
50+
super(fullyQualifiedName){{Codec.ConstructorBody}}
3751

3852
{{#Codec.HasCustomEncoding}}
3953
factory {{Codec.Name}}.fromJson(Object json) => _{{Codec.Name}}Helper.decode(json);

0 commit comments

Comments
 (0)