Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/serviceconfig/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ var APIs = []API{
{Path: "google/shopping/type", Languages: []string{LangGo, LangPython}, Transports: map[string]Transport{LangPython: GRPCRest}},
{Path: "google/spanner/admin/database/v1"},
{Path: "google/spanner/admin/instance/v1"},
{Path: "google/spanner/v1", Transports: map[string]Transport{LangGo: GRPCRest, LangJava: GRPCRest, LangNodejs: GRPCRest, LangPhp: GRPCRest, LangPython: GRPCRest}},
{Path: "google/storage/control/v2"},
{Path: "google/storage/v2", Transports: map[string]Transport{LangGo: GRPC, LangJava: GRPC, LangNodejs: GRPC, LangPhp: GRPCRest, LangPython: GRPC}},
{Path: "google/storagetransfer/v1"},
Expand Down
39 changes: 39 additions & 0 deletions internal/sidekick/rust/annotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ type fieldAnnotations struct {
ValueField *api.Field
// The templates need to generate different code for boxed fields.
IsBoxed bool
// If true, the field is boxed in the prost generated type.
MapToBoxed bool
// If true, it requires a serde_with::serde_as() transformation.
SerdeAs string
// If true, use `wkt::internal::is_default()` to skip the field
Expand Down Expand Up @@ -1479,6 +1481,7 @@ func (c *codec) annotateField(field *api.Field, message *api.Message, model *api
if field.Recursive || (field.Typez == api.MESSAGE_TYPE && field.IsOneOf) {
ann.IsBoxed = true
}
ann.MapToBoxed = mapToBoxed(field, message, model)
field.Codec = ann
if field.Typez == api.MESSAGE_TYPE {
if msg, ok := model.State.MessageByID[field.TypezID]; ok && msg.IsMap {
Expand Down Expand Up @@ -1651,3 +1654,39 @@ func isIdempotent(p *api.PathInfo) string {
}
return "true"
}

// mapToBoxed returns true if the prost generated type for this field is boxed.
// Prost boxes fields that would cause an infinitely sized struct, which happens
// on recursive cycles that are not broken by a repeated or map field.
func mapToBoxed(field *api.Field, message *api.Message, model *api.API) bool {
if field.Typez != api.MESSAGE_TYPE || field.Repeated || field.Map {
return false
}

var check func(typezID string, targetID string, visited map[string]bool) bool
check = func(typezID string, targetID string, visited map[string]bool) bool {
if typezID == targetID {
return true
}
if visited[typezID] {
return false
}
visited[typezID] = true
msg, ok := model.State.MessageByID[typezID]
if !ok {
return false
}
for _, f := range msg.Fields {
if f.Typez != api.MESSAGE_TYPE || f.Repeated || f.Map {
continue
}
if check(f.TypezID, targetID, visited) {
return true
}
}
return false
}

visited := map[string]bool{message.ID: true}
return check(field.TypezID, message.ID, visited)
}
6 changes: 6 additions & 0 deletions internal/sidekick/rust/annotate_field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func TestFieldAnnotations(t *testing.T) {
PrimitiveFieldType: "crate::model::TestMessage",
AddQueryParameter: `let builder = req.boxed_field.as_ref().map(|p| serde_json::to_value(p).map_err(Error::ser) ).transpose()?.into_iter().fold(builder, |builder, v| { use gaxi::query_parameter::QueryParameter; v.add(builder, "boxedField") });`,
IsBoxed: true,
MapToBoxed: true,
SkipIfIsDefault: true,
FieldTypeIsParentType: true,
}
Expand Down Expand Up @@ -295,6 +296,7 @@ func TestRecursiveFieldAnnotations(t *testing.T) {
ValueField: value_field,
SerdeAs: "std::collections::HashMap<wkt::internal::I32, serde_with::Same>",
IsBoxed: true,
MapToBoxed: true,
SkipIfIsDefault: true,
FieldTypeIsParentType: true,
}
Expand All @@ -318,6 +320,7 @@ func TestRecursiveFieldAnnotations(t *testing.T) {
PrimitiveFieldType: "crate::model::TestMessage",
AddQueryParameter: `let builder = req.oneof_field().map(|p| serde_json::to_value(p).map_err(Error::ser) ).transpose()?.into_iter().fold(builder, |builder, p| { use gaxi::query_parameter::QueryParameter; p.add(builder, "oneofField") });`,
IsBoxed: true,
MapToBoxed: true,
SkipIfIsDefault: true,
OtherFieldsInGroup: []*api.Field{},
FieldTypeIsParentType: true,
Expand All @@ -342,6 +345,7 @@ func TestRecursiveFieldAnnotations(t *testing.T) {
PrimitiveFieldType: "crate::model::TestMessage",
AddQueryParameter: `let builder = req.repeated_field.as_ref().map(|p| serde_json::to_value(p).map_err(Error::ser) ).transpose()?.into_iter().fold(builder, |builder, v| { use gaxi::query_parameter::QueryParameter; v.add(builder, "repeatedField") });`,
IsBoxed: true,
MapToBoxed: false,
SkipIfIsDefault: true,
FieldTypeIsParentType: true,
}
Expand All @@ -365,6 +369,7 @@ func TestRecursiveFieldAnnotations(t *testing.T) {
PrimitiveFieldType: "crate::model::TestMessage",
AddQueryParameter: `let builder = { use gaxi::query_parameter::QueryParameter; serde_json::to_value(&req.message_field).map_err(Error::ser)?.add(builder, "messageField") };`,
IsBoxed: true,
MapToBoxed: true,
SkipIfIsDefault: true,
FieldTypeIsParentType: true,
}
Expand Down Expand Up @@ -508,6 +513,7 @@ func TestSameTypeNameFieldAnnotations(t *testing.T) {
PrimitiveFieldType: "rusty_test_inner_v1::model::TestMessage",
AddQueryParameter: `let builder = req.oneof_field().map(|p| serde_json::to_value(p).map_err(Error::ser) ).transpose()?.into_iter().fold(builder, |builder, p| { use gaxi::query_parameter::QueryParameter; p.add(builder, "oneofField") });`,
IsBoxed: true,
MapToBoxed: false,
SkipIfIsDefault: true,
OtherFieldsInGroup: []*api.Field{},
AliasInExamples: "OneofField",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ impl gaxi::prost::ToProto<{{Codec.RelativeName}}> for {{Codec.QualifiedName}} {
{{Codec.FieldName}}: self.{{Codec.FieldName}}.to_proto()?,
{{/Optional}}
{{#Optional}}
{{^Codec.MapToBoxed}}
{{Codec.FieldName}}: self.{{Codec.FieldName}}.map(|v| v.to_proto()).transpose()?,
{{/Codec.MapToBoxed}}
{{#Codec.MapToBoxed}}
{{Codec.FieldName}}: self.{{Codec.FieldName}}.map(|v| v.to_proto().map(std::boxed::Box::new)).transpose()?,
{{/Codec.MapToBoxed}}
{{/Optional}}
{{/Singular}}
{{#Repeated}}
Expand Down
Loading