Skip to content

Commit e45f132

Browse files
fix(dart): Correctly encode repeated/map (#3326)
Change the previous strategy of having per-type encoders for `map` and `repeated` fields into using list/dictionary comprehensions and then the usual primitive encoders. --------- Signed-off-by: Brian Quinlan <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent d6718d1 commit e45f132

File tree

3 files changed

+355
-83
lines changed

3 files changed

+355
-83
lines changed

internal/sidekick/dart/annotate.go

Lines changed: 99 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ func (annotate *annotateModel) annotateField(field *api.Field) {
797797
FieldBehaviorRequired: fieldRequired,
798798
DefaultValue: defaultValue,
799799
FromJson: annotate.createFromJsonLine(field, state, implicitPresence),
800-
ToJson: createToJsonLine(field, state, implicitPresence),
800+
ToJson: createToJsonLine(field, state),
801801
ConstDefault: constDefault,
802802
}
803803
}
@@ -853,8 +853,85 @@ func (annotate *annotateModel) keyDecoder(typez api.Typez) string {
853853
api.SINT64_TYPE,
854854
api.SFIXED64_TYPE:
855855
return "decodeIntKey"
856+
case api.UINT64_TYPE,
857+
api.FIXED64_TYPE:
858+
return "decodeUint64Key"
859+
case api.BOOL_TYPE:
860+
return "decodeBoolKey"
861+
default:
862+
panic(fmt.Sprintf("unsupported key type: %d", typez))
863+
}
864+
}
865+
866+
// encoder returns a string that encodes the given field name and a bool that indicates whether the
867+
// field requires encoding.
868+
//
869+
// For example:
870+
//
871+
// encoder(api.STRING_TYPE, "a_string_field") => ("a_string_field", false)
872+
// encoder(api.MESSAGE_TYPE, "a_message_field") => ("a_message_field.toJson()", true)
873+
func encoder(typez api.Typez, name string) (string, bool) {
874+
switch typez {
875+
case api.INT64_TYPE,
876+
api.SINT64_TYPE,
877+
api.SFIXED64_TYPE,
878+
api.FIXED64_TYPE,
879+
api.UINT64_TYPE:
880+
// All 64-bit integer types are encoded as strings. In Dart, these may be
881+
// represented as `int` or `BigInt`.
882+
return fmt.Sprintf("%s.toString()", name), true
883+
case api.FLOAT_TYPE,
884+
api.DOUBLE_TYPE:
885+
// A special encoder is needed to handle NaN and Infinity.
886+
return fmt.Sprintf("encodeDouble(%s)", name), true
887+
case api.INT32_TYPE,
888+
api.FIXED32_TYPE,
889+
api.SFIXED32_TYPE,
890+
api.SINT32_TYPE,
891+
api.UINT32_TYPE:
892+
// All 32-bit integer types are encoded as JSON numbers.
893+
return name, false
894+
case api.BOOL_TYPE:
895+
return name, false
896+
case api.STRING_TYPE:
897+
return name, false
898+
case api.BYTES_TYPE:
899+
return fmt.Sprintf("encodeBytes(%s)", name), true
900+
case api.MESSAGE_TYPE, api.ENUM_TYPE:
901+
return fmt.Sprintf("%s.toJson()", name), true
902+
default:
903+
panic(fmt.Sprintf("unsupported type: %d", typez))
904+
}
905+
}
906+
907+
// keyEncoder returns a string that encodes the given field name and a bool that indicates whether the
908+
// field requires encoding
909+
//
910+
// For example:
911+
//
912+
// keyEncoder(api.STRING_TYPE, "e.key") => ("e.key", false)
913+
// keyEncoder(api.INT32_TYPE, "e.key") => ("e.key.toString()", true)
914+
func keyEncoder(typez api.Typez, name string) (string, bool) {
915+
// JSON objects can only contain string keys so non-String types need to be encoded as strings.
916+
// Supported key types are defined here:
917+
// https://protobuf.dev/programming-guides/proto3/#maps
918+
switch typez {
919+
case api.STRING_TYPE:
920+
return name, false
921+
case api.INT32_TYPE,
922+
api.FIXED32_TYPE,
923+
api.SFIXED32_TYPE,
924+
api.SINT32_TYPE,
925+
api.UINT32_TYPE,
926+
api.INT64_TYPE,
927+
api.SINT64_TYPE,
928+
api.SFIXED64_TYPE,
929+
api.UINT64_TYPE,
930+
api.FIXED64_TYPE:
931+
return fmt.Sprintf("%s.toString()", name), true
932+
case api.BOOL_TYPE:
933+
return fmt.Sprintf("%s.toString()", name), true
856934
default:
857-
// TODO(https://github.com/googleapis/google-cloud-dart/issues/95): Support all key types.
858935
panic(fmt.Sprintf("unsupported key type: %d", typez))
859936
}
860937
}
@@ -904,57 +981,34 @@ func (annotate *annotateModel) createFromJsonLine(field *api.Field, state *api.A
904981
return fmt.Sprintf("switch (%s) { null => %s, Object $1 => %s($1)}", data, defaultValue, decoder)
905982
}
906983

907-
func createToJsonLine(field *api.Field, state *api.APIState, required bool) string {
984+
func createToJsonLine(field *api.Field, state *api.APIState) string {
908985
name := fieldName(field)
909-
message := state.MessageByID[field.TypezID]
910-
911-
isList := field.Repeated
912-
isMap := message != nil && message.IsMap
913-
914-
bang := "!"
915-
if required {
916-
bang = ""
917-
}
918986

919987
switch {
920-
case isList:
921-
switch field.Typez {
922-
case api.BYTES_TYPE:
923-
return fmt.Sprintf("encodeListBytes(%s)", name)
924-
case api.MESSAGE_TYPE, api.ENUM_TYPE:
925-
return fmt.Sprintf("encodeList(%s)", name)
926-
default:
927-
// identity
928-
return name
988+
case field.Repeated:
989+
if encoder, encodingRequired := encoder(field.Typez, "i"); encodingRequired {
990+
return fmt.Sprintf(
991+
"[for (final i in %s) %s]",
992+
name, encoder)
929993
}
930-
case isMap:
931-
valueField := message.Fields[1]
994+
return name
995+
case field.Map:
996+
message := state.MessageByID[field.TypezID]
997+
keyType := message.Fields[0].Typez
998+
keyEncoder, keyEncodingRequired := keyEncoder(keyType, "e.key")
999+
valueType := message.Fields[1].Typez
1000+
valueEncoder, valueEncodingRequired := encoder(valueType, "e.value")
9321001

933-
switch valueField.Typez {
934-
case api.BYTES_TYPE:
935-
return fmt.Sprintf("encodeMapBytes(%s)", name)
936-
case api.MESSAGE_TYPE, api.ENUM_TYPE:
937-
return fmt.Sprintf("encodeMap(%s)", name)
938-
default:
939-
// identity
940-
return name
1002+
if keyEncodingRequired || valueEncodingRequired {
1003+
return fmt.Sprintf(
1004+
"{for (final e in %s.entries) %s: %s}",
1005+
name, keyEncoder, valueEncoder)
9411006
}
942-
case field.Typez == api.MESSAGE_TYPE || field.Typez == api.ENUM_TYPE:
943-
return fmt.Sprintf("%s%s.toJson()", name, bang)
944-
case field.Typez == api.BYTES_TYPE:
945-
return fmt.Sprintf("encodeBytes(%s)", name)
946-
case field.Typez == api.INT64_TYPE || field.Typez == api.SINT64_TYPE ||
947-
field.Typez == api.SFIXED64_TYPE:
948-
return fmt.Sprintf("encodeInt64(%s)", name)
949-
case field.Typez == api.FIXED64_TYPE || field.Typez == api.UINT64_TYPE:
950-
return fmt.Sprintf("encodeUint64(%s)", name)
951-
case field.Typez == api.FLOAT_TYPE || field.Typez == api.DOUBLE_TYPE:
952-
return fmt.Sprintf("encodeDouble(%s)", name)
953-
default:
1007+
return name
9541008
}
9551009

956-
// No encoding necessary.
957-
return name
1010+
enc, _ := encoder(field.Typez, name)
1011+
return enc
9581012
}
9591013

9601014
// buildQueryLines builds a string or strings representing query parameters for the given field.

0 commit comments

Comments
 (0)