-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proto gen: support optionals for proto syntax 3 #105
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -203,17 +203,30 @@ func addValueTyped(f *descriptor.Field) string { | |
if valueFormatter != "" { | ||
return fmt.Sprintf(valueFormatter, getter) | ||
} | ||
|
||
if f.GetProto3Optional() { | ||
getter = fmt.Sprintf("*%s", getter) | ||
} | ||
|
||
return fmt.Sprintf(`fmt.Sprintf("%s", %s)`, valueVerb, getter) | ||
} | ||
|
||
if !isRepeated { | ||
if isRepeated { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not possible to have both optional and repeated for the same field, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To the best of my knowledge, repeated fields are inherently optional. If we try to put both optional and repeated on the same field, the parser will raise an error (did a quick experiment btw) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, an answer on Stack Overflow -- https://stackoverflow.com/a/25637833 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for checking it |
||
format := `for _, v := range in.%s { | ||
values.Add(%q, %s) | ||
}` | ||
return fmt.Sprintf(format, goName, f.GetName(), valueTemplater("v")) | ||
} | ||
|
||
if !f.GetProto3Optional() { | ||
return fmt.Sprintf(`values.Add(%q, %s)`, f.GetName(), valueTemplater("in."+goName)) | ||
} | ||
|
||
format := `for _, v := range in.%s { | ||
values.Add(%q, %s) | ||
format := `if in.%s != nil { | ||
values.Add(%q, %s) | ||
}` | ||
return fmt.Sprintf(format, goName, f.GetName(), valueTemplater("v")) | ||
return fmt.Sprintf(format, goName, f.GetName(), valueTemplater("in."+goName)) | ||
|
||
} | ||
|
||
var ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
include ../env.mk | ||
|
||
$(shell go get github.com/googleapis/googleapis) | ||
GOOGLEAPIS_PKG:=$(shell go list -m all | grep github.com/googleapis/googleapis | awk '{print ($$4 != "" ? $$4 : $$1)}') | ||
GOOGLEAPIS_VERSION:=$(shell go list -m all | grep github.com/googleapis/googleapis | awk '{print ($$5 != "" ? $$5 : $$2)}') | ||
GOOGLEAPIS_PATH:=${FIRST_GOPATH}/pkg/mod/${GOOGLEAPIS_PKG}@${GOOGLEAPIS_VERSION} | ||
|
||
pwd: | ||
@pwd | ||
|
||
clean: | ||
rm -f ./pb/strings.pb.go | ||
rm -f ./pb/strings_grpc.pb.go | ||
rm -f ./pb/strings.pb.goclay.go | ||
rm -f ./strings/strings.go | ||
rm -f main | ||
|
||
protoc: .protoc_pb | ||
|
||
build: .build | ||
|
||
test: pwd clean protoc build | ||
go test -v ./... |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package main | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/go-chi/chi" | ||
"github.com/utrack/clay/integration/binding_with_optional_field/strings" | ||
) | ||
|
||
func main() { | ||
r := chi.NewMux() | ||
desc := strings.NewStrings().GetDescription() | ||
desc.RegisterHTTP(r) | ||
|
||
r.Handle("/swagger.json", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
w.Header().Set("Content-Type", "application/javascript") | ||
w.Write(desc.SwaggerDef()) | ||
})) | ||
|
||
http.ListenAndServe(":8080", r) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
strings_pb "github.com/utrack/clay/integration/binding_with_optional_field/pb" | ||
strings_srv "github.com/utrack/clay/integration/binding_with_optional_field/strings" | ||
) | ||
|
||
func TestToUpper(t *testing.T) { | ||
ts := testServer() | ||
defer ts.Close() | ||
|
||
t.Run("GET nullable string in request and an object in response", func(t *testing.T) { | ||
httpClient := ts.Client() | ||
client := strings_pb.NewStringsHTTPClient(httpClient, ts.URL) | ||
|
||
strVal := "foo" | ||
req := &strings_pb.String{ | ||
Str: &strVal, | ||
} | ||
|
||
resp, err := client.ToUpper(context.Background(), req) | ||
if err != nil { | ||
t.Fatalf("expected err <nil>, got: %q", err) | ||
} | ||
|
||
got := resp.GetStr() | ||
expected := strings.ToUpper(req.GetStr()) | ||
if got != expected { | ||
t.Fatalf("expected %q, got: %q", expected, got) | ||
} | ||
}) | ||
|
||
t.Run("GET nil in request and nil in response", func(t *testing.T) { | ||
httpClient := ts.Client() | ||
client := strings_pb.NewStringsHTTPClient(httpClient, ts.URL) | ||
|
||
req := &strings_pb.String{ | ||
Str: nil, | ||
} | ||
resp, err := client.ToUpper(context.Background(), req) | ||
if err != nil { | ||
t.Fatalf("expected err <nil>, got: %q", err) | ||
} | ||
|
||
got := resp.Str | ||
if got != nil { | ||
t.Fatalf("expected nil, got: %s", *got) | ||
} | ||
|
||
}) | ||
} | ||
|
||
func testServer() *httptest.Server { | ||
mux := http.NewServeMux() | ||
desc := strings_srv.NewStrings().GetDescription() | ||
desc.RegisterHTTP(mux) | ||
mux.Handle("/swagger.json", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
w.Header().Set("Content-Type", "application/javascript") | ||
w.Write(desc.SwaggerDef()) | ||
})) | ||
ts := httptest.NewServer(mux) | ||
return ts | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package strings |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
syntax = "proto3"; | ||
|
||
option go_package = "github.com/utrack/clay/integration/binding_with_optional_field/pb;strings"; | ||
// or just | ||
//option go_package = "./pb;strings"; | ||
|
||
import "google/api/annotations.proto"; | ||
|
||
service Strings { | ||
rpc ToUpper (String) returns (String) { | ||
option (google.api.http) = { | ||
get: "/strings/to_upper/v2" | ||
}; | ||
} | ||
} | ||
|
||
message String { | ||
optional string str = 1; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to have both value formatter and optional for the same field?
I guess not, but could you please check it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can have both -- currently, the valueformatter is used for encoding bytes. Bytes evaluate to the byte[] scalar type on go which is nullable. So the current implementation works.
The question is do we want to create pointers for bytes[] on code generation instead? And if yes, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. I don't think we need a pointer to a slice