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
+}