Skip to content

Commit

Permalink
feat(generator/rust): improve codegen for well-known types (#159)
Browse files Browse the repository at this point in the history
Add a proper package name to the well-known types use the package when
generating any usage for these types.

I also cleaned up how all messages were named in the requests and responses.
  • Loading branch information
coryan authored Nov 12, 2024
1 parent 389d33f commit 38c61f6
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 132 deletions.
62 changes: 39 additions & 23 deletions generator/internal/genclient/language/internal/rust/rust.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,29 @@ func (c *Codec) LoadWellKnownTypes(s *genclient.APIState) {
// TODO(#77) - replace these placeholders with real types
wellKnown := []*genclient.Message{
{
ID: ".google.protobuf.Any",
Name: "serde_json::Value",
ID: ".google.protobuf.Any",
Name: "Any",
Package: "gax_placeholder",
},
{
ID: ".google.protobuf.FieldMask",
Name: "gax_placeholder::FieldMask",
ID: ".google.protobuf.Empty",
Name: "Empty",
Package: "gax_placeholder",
},
{
ID: ".google.protobuf.Duration",
Name: "gax_placeholder::Duration",
ID: ".google.protobuf.FieldMask",
Name: "FieldMask",
Package: "gax_placeholder",
},
{
ID: ".google.protobuf.Timestamp",
Name: "gax_placeholder::Timestamp",
ID: ".google.protobuf.Duration",
Name: "Duration",
Package: "gax_placeholder",
},
{
ID: ".google.protobuf.Timestamp",
Name: "Timestamp",
Package: "gax_placeholder",
},
}
for _, message := range wellKnown {
Expand Down Expand Up @@ -122,10 +131,6 @@ func (c *Codec) baseFieldType(f *genclient.Field, state *genclient.APIState) str
val := c.FieldType(m.Fields[1], state)
return "std::collections::HashMap<" + key + "," + val + ">"
}
if strings.HasPrefix(m.ID, ".google.protobuf.") {
// TODO(#77): Better handling of well-known types
return m.Name
}
return c.FQMessageName(m, state)
} else if f.Typez == genclient.ENUM_TYPE {
e, ok := state.EnumByID[f.TypezID]
Expand Down Expand Up @@ -153,46 +158,57 @@ func (c *Codec) TemplateDir() string {
return "rust"
}

func (c *Codec) MethodInOutTypeName(id string, s *genclient.APIState) string {
func (c *Codec) MethodInOutTypeName(id string, state *genclient.APIState) string {
if id == "" {
return ""
}
m, ok := s.MessageByID[id]
m, ok := state.MessageByID[id]
if !ok {
slog.Error("unable to lookup type", "id", id)
return ""
}
return c.ToPascal(m.Name)
return c.FQMessageName(m, state)
}

func (c *Codec) rustPackage(packageName string) string {
if packageName == "" {
return "crate::model"
}
// TODO(#158) - this should be mapped via some configuration.
return packageName
}

func (c *Codec) MessageName(m *genclient.Message, state *genclient.APIState) string {
if m.Package != "" {
return m.Package + "." + c.ToPascal(m.Name)
return c.rustPackage(m.Package) + "::" + c.ToPascal(m.Name)
}
return c.ToPascal(m.Name)
}

func (c *Codec) messageScopeName(m *genclient.Message) string {
func (c *Codec) messageScopeName(m *genclient.Message, childPackageName string) string {
if m == nil {
return "crate::model"
return c.rustPackage(childPackageName)
}
if m.Parent == nil {
return c.rustPackage(m.Package) + "::" + c.ToSnake(m.Name)
}
return c.messageScopeName(m.Parent) + "::" + c.ToSnake(m.Name)
return c.messageScopeName(m.Parent, m.Package) + "::" + c.ToSnake(m.Name)
}

func (c *Codec) enumScopeName(e *genclient.Enum) string {
return c.messageScopeName(e.Parent)
return c.messageScopeName(e.Parent, "")
}

func (c *Codec) FQMessageName(m *genclient.Message, _ *genclient.APIState) string {
return c.messageScopeName(m.Parent) + "::" + c.ToPascal(m.Name)
return c.messageScopeName(m.Parent, m.Package) + "::" + c.ToPascal(m.Name)
}

func (c *Codec) EnumName(e *genclient.Enum, state *genclient.APIState) string {
return c.ToPascal(e.Name)
}

func (c *Codec) FQEnumName(e *genclient.Enum, _ *genclient.APIState) string {
return c.messageScopeName(e.Parent) + "::" + c.ToPascal(e.Name)
return c.messageScopeName(e.Parent, "") + "::" + c.ToPascal(e.Name)
}

func (c *Codec) EnumValueName(e *genclient.EnumValue, _ *genclient.APIState) string {
Expand All @@ -206,7 +222,7 @@ func (c *Codec) FQEnumValueName(v *genclient.EnumValue, state *genclient.APIStat
}

func (c *Codec) OneOfType(o *genclient.OneOf, _ *genclient.APIState) string {
return c.messageScopeName(o.Parent) + "::" + c.ToPascal(o.Name)
return c.messageScopeName(o.Parent, "") + "::" + c.ToPascal(o.Name)
}

func (c *Codec) BodyAccessor(m *genclient.Method, state *genclient.APIState) string {
Expand Down
61 changes: 57 additions & 4 deletions generator/internal/genclient/language/internal/rust/rust_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,63 @@
package rust

import (
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/googleapis/google-cloud-rust/generator/internal/genclient"
)

func TestWellKnownTypesExist(t *testing.T) {
api := genclient.NewTestAPI([]*genclient.Message{}, []*genclient.Enum{}, []*genclient.Service{})
c := &Codec{}
c.LoadWellKnownTypes(api.State)
for _, name := range []string{"Any", "Duration", "Empty", "FieldMask", "Timestamp"} {
if _, ok := api.State.MessageByID[fmt.Sprintf(".google.protobuf.%s", name)]; !ok {
t.Errorf("cannot find well-known message %s in API", name)
}
}
}

func TestWellKnownTypesAsMethod(t *testing.T) {
api := genclient.NewTestAPI([]*genclient.Message{}, []*genclient.Enum{}, []*genclient.Service{})
c := &Codec{}
c.LoadWellKnownTypes(api.State)

want := "gax_placeholder::Empty"
got := c.MethodInOutTypeName(".google.protobuf.Empty", api.State)
if want != got {
t.Errorf("mismatched well-known type name as method argument or response, want=%s, got=%s", want, got)
}
}

func TestMethodInOut(t *testing.T) {
message := &genclient.Message{
Name: "Target",
ID: "..Target",
}
nested := &genclient.Message{
Name: "Nested",
ID: "..Target.Nested",
Parent: message,
}
api := genclient.NewTestAPI([]*genclient.Message{message, nested}, []*genclient.Enum{}, []*genclient.Service{})
c := &Codec{}
c.LoadWellKnownTypes(api.State)

want := "crate::model::Target"
got := c.MethodInOutTypeName("..Target", api.State)
if want != got {
t.Errorf("mismatched well-known type name as method argument or response, want=%s, got=%s", want, got)
}

want = "crate::model::target::Nested"
got = c.MethodInOutTypeName("..Target.Nested", api.State)
if want != got {
t.Errorf("mismatched well-known type name as method argument or response, want=%s, got=%s", want, got)
}
}

func TestFieldType(t *testing.T) {
target := &genclient.Message{
Name: "Target",
Expand Down Expand Up @@ -205,8 +256,9 @@ func TestMessageNames(t *testing.T) {
},
}
nested := &genclient.Message{
Name: "Automatic",
ID: "..Replication.Automatic",
Name: "Automatic",
ID: "..Replication.Automatic",
Parent: message,
}

api := genclient.NewTestAPI([]*genclient.Message{message, nested}, []*genclient.Enum{}, []*genclient.Service{})
Expand Down Expand Up @@ -242,8 +294,9 @@ func TestEnumNames(t *testing.T) {
},
}
nested := &genclient.Enum{
Name: "State",
ID: "..SecretVersion.State",
Name: "State",
ID: "..SecretVersion.State",
Parent: message,
}

api := genclient.NewTestAPI([]*genclient.Message{message}, []*genclient.Enum{nested}, []*genclient.Service{})
Expand Down
4 changes: 2 additions & 2 deletions generator/templates/rust/src/lib.rs.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl {{NameToPascal}} {
{{#DocLines}}
{{{.}}}
{{/DocLines}}
pub async fn {{NameToSnake}}(&self, req: model::{{InputTypeName}}) -> Result<model::{{OutputTypeName}}, Box<dyn std::error::Error>> {
pub async fn {{NameToSnake}}(&self, req: {{InputTypeName}}) -> Result<{{OutputTypeName}}, Box<dyn std::error::Error>> {
let query_parameters = [
{{^QueryParams}}
None::<(&str, String)>; 0
Expand Down Expand Up @@ -105,7 +105,7 @@ impl {{NameToPascal}} {
if !res.status().is_success() {
return Err("sorry the api you are looking for is not available, please try again".into());
}
let response = res.json::<model::{{OutputTypeName}}>().await?;
let response = res.json::<{{OutputTypeName}}>().await?;
Ok(response)
}
{{/Methods}}
Expand Down
12 changes: 6 additions & 6 deletions generator/testdata/rust/gclient/golden/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ impl SecretManagerService {
/// [SecretVersions][google.cloud.secretmanager.v1.SecretVersion].
pub async fn create_secret(
&self,
req: model::CreateSecretRequest,
) -> Result<model::Secret, Box<dyn std::error::Error>> {
req: crate::model::CreateSecretRequest,
) -> Result<crate::model::Secret, Box<dyn std::error::Error>> {
let query_parameters = [gax::query_parameter::format("secretId", &req.secret_id)?];
let client = self.client.inner.clone();
let res = client
Expand All @@ -116,7 +116,7 @@ impl SecretManagerService {
"sorry the api you are looking for is not available, please try again".into(),
);
}
let response = res.json::<model::Secret>().await?;
let response = res.json::<crate::model::Secret>().await?;
Ok(response)
}

Expand Down Expand Up @@ -155,8 +155,8 @@ impl SecretManagerService {
/// Gets metadata for a given [Secret][google.cloud.secretmanager.v1.Secret].
pub async fn get_secret(
&self,
req: model::GetSecretRequest,
) -> Result<model::Secret, Box<dyn std::error::Error>> {
req: crate::model::GetSecretRequest,
) -> Result<crate::model::Secret, Box<dyn std::error::Error>> {
let query_parameters = [None::<(&str, String)>; 0];
let client = self.client.inner.clone();
let res = client
Expand All @@ -177,7 +177,7 @@ impl SecretManagerService {
"sorry the api you are looking for is not available, please try again".into(),
);
}
let response = res.json::<model::Secret>().await?;
let response = res.json::<crate::model::Secret>().await?;
Ok(response)
}

Expand Down
Loading

0 comments on commit 38c61f6

Please sign in to comment.