diff --git a/Makefile b/Makefile index a56f44a..f68fd42 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ kit: go build -o ~/go/bin/protoc-gen-kit cmd/kit/main.go +gin-kit: + go build -o ~/go/bin/protoc-gen-gin-kit cmd/gin-kit/main.go ts: go build -o ~/go/bin/protoc-gen-ts cmd/ts/main.go all: make kit + make gin-kit make ts testts: make ts \ No newline at end of file diff --git a/app.go b/app.go index 2820551..da6ffe8 100644 --- a/app.go +++ b/app.go @@ -1,11 +1,13 @@ -package protoc_gen_kit +package kit import ( "context" "errors" - "git.diulo.com/mogfee/protoc-gen-kit/log" - "git.diulo.com/mogfee/protoc-gen-kit/registry" - "git.diulo.com/mogfee/protoc-gen-kit/transport" + _ "git.diulo.com/mogfee/kit/encoding/form" + _ "git.diulo.com/mogfee/kit/encoding/json" + "git.diulo.com/mogfee/kit/log" + "git.diulo.com/mogfee/kit/registry" + "git.diulo.com/mogfee/kit/transport" "github.com/google/uuid" "golang.org/x/sync/errgroup" "os" diff --git a/cmd/gin-kit/main.go b/cmd/gin-kit/main.go new file mode 100644 index 0000000..61efa8b --- /dev/null +++ b/cmd/gin-kit/main.go @@ -0,0 +1,111 @@ +package main + +import ( + protogen2 "git.diulo.com/mogfee/kit/protogen" + "google.golang.org/protobuf/compiler/protogen" + "strings" +) + +func main() { + u := &Kit{ + imports: map[string]string{}, + } + protogen.Options{}.Run(u.Generate) +} + +type Kit struct { + imports map[string]string +} + +func (u *Kit) addImports(imp string) { + u.imports[imp] = imp +} +func (u *Kit) Generate(plugin *protogen.Plugin) error { + if len(plugin.Files) < 1 { + return nil + } + u.addImports("context") + u.addImports("git.diulo.com/mogfee/kit/transport/http") + for _, f := range plugin.Files { + if len(f.Services) == 0 { + continue + } + fname := f.GeneratedFilenamePrefix + "_http.pb.go" + t := plugin.NewGeneratedFile(fname, f.GoImportPath) + t.P("package " + f.Desc.Name()) + t.P("import (") + for _, v := range u.imports { + t.P(`"` + v + `"`) + } + t.P(")") + + for _, s := range f.Services { + t.P(`type `, s.GoName, `HTTPServer interface {`) + for _, m := range s.Methods { + t.P(m.GoName, `(context.Context, *`, m.Input.GoIdent.GoName, `) (*`, m.Output.GoIdent.GoName, `,error)`) + } + t.P(`}`) + } + + for _, s := range f.Services { + serverName := s.GoName + t.P(`func Register`, serverName, `HTTPServer(s *http.Server,srv `, serverName, `Server) {`) + t.P(`r:=s.Route("/")`) + for _, m := range s.Methods { + method, path := protogen2.GetProtoMethod(m) + if method == "" { + continue + } + t.P(`r.`, method, `("`, path, `",_`, s.GoName, `_`, m.GoName, `0_HTTP_Handler(srv))`) + } + t.P(`}`) + } + for _, s := range f.Services { + for _, m := range s.Methods { + method, _ := protogen2.GetProtoMethod(m) + if method == "" { + continue + } + u.genGet(f, s, t, m) + } + } + } + return nil +} + +func (u *Kit) genGet(f *protogen.File, s *protogen.Service, t *protogen.GeneratedFile, m *protogen.Method) { + method, path := protogen2.GetProtoMethod(m) + if method == "" { + return + } + + t.P(`func _`, s.GoName, `_`, m.GoName, `0_HTTP_Handler(srv `, s.GoName, `HTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in `, m.Input.GoIdent.GoName) + if method == protogen2.METHOD_GET { + t.P(`if err := ctx.BindQuery(&in); err != nil { + return err + }`) + } else if method == protogen2.METHOD_POST { + t.P(`if err := ctx.Bind(&in); err != nil { + return err + }`) + } + if strings.LastIndexByte(path, '{') != -1 { + t.P(`if err := ctx.BindVars(&in); err != nil { + return err + }`) + } + t.P(`http.SetOperation(ctx, "/`, f.Desc.Package(), `.`, s.Desc.Name(), `/`, m.Desc.Name(), `") + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.`, m.GoName, `(ctx, req.(*`, m.Input.GoIdent.GoName, `)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*`, m.Output.GoIdent.GoName, `) + return ctx.Result(200, reply) + } +}`) +} diff --git a/cmd/kit/main.go b/cmd/kit/main.go index 9ccb233..ad16310 100644 --- a/cmd/kit/main.go +++ b/cmd/kit/main.go @@ -1,7 +1,7 @@ package main import ( - protogen2 "git.diulo.com/mogfee/protoc-gen-kit/protogen" + protogen2 "git.diulo.com/mogfee/kit/protogen" "google.golang.org/protobuf/compiler/protogen" ) @@ -24,15 +24,15 @@ func (u *Kit) Generate(plugin *protogen.Plugin) error { return nil } u.addImports("context") - u.addImports("git.diulo.com/mogfee/protoc-gen-kit/middleware") - u.addImports("git.diulo.com/mogfee/protoc-gen-kit/pkg/response") - u.addImports("git.diulo.com/mogfee/protoc-gen-kit/pkg/errors") + u.addImports("git.diulo.com/mogfee/kit/middleware") + u.addImports("git.diulo.com/mogfee/kit/response") + u.addImports("git.diulo.com/mogfee/kit/errors") u.addImports("github.com/gin-gonic/gin") for _, f := range plugin.Files { if len(f.Services) == 0 { continue } - fname := f.GeneratedFilenamePrefix + ".gin.go" + fname := f.GeneratedFilenamePrefix + ".http.go" t := plugin.NewGeneratedFile(fname, f.GoImportPath) t.P("package " + f.Desc.Name()) t.P("import (") diff --git a/cmd/ts/main.go b/cmd/ts/main.go index e4a7a42..39a2bba 100644 --- a/cmd/ts/main.go +++ b/cmd/ts/main.go @@ -2,7 +2,7 @@ package main import ( "fmt" - protogen2 "git.diulo.com/mogfee/protoc-gen-kit/protogen" + protogen2 "git.diulo.com/mogfee/kit/protogen" "google.golang.org/protobuf/compiler/protogen" "strings" ) diff --git a/encoding/README.md b/encoding/README.md new file mode 100644 index 0000000..c147b3b --- /dev/null +++ b/encoding/README.md @@ -0,0 +1,7 @@ +# encoding + +## msgpack + +```shell +go get -u github.com/go-kratos/kratos/contrib/encoding/msgpack/v2 +``` diff --git a/encoding/encoding.go b/encoding/encoding.go index e26013d..f9defe6 100644 --- a/encoding/encoding.go +++ b/encoding/encoding.go @@ -1,15 +1,27 @@ package encoding -import "strings" +import ( + "strings" +) +// Codec defines the interface Transport uses to encode and decode messages. Note +// that implementations of this interface must be thread safe; a Codec's +// methods can be called from concurrent goroutines. type Codec interface { - Marshal(any) ([]byte, error) - Unmarshal([]byte, any) error + // Marshal returns the wire format of v. + Marshal(v interface{}) ([]byte, error) + // Unmarshal parses the wire format into v. + Unmarshal(data []byte, v interface{}) error + // Name returns the name of the Codec implementation. The returned string + // will be used as part of content type in transmission. The result must be + // static; the result cannot change between calls. Name() string } var registeredCodecs = make(map[string]Codec) +// RegisterCodec registers the provided Codec for use with all Transport clients and +// servers. func RegisterCodec(codec Codec) { if codec == nil { panic("cannot register a nil Codec") @@ -17,9 +29,14 @@ func RegisterCodec(codec Codec) { if codec.Name() == "" { panic("cannot register Codec with empty string result for Name()") } - contentSubType := strings.ToLower(codec.Name()) - registeredCodecs[contentSubType] = codec + contentSubtype := strings.ToLower(codec.Name()) + registeredCodecs[contentSubtype] = codec } -func GetCodec(contentSubType string) Codec { - return registeredCodecs[contentSubType] + +// GetCodec gets a registered Codec by content-subtype, or nil if no Codec is +// registered for the content-subtype. +// +// The content-subtype is expected to be lowercase. +func GetCodec(contentSubtype string) Codec { + return registeredCodecs[contentSubtype] } diff --git a/encoding/encoding_test.go b/encoding/encoding_test.go new file mode 100644 index 0000000..1d79ebb --- /dev/null +++ b/encoding/encoding_test.go @@ -0,0 +1,88 @@ +package encoding + +import ( + "encoding/xml" + "fmt" + "runtime/debug" + "testing" +) + +type codec struct{} + +func (c codec) Marshal(v interface{}) ([]byte, error) { + panic("implement me") +} + +func (c codec) Unmarshal(data []byte, v interface{}) error { + panic("implement me") +} + +func (c codec) Name() string { + return "" +} + +// codec2 is a Codec implementation with xml. +type codec2 struct{} + +func (codec2) Marshal(v interface{}) ([]byte, error) { + return xml.Marshal(v) +} + +func (codec2) Unmarshal(data []byte, v interface{}) error { + return xml.Unmarshal(data, v) +} + +func (codec2) Name() string { + return "xml" +} + +func TestRegisterCodec(t *testing.T) { + f := func() { RegisterCodec(nil) } + funcDidPanic, panicValue, _ := didPanic(f) + if !funcDidPanic { + t.Fatalf(fmt.Sprintf("func should panic\n\tPanic value:\t%#v", panicValue)) + } + if panicValue != "cannot register a nil Codec" { + t.Fatalf("panic error got %s want cannot register a nil Codec", panicValue) + } + f = func() { + RegisterCodec(codec{}) + } + funcDidPanic, panicValue, _ = didPanic(f) + if !funcDidPanic { + t.Fatalf(fmt.Sprintf("func should panic\n\tPanic value:\t%#v", panicValue)) + } + if panicValue != "cannot register Codec with empty string result for Name()" { + t.Fatalf("panic error got %s want cannot register Codec with empty string result for Name()", panicValue) + } + codec := codec2{} + RegisterCodec(codec) + got := GetCodec("xml") + if got != codec { + t.Fatalf("RegisterCodec(%v) want %v got %v", codec, codec, got) + } +} + +// PanicTestFunc defines a func that should be passed to assert.Panics and assert.NotPanics +// methods, and represents a simple func that takes no arguments, and returns nothing. +type PanicTestFunc func() + +// didPanic returns true if the function passed to it panics. Otherwise, it returns false. +func didPanic(f PanicTestFunc) (bool, interface{}, string) { + didPanic := false + var message interface{} + var stack string + func() { + defer func() { + if message = recover(); message != nil { + didPanic = true + stack = string(debug.Stack()) + } + }() + + // call the target function + f() + }() + + return didPanic, message, stack +} diff --git a/encoding/form/form.go b/encoding/form/form.go index 1058037..c613fcb 100644 --- a/encoding/form/form.go +++ b/encoding/form/form.go @@ -3,13 +3,16 @@ package form import ( "git.diulo.com/mogfee/kit/encoding" "github.com/go-playground/form" - "google.golang.org/protobuf/proto" "net/url" "reflect" + + "google.golang.org/protobuf/proto" ) const ( - Name = "x-www-form-urlencoded" + // Name is form codec name + Name = "x-www-form-urlencoded" + // Null value string nullStr = "null" ) @@ -21,10 +24,7 @@ var ( func init() { decoder.SetTagName("json") encoder.SetTagName("json") - encoding.RegisterCodec(codec{ - encoder: encoder, - decoder: decoder, - }) + encoding.RegisterCodec(codec{encoder: encoder, decoder: decoder}) } type codec struct { @@ -32,7 +32,7 @@ type codec struct { decoder *form.Decoder } -func (c codec) Marshal(v any) ([]byte, error) { +func (c codec) Marshal(v interface{}) ([]byte, error) { var vs url.Values var err error if m, ok := v.(proto.Message); ok { @@ -51,16 +51,17 @@ func (c codec) Marshal(v any) ([]byte, error) { delete(vs, k) } } - return []byte(vs.Encode()), err + return []byte(vs.Encode()), nil } -func (c codec) Unmarshal(data []byte, v any) error { +func (c codec) Unmarshal(data []byte, v interface{}) error { vs, err := url.ParseQuery(string(data)) if err != nil { return err } + rv := reflect.ValueOf(v) - if rv.Kind() == reflect.Ptr { + for rv.Kind() == reflect.Ptr { if rv.IsNil() { rv.Set(reflect.New(rv.Type().Elem())) } @@ -72,9 +73,10 @@ func (c codec) Unmarshal(data []byte, v any) error { if m, ok := rv.Interface().(proto.Message); ok { return DecodeValues(m, vs) } + return c.decoder.Decode(v, vs) } -func (c codec) Name() string { +func (codec) Name() string { return Name } diff --git a/encoding/form/form_test.go b/encoding/form/form_test.go new file mode 100644 index 0000000..013edbd --- /dev/null +++ b/encoding/form/form_test.go @@ -0,0 +1,211 @@ +package form + +import ( + "encoding/base64" + "reflect" + "testing" + + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/go-kratos/kratos/v2/encoding" + bdtest "github.com/go-kratos/kratos/v2/internal/testdata/binding" + "github.com/go-kratos/kratos/v2/internal/testdata/complex" + ectest "github.com/go-kratos/kratos/v2/internal/testdata/encoding" +) + +type LoginRequest struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +func TestFormCodecMarshal(t *testing.T) { + req := &LoginRequest{ + Username: "kratos", + Password: "kratos_pwd", + } + content, err := encoding.GetCodec(Name).Marshal(req) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual([]byte("password=kratos_pwd&username=kratos"), content) { + t.Errorf("expect %s, got %s", "password=kratos_pwd&username=kratos", content) + } + + req = &LoginRequest{ + Username: "kratos", + Password: "", + } + content, err = encoding.GetCodec(Name).Marshal(req) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual([]byte("username=kratos"), content) { + t.Errorf("expect %s, got %s", "username=kratos", content) + } + + m := struct { + ID int32 `json:"id"` + Name string `json:"name"` + }{ + ID: 1, + Name: "kratos", + } + content, err = encoding.GetCodec(Name).Marshal(m) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual([]byte("id=1&name=kratos"), content) { + t.Errorf("expect %s, got %s", "id=1&name=kratos", content) + } +} + +func TestFormCodecUnmarshal(t *testing.T) { + req := &LoginRequest{ + Username: "kratos", + Password: "kratos_pwd", + } + content, err := encoding.GetCodec(Name).Marshal(req) + if err != nil { + t.Fatal(err) + } + + bindReq := new(LoginRequest) + err = encoding.GetCodec(Name).Unmarshal(content, bindReq) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual("kratos", bindReq.Username) { + t.Errorf("expect %v, got %v", "kratos", bindReq.Username) + } + if !reflect.DeepEqual("kratos_pwd", bindReq.Password) { + t.Errorf("expect %v, got %v", "kratos_pwd", bindReq.Password) + } +} + +func TestProtoEncodeDecode(t *testing.T) { + in := &complex.Complex{ + Id: 2233, + NoOne: "2233", + Simple: &complex.Simple{Component: "5566"}, + Simples: []string{"3344", "5566"}, + B: true, + Sex: complex.Sex_woman, + Age: 18, + A: 19, + Count: 3, + Price: 11.23, + D: 22.22, + Byte: []byte("123"), + Map: map[string]string{"kratos": "https://go-kratos.dev/"}, + + Timestamp: ×tamppb.Timestamp{Seconds: 20, Nanos: 2}, + Duration: &durationpb.Duration{Seconds: 120, Nanos: 22}, + Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}}, + Double: &wrapperspb.DoubleValue{Value: 12.33}, + Float: &wrapperspb.FloatValue{Value: 12.34}, + Int64: &wrapperspb.Int64Value{Value: 64}, + Int32: &wrapperspb.Int32Value{Value: 32}, + Uint64: &wrapperspb.UInt64Value{Value: 64}, + Uint32: &wrapperspb.UInt32Value{Value: 32}, + Bool: &wrapperspb.BoolValue{Value: false}, + String_: &wrapperspb.StringValue{Value: "go-kratos"}, + Bytes: &wrapperspb.BytesValue{Value: []byte("123")}, + } + content, err := encoding.GetCodec(Name).Marshal(in) + if err != nil { + t.Fatal(err) + } + if "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration="+ + "2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&"+ + "numberOne=2233&price=11.23&sex=woman&simples=3344&simples=5566&string=go-kratos"+ + "×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" != string(content) { + t.Errorf("rawpath is not equal to %s", content) + } + in2 := &complex.Complex{} + err = encoding.GetCodec(Name).Unmarshal(content, in2) + if err != nil { + t.Fatal(err) + } + if int64(2233) != in2.Id { + t.Errorf("expect %v, got %v", int64(2233), in2.Id) + } + if "2233" != in2.NoOne { + t.Errorf("expect %v, got %v", "2233", in2.NoOne) + } + if in2.Simple == nil { + t.Errorf("expect %v, got %v", nil, in2.Simple) + } + if "5566" != in2.Simple.Component { + t.Errorf("expect %v, got %v", "5566", in2.Simple.Component) + } + if in2.Simples == nil { + t.Errorf("expect %v, got %v", nil, in2.Simples) + } + if len(in2.Simples) != 2 { + t.Errorf("expect %v, got %v", 2, len(in2.Simples)) + } + if "3344" != in2.Simples[0] { + t.Errorf("expect %v, got %v", "3344", in2.Simples[0]) + } + if "5566" != in2.Simples[1] { + t.Errorf("expect %v, got %v", "5566", in2.Simples[1]) + } +} + +func TestDecodeStructPb(t *testing.T) { + req := new(ectest.StructPb) + query := `data={"name":"kratos"}&data_list={"name1": "kratos"}&data_list={"name2": "go-kratos"}` + if err := encoding.GetCodec(Name).Unmarshal([]byte(query), req); err != nil { + t.Fatal(err) + } + if "kratos" != req.Data.GetFields()["name"].GetStringValue() { + t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name"].GetStringValue()) + } + if len(req.DataList) != 2 { + t.Fatalf("execpt %v, got %v", 2, len(req.DataList)) + } + if "kratos" != req.DataList[0].GetFields()["name1"].GetStringValue() { + t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name1"].GetStringValue()) + } + if "go-kratos" != req.DataList[1].GetFields()["name2"].GetStringValue() { + t.Errorf("except %v, got %v", "go-kratos", req.Data.GetFields()["name2"].GetStringValue()) + } +} + +func TestDecodeBytesValuePb(t *testing.T) { + url := "https://example.com/xx/?a=1&b=2&c=3" + val := base64.URLEncoding.EncodeToString([]byte(url)) + content := "bytes=" + val + in2 := &complex.Complex{} + if err := encoding.GetCodec(Name).Unmarshal([]byte(content), in2); err != nil { + t.Error(err) + } + if url != string(in2.Bytes.Value) { + t.Errorf("except %s, got %s", val, in2.Bytes.Value) + } +} + +func TestEncodeFieldMask(t *testing.T) { + req := &bdtest.HelloRequest{ + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"foo", "bar"}}, + } + if v := EncodeFieldMask(req.ProtoReflect()); v != "updateMask=foo,bar" { + t.Errorf("got %s", v) + } +} + +func TestOptional(t *testing.T) { + v := int32(100) + req := &bdtest.HelloRequest{ + Name: "foo", + Sub: &bdtest.Sub{Name: "bar"}, + OptInt32: &v, + } + query, _ := EncodeValues(req) + if query.Encode() != "name=foo&optInt32=100&sub.naming=bar" { + t.Fatalf("got %s", query.Encode()) + } +} diff --git a/encoding/form/proto_decode.go b/encoding/form/proto_decode.go new file mode 100644 index 0000000..1620162 --- /dev/null +++ b/encoding/form/proto_decode.go @@ -0,0 +1,333 @@ +package form + +import ( + "encoding/base64" + "errors" + "fmt" + "net/url" + "strconv" + "strings" + "time" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// DecodeValues decode url value into proto message. +func DecodeValues(msg proto.Message, values url.Values) error { + for key, values := range values { + if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), values); err != nil { + return err + } + } + return nil +} + +func populateFieldValues(v protoreflect.Message, fieldPath []string, values []string) error { + if len(fieldPath) < 1 { + return errors.New("no field path") + } + if len(values) < 1 { + return errors.New("no value provided") + } + + var fd protoreflect.FieldDescriptor + for i, fieldName := range fieldPath { + if fd = getFieldDescriptor(v, fieldName); fd == nil { + // ignore unexpected field. + return nil + } + + if i == len(fieldPath)-1 { + break + } + + if fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated { + if fd.IsMap() && len(fieldPath) > 1 { + // post subfield + return populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values) + } + return fmt.Errorf("invalid path: %q is not a message", fieldName) + } + + v = v.Mutable(fd).Message() + } + if of := fd.ContainingOneof(); of != nil { + if f := v.WhichOneof(of); f != nil { + return fmt.Errorf("field already set for oneof %q", of.FullName().Name()) + } + } + switch { + case fd.IsList(): + return populateRepeatedField(fd, v.Mutable(fd).List(), values) + case fd.IsMap(): + return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values) + } + if len(values) > 1 { + return fmt.Errorf("too many values for field %q: %s", fd.FullName().Name(), strings.Join(values, ", ")) + } + return populateField(fd, v, values[0]) +} + +func getFieldDescriptor(v protoreflect.Message, fieldName string) protoreflect.FieldDescriptor { + fields := v.Descriptor().Fields() + var fd protoreflect.FieldDescriptor + if fd = getDescriptorByFieldAndName(fields, fieldName); fd == nil { + if v.Descriptor().FullName() == structMessageFullname { + fd = fields.ByNumber(structFieldsFieldNumber) + } else if len(fieldName) > 2 && strings.HasSuffix(fieldName, "[]") { + fd = getDescriptorByFieldAndName(fields, strings.TrimSuffix(fieldName, "[]")) + } + } + return fd +} + +func getDescriptorByFieldAndName(fields protoreflect.FieldDescriptors, fieldName string) protoreflect.FieldDescriptor { + var fd protoreflect.FieldDescriptor + if fd = fields.ByName(protoreflect.Name(fieldName)); fd == nil { + fd = fields.ByJSONName(fieldName) + } + return fd +} + +func populateField(fd protoreflect.FieldDescriptor, v protoreflect.Message, value string) error { + if value == "" { + return nil + } + val, err := parseField(fd, value) + if err != nil { + return fmt.Errorf("parsing field %q: %w", fd.FullName().Name(), err) + } + v.Set(fd, val) + return nil +} + +func populateRepeatedField(fd protoreflect.FieldDescriptor, list protoreflect.List, values []string) error { + for _, value := range values { + v, err := parseField(fd, value) + if err != nil { + return fmt.Errorf("parsing list %q: %w", fd.FullName().Name(), err) + } + list.Append(v) + } + return nil +} + +func populateMapField(fd protoreflect.FieldDescriptor, mp protoreflect.Map, fieldPath []string, values []string) error { + // post sub key. + nkey := len(fieldPath) - 1 + key, err := parseField(fd.MapKey(), fieldPath[nkey]) + if err != nil { + return fmt.Errorf("parsing map key %q: %w", fd.FullName().Name(), err) + } + vkey := len(values) - 1 + value, err := parseField(fd.MapValue(), values[vkey]) + if err != nil { + return fmt.Errorf("parsing map value %q: %w", fd.FullName().Name(), err) + } + mp.Set(key.MapKey(), value) + return nil +} + +func parseField(fd protoreflect.FieldDescriptor, value string) (protoreflect.Value, error) { + switch fd.Kind() { + case protoreflect.BoolKind: + v, err := strconv.ParseBool(value) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfBool(v), nil + case protoreflect.EnumKind: + enum, err := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName()) + switch { + case errors.Is(err, protoregistry.NotFound): + return protoreflect.Value{}, fmt.Errorf("enum %q is not registered", fd.Enum().FullName()) + case err != nil: + return protoreflect.Value{}, fmt.Errorf("failed to look up enum: %w", err) + } + v := enum.Descriptor().Values().ByName(protoreflect.Name(value)) + if v == nil { + i, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value) + } + v = enum.Descriptor().Values().ByNumber(protoreflect.EnumNumber(i)) + if v == nil { + return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value) + } + } + return protoreflect.ValueOfEnum(v.Number()), nil + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: + v, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfInt32(int32(v)), nil + case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + v, err := strconv.ParseInt(value, 10, 64) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfInt64(v), nil + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: + v, err := strconv.ParseUint(value, 10, 32) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfUint32(uint32(v)), nil + case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: + v, err := strconv.ParseUint(value, 10, 64) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfUint64(v), nil + case protoreflect.FloatKind: + v, err := strconv.ParseFloat(value, 32) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfFloat32(float32(v)), nil + case protoreflect.DoubleKind: + v, err := strconv.ParseFloat(value, 64) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfFloat64(v), nil + case protoreflect.StringKind: + return protoreflect.ValueOfString(value), nil + case protoreflect.BytesKind: + v, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfBytes(v), nil + case protoreflect.MessageKind, protoreflect.GroupKind: + return parseMessage(fd.Message(), value) + default: + panic(fmt.Sprintf("unknown field kind: %v", fd.Kind())) + } +} + +func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect.Value, error) { + var msg proto.Message + switch md.FullName() { + case "google.protobuf.Timestamp": + if value == nullStr { + break + } + t, err := time.Parse(time.RFC3339Nano, value) + if err != nil { + return protoreflect.Value{}, err + } + msg = timestamppb.New(t) + case "google.protobuf.Duration": + if value == nullStr { + break + } + d, err := time.ParseDuration(value) + if err != nil { + return protoreflect.Value{}, err + } + msg = durationpb.New(d) + case "google.protobuf.DoubleValue": + v, err := strconv.ParseFloat(value, 64) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + msg = wrapperspb.Double(v) + case "google.protobuf.FloatValue": + v, err := strconv.ParseFloat(value, 32) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + msg = wrapperspb.Float(float32(v)) + case "google.protobuf.Int64Value": + v, err := strconv.ParseInt(value, 10, 64) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + msg = wrapperspb.Int64(v) + case "google.protobuf.Int32Value": + v, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + msg = wrapperspb.Int32(int32(v)) + case "google.protobuf.UInt64Value": + v, err := strconv.ParseUint(value, 10, 64) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + msg = wrapperspb.UInt64(v) + case "google.protobuf.UInt32Value": + v, err := strconv.ParseUint(value, 10, 32) //nolint:gomnd + if err != nil { + return protoreflect.Value{}, err + } + msg = wrapperspb.UInt32(uint32(v)) + case "google.protobuf.BoolValue": + v, err := strconv.ParseBool(value) + if err != nil { + return protoreflect.Value{}, err + } + msg = wrapperspb.Bool(v) + case "google.protobuf.StringValue": + msg = wrapperspb.String(value) + case "google.protobuf.BytesValue": + v, err := base64.StdEncoding.DecodeString(value) + if err != nil { + if v, err = base64.URLEncoding.DecodeString(value); err != nil { + return protoreflect.Value{}, err + } + } + msg = wrapperspb.Bytes(v) + case "google.protobuf.FieldMask": + fm := &fieldmaskpb.FieldMask{} + for _, fv := range strings.Split(value, ",") { + fm.Paths = append(fm.Paths, jsonSnakeCase(fv)) + } + msg = fm + case "google.protobuf.Value": + fm, err := structpb.NewValue(value) + if err != nil { + return protoreflect.Value{}, err + } + msg = fm + case "google.protobuf.Struct": + var v structpb.Struct + if err := protojson.Unmarshal([]byte(value), &v); err != nil { + return protoreflect.Value{}, err + } + msg = &v + default: + return protoreflect.Value{}, fmt.Errorf("unsupported message type: %q", string(md.FullName())) + } + return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil +} + +// jsonSnakeCase converts a camelCase identifier to a snake_case identifier, +// according to the protobuf JSON specification. +// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L864 +func jsonSnakeCase(s string) string { + var b []byte + for i := 0; i < len(s); i++ { // proto identifiers are always ASCII + c := s[i] + if isASCIIUpper(c) { + b = append(b, '_') + c += 'a' - 'A' // convert to lowercase + } + b = append(b, c) + } + return string(b) +} + +func isASCIIUpper(c byte) bool { + return 'A' <= c && c <= 'Z' +} diff --git a/encoding/form/proto_decode_test.go b/encoding/form/proto_decode_test.go new file mode 100644 index 0000000..fbe2343 --- /dev/null +++ b/encoding/form/proto_decode_test.go @@ -0,0 +1,217 @@ +package form + +import ( + "fmt" + "net/url" + "reflect" + "strconv" + "testing" + + "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/go-kratos/kratos/v2/internal/testdata/complex" +) + +func TestDecodeValues(t *testing.T) { + form, err := url.ParseQuery("a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=" + + "2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&numberOne=2233&price=11.23&sex=woman&simples=3344&" + + "simples=5566&string=go-kratos×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566") + if err != nil { + t.Fatal(err) + } + + comp := &complex.Complex{} + err = DecodeValues(comp, form) + if err != nil { + t.Fatal(err) + } + if comp.Id != int64(2233) { + t.Errorf("want %v, got %v", int64(2233), comp.Id) + } + if comp.NoOne != "2233" { + t.Errorf("want %v, got %v", "2233", comp.NoOne) + } + if comp.Simple == nil { + t.Fatalf("want %v, got %v", nil, comp.Simple) + } + if comp.Simple.Component != "5566" { + t.Errorf("want %v, got %v", "5566", comp.Simple.Component) + } + if len(comp.Simples) != 2 { + t.Fatalf("want %v, got %v", 2, len(comp.Simples)) + } + if comp.Simples[0] != "3344" { + t.Errorf("want %v, got %v", "3344", comp.Simples[0]) + } + if comp.Simples[1] != "5566" { + t.Errorf("want %v, got %v", "5566", comp.Simples[1]) + } +} + +func TestGetFieldDescriptor(t *testing.T) { + comp := &complex.Complex{} + + field := getFieldDescriptor(comp.ProtoReflect(), "id") + if field.Kind() != protoreflect.Int64Kind { + t.Errorf("want: %d, got: %d", protoreflect.Int64Kind, field.Kind()) + } + + field = getFieldDescriptor(comp.ProtoReflect(), "simples") + if field.Kind() != protoreflect.StringKind { + t.Errorf("want: %d, got: %d", protoreflect.StringKind, field.Kind()) + } +} + +func TestPopulateRepeatedField(t *testing.T) { + query, err := url.ParseQuery("simples=3344&simples=5566") + if err != nil { + t.Fatal(err) + } + comp := &complex.Complex{} + field := getFieldDescriptor(comp.ProtoReflect(), "simples") + + err = populateRepeatedField(field, comp.ProtoReflect().Mutable(field).List(), query["simples"]) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual([]string{"3344", "5566"}, comp.GetSimples()) { + t.Errorf("want: %v, got: %v", []string{"3344", "5566"}, comp.GetSimples()) + } +} + +func TestPopulateMapField(t *testing.T) { + query, err := url.ParseQuery("map%5Bkratos%5D=https://go-kratos.dev/") + if err != nil { + t.Fatal(err) + } + comp := &complex.Complex{} + field := getFieldDescriptor(comp.ProtoReflect(), "map") + // Fill the comp map field with the url query values + err = populateMapField(field, comp.ProtoReflect().Mutable(field).Map(), []string{"kratos"}, query["map[kratos]"]) + if err != nil { + t.Fatal(err) + } + // Get the comp map field value + if query["map[kratos]"][0] != comp.Map["kratos"] { + t.Errorf("want: %s, got: %s", query["map[kratos]"], comp.Map["kratos"]) + } +} + +func TestParseField(t *testing.T) { + tests := []struct { + name string + fieldName string + protoReflectKind protoreflect.Kind + value string + targetProtoReflectValue protoreflect.Value + targetErr error + }{ + { + name: "BoolKind", + fieldName: "b", + protoReflectKind: protoreflect.BoolKind, + value: "true", + targetProtoReflectValue: protoreflect.ValueOfBool(true), + targetErr: nil, + }, + { + name: "BoolKind", + fieldName: "b", + protoReflectKind: protoreflect.BoolKind, + value: "a", + targetProtoReflectValue: protoreflect.Value{}, + targetErr: &strconv.NumError{Func: "ParseBool", Num: "a", Err: strconv.ErrSyntax}, + }, + { + name: "EnumKind", + fieldName: "sex", + protoReflectKind: protoreflect.EnumKind, + value: "1", + targetProtoReflectValue: protoreflect.ValueOfEnum(1), + targetErr: nil, + }, + { + name: "EnumKind", + fieldName: "sex", + protoReflectKind: protoreflect.EnumKind, + value: "2", + targetProtoReflectValue: protoreflect.Value{}, + targetErr: fmt.Errorf("%q is not a valid value", "2"), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + comp := &complex.Complex{} + field := getFieldDescriptor(comp.ProtoReflect(), test.fieldName) + if test.protoReflectKind != field.Kind() { + t.Fatalf("want: %d, got: %d", test.protoReflectKind, field.Kind()) + } + val, err := parseField(field, test.value) + if !reflect.DeepEqual(test.targetErr, err) { + t.Fatalf("want: %s, got: %s", test.targetErr, err) + } + if !reflect.DeepEqual(test.targetProtoReflectValue, val) { + t.Errorf("want: %s, got: %s", test.targetProtoReflectValue, val) + } + }) + } +} + +func TestJsonSnakeCase(t *testing.T) { + tests := []struct { + camelCase string + snakeCase string + }{ + { + "userId", "user_id", + }, + { + "user", "user", + }, + { + "userIdAndUsername", "user_id_and_username", + }, + { + "", "", + }, + } + for _, test := range tests { + t.Run(test.camelCase, func(t *testing.T) { + snake := jsonSnakeCase(test.camelCase) + if snake != test.snakeCase { + t.Errorf("want: %s, got: %s", test.snakeCase, snake) + } + }) + } +} + +func TestIsASCIIUpper(t *testing.T) { + tests := []struct { + b byte + upper bool + }{ + { + 'A', true, + }, + { + 'a', false, + }, + { + ',', false, + }, + { + '1', false, + }, + { + ' ', false, + }, + } + for _, test := range tests { + t.Run(string(test.b), func(t *testing.T) { + upper := isASCIIUpper(test.b) + if test.upper != upper { + t.Errorf("'%s' is not ascii upper", string(test.b)) + } + }) + } +} diff --git a/encoding/form/proto_encode.go b/encoding/form/proto_encode.go new file mode 100644 index 0000000..25d4ac1 --- /dev/null +++ b/encoding/form/proto_encode.go @@ -0,0 +1,224 @@ +package form + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/fieldmaskpb" +) + +// EncodeValues encode a message into url values. +func EncodeValues(msg interface{}) (url.Values, error) { + if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) { + return url.Values{}, nil + } + if v, ok := msg.(proto.Message); ok { + u := make(url.Values) + err := encodeByField(u, "", v.ProtoReflect()) + if err != nil { + return nil, err + } + return u, nil + } + return encoder.Encode(msg) +} + +func encodeByField(u url.Values, path string, m protoreflect.Message) (finalErr error) { + m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { + var ( + key string + newPath string + ) + if fd.HasJSONName() { + key = fd.JSONName() + } else { + key = fd.TextName() + } + if path == "" { + newPath = key + } else { + newPath = path + "." + key + } + if of := fd.ContainingOneof(); of != nil { + if f := m.WhichOneof(of); f != nil && f != fd { + return true + } + } + switch { + case fd.IsList(): + if v.List().Len() > 0 { + list, err := encodeRepeatedField(fd, v.List()) + if err != nil { + finalErr = err + return false + } + for _, item := range list { + u.Add(newPath, item) + } + } + case fd.IsMap(): + if v.Map().Len() > 0 { + m, err := encodeMapField(fd, v.Map()) + if err != nil { + finalErr = err + return false + } + for k, value := range m { + u.Set(fmt.Sprintf("%s[%s]", newPath, k), value) + } + } + case (fd.Kind() == protoreflect.MessageKind) || (fd.Kind() == protoreflect.GroupKind): + value, err := encodeMessage(fd.Message(), v) + if err == nil { + u.Set(newPath, value) + return true + } + if err = encodeByField(u, newPath, v.Message()); err != nil { + finalErr = err + return false + } + default: + value, err := EncodeField(fd, v) + if err != nil { + finalErr = err + return false + } + u.Set(newPath, value) + } + return true + }) + return +} + +func encodeRepeatedField(fieldDescriptor protoreflect.FieldDescriptor, list protoreflect.List) ([]string, error) { + var values []string + for i := 0; i < list.Len(); i++ { + value, err := EncodeField(fieldDescriptor, list.Get(i)) + if err != nil { + return nil, err + } + values = append(values, value) + } + return values, nil +} + +func encodeMapField(fieldDescriptor protoreflect.FieldDescriptor, mp protoreflect.Map) (map[string]string, error) { + m := make(map[string]string) + mp.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool { + key, err := EncodeField(fieldDescriptor.MapValue(), k.Value()) + if err != nil { + return false + } + value, err := EncodeField(fieldDescriptor.MapValue(), v) + if err != nil { + return false + } + m[key] = value + return true + }) + + return m, nil +} + +// EncodeField encode proto message filed +func EncodeField(fieldDescriptor protoreflect.FieldDescriptor, value protoreflect.Value) (string, error) { + switch fieldDescriptor.Kind() { + case protoreflect.BoolKind: + return strconv.FormatBool(value.Bool()), nil + case protoreflect.EnumKind: + if fieldDescriptor.Enum().FullName() == "google.protobuf.NullValue" { + return nullStr, nil + } + desc := fieldDescriptor.Enum().Values().ByNumber(value.Enum()) + return string(desc.Name()), nil + case protoreflect.StringKind: + return value.String(), nil + case protoreflect.BytesKind: + return base64.URLEncoding.EncodeToString(value.Bytes()), nil + case protoreflect.MessageKind, protoreflect.GroupKind: + return encodeMessage(fieldDescriptor.Message(), value) + default: + return fmt.Sprint(value.Interface()), nil + } +} + +// encodeMessage marshals the fields in the given protoreflect.Message. +// If the typeURL is non-empty, then a synthetic "@type" field is injected +// containing the URL as the value. +func encodeMessage(msgDescriptor protoreflect.MessageDescriptor, value protoreflect.Value) (string, error) { + switch msgDescriptor.FullName() { + case timestampMessageFullname: + return marshalTimestamp(value.Message()) + case durationMessageFullname: + return marshalDuration(value.Message()) + case bytesMessageFullname: + return marshalBytes(value.Message()) + case "google.protobuf.DoubleValue", "google.protobuf.FloatValue", "google.protobuf.Int64Value", "google.protobuf.Int32Value", + "google.protobuf.UInt64Value", "google.protobuf.UInt32Value", "google.protobuf.BoolValue", "google.protobuf.StringValue": + fd := msgDescriptor.Fields() + v := value.Message().Get(fd.ByName("value")) + return fmt.Sprint(v.Interface()), nil + case fieldMaskFullName: + m, ok := value.Message().Interface().(*fieldmaskpb.FieldMask) + if !ok || m == nil { + return "", nil + } + for i, v := range m.Paths { + m.Paths[i] = jsonCamelCase(v) + } + return strings.Join(m.Paths, ","), nil + default: + return "", fmt.Errorf("unsupported message type: %q", string(msgDescriptor.FullName())) + } +} + +// EncodeFieldMask return field mask name=paths +func EncodeFieldMask(m protoreflect.Message) (query string) { + m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { + if fd.Kind() == protoreflect.MessageKind { + if msg := fd.Message(); msg.FullName() == fieldMaskFullName { + value, err := encodeMessage(msg, v) + if err != nil { + return false + } + if fd.HasJSONName() { + query = fd.JSONName() + "=" + value + } else { + query = fd.TextName() + "=" + value + } + return false + } + } + return true + }) + return +} + +// JSONCamelCase converts a snake_case identifier to a camelCase identifier, +// according to the protobuf JSON specification. +// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L842 +func jsonCamelCase(s string) string { + var b []byte + var wasUnderscore bool + for i := 0; i < len(s); i++ { // proto identifiers are always ASCII + c := s[i] + if c != '_' { + if wasUnderscore && isASCIILower(c) { + c -= 'a' - 'A' // convert to uppercase + } + b = append(b, c) + } + wasUnderscore = c == '_' + } + return string(b) +} + +func isASCIILower(c byte) bool { + return 'a' <= c && c <= 'z' +} diff --git a/encoding/form/proto_encode_test.go b/encoding/form/proto_encode_test.go new file mode 100644 index 0000000..7d0cfa9 --- /dev/null +++ b/encoding/form/proto_encode_test.go @@ -0,0 +1,110 @@ +package form + +import ( + "testing" + + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/go-kratos/kratos/v2/internal/testdata/complex" +) + +func TestEncodeValues(t *testing.T) { + in := &complex.Complex{ + Id: 2233, + NoOne: "2233", + Simple: &complex.Simple{Component: "5566"}, + Simples: []string{"3344", "5566"}, + B: true, + Sex: complex.Sex_woman, + Age: 18, + A: 19, + Count: 3, + Price: 11.23, + D: 22.22, + Byte: []byte("123"), + Map: map[string]string{"kratos": "https://go-kratos.dev/", "kratos_start": "https://go-kratos.dev/en/docs/getting-started/start/"}, + + Timestamp: ×tamppb.Timestamp{Seconds: 20, Nanos: 2}, + Duration: &durationpb.Duration{Seconds: 120, Nanos: 22}, + Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}}, + Double: &wrapperspb.DoubleValue{Value: 12.33}, + Float: &wrapperspb.FloatValue{Value: 12.34}, + Int64: &wrapperspb.Int64Value{Value: 64}, + Int32: &wrapperspb.Int32Value{Value: 32}, + Uint64: &wrapperspb.UInt64Value{Value: 64}, + Uint32: &wrapperspb.UInt32Value{Value: 32}, + Bool: &wrapperspb.BoolValue{Value: false}, + String_: &wrapperspb.StringValue{Value: "go-kratos"}, + Bytes: &wrapperspb.BytesValue{Value: []byte("123")}, + } + query, err := EncodeValues(in) + if err != nil { + t.Fatal(err) + } + want := "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&map%5Bkratos_start%5D=https%3A%2F%2Fgo-kratos.dev%2Fen%2Fdocs%2Fgetting-started%2Fstart%2F&numberOne=2233&price=11.23&sex=woman&simples=3344&simples=5566&string=go-kratos×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" // nolint:lll + if got := query.Encode(); want != got { + t.Errorf("want: %s, got: %s", want, got) + } +} + +func TestJsonCamelCase(t *testing.T) { + tests := []struct { + camelCase string + snakeCase string + }{ + { + "userId", "user_id", + }, + { + "user", "user", + }, + { + "userIdAndUsername", "user_id_and_username", + }, + { + "", "", + }, + } + for _, test := range tests { + t.Run(test.snakeCase, func(t *testing.T) { + camel := jsonCamelCase(test.snakeCase) + if camel != test.camelCase { + t.Errorf("want: %s, got: %s", test.camelCase, camel) + } + }) + } +} + +func TestIsASCIILower(t *testing.T) { + tests := []struct { + b byte + lower bool + }{ + { + 'A', false, + }, + { + 'a', true, + }, + { + ',', false, + }, + { + '1', false, + }, + { + ' ', false, + }, + } + for _, test := range tests { + t.Run(string(test.b), func(t *testing.T) { + lower := isASCIILower(test.b) + if test.lower != lower { + t.Errorf("'%s' is not ascii lower", string(test.b)) + } + }) + } +} diff --git a/encoding/form/well_known_types.go b/encoding/form/well_known_types.go new file mode 100644 index 0000000..8b0894d --- /dev/null +++ b/encoding/form/well_known_types.go @@ -0,0 +1,94 @@ +package form + +import ( + "encoding/base64" + "fmt" + "math" + "strings" + "time" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +const ( + // timestamp + timestampMessageFullname protoreflect.FullName = "google.protobuf.Timestamp" + maxTimestampSeconds = 253402300799 + minTimestampSeconds = -6213559680013 + timestampSecondsFieldNumber protoreflect.FieldNumber = 1 + timestampNanosFieldNumber protoreflect.FieldNumber = 2 + + // duration + durationMessageFullname protoreflect.FullName = "google.protobuf.Duration" + secondsInNanos = 999999999 + durationSecondsFieldNumber protoreflect.FieldNumber = 1 + durationNanosFieldNumber protoreflect.FieldNumber = 2 + + // bytes + bytesMessageFullname protoreflect.FullName = "google.protobuf.BytesValue" + bytesValueFieldNumber protoreflect.FieldNumber = 1 + + // google.protobuf.Struct. + structMessageFullname protoreflect.FullName = "google.protobuf.Struct" + structFieldsFieldNumber protoreflect.FieldNumber = 1 + + fieldMaskFullName protoreflect.FullName = "google.protobuf.FieldMask" +) + +func marshalTimestamp(m protoreflect.Message) (string, error) { + fds := m.Descriptor().Fields() + fdSeconds := fds.ByNumber(timestampSecondsFieldNumber) + fdNanos := fds.ByNumber(timestampNanosFieldNumber) + + secsVal := m.Get(fdSeconds) + nanosVal := m.Get(fdNanos) + secs := secsVal.Int() + nanos := nanosVal.Int() + if secs < minTimestampSeconds || secs > maxTimestampSeconds { + return "", fmt.Errorf("%s: seconds out of range %v", timestampMessageFullname, secs) + } + if nanos < 0 || nanos > secondsInNanos { + return "", fmt.Errorf("%s: nanos out of range %v", timestampMessageFullname, nanos) + } + // Uses RFC 3339, where generated output will be Z-normalized and uses 0, 3, + // 6 or 9 fractional digits. + t := time.Unix(secs, nanos).UTC() + x := t.Format("2006-01-02T15:04:05.000000000") + x = strings.TrimSuffix(x, "000") + x = strings.TrimSuffix(x, "000") + x = strings.TrimSuffix(x, ".000") + return x + "Z", nil +} + +func marshalDuration(m protoreflect.Message) (string, error) { + fds := m.Descriptor().Fields() + fdSeconds := fds.ByNumber(durationSecondsFieldNumber) + fdNanos := fds.ByNumber(durationNanosFieldNumber) + + secsVal := m.Get(fdSeconds) + nanosVal := m.Get(fdNanos) + secs := secsVal.Int() + nanos := nanosVal.Int() + d := time.Duration(secs) * time.Second + overflow := d/time.Second != time.Duration(secs) + d += time.Duration(nanos) * time.Nanosecond + overflow = overflow || (secs < 0 && nanos < 0 && d > 0) + overflow = overflow || (secs > 0 && nanos > 0 && d < 0) + if overflow { + switch { + case secs < 0: + return time.Duration(math.MinInt64).String(), nil + case secs > 0: + return time.Duration(math.MaxInt64).String(), nil + } + } + return d.String(), nil +} + +func marshalBytes(m protoreflect.Message) (string, error) { + fds := m.Descriptor().Fields() + fdBytes := fds.ByNumber(bytesValueFieldNumber) + bytesVal := m.Get(fdBytes) + val := bytesVal.Bytes() + return base64.StdEncoding.EncodeToString(val), nil +} diff --git a/encoding/form/well_known_types_test.go b/encoding/form/well_known_types_test.go new file mode 100644 index 0000000..5018cf7 --- /dev/null +++ b/encoding/form/well_known_types_test.go @@ -0,0 +1,95 @@ +package form + +import ( + "encoding/base64" + "testing" + "time" + + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestMarshalTimeStamp(t *testing.T) { + tests := []struct { + input *timestamppb.Timestamp + expect string + }{ + { + input: timestamppb.New(time.Date(2022, 1, 2, 3, 4, 5, 6, time.UTC)), + expect: "2022-01-02T03:04:05.000000006Z", + }, + { + input: timestamppb.New(time.Date(2022, 13, 1, 13, 61, 61, 100, time.UTC)), + expect: "2023-01-01T14:02:01.000000100Z", + }, + } + for _, v := range tests { + got, err := marshalTimestamp(v.input.ProtoReflect()) + if err != nil { + t.Fatal(err) + } + if want := v.expect; got != want { + t.Errorf("expect %v, got %v", want, got) + } + } +} + +func TestMarshalDuration(t *testing.T) { + tests := []struct { + input *durationpb.Duration + expect string + }{ + { + input: durationpb.New(time.Duration(1<<63 - 1)), + expect: "2562047h47m16.854775807s", + }, + { + input: durationpb.New(time.Duration(-1 << 63)), + expect: "-2562047h47m16.854775808s", + }, + { + input: durationpb.New(100 * time.Second), + expect: "1m40s", + }, + { + input: durationpb.New(-100 * time.Second), + expect: "-1m40s", + }, + } + for _, v := range tests { + got, err := marshalDuration(v.input.ProtoReflect()) + if err != nil { + t.Fatal(err) + } + if want := v.expect; got != want { + t.Errorf("expect %s, got %s", want, got) + } + } +} + +func TestMarshalBytes(t *testing.T) { + tests := []struct { + input protoreflect.Message + expect string + }{ + { + input: wrapperspb.Bytes([]byte("abc123!?$*&()'-=@~")).ProtoReflect(), + expect: base64.StdEncoding.EncodeToString([]byte("abc123!?$*&()'-=@~")), + }, + { + input: wrapperspb.Bytes([]byte("kratos")).ProtoReflect(), + expect: base64.StdEncoding.EncodeToString([]byte("kratos")), + }, + } + for _, v := range tests { + got, err := marshalBytes(v.input) + if err != nil { + t.Fatal(err) + } + if want := v.expect; got != want { + t.Errorf("expect %v, got %v", want, got) + } + } +} diff --git a/encoding/json/json.go b/encoding/json/json.go new file mode 100644 index 0000000..f5b05a6 --- /dev/null +++ b/encoding/json/json.go @@ -0,0 +1,67 @@ +package json + +import ( + "encoding/json" + "git.diulo.com/mogfee/kit/encoding" + "reflect" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +// Name is the name registered for the json codec. +const Name = "json" + +var ( + // MarshalOptions is a configurable JSON format marshaller. + MarshalOptions = protojson.MarshalOptions{ + EmitUnpopulated: true, + } + // UnmarshalOptions is a configurable JSON format parser. + UnmarshalOptions = protojson.UnmarshalOptions{ + DiscardUnknown: true, + } +) + +func init() { + encoding.RegisterCodec(codec{}) +} + +// codec is a Codec implementation with json. +type codec struct{} + +func (codec) Marshal(v interface{}) ([]byte, error) { + switch m := v.(type) { + case json.Marshaler: + return m.MarshalJSON() + case proto.Message: + return MarshalOptions.Marshal(m) + default: + return json.Marshal(m) + } +} + +func (codec) Unmarshal(data []byte, v interface{}) error { + switch m := v.(type) { + case json.Unmarshaler: + return m.UnmarshalJSON(data) + case proto.Message: + return UnmarshalOptions.Unmarshal(data, m) + default: + rv := reflect.ValueOf(v) + for rv := rv; rv.Kind() == reflect.Ptr; { + if rv.IsNil() { + rv.Set(reflect.New(rv.Type().Elem())) + } + rv = rv.Elem() + } + if m, ok := reflect.Indirect(rv).Interface().(proto.Message); ok { + return UnmarshalOptions.Unmarshal(data, m) + } + return json.Unmarshal(data, m) + } +} + +func (codec) Name() string { + return Name +} diff --git a/encoding/json/json_test.go b/encoding/json/json_test.go new file mode 100644 index 0000000..b43e4ef --- /dev/null +++ b/encoding/json/json_test.go @@ -0,0 +1,147 @@ +package json + +import ( + "encoding/json" + "reflect" + "strings" + "testing" + + testData "github.com/go-kratos/kratos/v2/internal/testdata/encoding" +) + +type testEmbed struct { + Level1a int `json:"a"` + Level1b int `json:"b"` + Level1c int `json:"c"` +} + +type testMessage struct { + Field1 string `json:"a"` + Field2 string `json:"b"` + Field3 string `json:"c"` + Embed *testEmbed `json:"embed,omitempty"` +} + +type mock struct { + value int +} + +const ( + Unknown = iota + Gopher + Zebra +) + +func (a *mock) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + switch strings.ToLower(s) { + default: + a.value = Unknown + case "gopher": + a.value = Gopher + case "zebra": + a.value = Zebra + } + + return nil +} + +func (a *mock) MarshalJSON() ([]byte, error) { + var s string + switch a.value { + default: + s = "unknown" + case Gopher: + s = "gopher" + case Zebra: + s = "zebra" + } + + return json.Marshal(s) +} + +func TestJSON_Marshal(t *testing.T) { + tests := []struct { + input interface{} + expect string + }{ + { + input: &testMessage{}, + expect: `{"a":"","b":"","c":""}`, + }, + { + input: &testMessage{Field1: "a", Field2: "b", Field3: "c"}, + expect: `{"a":"a","b":"b","c":"c"}`, + }, + { + input: &testData.TestModel{Id: 1, Name: "go-kratos", Hobby: []string{"1", "2"}}, + expect: `{"id":"1","name":"go-kratos","hobby":["1","2"],"attrs":{}}`, + }, + { + input: &mock{value: Gopher}, + expect: `"gopher"`, + }, + } + for _, v := range tests { + data, err := (codec{}).Marshal(v.input) + if err != nil { + t.Errorf("marshal(%#v): %s", v.input, err) + } + if got, want := string(data), v.expect; strings.ReplaceAll(got, " ", "") != want { + if strings.Contains(want, "\n") { + t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", v.input, got, want) + } else { + t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want) + } + } + } +} + +func TestJSON_Unmarshal(t *testing.T) { + p := testMessage{} + p2 := testData.TestModel{} + p3 := &testData.TestModel{} + p4 := &mock{} + tests := []struct { + input string + expect interface{} + }{ + { + input: `{"a":"","b":"","c":""}`, + expect: &testMessage{}, + }, + { + input: `{"a":"a","b":"b","c":"c"}`, + expect: &p, + }, + { + input: `{"id":"1","name":"go-kratos","hobby":["1","2"],"attrs":{}}`, + expect: &p2, + }, + { + input: `{"id":1,"name":"go-kratos","hobby":["1","2"]}`, + expect: &p3, + }, + { + input: `"zebra"`, + expect: p4, + }, + } + for _, v := range tests { + want := []byte(v.input) + err := (codec{}).Unmarshal(want, v.expect) + if err != nil { + t.Errorf("marshal(%#v): %s", v.input, err) + } + got, err := codec{}.Marshal(v.expect) + if err != nil { + t.Errorf("marshal(%#v): %s", v.input, err) + } + if !reflect.DeepEqual(strings.ReplaceAll(string(got), " ", ""), strings.ReplaceAll(string(want), " ", "")) { + t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want) + } + } +} diff --git a/encoding/proto/proto.go b/encoding/proto/proto.go new file mode 100644 index 0000000..0da5e0d --- /dev/null +++ b/encoding/proto/proto.go @@ -0,0 +1,31 @@ +// Package proto defines the protobuf codec. Importing this package will +// register the codec. +package proto + +import ( + "google.golang.org/protobuf/proto" + + "github.com/go-kratos/kratos/v2/encoding" +) + +// Name is the name registered for the proto compressor. +const Name = "proto" + +func init() { + encoding.RegisterCodec(codec{}) +} + +// codec is a Codec implementation with protobuf. It is the default codec for Transport. +type codec struct{} + +func (codec) Marshal(v interface{}) ([]byte, error) { + return proto.Marshal(v.(proto.Message)) +} + +func (codec) Unmarshal(data []byte, v interface{}) error { + return proto.Unmarshal(data, v.(proto.Message)) +} + +func (codec) Name() string { + return Name +} diff --git a/encoding/proto/proto_test.go b/encoding/proto/proto_test.go new file mode 100644 index 0000000..1d33b33 --- /dev/null +++ b/encoding/proto/proto_test.go @@ -0,0 +1,46 @@ +package proto + +import ( + "reflect" + "testing" + + testData "github.com/go-kratos/kratos/v2/internal/testdata/encoding" +) + +func TestName(t *testing.T) { + c := new(codec) + if !reflect.DeepEqual(c.Name(), "proto") { + t.Errorf("no expect float_key value: %v, but got: %v", c.Name(), "proto") + } +} + +func TestCodec(t *testing.T) { + c := new(codec) + + model := testData.TestModel{ + Id: 1, + Name: "kratos", + Hobby: []string{"study", "eat", "play"}, + } + + m, err := c.Marshal(&model) + if err != nil { + t.Errorf("Marshal() should be nil, but got %s", err) + } + + var res testData.TestModel + + err = c.Unmarshal(m, &res) + if err != nil { + t.Errorf("Unmarshal() should be nil, but got %s", err) + } + if !reflect.DeepEqual(res.Id, model.Id) { + t.Errorf("ID should be %d, but got %d", res.Id, model.Id) + } + if !reflect.DeepEqual(res.Name, model.Name) { + t.Errorf("Name should be %s, but got %s", res.Name, model.Name) + } + if !reflect.DeepEqual(res.Hobby, model.Hobby) { + t.Errorf("Hobby should be %s, but got %s", res.Hobby, model.Hobby) + } +} diff --git a/encoding/xml/xml.go b/encoding/xml/xml.go new file mode 100644 index 0000000..3352fdf --- /dev/null +++ b/encoding/xml/xml.go @@ -0,0 +1,29 @@ +package xml + +import ( + "encoding/xml" + + "github.com/go-kratos/kratos/v2/encoding" +) + +// Name is the name registered for the xml codec. +const Name = "xml" + +func init() { + encoding.RegisterCodec(codec{}) +} + +// codec is a Codec implementation with xml. +type codec struct{} + +func (codec) Marshal(v interface{}) ([]byte, error) { + return xml.Marshal(v) +} + +func (codec) Unmarshal(data []byte, v interface{}) error { + return xml.Unmarshal(data, v) +} + +func (codec) Name() string { + return Name +} diff --git a/encoding/xml/xml_test.go b/encoding/xml/xml_test.go new file mode 100644 index 0000000..09e7d03 --- /dev/null +++ b/encoding/xml/xml_test.go @@ -0,0 +1,117 @@ +package xml + +import ( + "reflect" + "strings" + "testing" +) + +type Plain struct { + V interface{} +} + +type NestedOrder struct { + XMLName struct{} `xml:"result"` + Field1 string `xml:"parent>c"` + Field2 string `xml:"parent>b"` + Field3 string `xml:"parent>a"` +} + +func TestCodec_Marshal(t *testing.T) { + tests := []struct { + Value interface{} + ExpectXML string + }{ + // Test value types + {Value: &Plain{true}, ExpectXML: `true`}, + {Value: &Plain{false}, ExpectXML: `false`}, + {Value: &Plain{42}, ExpectXML: `42`}, + { + Value: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"}, + ExpectXML: `` + + `` + + `C` + + `B` + + `A` + + `` + + ``, + }, + } + for _, tt := range tests { + data, err := (codec{}).Marshal(tt.Value) + if err != nil { + t.Errorf("marshal(%#v): %s", tt.Value, err) + } + if got, want := string(data), tt.ExpectXML; got != want { + if strings.Contains(want, "\n") { + t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", tt.Value, got, want) + } else { + t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", tt.Value, got, want) + } + } + } +} + +func TestCodec_Unmarshal(t *testing.T) { + tests := []struct { + want interface{} + InputXML string + }{ + { + want: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"}, + InputXML: `` + + `` + + `C` + + `B` + + `A` + + `` + + ``, + }, + } + + for _, tt := range tests { + vt := reflect.TypeOf(tt.want) + dest := reflect.New(vt.Elem()).Interface() + data := []byte(tt.InputXML) + err := (codec{}).Unmarshal(data, dest) + if err != nil { + t.Errorf("unmarshal(%#v, %#v): %s", tt.InputXML, dest, err) + } + if got, want := dest, tt.want; !reflect.DeepEqual(got, want) { + t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", tt.InputXML, got, want) + } + } +} + +func TestCodec_NilUnmarshal(t *testing.T) { + tests := []struct { + want interface{} + InputXML string + }{ + { + want: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"}, + InputXML: `` + + `` + + `C` + + `B` + + `A` + + `` + + ``, + }, + } + + for _, tt := range tests { + s := struct { + A string `xml:"a"` + B *NestedOrder + }{A: "a"} + data := []byte(tt.InputXML) + err := (codec{}).Unmarshal(data, &s.B) + if err != nil { + t.Errorf("unmarshal(%#v, %#v): %s", tt.InputXML, s.B, err) + } + if got, want := s.B, tt.want; !reflect.DeepEqual(got, want) { + t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", tt.InputXML, got, want) + } + } +} diff --git a/encoding/yaml/yaml.go b/encoding/yaml/yaml.go new file mode 100644 index 0000000..1364b24 --- /dev/null +++ b/encoding/yaml/yaml.go @@ -0,0 +1,29 @@ +package yaml + +import ( + "gopkg.in/yaml.v3" + + "github.com/go-kratos/kratos/v2/encoding" +) + +// Name is the name registered for the yaml codec. +const Name = "yaml" + +func init() { + encoding.RegisterCodec(codec{}) +} + +// codec is a Codec implementation with yaml. +type codec struct{} + +func (codec) Marshal(v interface{}) ([]byte, error) { + return yaml.Marshal(v) +} + +func (codec) Unmarshal(data []byte, v interface{}) error { + return yaml.Unmarshal(data, v) +} + +func (codec) Name() string { + return Name +} diff --git a/encoding/yaml/yaml_test.go b/encoding/yaml/yaml_test.go new file mode 100644 index 0000000..5cb7464 --- /dev/null +++ b/encoding/yaml/yaml_test.go @@ -0,0 +1,104 @@ +package yaml + +import ( + "math" + "reflect" + "testing" +) + +func TestCodec_Unmarshal(t *testing.T) { + tests := []struct { + data string + value interface{} + }{ + { + "", + (*struct{})(nil), + }, + { + "{}", &struct{}{}, + }, + { + "v: hi", + map[string]string{"v": "hi"}, + }, + { + "v: hi", map[string]interface{}{"v": "hi"}, + }, + { + "v: true", + map[string]string{"v": "true"}, + }, + { + "v: true", + map[string]interface{}{"v": true}, + }, + { + "v: 10", + map[string]interface{}{"v": 10}, + }, + { + "v: 0b10", + map[string]interface{}{"v": 2}, + }, + { + "v: 0xA", + map[string]interface{}{"v": 10}, + }, + { + "v: 4294967296", + map[string]int64{"v": 4294967296}, + }, + { + "v: 0.1", + map[string]interface{}{"v": 0.1}, + }, + { + "v: .1", + map[string]interface{}{"v": 0.1}, + }, + { + "v: .Inf", + map[string]interface{}{"v": math.Inf(+1)}, + }, + { + "v: -.Inf", + map[string]interface{}{"v": math.Inf(-1)}, + }, + { + "v: -10", + map[string]interface{}{"v": -10}, + }, + { + "v: -.1", + map[string]interface{}{"v": -0.1}, + }, + } + for _, tt := range tests { + v := reflect.ValueOf(tt.value).Type() + value := reflect.New(v) + err := (codec{}).Unmarshal([]byte(tt.data), value.Interface()) + if err != nil { + t.Fatalf("(codec{}).Unmarshal should not return err") + } + } + spec := struct { + A string + B map[string]interface{} + }{A: "a"} + err := (codec{}).Unmarshal([]byte("v: hi"), &spec.B) + if err != nil { + t.Fatalf("(codec{}).Unmarshal should not return err") + } +} + +func TestCodec_Marshal(t *testing.T) { + value := map[string]string{"v": "hi"} + got, err := (codec{}).Marshal(value) + if err != nil { + t.Fatalf("should not return err") + } + if string(got) != "v: hi\n" { + t.Fatalf("want \"v: hi\n\" return \"%s\"", string(got)) + } +} diff --git a/example/main.go b/example/main.go index 87b9acd..eebe5ce 100644 --- a/example/main.go +++ b/example/main.go @@ -1,20 +1,93 @@ package main import ( + "context" "fmt" - "git.diulo.com/mogfee/protoc-gen-kit/example/service" - "git.diulo.com/mogfee/protoc-gen-kit/log" - "git.diulo.com/mogfee/protoc-gen-kit/middleware" - user "git.diulo.com/mogfee/protoc-gen-kit/proto/v1" - "github.com/gin-gonic/gin" + "git.diulo.com/mogfee/kit" + "git.diulo.com/mogfee/kit/errors" + "git.diulo.com/mogfee/kit/example/service" + "git.diulo.com/mogfee/kit/middleware" + "git.diulo.com/mogfee/kit/middleware/logging" + user "git.diulo.com/mogfee/kit/proto/v1" + "git.diulo.com/mogfee/kit/transport/http" + "strings" ) func main() { - gin.SetMode(gin.ReleaseMode) - app := gin.Default() - srv := service.UserService{} - logger := log.With(log.DefaultLogger) - user.RegisterUserHandler(app, &srv, middleware.Logger(logger), middleware.Validate()) - fmt.Println("http://localhost:8888") - app.Run("localhost:8888") + hs := http.NewServer( + http.Address("localhost:9093"), + http.Middleware( + logging.Server(), + func(handler middleware.Handler) middleware.Handler { + return func(ctx context.Context, a any) (any, error) { + if v, ok := a.(interface { + Validate() error + }); ok { + if err := v.Validate(); err != nil { + if a, ok := err.(interface { + Field() string + Reason() string + }); ok { + field := a.Field() + return nil, errors.New(400, "InvalidArgument", "").WithMetadata(map[string]string{ + strings.ToLower(field[0:1]) + field[1:]: a.Reason(), + }) + } + //if vv, ok := err.(ValidateError); ok { + // s.Result(http.StatusBadRequest, "InvalidArgument", vv.ErrorName(), map[string]string{ + // lcfirst(vv.Field()): vv.Reason(), + // }) + // return + //} + } + } + return handler(ctx, a) + } + }, + ), + ) + route := hs.Route("/") + route.GET("/api/abc", func(ctx http.Context) error { + in := UserAddRequest{Name: "tom"} + http.SetOperation(ctx, "/api/abc") + h := ctx.Middleware(func(ctx context.Context, a any) (any, error) { + return AddUser(ctx, a.(*UserAddRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply, _ := out.(*UserAddResponse) + return ctx.Result(200, reply) + }) + + user.RegisterUserHTTPServer(hs, &service.UserService{}) + + app := kit.New( + kit.Name("kit-server"), + kit.Version("v1.0"), + kit.Server(hs), + ) + fmt.Println(app.Run()) + fmt.Println(app.Stop()) +} +func ma1in() { + //gin.SetMode(gin.ReleaseMode) + //app := gin.Default() + //srv := service.UserService{} + //logger := log.With(log.DefaultLogger) + //user.RegisterUserHandler(app, &srv, middleware.Logger(logger), middleware.Validate()) + //fmt.Println("http://localhost:8888") + //app.Run("localhost:8888") +} + +type UserAddRequest struct { + Name string +} +type UserAddResponse struct { + Id string +} + +func AddUser(ctx context.Context, request *UserAddRequest) (*UserAddResponse, error) { + return &UserAddResponse{Id: request.Name}, errors.New(500, "xx", "") } diff --git a/example/service/service.go b/example/service/service.go index 931e00a..aee4b07 100644 --- a/example/service/service.go +++ b/example/service/service.go @@ -3,8 +3,8 @@ package service import ( "context" "encoding/json" - user "git.diulo.com/mogfee/protoc-gen-kit/proto/v1" - "git.diulo.com/mogfee/protoc-gen-kit/xerrors" + user "git.diulo.com/mogfee/kit/proto/v1" + "git.diulo.com/mogfee/kit/transport" ) type UserService struct { @@ -12,12 +12,14 @@ type UserService struct { } func (*UserService) Login(ctx context.Context, req *user.LoginRequest) (*user.LoginResponse, error) { - return nil, xerrors.BadRequest("BadRequest", "B") - - b, _ := json.Marshal(req) - return &user.LoginResponse{Token: string(b)}, nil + //return nil, errors.BadRequest("BadRequest", "B") + return &user.LoginResponse{Token: "182131292"}, nil } func (*UserService) List(ctx context.Context, req *user.LoginRequest) (*user.LoginResponse, error) { + tr, ok := transport.FromServerContext(ctx) + if ok { + return &user.LoginResponse{Token: tr.Operation()}, nil + } //fmt.Println(ctx.Value("userId")) //return nil, errors.Wrap(errors.InternalServer("InternalServer", "B"), "") diff --git a/internal/endpoint/endpoint.go b/internal/endpoint/endpoint.go new file mode 100644 index 0000000..160b694 --- /dev/null +++ b/internal/endpoint/endpoint.go @@ -0,0 +1,34 @@ +package endpoint + +import ( + "net/url" +) + +// NewEndpoint new an Endpoint URL. +func NewEndpoint(scheme, host string) *url.URL { + return &url.URL{Scheme: scheme, Host: host} +} + +// ParseEndpoint parses an Endpoint URL. +func ParseEndpoint(endpoints []string, scheme string) (string, error) { + for _, e := range endpoints { + u, err := url.Parse(e) + if err != nil { + return "", err + } + + if u.Scheme == scheme { + return u.Host, nil + } + } + return "", nil +} + +// Scheme is the scheme of endpoint url. +// examples: scheme="http",isSecure=true get "https" +func Scheme(scheme string, isSecure bool) string { + if isSecure { + return scheme + "s" + } + return scheme +} diff --git a/internal/endpoint/endpoint_test.go b/internal/endpoint/endpoint_test.go new file mode 100644 index 0000000..c02878f --- /dev/null +++ b/internal/endpoint/endpoint_test.go @@ -0,0 +1,126 @@ +package endpoint + +import ( + "net/url" + "reflect" + "testing" +) + +func TestNewEndpoint(t *testing.T) { + type args struct { + scheme string + host string + } + tests := []struct { + name string + args args + want *url.URL + }{ + { + name: "https://github.com/go-kratos/kratos/", + args: args{"https", "github.com/go-kratos/kratos/"}, + want: &url.URL{Scheme: "https", Host: "github.com/go-kratos/kratos/"}, + }, + { + name: "https://go-kratos.dev/", + args: args{"https", "go-kratos.dev/"}, + want: &url.URL{Scheme: "https", Host: "go-kratos.dev/"}, + }, + { + name: "https://www.google.com/", + args: args{"https", "www.google.com/"}, + want: &url.URL{Scheme: "https", Host: "www.google.com/"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewEndpoint(tt.args.scheme, tt.args.host); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewEndpoint() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseEndpoint(t *testing.T) { + type args struct { + endpoints []string + scheme string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "kratos", + args: args{endpoints: []string{"https://github.com/go-kratos/kratos"}, scheme: "https"}, + want: "github.com", + wantErr: false, + }, + { + name: "test", + args: args{endpoints: []string{"http://go-kratos.dev/"}, scheme: "https"}, + want: "", + wantErr: false, + }, + { + name: "localhost:8080", + args: args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpcs"}, + want: "localhost:8080", + wantErr: false, + }, + { + name: "localhost:8081", + args: args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpc"}, + want: "", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseEndpoint(tt.args.endpoints, tt.args.scheme) + if (err != nil) != tt.wantErr { + t.Errorf("ParseEndpoint() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseEndpoint() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSchema(t *testing.T) { + tests := []struct { + schema string + secure bool + want string + }{ + { + schema: "http", + secure: true, + want: "https", + }, + { + schema: "http", + secure: false, + want: "http", + }, + { + schema: "grpc", + secure: true, + want: "grpcs", + }, + { + schema: "grpc", + secure: false, + want: "grpc", + }, + } + for _, tt := range tests { + if got := Scheme(tt.schema, tt.secure); got != tt.want { + t.Errorf("Schema() = %v, want %v", got, tt.want) + } + } +} diff --git a/internal/host/host.go b/internal/host/host.go new file mode 100644 index 0000000..774f7ae --- /dev/null +++ b/internal/host/host.go @@ -0,0 +1,92 @@ +package host + +import ( + "fmt" + "net" + "strconv" +) + +// ExtractHostPort from address +func ExtractHostPort(addr string) (host string, port uint64, err error) { + var ports string + host, ports, err = net.SplitHostPort(addr) + if err != nil { + return + } + port, err = strconv.ParseUint(ports, 10, 16) //nolint:gomnd + return +} + +func isValidIP(addr string) bool { + ip := net.ParseIP(addr) + return ip.IsGlobalUnicast() && !ip.IsInterfaceLocalMulticast() +} + +// Port return a real port. +func Port(lis net.Listener) (int, bool) { + if addr, ok := lis.Addr().(*net.TCPAddr); ok { + return addr.Port, true + } + return 0, false +} + +// Extract returns a private addr and port. +func Extract(hostPort string, lis net.Listener) (string, error) { + addr, port, err := net.SplitHostPort(hostPort) + if err != nil && lis == nil { + return "", err + } + if lis != nil { + p, ok := Port(lis) + if !ok { + return "", fmt.Errorf("failed to extract port: %v", lis.Addr()) + } + port = strconv.Itoa(p) + } + if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]" && addr != "::") { + return net.JoinHostPort(addr, port), nil + } + ifaces, err := net.Interfaces() + if err != nil { + return "", err + } + minIndex := int(^uint(0) >> 1) + ips := make([]net.IP, 0) + for _, iface := range ifaces { + if (iface.Flags & net.FlagUp) == 0 { + continue + } + if iface.Index >= minIndex && len(ips) != 0 { + continue + } + addrs, err := iface.Addrs() + if err != nil { + continue + } + for i, rawAddr := range addrs { + var ip net.IP + switch addr := rawAddr.(type) { + case *net.IPAddr: + ip = addr.IP + case *net.IPNet: + ip = addr.IP + default: + continue + } + if isValidIP(ip.String()) { + minIndex = iface.Index + if i == 0 { + ips = make([]net.IP, 0, 1) + } + ips = append(ips, ip) + if ip.To4() != nil { + break + } + } + } + } + if len(ips) != 0 { + return net.JoinHostPort(ips[len(ips)-1].String(), port), nil + } + return "", nil +} diff --git a/internal/host/host_test.go b/internal/host/host_test.go new file mode 100644 index 0000000..8551875 --- /dev/null +++ b/internal/host/host_test.go @@ -0,0 +1,149 @@ +package host + +import ( + "net" + "reflect" + "testing" +) + +func TestValidIP(t *testing.T) { + tests := []struct { + addr string + expect bool + }{ + {"127.0.0.1", false}, + {"255.255.255.255", false}, + {"0.0.0.0", false}, + {"localhost", false}, + {"10.1.0.1", true}, + {"172.16.0.1", true}, + {"192.168.1.1", true}, + {"8.8.8.8", true}, + {"1.1.1.1", true}, + {"9.255.255.255", true}, + {"10.0.0.0", true}, + {"10.255.255.255", true}, + {"11.0.0.0", true}, + {"172.15.255.255", true}, + {"172.16.0.0", true}, + {"172.16.255.255", true}, + {"172.23.18.255", true}, + {"172.31.255.255", true}, + {"172.31.0.0", true}, + {"172.32.0.0", true}, + {"192.167.255.255", true}, + {"192.168.0.0", true}, + {"192.168.255.255", true}, + {"192.169.0.0", true}, + {"fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true}, + {"fc00::", true}, + {"fcff:1200:0:44::", true}, + {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true}, + {"fe00::", true}, + } + for _, test := range tests { + t.Run(test.addr, func(t *testing.T) { + res := isValidIP(test.addr) + if res != test.expect { + t.Fatalf("expected %t got %t", test.expect, res) + } + }) + } +} + +func TestExtract(t *testing.T) { + tests := []struct { + addr string + expect string + }{ + {"127.0.0.1:80", "127.0.0.1:80"}, + {"10.0.0.1:80", "10.0.0.1:80"}, + {"172.16.0.1:80", "172.16.0.1:80"}, + {"192.168.1.1:80", "192.168.1.1:80"}, + {"0.0.0.0:80", ""}, + {"[::]:80", ""}, + {":80", ""}, + } + for _, test := range tests { + t.Run(test.addr, func(t *testing.T) { + res, err := Extract(test.addr, nil) + if err != nil { + t.Fatal(err) + } + if res != test.expect && (test.expect == "" && test.addr == test.expect) { + t.Fatalf("expected %s got %s", test.expect, res) + } + }) + } + lis, err := net.Listen("tcp", ":12345") + if err != nil { + t.Errorf("expected: %v got %v", nil, err) + } + res, err := Extract("", lis) + if err != nil { + t.Errorf("expected: %v got %v", nil, err) + } + expect, err := Extract(lis.Addr().String(), nil) + if err != nil { + t.Errorf("expected: %v got %v", nil, err) + } + if !reflect.DeepEqual(res, expect) { + t.Errorf("expected %s got %s", expect, res) + } +} + +func TestExtract2(t *testing.T) { + addr := "localhost:9001" + lis, err := net.Listen("tcp", addr) + if err != nil { + t.Errorf("expected: %v got %v", nil, err) + } + res, err := Extract(addr, lis) + if err != nil { + t.Errorf("expected: %v got %v", nil, err) + } + if !reflect.DeepEqual(res, "localhost:9001") { + t.Errorf("expected %s got %s", "localhost:9001", res) + } +} + +func TestPort(t *testing.T) { + lis, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatal(err) + } + port, ok := Port(lis) + if !ok || port == 0 { + t.Fatalf("expected: %s got %d", lis.Addr().String(), port) + } +} + +func TestExtractHostPort(t *testing.T) { + host, port, err := ExtractHostPort("127.0.0.1:8000") + if err != nil { + t.Fatalf("expected: %v got %v", nil, err) + } + t.Logf("host port: %s, %d", host, port) + + host, port, err = ExtractHostPort("www.bilibili.com:80") + if err != nil { + t.Fatalf("expected: %v got %v", nil, err) + } + t.Logf("host port: %s, %d", host, port) + + host, port, err = ExtractHostPort("consul://2/33") + if err == nil { + t.Fatalf("expected: not nil got %v", nil) + } + t.Logf("host port: %s, %d", host, port) +} + +func TestIpIsUp(t *testing.T) { + interfaces, err := net.Interfaces() + if err != nil { + t.Fail() + } + for i := range interfaces { + println(interfaces[i].Name, interfaces[i].Flags&net.FlagUp) + } +} diff --git a/log/mq/nsq.go b/log/mq/nsq.go index 311b2cb..a9696b0 100644 --- a/log/mq/nsq.go +++ b/log/mq/nsq.go @@ -4,8 +4,8 @@ import ( "context" "encoding/json" "fmt" - "git.diulo.com/mogfee/protoc-gen-kit/log" - "git.diulo.com/mogfee/protoc-gen-kit/xgo" + "git.diulo.com/mogfee/kit/log" + "git.diulo.com/mogfee/kit/xgo" "github.com/nsqio/go-nsq" "sync" "sync/atomic" diff --git a/log/mq/nsq_test.go b/log/mq/nsq_test.go index 144f5fc..221d4d0 100644 --- a/log/mq/nsq_test.go +++ b/log/mq/nsq_test.go @@ -3,7 +3,7 @@ package mq import ( "context" "fmt" - "git.diulo.com/mogfee/protoc-gen-kit/log" + "git.diulo.com/mogfee/kit/log" "testing" ) diff --git a/middleware/jwt.go b/middleware/jwt.go index 4fb3c9c..2c98aae 100644 --- a/middleware/jwt.go +++ b/middleware/jwt.go @@ -2,7 +2,7 @@ package middleware import ( "context" - "git.diulo.com/mogfee/protoc-gen-kit/constants" + "git.diulo.com/mogfee/kit/constants" "google.golang.org/grpc/metadata" ) diff --git a/middleware/logger.go b/middleware/logger.go index e5a290a..09b1e8f 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -2,7 +2,7 @@ package middleware import ( "context" - "git.diulo.com/mogfee/protoc-gen-kit/log" + "git.diulo.com/mogfee/kit/log" ) func Logger(logger log.Logger) Middleware { diff --git a/middleware/logging/logging.go b/middleware/logging/logging.go new file mode 100644 index 0000000..711b7a8 --- /dev/null +++ b/middleware/logging/logging.go @@ -0,0 +1,14 @@ +package logging + +import ( + "context" + "git.diulo.com/mogfee/kit/middleware" +) + +func Server() middleware.Middleware { + return func(handler middleware.Handler) middleware.Handler { + return func(ctx context.Context, a any) (any, error) { + return handler(ctx, a) + } + } +} diff --git a/options.go b/options.go index c124b4b..40998fd 100644 --- a/options.go +++ b/options.go @@ -1,4 +1,4 @@ -package protoc_gen_kit +package kit import ( "context" diff --git a/proto/buf.gen.yaml b/proto/buf.gen.yaml index 7fbdde6..da4a700 100644 --- a/proto/buf.gen.yaml +++ b/proto/buf.gen.yaml @@ -8,13 +8,18 @@ plugins: out: v1 opt: - paths=source_relative - # - name: kit - # path: ./main - # out: v1 - # opt: - # - paths=source_relative + - name: gin-kit + path: ./gin-kit + out: v1 + opt: + - paths=source_relative + - name: kit + # path: ./main + out: v1 + opt: + - paths=source_relative - name: ts - path: ./main +# path: ./main out: v1 opt: - paths=source_relative diff --git a/proto/gin-kit b/proto/gin-kit new file mode 100755 index 0000000..c9fbce0 Binary files /dev/null and b/proto/gin-kit differ diff --git a/proto/user.proto b/proto/user.proto index f5a5345..ed94994 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -5,7 +5,7 @@ option go_package = "./;user"; import "validate/validate.proto"; import "google/api/annotations.proto"; -import "google/protobuf/wrappers.proto"; +//import "google/protobuf/wrappers.proto"; import "google/protobuf/descriptor.proto"; diff --git a/proto/v1/google/api/client.pb.go b/proto/v1/google/api/client.pb.go index fff994b..5ce78bf 100644 --- a/proto/v1/google/api/client.pb.go +++ b/proto/v1/google/api/client.pb.go @@ -68,10 +68,10 @@ var ( // // Example: // - // service Foo { - // option (google.api.default_host) = "foo.googleapi.com"; - // ... - // } + // service Foo { + // option (google.api.default_host) = "foo.googleapi.com"; + // ... + // } // // optional string default_host = 1049; E_DefaultHost = &file_google_api_client_proto_extTypes[0] @@ -79,22 +79,22 @@ var ( // // Example: // - // service Foo { - // option (google.api.oauth_scopes) = \ - // "https://www.googleapis.com/auth/cloud-platform"; - // ... - // } + // service Foo { + // option (google.api.oauth_scopes) = \ + // "https://www.googleapis.com/auth/cloud-platform"; + // ... + // } // // If there is more than one scope, use a comma-separated string: // // Example: // - // service Foo { - // option (google.api.oauth_scopes) = \ - // "https://www.googleapis.com/auth/cloud-platform," - // "https://www.googleapis.com/auth/monitoring"; - // ... - // } + // service Foo { + // option (google.api.oauth_scopes) = \ + // "https://www.googleapis.com/auth/cloud-platform," + // "https://www.googleapis.com/auth/monitoring"; + // ... + // } // // optional string oauth_scopes = 1050; E_OauthScopes = &file_google_api_client_proto_extTypes[1] @@ -116,26 +116,26 @@ var ( // // For example, the proto RPC and annotation: // - // rpc CreateSubscription(CreateSubscriptionRequest) - // returns (Subscription) { - // option (google.api.method_signature) = "name,topic"; - // } + // rpc CreateSubscription(CreateSubscriptionRequest) + // returns (Subscription) { + // option (google.api.method_signature) = "name,topic"; + // } // // Would add the following Java overload (in addition to the method accepting // the request object): // - // public final Subscription createSubscription(String name, String topic) + // public final Subscription createSubscription(String name, String topic) // // The following backwards-compatibility guidelines apply: // - // * Adding this annotation to an unannotated method is backwards + // - Adding this annotation to an unannotated method is backwards // compatible. - // * Adding this annotation to a method which already has existing + // - Adding this annotation to a method which already has existing // method signature annotations is backwards compatible if and only if // the new method signature annotation is last in the sequence. - // * Modifying or removing an existing method signature annotation is + // - Modifying or removing an existing method signature annotation is // a breaking change. - // * Re-ordering existing method signature annotations is a breaking + // - Re-ordering existing method signature annotations is a breaking // change. // // repeated string method_signature = 1051; diff --git a/proto/v1/google/api/field_behavior.pb.go b/proto/v1/google/api/field_behavior.pb.go index 0638be1..176b31d 100644 --- a/proto/v1/google/api/field_behavior.pb.go +++ b/proto/v1/google/api/field_behavior.pb.go @@ -134,13 +134,13 @@ var ( // // Examples: // - // string name = 1 [(google.api.field_behavior) = REQUIRED]; - // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; - // google.protobuf.Duration ttl = 1 - // [(google.api.field_behavior) = INPUT_ONLY]; - // google.protobuf.Timestamp expire_time = 1 - // [(google.api.field_behavior) = OUTPUT_ONLY, - // (google.api.field_behavior) = IMMUTABLE]; + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; // // repeated google.api.FieldBehavior field_behavior = 1052; E_FieldBehavior = &file_google_api_field_behavior_proto_extTypes[0] diff --git a/proto/v1/google/api/http.pb.go b/proto/v1/google/api/http.pb.go index 21a08eb..e54bb07 100644 --- a/proto/v1/google/api/http.pb.go +++ b/proto/v1/google/api/http.pb.go @@ -126,19 +126,19 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // // Example: // -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/{name=messages/*}" -// }; -// } -// } -// message GetMessageRequest { -// string name = 1; // Mapped to URL path. -// } -// message Message { -// string text = 1; // The resource content. -// } +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } // // This enables an HTTP REST to gRPC mapping as below: // @@ -150,21 +150,21 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // automatically become HTTP query parameters if there is no HTTP request body. // For example: // -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get:"/v1/messages/{message_id}" -// }; -// } -// } -// message GetMessageRequest { -// message SubMessage { -// string subfield = 1; -// } -// string message_id = 1; // Mapped to URL path. -// int64 revision = 2; // Mapped to URL query parameter `revision`. -// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. -// } +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } // // This enables a HTTP JSON to RPC mapping as below: // @@ -185,18 +185,18 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // specifies the mapping. Consider a REST update method on the // message resource collection: // -// service Messaging { -// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "message" -// }; -// } -// } -// message UpdateMessageRequest { -// string message_id = 1; // mapped to the URL -// Message message = 2; // mapped to the body -// } +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } // // The following HTTP JSON to RPC mapping is enabled, where the // representation of the JSON in the request body is determined by @@ -212,19 +212,18 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // request body. This enables the following alternative definition of // the update method: // -// service Messaging { -// rpc UpdateMessage(Message) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "*" -// }; -// } -// } -// message Message { -// string message_id = 1; -// string text = 2; -// } -// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } // // The following HTTP JSON to RPC mapping is enabled: // @@ -242,20 +241,20 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // It is possible to define multiple HTTP methods for one RPC by using // the `additional_bindings` option. Example: // -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/messages/{message_id}" -// additional_bindings { -// get: "/v1/users/{user_id}/messages/{message_id}" -// } -// }; -// } -// } -// message GetMessageRequest { -// string message_id = 1; -// string user_id = 2; -// } +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } // // This enables the following two alternative HTTP JSON to RPC mappings: // @@ -267,15 +266,15 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // // ## Rules for HTTP mapping // -// 1. Leaf request fields (recursive expansion nested messages in the request -// message) are classified into three categories: -// - Fields referred by the path template. They are passed via the URL path. -// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP -// request body. -// - All other fields are passed via the URL query parameters, and the -// parameter name is the field path in the request message. A repeated -// field can be represented as multiple query parameters under the same -// name. +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. // 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields // are passed via URL path and HTTP request body. // 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all @@ -283,12 +282,12 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // // ### Path template syntax // -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; // // The syntax `*` matches a single URL path segment. The syntax `**` matches // zero or more URL path segments, which must be the last part of the URL path @@ -337,11 +336,11 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool { // // Example: // -// http: -// rules: -// # Selects a gRPC method and applies HttpRule to it. -// - selector: example.v1.Messaging.GetMessage -// get: /v1/messages/{message_id}/{sub.subfield} +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} // // ## Special notes // @@ -384,6 +383,7 @@ type HttpRule struct { // can be defined using the 'custom' field. // // Types that are assignable to Pattern: + // // *HttpRule_Get // *HttpRule_Put // *HttpRule_Post diff --git a/proto/v1/google/api/httpbody.pb.go b/proto/v1/google/api/httpbody.pb.go index b37dae3..81579f5 100644 --- a/proto/v1/google/api/httpbody.pb.go +++ b/proto/v1/google/api/httpbody.pb.go @@ -39,7 +39,6 @@ const ( // payload formats that can't be represented as JSON, such as raw binary or // an HTML page. // -// // This message can be used both in streaming and non-streaming API methods in // the request as well as the response. // @@ -49,28 +48,28 @@ const ( // // Example: // -// message GetResourceRequest { -// // A unique request id. -// string request_id = 1; +// message GetResourceRequest { +// // A unique request id. +// string request_id = 1; // -// // The raw HTTP body is bound to this field. -// google.api.HttpBody http_body = 2; -// } +// // The raw HTTP body is bound to this field. +// google.api.HttpBody http_body = 2; +// } // -// service ResourceService { -// rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); -// rpc UpdateResource(google.api.HttpBody) returns -// (google.protobuf.Empty); -// } +// service ResourceService { +// rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); +// rpc UpdateResource(google.api.HttpBody) returns +// (google.protobuf.Empty); +// } // // Example with streaming methods: // -// service CaldavService { -// rpc GetCalendar(stream google.api.HttpBody) -// returns (stream google.api.HttpBody); -// rpc UpdateCalendar(stream google.api.HttpBody) -// returns (stream google.api.HttpBody); -// } +// service CaldavService { +// rpc GetCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// rpc UpdateCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// } // // Use of this type only changes how the request and response bodies are // handled, all other features will continue to work unchanged. diff --git a/proto/v1/google/protobuf/any.pb.go b/proto/v1/google/protobuf/any.pb.go index 18c997a..ca0bf5f 100644 --- a/proto/v1/google/protobuf/any.pb.go +++ b/proto/v1/google/protobuf/any.pb.go @@ -40,8 +40,7 @@ // It is functionally a tuple of the full name of the remote message type and // the serialized bytes of the remote message value. // -// -// Constructing an Any +// # Constructing an Any // // An Any message containing another message value is constructed using New: // @@ -51,8 +50,7 @@ // } // ... // make use of any // -// -// Unmarshaling an Any +// # Unmarshaling an Any // // With a populated Any message, the underlying message can be serialized into // a remote concrete message value in a few ways. @@ -98,8 +96,7 @@ // listed in the case clauses are linked into the Go binary and therefore also // registered in the global registry. // -// -// Type checking an Any +// # Type checking an Any // // In order to type check whether an Any message represents some other message, // then use the MessageIs method: @@ -118,7 +115,6 @@ // } // ... // make use of m // } -// package anypb import ( @@ -146,45 +142,45 @@ const ( // // Example 1: Pack and unpack a message in C++. // -// Foo foo = ...; -// Any any; -// any.PackFrom(foo); -// ... -// if (any.UnpackTo(&foo)) { -// ... -// } +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } // // Example 2: Pack and unpack a message in Java. // -// Foo foo = ...; -// Any any = Any.pack(foo); -// ... -// if (any.is(Foo.class)) { -// foo = any.unpack(Foo.class); -// } +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } // // Example 3: Pack and unpack a message in Python. // -// foo = Foo(...) -// any = Any() -// any.Pack(foo) -// ... -// if any.Is(Foo.DESCRIPTOR): -// any.Unpack(foo) -// ... +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... // // Example 4: Pack and unpack a message in Go // -// foo := &pb.Foo{...} -// any, err := anypb.New(foo) -// if err != nil { -// ... -// } -// ... -// foo := &pb.Foo{} -// if err := any.UnmarshalTo(foo); err != nil { -// ... -// } +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } // // The pack methods provided by protobuf library will by default use // 'type.googleapis.com/full.type.name' as the type URL and the unpack @@ -192,35 +188,33 @@ const ( // in the type URL, for example "foo.bar.com/x/y.z" will yield type // name "y.z". // -// -// JSON +// # JSON // // The JSON representation of an `Any` value uses the regular // representation of the deserialized, embedded message, with an // additional field `@type` which contains the type URL. Example: // -// package google.profile; -// message Person { -// string first_name = 1; -// string last_name = 2; -// } +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } // -// { -// "@type": "type.googleapis.com/google.profile.Person", -// "firstName": , -// "lastName": -// } +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } // // If the embedded message type is well-known and has a custom JSON // representation, that representation will be embedded adding a field // `value` which holds the custom JSON in addition to the `@type` // field. Example (for message [google.protobuf.Duration][]): // -// { -// "@type": "type.googleapis.com/google.protobuf.Duration", -// "value": "1.212s" -// } -// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } type Any struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -238,14 +232,14 @@ type Any struct { // scheme `http`, `https`, or no scheme, one can optionally set up a type // server that maps type URLs to message definitions as follows: // - // * If no scheme is provided, `https` is assumed. - // * An HTTP GET on the URL must yield a [google.protobuf.Type][] - // value in binary format, or produce an error. - // * Applications are allowed to cache lookup results based on the - // URL, or have them precompiled into a binary to avoid any - // lookup. Therefore, binary compatibility needs to be preserved - // on changes to types. (Use versioned type names to manage - // breaking changes.) + // - If no scheme is provided, `https` is assumed. + // - An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // - Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) // // Note: this functionality is not currently available in the official // protobuf release, and it is not used for type URLs beginning with @@ -253,7 +247,6 @@ type Any struct { // // Schemes other than `http`, `https` (or the empty scheme) might be // used with implementation specific semantics. - // TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"` // Must be a valid serialized protocol buffer of the above specified type. Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` diff --git a/proto/v1/google/protobuf/api.pb.go b/proto/v1/google/protobuf/api.pb.go index 4f38ccd..4f30af4 100644 --- a/proto/v1/google/protobuf/api.pb.go +++ b/proto/v1/google/protobuf/api.pb.go @@ -92,8 +92,6 @@ type Api struct { // `google.feature.v1`. For major versions 0 and 1, the suffix can // be omitted. Zero major versions must only be used for // experimental, non-GA interfaces. - // - // Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` // Source context for the protocol buffer service represented by this // message. @@ -292,45 +290,45 @@ func (x *Method) GetSyntax() typepb.Syntax { // interface must redeclare all the methods from the included interface, but // documentation and options are inherited as follows: // -// - If after comment and whitespace stripping, the documentation -// string of the redeclared method is empty, it will be inherited -// from the original method. +// - If after comment and whitespace stripping, the documentation +// string of the redeclared method is empty, it will be inherited +// from the original method. // -// - Each annotation belonging to the service config (http, -// visibility) which is not set in the redeclared method will be -// inherited. +// - Each annotation belonging to the service config (http, +// visibility) which is not set in the redeclared method will be +// inherited. // -// - If an http annotation is inherited, the path pattern will be -// modified as follows. Any version prefix will be replaced by the -// version of the including interface plus the [root][] path if -// specified. +// - If an http annotation is inherited, the path pattern will be +// modified as follows. Any version prefix will be replaced by the +// version of the including interface plus the [root][] path if +// specified. // // Example of a simple mixin: // -// package google.acl.v1; -// service AccessControl { -// // Get the underlying ACL object. -// rpc GetAcl(GetAclRequest) returns (Acl) { -// option (google.api.http).get = "/v1/{resource=**}:getAcl"; -// } -// } +// package google.acl.v1; +// service AccessControl { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// } // -// package google.storage.v2; -// service Storage { -// rpc GetAcl(GetAclRequest) returns (Acl); +// package google.storage.v2; +// service Storage { +// rpc GetAcl(GetAclRequest) returns (Acl); // -// // Get a data record. -// rpc GetData(GetDataRequest) returns (Data) { -// option (google.api.http).get = "/v2/{resource=**}"; -// } -// } +// // Get a data record. +// rpc GetData(GetDataRequest) returns (Data) { +// option (google.api.http).get = "/v2/{resource=**}"; +// } +// } // // Example of a mixin configuration: // -// apis: -// - name: google.storage.v2.Storage -// mixins: -// - name: google.acl.v1.AccessControl +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl // // The mixin construct implies that all methods in `AccessControl` are // also declared with same name and request/response types in @@ -338,34 +336,34 @@ func (x *Method) GetSyntax() typepb.Syntax { // see the effective `Storage.GetAcl` method after inheriting // documentation and annotations as follows: // -// service Storage { -// // Get the underlying ACL object. -// rpc GetAcl(GetAclRequest) returns (Acl) { -// option (google.api.http).get = "/v2/{resource=**}:getAcl"; -// } -// ... -// } +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/{resource=**}:getAcl"; +// } +// ... +// } // // Note how the version in the path pattern changed from `v1` to `v2`. // // If the `root` field in the mixin is specified, it should be a // relative path under which inherited HTTP paths are placed. Example: // -// apis: -// - name: google.storage.v2.Storage -// mixins: -// - name: google.acl.v1.AccessControl -// root: acls +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// root: acls // // This implies the following inherited HTTP annotation: // -// service Storage { -// // Get the underlying ACL object. -// rpc GetAcl(GetAclRequest) returns (Acl) { -// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; -// } -// ... -// } +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; +// } +// ... +// } type Mixin struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/proto/v1/google/protobuf/compiler/plugin.pb.go b/proto/v1/google/protobuf/compiler/plugin.pb.go index 1a8df62..d5787fa 100644 --- a/proto/v1/google/protobuf/compiler/plugin.pb.go +++ b/proto/v1/google/protobuf/compiler/plugin.pb.go @@ -387,7 +387,9 @@ type CodeGeneratorResponse_File struct { // produced by another code generator. The original generator may provide // insertion points by placing special annotations in the file that look // like: - // @@protoc_insertion_point(NAME) + // + // @@protoc_insertion_point(NAME) + // // The annotation can have arbitrary text before and after it on the line, // which allows it to be placed in a comment. NAME should be replaced with // an identifier naming the point -- this is what other generators will use @@ -399,7 +401,9 @@ type CodeGeneratorResponse_File struct { // // For example, the C++ code generator places the following line in the // .pb.h files that it generates: - // // @@protoc_insertion_point(namespace_scope) + // + // // @@protoc_insertion_point(namespace_scope) + // // This line appears within the scope of the file's package namespace, but // outside of any particular class. Another plugin can then specify the // insertion_point "namespace_scope" to generate additional classes or diff --git a/proto/v1/google/protobuf/descriptor.pb.go b/proto/v1/google/protobuf/descriptor.pb.go index fde5964..0adecb6 100644 --- a/proto/v1/google/protobuf/descriptor.pb.go +++ b/proto/v1/google/protobuf/descriptor.pb.go @@ -1679,10 +1679,12 @@ type MessageOptions struct { // efficient, has fewer features, and is more complicated. // // The message must be defined exactly as follows: - // message Foo { - // option message_set_wire_format = true; - // extensions 4 to max; - // } + // + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // // Note that the message cannot have any defined fields; MessageSets only // have extensions. // @@ -1705,14 +1707,17 @@ type MessageOptions struct { // maps field. // // For maps fields: - // map map_field = 1; + // + // map map_field = 1; + // // The parsed descriptor looks like: - // message MapFieldEntry { - // option map_entry = true; - // optional KeyType key = 1; - // optional ValueType value = 2; - // } - // repeated MapFieldEntry map_field = 1; + // + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; // // Implementations may choose not to generate the map_entry=true message, but // use a native map in the target language to hold the keys and values. @@ -1847,7 +1852,6 @@ type FieldOptions struct { // call from multiple threads concurrently, while non-const methods continue // to require exclusive access. // - // // Note that implementations may choose not to check required fields within // a lazy sub-message. That is, calling IsInitialized() on the outer message // may return true even if the inner message has missing required fields. @@ -2426,43 +2430,48 @@ type SourceCodeInfo struct { // tools. // // For example, say we have a file like: - // message Foo { - // optional string foo = 1; - // } + // + // message Foo { + // optional string foo = 1; + // } + // // Let's look at just the field definition: - // optional string foo = 1; - // ^ ^^ ^^ ^ ^^^ - // a bc de f ghi + // + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // // We have the following locations: - // span path represents - // [a,i) [ 4, 0, 2, 0 ] The whole field definition. - // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). - // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). - // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). - // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). // // Notes: - // - A location may refer to a repeated field itself (i.e. not to any - // particular index within it). This is used whenever a set of elements are - // logically enclosed in a single code segment. For example, an entire - // extend block (possibly containing multiple extension definitions) will - // have an outer location whose path refers to the "extensions" repeated - // field without an index. - // - Multiple locations may have the same path. This happens when a single - // logical declaration is spread out across multiple places. The most - // obvious example is the "extend" block again -- there may be multiple - // extend blocks in the same scope, each of which will have the same path. - // - A location's span is not always a subset of its parent's span. For - // example, the "extendee" of an extension declaration appears at the - // beginning of the "extend" block and is shared by all extensions within - // the block. - // - Just because a location's span is a subset of some other location's span - // does not mean that it is a descendant. For example, a "group" defines - // both a type and a field in a single declaration. Thus, the locations - // corresponding to the type and field and their components will overlap. - // - Code which tries to interpret locations should probably be designed to - // ignore those that it doesn't understand, as more types of locations could - // be recorded in the future. + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. Location []*SourceCodeInfo_Location `protobuf:"bytes,1,rep,name=location" json:"location,omitempty"` } @@ -2810,21 +2819,32 @@ type SourceCodeInfo_Location struct { // Each element is a field number or an index. They form a path from // the root FileDescriptorProto to the place where the definition occurs. // For example, this path: - // [ 4, 3, 2, 7, 1 ] + // + // [ 4, 3, 2, 7, 1 ] + // // refers to: - // file.message_type(3) // 4, 3 - // .field(7) // 2, 7 - // .name() // 1 + // + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // // This is because FileDescriptorProto.message_type has field number 4: - // repeated DescriptorProto message_type = 4; + // + // repeated DescriptorProto message_type = 4; + // // and DescriptorProto.field has field number 2: - // repeated FieldDescriptorProto field = 2; + // + // repeated FieldDescriptorProto field = 2; + // // and FieldDescriptorProto.name has field number 1: - // optional string name = 1; + // + // optional string name = 1; // // Thus, the above path gives the location of a field name. If we removed // the last element: - // [ 4, 3, 2, 7 ] + // + // [ 4, 3, 2, 7 ] + // // this path refers to the whole field declaration (from the beginning // of the label to the terminating semicolon). Path []int32 `protobuf:"varint,1,rep,packed,name=path" json:"path,omitempty"` @@ -2853,34 +2873,34 @@ type SourceCodeInfo_Location struct { // // Examples: // - // optional int32 foo = 1; // Comment attached to foo. - // // Comment attached to bar. - // optional int32 bar = 2; + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; // - // optional string baz = 3; - // // Comment attached to baz. - // // Another line attached to baz. + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. // - // // Comment attached to qux. - // // - // // Another line attached to qux. - // optional double qux = 4; + // // Comment attached to qux. + // // + // // Another line attached to qux. + // optional double qux = 4; // - // // Detached comment for corge. This is not leading or trailing comments - // // to qux or corge because there are blank lines separating it from - // // both. + // // Detached comment for corge. This is not leading or trailing comments + // // to qux or corge because there are blank lines separating it from + // // both. // - // // Detached comment for corge paragraph 2. + // // Detached comment for corge paragraph 2. // - // optional string corge = 5; - // /* Block comment attached - // * to corge. Leading asterisks - // * will be removed. */ - // /* Block comment attached to - // * grault. */ - // optional int32 grault = 6; + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; // - // // ignored detached comments. + // // ignored detached comments. LeadingComments *string `protobuf:"bytes,3,opt,name=leading_comments,json=leadingComments" json:"leading_comments,omitempty"` TrailingComments *string `protobuf:"bytes,4,opt,name=trailing_comments,json=trailingComments" json:"trailing_comments,omitempty"` LeadingDetachedComments []string `protobuf:"bytes,6,rep,name=leading_detached_comments,json=leadingDetachedComments" json:"leading_detached_comments,omitempty"` diff --git a/proto/v1/google/protobuf/duration.pb.go b/proto/v1/google/protobuf/duration.pb.go index 0d1de59..c086b31 100644 --- a/proto/v1/google/protobuf/duration.pb.go +++ b/proto/v1/google/protobuf/duration.pb.go @@ -38,8 +38,7 @@ // // The Duration message represents a signed span of time. // -// -// Conversion to a Go Duration +// # Conversion to a Go Duration // // The AsDuration method can be used to convert a Duration message to a // standard Go time.Duration value: @@ -68,15 +67,13 @@ // the resulting value to the closest representable value (e.g., math.MaxInt64 // for positive overflow and math.MinInt64 for negative overflow). // -// -// Conversion from a Go Duration +// # Conversion from a Go Duration // // The durationpb.New function can be used to construct a Duration message // from a standard Go time.Duration value: // // dur := durationpb.New(d) // ... // make use of d as a *durationpb.Duration -// package durationpb import ( @@ -106,43 +103,43 @@ const ( // // Example 1: Compute Duration from two Timestamps in pseudo code. // -// Timestamp start = ...; -// Timestamp end = ...; -// Duration duration = ...; +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; // -// duration.seconds = end.seconds - start.seconds; -// duration.nanos = end.nanos - start.nanos; +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; // -// if (duration.seconds < 0 && duration.nanos > 0) { -// duration.seconds += 1; -// duration.nanos -= 1000000000; -// } else if (duration.seconds > 0 && duration.nanos < 0) { -// duration.seconds -= 1; -// duration.nanos += 1000000000; -// } +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } // // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. // -// Timestamp start = ...; -// Duration duration = ...; -// Timestamp end = ...; +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; // -// end.seconds = start.seconds + duration.seconds; -// end.nanos = start.nanos + duration.nanos; +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; // -// if (end.nanos < 0) { -// end.seconds -= 1; -// end.nanos += 1000000000; -// } else if (end.nanos >= 1000000000) { -// end.seconds += 1; -// end.nanos -= 1000000000; -// } +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } // // Example 3: Compute Duration from datetime.timedelta in Python. // -// td = datetime.timedelta(days=3, minutes=10) -// duration = Duration() -// duration.FromTimedelta(td) +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) // // # JSON Mapping // @@ -153,8 +150,6 @@ const ( // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 // microsecond should be expressed in JSON format as "3.000001s". -// -// type Duration struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/proto/v1/google/protobuf/empty.pb.go b/proto/v1/google/protobuf/empty.pb.go index ca2d961..be00407 100644 --- a/proto/v1/google/protobuf/empty.pb.go +++ b/proto/v1/google/protobuf/empty.pb.go @@ -54,9 +54,9 @@ const ( // empty messages in your APIs. A typical example is to use it as the request // or the response type of an API method. For instance: // -// service Foo { -// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); -// } +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } // // The JSON representation for `Empty` is empty JSON object `{}`. type Empty struct { diff --git a/proto/v1/google/protobuf/field_mask.pb.go b/proto/v1/google/protobuf/field_mask.pb.go index cec0a8f..89a43d8 100644 --- a/proto/v1/google/protobuf/field_mask.pb.go +++ b/proto/v1/google/protobuf/field_mask.pb.go @@ -40,8 +40,7 @@ // The paths are specific to some target message type, // which is not stored within the FieldMask message itself. // -// -// Constructing a FieldMask +// # Constructing a FieldMask // // The New function is used construct a FieldMask: // @@ -64,8 +63,7 @@ // ... // handle error // } // -// -// Type checking a FieldMask +// # Type checking a FieldMask // // In order to verify that a FieldMask represents a set of fields that are // reachable from some target message type, use the IsValid method: @@ -99,8 +97,8 @@ const ( // `FieldMask` represents a set of symbolic field paths, for example: // -// paths: "f.a" -// paths: "f.b.d" +// paths: "f.a" +// paths: "f.b.d" // // Here `f` represents a field in some root message, `a` and `b` // fields in the message found in `f`, and `d` a field found in the @@ -117,27 +115,26 @@ const ( // specified in the mask. For example, if the mask in the previous // example is applied to a response message as follows: // -// f { -// a : 22 -// b { -// d : 1 -// x : 2 -// } -// y : 13 -// } -// z: 8 +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 // // The result will not contain specific values for fields x,y and z // (their value will be set to the default, and omitted in proto text // output): // -// -// f { -// a : 22 -// b { -// d : 1 -// } -// } +// f { +// a : 22 +// b { +// d : 1 +// } +// } // // A repeated field is not allowed except at the last position of a // paths string. @@ -175,36 +172,36 @@ const ( // // For example, given the target message: // -// f { -// b { -// d: 1 -// x: 2 -// } -// c: [1] -// } +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } // // And an update message: // -// f { -// b { -// d: 10 -// } -// c: [2] -// } +// f { +// b { +// d: 10 +// } +// c: [2] +// } // // then if the field mask is: // -// paths: ["f.b", "f.c"] +// paths: ["f.b", "f.c"] // // then the result will be: // -// f { -// b { -// d: 10 -// x: 2 -// } -// c: [1, 2] -// } +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } // // An implementation may provide options to override this default behavior for // repeated and message fields. @@ -242,51 +239,51 @@ const ( // // As an example, consider the following message declarations: // -// message Profile { -// User user = 1; -// Photo photo = 2; -// } -// message User { -// string display_name = 1; -// string address = 2; -// } +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } // // In proto a field mask for `Profile` may look as such: // -// mask { -// paths: "user.display_name" -// paths: "photo" -// } +// mask { +// paths: "user.display_name" +// paths: "photo" +// } // // In JSON, the same mask is represented as below: // -// { -// mask: "user.displayName,photo" -// } +// { +// mask: "user.displayName,photo" +// } // // # Field Masks and Oneof Fields // // Field masks treat fields in oneofs just as regular fields. Consider the // following message: // -// message SampleMessage { -// oneof test_oneof { -// string name = 4; -// SubMessage sub_message = 9; -// } -// } +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } // // The field mask can be: // -// mask { -// paths: "name" -// } +// mask { +// paths: "name" +// } // // Or: // -// mask { -// paths: "sub_message" -// } +// mask { +// paths: "sub_message" +// } // // Note that oneof type names ("test_oneof" in this case) cannot be used in // paths. diff --git a/proto/v1/google/protobuf/struct.pb.go b/proto/v1/google/protobuf/struct.pb.go index 0be1f64..6630953 100644 --- a/proto/v1/google/protobuf/struct.pb.go +++ b/proto/v1/google/protobuf/struct.pb.go @@ -47,8 +47,7 @@ // "google.golang.org/protobuf/encoding/protojson" package // ensures that they will be serialized as their JSON equivalent. // -// -// Conversion to and from a Go interface +// # Conversion to and from a Go interface // // The standard Go "encoding/json" package has functionality to serialize // arbitrary types to a large degree. The Value.AsInterface, Struct.AsMap, and @@ -61,8 +60,7 @@ // forms back as Value, Struct, and ListValue messages, use the NewStruct, // NewList, and NewValue constructor functions. // -// -// Example usage +// # Example usage // // Consider the following example JSON object: // @@ -121,7 +119,6 @@ // ... // handle error // } // ... // make use of m as a *structpb.Value -// package structpb import ( @@ -145,7 +142,7 @@ const ( // `NullValue` is a singleton enumeration to represent the null value for the // `Value` type union. // -// The JSON representation for `NullValue` is JSON `null`. +// The JSON representation for `NullValue` is JSON `null`. type NullValue int32 const ( @@ -296,6 +293,7 @@ type Value struct { // The kind of value. // // Types that are assignable to Kind: + // // *Value_NullValue // *Value_NumberValue // *Value_StringValue diff --git a/proto/v1/google/protobuf/timestamp.pb.go b/proto/v1/google/protobuf/timestamp.pb.go index 9d433cd..1357c42 100644 --- a/proto/v1/google/protobuf/timestamp.pb.go +++ b/proto/v1/google/protobuf/timestamp.pb.go @@ -39,8 +39,7 @@ // The Timestamp message represents a timestamp, // an instant in time since the Unix epoch (January 1st, 1970). // -// -// Conversion to a Go Time +// # Conversion to a Go Time // // The AsTime method can be used to convert a Timestamp message to a // standard Go time.Time value in UTC: @@ -62,8 +61,7 @@ // ... // handle error // } // -// -// Conversion from a Go Time +// # Conversion from a Go Time // // The timestamppb.New function can be used to construct a Timestamp message // from a standard Go time.Time value: @@ -75,7 +73,6 @@ // // ts := timestamppb.Now() // ... // make use of ts as a *timestamppb.Timestamp -// package timestamppb import ( @@ -111,52 +108,50 @@ const ( // // Example 1: Compute Timestamp from POSIX `time()`. // -// Timestamp timestamp; -// timestamp.set_seconds(time(NULL)); -// timestamp.set_nanos(0); +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); // // Example 2: Compute Timestamp from POSIX `gettimeofday()`. // -// struct timeval tv; -// gettimeofday(&tv, NULL); +// struct timeval tv; +// gettimeofday(&tv, NULL); // -// Timestamp timestamp; -// timestamp.set_seconds(tv.tv_sec); -// timestamp.set_nanos(tv.tv_usec * 1000); +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); // // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. // -// FILETIME ft; -// GetSystemTimeAsFileTime(&ft); -// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; // -// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z -// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. -// Timestamp timestamp; -// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); -// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); // // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. // -// long millis = System.currentTimeMillis(); -// -// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) -// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// long millis = System.currentTimeMillis(); // +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); // // Example 5: Compute Timestamp from Java `Instant.now()`. // -// Instant now = Instant.now(); -// -// Timestamp timestamp = -// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) -// .setNanos(now.getNano()).build(); +// Instant now = Instant.now(); // +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); // // Example 6: Compute Timestamp from current time in Python. // -// timestamp = Timestamp() -// timestamp.GetCurrentTime() +// timestamp = Timestamp() +// timestamp.GetCurrentTime() // // # JSON Mapping // @@ -184,8 +179,6 @@ const ( // the Joda Time's [`ISODateTimeFormat.dateTime()`]( // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D // ) to obtain a formatter capable of generating timestamps in this format. -// -// type Timestamp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/proto/v1/http.ts b/proto/v1/http.ts deleted file mode 100644 index b320347..0000000 --- a/proto/v1/http.ts +++ /dev/null @@ -1,54 +0,0 @@ -//复制时倒入qs -// @ts-ignore -import qs from "qs"; - -const API_SERVER = process.env.NEXT_PUBLIC_WEB_SITE - -export interface Config extends RequestInit { - token?: string - data?: T - method?: 'POST' | 'GET' -} - -interface IError { - status: number - details: [], - message: string - metadata: any - reason: string -} - -const http = async (endpoint: string, {data, token, headers, ...customConfig}: Config) => { - const config = { - method: "GET", - headers: { - Authorization: token ? `Bearer ${token}` : '', - 'Content-type': data ? 'application/json' : '', - }, - ...customConfig - } - if (config.method.toUpperCase() === "GET") { - endpoint += `?${qs.stringify(data)}` - } else { - config.body = JSON.stringify(data || {}) - } - console.log(API_SERVER + endpoint, token) - return fetch(API_SERVER + endpoint, config).then(async response => { - if (response.status == 401) { - if (typeof window != 'undefined') { - // window.location.reload(); - } - return Promise.reject({message: "请重新登录", status: 401}) - } - const data = await response.json(); - if (response.ok) { - return data as V; - } else { - return Promise.reject(data as IError); - } - }) -} - -export { - http -} diff --git a/proto/v1/user.gin.go b/proto/v1/user.http.go similarity index 82% rename from proto/v1/user.gin.go rename to proto/v1/user.http.go index f8a9d7c..0708dda 100644 --- a/proto/v1/user.gin.go +++ b/proto/v1/user.http.go @@ -2,9 +2,9 @@ package user import ( "context" - "git.diulo.com/mogfee/protoc-gen-kit/middleware" - "git.diulo.com/mogfee/protoc-gen-kit/response" - "git.diulo.com/mogfee/protoc-gen-kit/xerrors" + "git.diulo.com/mogfee/kit/middleware" + "git.diulo.com/mogfee/kit/response" + "git.diulo.com/mogfee/kit/errors" "github.com/gin-gonic/gin" ) @@ -30,7 +30,7 @@ func httpListHandler(srv UserServer, m ...middleware.Middleware) func(c *gin.Con if v, ok := out.(*LoginResponse); ok { resp.Success(v) } else { - resp.Error(xerrors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse")) + resp.Error(errors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse")) } } } @@ -53,7 +53,7 @@ func httpLoginHandler(srv UserServer, m ...middleware.Middleware) func(c *gin.Co if v, ok := out.(*LoginResponse); ok { resp.Success(v) } else { - resp.Error(xerrors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse")) + resp.Error(errors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse")) } } } diff --git a/proto/v1/user.pb.go b/proto/v1/user.pb.go index f4a21ad..9941388 100644 --- a/proto/v1/user.pb.go +++ b/proto/v1/user.pb.go @@ -12,7 +12,6 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" _ "google.golang.org/protobuf/types/descriptorpb" - _ "google.golang.org/protobuf/types/known/wrapperspb" reflect "reflect" sync "sync" ) @@ -29,7 +28,7 @@ type LoginRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - //用户名 + // 用户名 Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` FirstName string `protobuf:"bytes,3,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"` @@ -93,7 +92,7 @@ type LoginResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - //密钥 + // 密钥 Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` } @@ -144,8 +143,6 @@ var file_user_proto_rawDesc = []byte{ 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x0c, 0x6c, 0x6f, 0x67, 0x69, 0x6e, diff --git a/proto/v1/user.ts b/proto/v1/user.ts index 20c3ccb..102215e 100644 --- a/proto/v1/user.ts +++ b/proto/v1/user.ts @@ -1,3 +1,4 @@ +// @ts-ignore import {Config,http} from "./http"; export interface loginRequest { //用户名 diff --git a/proto/v1/user_grpc.pb.go b/proto/v1/user_grpc.pb.go index ecf8f91..1ebce44 100644 --- a/proto/v1/user_grpc.pb.go +++ b/proto/v1/user_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 +// - protoc-gen-go-grpc v1.3.0 // - protoc v3.18.1 // source: user.proto @@ -18,13 +18,19 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + User_List_FullMethodName = "/com.web.api.user.user/list" + User_Login_FullMethodName = "/com.web.api.user.user/login" + User_Delete_FullMethodName = "/com.web.api.user.user/delete" +) + // UserClient is the client API for User service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type UserClient interface { - //列表 + // 列表 List(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) - //等了 + // 等了 Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) Delete(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) } @@ -39,7 +45,7 @@ func NewUserClient(cc grpc.ClientConnInterface) UserClient { func (c *userClient) List(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { out := new(LoginResponse) - err := c.cc.Invoke(ctx, "/com.web.api.user.user/list", in, out, opts...) + err := c.cc.Invoke(ctx, User_List_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -48,7 +54,7 @@ func (c *userClient) List(ctx context.Context, in *LoginRequest, opts ...grpc.Ca func (c *userClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { out := new(LoginResponse) - err := c.cc.Invoke(ctx, "/com.web.api.user.user/login", in, out, opts...) + err := c.cc.Invoke(ctx, User_Login_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -57,7 +63,7 @@ func (c *userClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.C func (c *userClient) Delete(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { out := new(LoginResponse) - err := c.cc.Invoke(ctx, "/com.web.api.user.user/delete", in, out, opts...) + err := c.cc.Invoke(ctx, User_Delete_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -68,9 +74,9 @@ func (c *userClient) Delete(ctx context.Context, in *LoginRequest, opts ...grpc. // All implementations must embed UnimplementedUserServer // for forward compatibility type UserServer interface { - //列表 + // 列表 List(context.Context, *LoginRequest) (*LoginResponse, error) - //等了 + // 等了 Login(context.Context, *LoginRequest) (*LoginResponse, error) Delete(context.Context, *LoginRequest) (*LoginResponse, error) mustEmbedUnimplementedUserServer() @@ -112,7 +118,7 @@ func _User_List_Handler(srv interface{}, ctx context.Context, dec func(interface } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/com.web.api.user.user/list", + FullMethod: User_List_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(UserServer).List(ctx, req.(*LoginRequest)) @@ -130,7 +136,7 @@ func _User_Login_Handler(srv interface{}, ctx context.Context, dec func(interfac } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/com.web.api.user.user/login", + FullMethod: User_Login_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(UserServer).Login(ctx, req.(*LoginRequest)) @@ -148,7 +154,7 @@ func _User_Delete_Handler(srv interface{}, ctx context.Context, dec func(interfa } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/com.web.api.user.user/delete", + FullMethod: User_Delete_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(UserServer).Delete(ctx, req.(*LoginRequest)) diff --git a/proto/v1/user_http.pb.go b/proto/v1/user_http.pb.go new file mode 100644 index 0000000..7e9510a --- /dev/null +++ b/proto/v1/user_http.pb.go @@ -0,0 +1,54 @@ +package user + +import ( + "context" + "git.diulo.com/mogfee/kit/transport/http" +) + +type UserHTTPServer interface { + List(context.Context, *LoginRequest) (*LoginResponse, error) + Login(context.Context, *LoginRequest) (*LoginResponse, error) + Delete(context.Context, *LoginRequest) (*LoginResponse, error) +} + +func RegisterUserHTTPServer(s *http.Server, srv UserServer) { + r := s.Route("/") + r.GET("/api/v1/user/list", _User_List0_HTTP_Handler(srv)) + r.POST("/api/v1/user/login", _User_Login0_HTTP_Handler(srv)) +} +func _User_List0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in LoginRequest + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, "/com.web.api.user.user/list") + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.List(ctx, req.(*LoginRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*LoginResponse) + return ctx.Result(200, reply) + } +} +func _User_Login0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in LoginRequest + if err := ctx.Bind(&in); err != nil { + return err + } + http.SetOperation(ctx, "/com.web.api.user.user/login") + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.Login(ctx, req.(*LoginRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*LoginResponse) + return ctx.Result(200, reply) + } +} diff --git a/proto/v1/validate/validate.pb.go b/proto/v1/validate/validate.pb.go index 97c3662..856e401 100644 --- a/proto/v1/validate/validate.pb.go +++ b/proto/v1/validate/validate.pb.go @@ -94,6 +94,7 @@ type FieldRules struct { Message *MessageRules `protobuf:"bytes,17,opt,name=message" json:"message,omitempty"` // Types that are assignable to Type: + // // *FieldRules_Float // *FieldRules_Double // *FieldRules_Int32 @@ -2000,6 +2001,7 @@ type StringRules struct { // patterns // // Types that are assignable to WellKnown: + // // *StringRules_Email // *StringRules_Hostname // *StringRules_Ip @@ -2371,6 +2373,7 @@ type BytesRules struct { // patterns // // Types that are assignable to WellKnown: + // // *BytesRules_Ip // *BytesRules_Ipv4 // *BytesRules_Ipv6 diff --git a/response/response.go b/response/response.go index 4556d4c..658301b 100644 --- a/response/response.go +++ b/response/response.go @@ -2,7 +2,7 @@ package response import ( "encoding/json" - "git.diulo.com/mogfee/protoc-gen-kit/xerrors" + "git.diulo.com/mogfee/kit/errors" "github.com/gin-gonic/gin" "github.com/go-playground/form" "net/http" @@ -53,7 +53,7 @@ func (s *result) Error(err error) { }) return } - gs := xerrors.FromError(err) + gs := errors.FromError(err) s.Result(gs.Status, gs.Reason, gs.Message, gs.Metadata) } diff --git a/test/main.go b/test/main.go index 0f1e71f..21bb7ed 100644 --- a/test/main.go +++ b/test/main.go @@ -2,13 +2,13 @@ package main import ( "fmt" - protoc_gen_kit "git.diulo.com/mogfee/protoc-gen-kit" + "git.diulo.com/mogfee/kit" ) func main() { - app := protoc_gen_kit.New( - protoc_gen_kit.Name("user-server"), - protoc_gen_kit.Server()) + app := kit.New( + kit.Name("user-server"), + kit.Server()) fmt.Println("run start") app.Run() fmt.Println("run end") diff --git a/transport/http/filter.go b/transport/http/filter.go index f4c1d29..43bec80 100644 --- a/transport/http/filter.go +++ b/transport/http/filter.go @@ -2,8 +2,10 @@ package http import "net/http" +// FilterFunc is a function which receives a http.Handler and returns another http.Handler. type FilterFunc func(http.Handler) http.Handler +// FilterChain returns a FilterFunc that specifies the chained handler for HTTP Router. func FilterChain(filters ...FilterFunc) FilterFunc { return func(next http.Handler) http.Handler { for i := len(filters) - 1; i >= 0; i-- { diff --git a/transport/http/router.go b/transport/http/router.go index bc0a803..e19c5c4 100644 --- a/transport/http/router.go +++ b/transport/http/router.go @@ -6,13 +6,19 @@ import ( "sync" ) -type WalkRoutFunc func(RouteInfo) error +// WalkRouteFunc is the type of the function called for each route visited by Walk. +type WalkRouteFunc func(RouteInfo) error +// RouteInfo is an HTTP route info. type RouteInfo struct { Path string Method string } -type HandlerFunc func(Context) error + +// HandlerFunc defines a function to serve HTTP requests. +type HandlerFunc func(ctx Context) error + +// Router is an HTTP router. type Router struct { prefix string pool sync.Pool @@ -26,18 +32,22 @@ func newRouter(prefix string, srv *Server, filters ...FilterFunc) *Router { srv: srv, filters: filters, } - r.pool.New = func() any { + r.pool.New = func() interface{} { return &wrapper{router: r} } return r } + +// Group returns a new router group. func (r *Router) Group(prefix string, filters ...FilterFunc) *Router { var newFilters []FilterFunc newFilters = append(newFilters, r.filters...) newFilters = append(newFilters, filters...) return newRouter(path.Join(r.prefix, prefix), r.srv, newFilters...) } -func (r *Router) Handle(method string, relativePath string, h HandlerFunc, filters ...FilterFunc) { + +// Handle registers a new route with a matcher for the URL path and method. +func (r *Router) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) { next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { ctx := r.pool.Get().(Context) ctx.Reset(res, req) @@ -51,9 +61,48 @@ func (r *Router) Handle(method string, relativePath string, h HandlerFunc, filte next = FilterChain(r.filters...)(next) r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method) } + +// GET registers a new GET route for a path with matching handler in the router. func (r *Router) GET(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodGet, path, h, m...) } + +// HEAD registers a new HEAD route for a path with matching handler in the router. +func (r *Router) HEAD(path string, h HandlerFunc, m ...FilterFunc) { + r.Handle(http.MethodHead, path, h, m...) +} + +// POST registers a new POST route for a path with matching handler in the router. func (r *Router) POST(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodPost, path, h, m...) } + +// PUT registers a new PUT route for a path with matching handler in the router. +func (r *Router) PUT(path string, h HandlerFunc, m ...FilterFunc) { + r.Handle(http.MethodPut, path, h, m...) +} + +// PATCH registers a new PATCH route for a path with matching handler in the router. +func (r *Router) PATCH(path string, h HandlerFunc, m ...FilterFunc) { + r.Handle(http.MethodPatch, path, h, m...) +} + +// DELETE registers a new DELETE route for a path with matching handler in the router. +func (r *Router) DELETE(path string, h HandlerFunc, m ...FilterFunc) { + r.Handle(http.MethodDelete, path, h, m...) +} + +// CONNECT registers a new CONNECT route for a path with matching handler in the router. +func (r *Router) CONNECT(path string, h HandlerFunc, m ...FilterFunc) { + r.Handle(http.MethodConnect, path, h, m...) +} + +// OPTIONS registers a new OPTIONS route for a path with matching handler in the router. +func (r *Router) OPTIONS(path string, h HandlerFunc, m ...FilterFunc) { + r.Handle(http.MethodOptions, path, h, m...) +} + +// TRACE registers a new TRACE route for a path with matching handler in the router. +func (r *Router) TRACE(path string, h HandlerFunc, m ...FilterFunc) { + r.Handle(http.MethodTrace, path, h, m...) +} diff --git a/transport/http/server.go b/transport/http/server.go index 2b4e6ef..4a89bda 100644 --- a/transport/http/server.go +++ b/transport/http/server.go @@ -3,14 +3,19 @@ package http import ( "context" "crypto/tls" + "errors" + "git.diulo.com/mogfee/kit/internal/endpoint" + "git.diulo.com/mogfee/kit/internal/host" "git.diulo.com/mogfee/kit/internal/matcher" + "git.diulo.com/mogfee/kit/log" "git.diulo.com/mogfee/kit/middleware" "git.diulo.com/mogfee/kit/transport" - "github.com/gorilla/mux" "net" "net/http" "net/url" "time" + + "github.com/gorilla/mux" ) var ( @@ -19,7 +24,8 @@ var ( _ http.Handler = (*Server)(nil) ) -type ServerOption func(server *Server) +// ServerOption is an HTTP server option. +type ServerOption func(*Server) // Network with server network. func Network(network string) ServerOption { @@ -127,6 +133,7 @@ func PathPrefix(prefix string) ServerOption { } } +// Server is an HTTP server wrapper. type Server struct { *http.Server lis net.Listener @@ -147,6 +154,7 @@ type Server struct { router *mux.Router } +// NewServer creates an HTTP server by options. func NewServer(opts ...ServerOption) *Server { srv := &Server{ network: "tcp", @@ -156,13 +164,14 @@ func NewServer(opts ...ServerOption) *Server { decVars: DefaultRequestVars, decQuery: DefaultRequestQuery, decBody: DefaultRequestDecoder, + enc: DefaultResponseEncoder, + ene: DefaultErrorEncoder, strictSlash: true, router: mux.NewRouter(), } for _, o := range opts { o(srv) } - srv.router.StrictSlash(srv.strictSlash) srv.router.NotFoundHandler = http.DefaultServeMux srv.router.MethodNotAllowedHandler = http.DefaultServeMux @@ -173,91 +182,157 @@ func NewServer(opts ...ServerOption) *Server { } return srv } + +// Use uses a service middleware with selector. +// selector: +// - '/*' +// - '/helloworld.v1.Greeter/*' +// - '/helloworld.v1.Greeter/SayHello' func (s *Server) Use(selector string, m ...middleware.Middleware) { s.middleware.Add(selector, m...) } -func (s *Server) WalkRoute(fn WalkRoutFunc) error { + +// WalkRoute walks the router and all its sub-routers, calling walkFn for each route in the tree. +func (s *Server) WalkRoute(fn WalkRouteFunc) error { return s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { methods, err := route.GetMethods() if err != nil { - return err + return nil // ignore no methods } path, err := route.GetPathTemplate() if err != nil { return err } for _, method := range methods { - if err = fn(RouteInfo{ - Method: method, - Path: path, - }); err != nil { + if err := fn(RouteInfo{Method: method, Path: path}); err != nil { return err } } return nil }) } -func (s *Server) Kind() transport.Kind { - return transport.htt -} + +// Route registers an HTTP router. func (s *Server) Route(prefix string, filters ...FilterFunc) *Router { return newRouter(prefix, s, filters...) } + +// Handle registers a new route with a matcher for the URL path. func (s *Server) Handle(path string, h http.Handler) { s.router.Handle(path, h) } + +// HandlePrefix registers a new route with a matcher for the URL path prefix. func (s *Server) HandlePrefix(prefix string, h http.Handler) { s.router.PathPrefix(prefix).Handler(h) } + +// HandleFunc registers a new route with a matcher for the URL path. func (s *Server) HandleFunc(path string, h http.HandlerFunc) { s.router.HandleFunc(path, h) } -func (s *Server) HandleHeader(key, val string, h http.Handler) { + +// HandleHeader registers a new route with a matcher for the header. +func (s *Server) HandleHeader(key, val string, h http.HandlerFunc) { s.router.Headers(key, val).Handler(h) } + +// ServeHTTP should write reply headers and data to the ResponseWriter and then return. func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { s.Handler.ServeHTTP(res, req) } + func (s *Server) filter() mux.MiddlewareFunc { - return func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { var ( ctx context.Context cancel context.CancelFunc ) - if s.timeout > 0 { - ctx, cancel = context.WithTimeout(request.Context(), s.timeout) + ctx, cancel = context.WithTimeout(req.Context(), s.timeout) } else { - ctx, cancel = context.WithCancel(request.Context()) + ctx, cancel = context.WithCancel(req.Context()) } defer cancel() - pathTemplate := request.URL.Path - if route := mux.CurrentRoute(request); route != nil { + pathTemplate := req.URL.Path + if route := mux.CurrentRoute(req); route != nil { + // /path/123 -> /path/{id} pathTemplate, _ = route.GetPathTemplate() } - tr := &Transport{} + + tr := &Transport{ + operation: pathTemplate, + pathTemplate: pathTemplate, + reqHeader: headerCarrier(req.Header), + replyHeader: headerCarrier(w.Header()), + request: req, + } + if s.endpoint != nil { + tr.endpoint = s.endpoint.String() + } + tr.request = req.WithContext(transport.NewServerContext(ctx, tr)) + next.ServeHTTP(w, tr.request) }) } } -func (s *Server) Endpoint() string { - //TODO implement me - panic("implement me") +// Endpoint return a real address to registry endpoint. +// examples: +// +// https://127.0.0.1:8000 +// Legacy: http://127.0.0.1:8000?isSecure=false +func (s *Server) Endpoint() (*url.URL, error) { + if err := s.listenAndEndpoint(); err != nil { + return nil, err + } + return s.endpoint, nil } -func (s *Server) Operation() string { - //TODO implement me - panic("implement me") +// Start start the HTTP server. +func (s *Server) Start(ctx context.Context) error { + if err := s.listenAndEndpoint(); err != nil { + return err + } + s.BaseContext = func(net.Listener) context.Context { + return ctx + } + log.Infof("[HTTP] server listening on: %s", s.lis.Addr().String()) + var err error + if s.tlsConf != nil { + err = s.ServeTLS(s.lis, "", "") + } else { + err = s.Serve(s.lis) + } + if !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil } -func (s *Server) RequestHeader() transport.Header { - //TODO implement me - panic("implement me") +// Stop stop the HTTP server. +func (s *Server) Stop(ctx context.Context) error { + log.Info("[HTTP] server stopping") + return s.Shutdown(ctx) } -func (s *Server) ReplyHeader() transport.Header { - //TODO implement me - panic("implement me") +func (s *Server) listenAndEndpoint() error { + if s.lis == nil { + lis, err := net.Listen(s.network, s.address) + if err != nil { + s.err = err + return err + } + s.lis = lis + } + if s.endpoint == nil { + addr, err := host.Extract(s.address, s.lis) + if err != nil { + s.err = err + return err + } + s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("http", s.tlsConf != nil), addr) + } + return s.err } diff --git a/transport/http/transport.go b/transport/http/transport.go index 1a0654c..58cc87a 100644 --- a/transport/http/transport.go +++ b/transport/http/transport.go @@ -6,12 +6,16 @@ import ( "net/http" ) +var _ Transporter = (*Transport)(nil) + +// Transporter is http Transporter type Transporter interface { transport.Transporter Request() *http.Request PathTemplate() string } +// Transport is an HTTP transport. type Transport struct { endpoint string operation string @@ -21,33 +25,42 @@ type Transport struct { pathTemplate string } -func (t *Transport) Kind() transport.Kind { +// Kind returns the transport kind. +func (tr *Transport) Kind() transport.Kind { return transport.KindHTTP } -func (t *Transport) Endpoint() string { - return t.endpoint +// Endpoint returns the transport endpoint. +func (tr *Transport) Endpoint() string { + return tr.endpoint } -func (t *Transport) Operation() string { - return t.operation +// Operation returns the transport operation. +func (tr *Transport) Operation() string { + return tr.operation } -func (t *Transport) RequestHeader() transport.Header { - return t.reqHeader +// Request returns the HTTP request. +func (tr *Transport) Request() *http.Request { + return tr.request } -func (t *Transport) ReplyHeader() transport.Header { - return t.replyHeader +// RequestHeader returns the request header. +func (tr *Transport) RequestHeader() transport.Header { + return tr.reqHeader } -func (t *Transport) Request() *http.Request { - return t.request +// ReplyHeader returns the reply header. +func (tr *Transport) ReplyHeader() transport.Header { + return tr.replyHeader } -func (t *Transport) PathTemplate() string { - return t.pathTemplate +// PathTemplate returns the http path template. +func (tr *Transport) PathTemplate() string { + return tr.pathTemplate } + +// SetOperation sets the transport operation. func SetOperation(ctx context.Context, op string) { if tr, ok := transport.FromServerContext(ctx); ok { if tr, ok := tr.(*Transport); ok { @@ -55,6 +68,8 @@ func SetOperation(ctx context.Context, op string) { } } } + +// RequestFromServerContext returns request from context. func RequestFromServerContext(ctx context.Context) (*http.Request, bool) { if tr, ok := transport.FromServerContext(ctx); ok { if tr, ok := tr.(*Transport); ok { @@ -63,3 +78,24 @@ func RequestFromServerContext(ctx context.Context) (*http.Request, bool) { } return nil, false } + +type headerCarrier http.Header + +// Get returns the value associated with the passed key. +func (hc headerCarrier) Get(key string) string { + return http.Header(hc).Get(key) +} + +// Set stores the key-value pair. +func (hc headerCarrier) Set(key string, value string) { + http.Header(hc).Set(key, value) +} + +// Keys lists the keys stored in this carrier. +func (hc headerCarrier) Keys() []string { + keys := make([]string, 0, len(hc)) + for k := range http.Header(hc) { + keys = append(keys, k) + } + return keys +}