李伟乐 2 years ago
parent 75c9c1c909
commit 3a77d1a930
  1. 3
      Makefile
  2. 10
      app.go
  3. 111
      cmd/gin-kit/main.go
  4. 10
      cmd/kit/main.go
  5. 2
      cmd/ts/main.go
  6. 7
      encoding/README.md
  7. 31
      encoding/encoding.go
  8. 88
      encoding/encoding_test.go
  9. 22
      encoding/form/form.go
  10. 211
      encoding/form/form_test.go
  11. 333
      encoding/form/proto_decode.go
  12. 217
      encoding/form/proto_decode_test.go
  13. 224
      encoding/form/proto_encode.go
  14. 110
      encoding/form/proto_encode_test.go
  15. 94
      encoding/form/well_known_types.go
  16. 95
      encoding/form/well_known_types_test.go
  17. 67
      encoding/json/json.go
  18. 147
      encoding/json/json_test.go
  19. 31
      encoding/proto/proto.go
  20. 46
      encoding/proto/proto_test.go
  21. 29
      encoding/xml/xml.go
  22. 117
      encoding/xml/xml_test.go
  23. 29
      encoding/yaml/yaml.go
  24. 104
      encoding/yaml/yaml_test.go
  25. 97
      example/main.go
  26. 14
      example/service/service.go
  27. 34
      internal/endpoint/endpoint.go
  28. 126
      internal/endpoint/endpoint_test.go
  29. 92
      internal/host/host.go
  30. 149
      internal/host/host_test.go
  31. 4
      log/mq/nsq.go
  32. 2
      log/mq/nsq_test.go
  33. 2
      middleware/jwt.go
  34. 2
      middleware/logger.go
  35. 14
      middleware/logging/logging.go
  36. 2
      options.go
  37. 15
      proto/buf.gen.yaml
  38. BIN
      proto/gin-kit
  39. 2
      proto/user.proto
  40. 8
      proto/v1/google/api/client.pb.go
  41. 2
      proto/v1/google/api/http.pb.go
  42. 1
      proto/v1/google/api/httpbody.pb.go
  43. 21
      proto/v1/google/protobuf/any.pb.go
  44. 2
      proto/v1/google/protobuf/api.pb.go
  45. 4
      proto/v1/google/protobuf/compiler/plugin.pb.go
  46. 22
      proto/v1/google/protobuf/descriptor.pb.go
  47. 9
      proto/v1/google/protobuf/duration.pb.go
  48. 7
      proto/v1/google/protobuf/field_mask.pb.go
  49. 8
      proto/v1/google/protobuf/struct.pb.go
  50. 11
      proto/v1/google/protobuf/timestamp.pb.go
  51. 54
      proto/v1/http.ts
  52. 10
      proto/v1/user.http.go
  53. 7
      proto/v1/user.pb.go
  54. 1
      proto/v1/user.ts
  55. 28
      proto/v1/user_grpc.pb.go
  56. 54
      proto/v1/user_http.pb.go
  57. 3
      proto/v1/validate/validate.pb.go
  58. 4
      response/response.go
  59. 8
      test/main.go
  60. 2
      transport/http/filter.go
  61. 57
      transport/http/router.go
  62. 141
      transport/http/server.go
  63. 62
      transport/http/transport.go

@ -1,9 +1,12 @@
kit: kit:
go build -o ~/go/bin/protoc-gen-kit cmd/kit/main.go 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: ts:
go build -o ~/go/bin/protoc-gen-ts cmd/ts/main.go go build -o ~/go/bin/protoc-gen-ts cmd/ts/main.go
all: all:
make kit make kit
make gin-kit
make ts make ts
testts: testts:
make ts make ts

@ -1,11 +1,13 @@
package protoc_gen_kit package kit
import ( import (
"context" "context"
"errors" "errors"
"git.diulo.com/mogfee/protoc-gen-kit/log" _ "git.diulo.com/mogfee/kit/encoding/form"
"git.diulo.com/mogfee/protoc-gen-kit/registry" _ "git.diulo.com/mogfee/kit/encoding/json"
"git.diulo.com/mogfee/protoc-gen-kit/transport" "git.diulo.com/mogfee/kit/log"
"git.diulo.com/mogfee/kit/registry"
"git.diulo.com/mogfee/kit/transport"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"os" "os"

@ -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)
}
}`)
}

@ -1,7 +1,7 @@
package main package main
import ( import (
protogen2 "git.diulo.com/mogfee/protoc-gen-kit/protogen" protogen2 "git.diulo.com/mogfee/kit/protogen"
"google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/compiler/protogen"
) )
@ -24,15 +24,15 @@ func (u *Kit) Generate(plugin *protogen.Plugin) error {
return nil return nil
} }
u.addImports("context") u.addImports("context")
u.addImports("git.diulo.com/mogfee/protoc-gen-kit/middleware") u.addImports("git.diulo.com/mogfee/kit/middleware")
u.addImports("git.diulo.com/mogfee/protoc-gen-kit/pkg/response") u.addImports("git.diulo.com/mogfee/kit/response")
u.addImports("git.diulo.com/mogfee/protoc-gen-kit/pkg/errors") u.addImports("git.diulo.com/mogfee/kit/errors")
u.addImports("github.com/gin-gonic/gin") u.addImports("github.com/gin-gonic/gin")
for _, f := range plugin.Files { for _, f := range plugin.Files {
if len(f.Services) == 0 { if len(f.Services) == 0 {
continue continue
} }
fname := f.GeneratedFilenamePrefix + ".gin.go" fname := f.GeneratedFilenamePrefix + ".http.go"
t := plugin.NewGeneratedFile(fname, f.GoImportPath) t := plugin.NewGeneratedFile(fname, f.GoImportPath)
t.P("package " + f.Desc.Name()) t.P("package " + f.Desc.Name())
t.P("import (") t.P("import (")

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
protogen2 "git.diulo.com/mogfee/protoc-gen-kit/protogen" protogen2 "git.diulo.com/mogfee/kit/protogen"
"google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/compiler/protogen"
"strings" "strings"
) )

@ -0,0 +1,7 @@
# encoding
## msgpack
```shell
go get -u github.com/go-kratos/kratos/contrib/encoding/msgpack/v2
```

@ -1,15 +1,27 @@
package encoding 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 { type Codec interface {
Marshal(any) ([]byte, error) // Marshal returns the wire format of v.
Unmarshal([]byte, any) error 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 Name() string
} }
var registeredCodecs = make(map[string]Codec) var registeredCodecs = make(map[string]Codec)
// RegisterCodec registers the provided Codec for use with all Transport clients and
// servers.
func RegisterCodec(codec Codec) { func RegisterCodec(codec Codec) {
if codec == nil { if codec == nil {
panic("cannot register a nil Codec") panic("cannot register a nil Codec")
@ -17,9 +29,14 @@ func RegisterCodec(codec Codec) {
if codec.Name() == "" { if codec.Name() == "" {
panic("cannot register Codec with empty string result for Name()") panic("cannot register Codec with empty string result for Name()")
} }
contentSubType := strings.ToLower(codec.Name()) contentSubtype := strings.ToLower(codec.Name())
registeredCodecs[contentSubType] = codec 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]
} }

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

@ -3,13 +3,16 @@ package form
import ( import (
"git.diulo.com/mogfee/kit/encoding" "git.diulo.com/mogfee/kit/encoding"
"github.com/go-playground/form" "github.com/go-playground/form"
"google.golang.org/protobuf/proto"
"net/url" "net/url"
"reflect" "reflect"
"google.golang.org/protobuf/proto"
) )
const ( const (
// Name is form codec name
Name = "x-www-form-urlencoded" Name = "x-www-form-urlencoded"
// Null value string
nullStr = "null" nullStr = "null"
) )
@ -21,10 +24,7 @@ var (
func init() { func init() {
decoder.SetTagName("json") decoder.SetTagName("json")
encoder.SetTagName("json") encoder.SetTagName("json")
encoding.RegisterCodec(codec{ encoding.RegisterCodec(codec{encoder: encoder, decoder: decoder})
encoder: encoder,
decoder: decoder,
})
} }
type codec struct { type codec struct {
@ -32,7 +32,7 @@ type codec struct {
decoder *form.Decoder decoder *form.Decoder
} }
func (c codec) Marshal(v any) ([]byte, error) { func (c codec) Marshal(v interface{}) ([]byte, error) {
var vs url.Values var vs url.Values
var err error var err error
if m, ok := v.(proto.Message); ok { if m, ok := v.(proto.Message); ok {
@ -51,16 +51,17 @@ func (c codec) Marshal(v any) ([]byte, error) {
delete(vs, k) 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)) vs, err := url.ParseQuery(string(data))
if err != nil { if err != nil {
return err return err
} }
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr { for rv.Kind() == reflect.Ptr {
if rv.IsNil() { if rv.IsNil() {
rv.Set(reflect.New(rv.Type().Elem())) 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 { if m, ok := rv.Interface().(proto.Message); ok {
return DecodeValues(m, vs) return DecodeValues(m, vs)
} }
return c.decoder.Decode(v, vs) return c.decoder.Decode(v, vs)
} }
func (c codec) Name() string { func (codec) Name() string {
return Name return Name
} }

@ -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: &timestamppb.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"+
"&timestamp=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())
}
}

@ -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'
}

@ -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&timestamp=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))
}
})
}
}

@ -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'
}

@ -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: &timestamppb.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&timestamp=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))
}
})
}
}

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

@ -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)
}
}
}

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

@ -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)
}
}
}

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

@ -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)
}
}

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

@ -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: `<Plain><V>true</V></Plain>`},
{Value: &Plain{false}, ExpectXML: `<Plain><V>false</V></Plain>`},
{Value: &Plain{42}, ExpectXML: `<Plain><V>42</V></Plain>`},
{
Value: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"},
ExpectXML: `<result>` +
`<parent>` +
`<c>C</c>` +
`<b>B</b>` +
`<a>A</a>` +
`</parent>` +
`</result>`,
},
}
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: `<result>` +
`<parent>` +
`<c>C</c>` +
`<b>B</b>` +
`<a>A</a>` +
`</parent>` +
`</result>`,
},
}
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: `<result>` +
`<parent>` +
`<c>C</c>` +
`<b>B</b>` +
`<a>A</a>` +
`</parent>` +
`</result>`,
},
}
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)
}
}
}

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

@ -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))
}
}

@ -1,20 +1,93 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"git.diulo.com/mogfee/protoc-gen-kit/example/service" "git.diulo.com/mogfee/kit"
"git.diulo.com/mogfee/protoc-gen-kit/log" "git.diulo.com/mogfee/kit/errors"
"git.diulo.com/mogfee/protoc-gen-kit/middleware" "git.diulo.com/mogfee/kit/example/service"
user "git.diulo.com/mogfee/protoc-gen-kit/proto/v1" "git.diulo.com/mogfee/kit/middleware"
"github.com/gin-gonic/gin" "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() { func main() {
gin.SetMode(gin.ReleaseMode) hs := http.NewServer(
app := gin.Default() http.Address("localhost:9093"),
srv := service.UserService{} http.Middleware(
logger := log.With(log.DefaultLogger) logging.Server(),
user.RegisterUserHandler(app, &srv, middleware.Logger(logger), middleware.Validate()) func(handler middleware.Handler) middleware.Handler {
fmt.Println("http://localhost:8888") return func(ctx context.Context, a any) (any, error) {
app.Run("localhost:8888") 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", "")
} }

@ -3,8 +3,8 @@ package service
import ( import (
"context" "context"
"encoding/json" "encoding/json"
user "git.diulo.com/mogfee/protoc-gen-kit/proto/v1" user "git.diulo.com/mogfee/kit/proto/v1"
"git.diulo.com/mogfee/protoc-gen-kit/xerrors" "git.diulo.com/mogfee/kit/transport"
) )
type UserService struct { type UserService struct {
@ -12,12 +12,14 @@ type UserService struct {
} }
func (*UserService) Login(ctx context.Context, req *user.LoginRequest) (*user.LoginResponse, error) { func (*UserService) Login(ctx context.Context, req *user.LoginRequest) (*user.LoginResponse, error) {
return nil, xerrors.BadRequest("BadRequest", "B") //return nil, errors.BadRequest("BadRequest", "B")
return &user.LoginResponse{Token: "182131292"}, nil
b, _ := json.Marshal(req)
return &user.LoginResponse{Token: string(b)}, nil
} }
func (*UserService) List(ctx context.Context, req *user.LoginRequest) (*user.LoginResponse, error) { 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")) //fmt.Println(ctx.Value("userId"))
//return nil, errors.Wrap(errors.InternalServer("InternalServer", "B"), "") //return nil, errors.Wrap(errors.InternalServer("InternalServer", "B"), "")

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

@ -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)
}
}
}

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

@ -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)
}
}

@ -4,8 +4,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.diulo.com/mogfee/protoc-gen-kit/log" "git.diulo.com/mogfee/kit/log"
"git.diulo.com/mogfee/protoc-gen-kit/xgo" "git.diulo.com/mogfee/kit/xgo"
"github.com/nsqio/go-nsq" "github.com/nsqio/go-nsq"
"sync" "sync"
"sync/atomic" "sync/atomic"

@ -3,7 +3,7 @@ package mq
import ( import (
"context" "context"
"fmt" "fmt"
"git.diulo.com/mogfee/protoc-gen-kit/log" "git.diulo.com/mogfee/kit/log"
"testing" "testing"
) )

@ -2,7 +2,7 @@ package middleware
import ( import (
"context" "context"
"git.diulo.com/mogfee/protoc-gen-kit/constants" "git.diulo.com/mogfee/kit/constants"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
) )

@ -2,7 +2,7 @@ package middleware
import ( import (
"context" "context"
"git.diulo.com/mogfee/protoc-gen-kit/log" "git.diulo.com/mogfee/kit/log"
) )
func Logger(logger log.Logger) Middleware { func Logger(logger log.Logger) Middleware {

@ -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)
}
}
}

@ -1,4 +1,4 @@
package protoc_gen_kit package kit
import ( import (
"context" "context"

@ -8,13 +8,18 @@ plugins:
out: v1 out: v1
opt: opt:
- paths=source_relative - paths=source_relative
# - name: kit - name: gin-kit
path: ./gin-kit
out: v1
opt:
- paths=source_relative
- name: kit
# path: ./main # path: ./main
# out: v1 out: v1
# opt: opt:
# - paths=source_relative - paths=source_relative
- name: ts - name: ts
path: ./main # path: ./main
out: v1 out: v1
opt: opt:
- paths=source_relative - paths=source_relative

Binary file not shown.

@ -5,7 +5,7 @@ option go_package = "./;user";
import "validate/validate.proto"; import "validate/validate.proto";
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/protobuf/wrappers.proto"; //import "google/protobuf/wrappers.proto";
import "google/protobuf/descriptor.proto"; import "google/protobuf/descriptor.proto";

@ -128,14 +128,14 @@ var (
// //
// The following backwards-compatibility guidelines apply: // 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. // 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 // method signature annotations is backwards compatible if and only if
// the new method signature annotation is last in the sequence. // 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. // a breaking change.
// * Re-ordering existing method signature annotations is a breaking // - Re-ordering existing method signature annotations is a breaking
// change. // change.
// //
// repeated string method_signature = 1051; // repeated string method_signature = 1051;

@ -225,7 +225,6 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
// string text = 2; // string text = 2;
// } // }
// //
//
// The following HTTP JSON to RPC mapping is enabled: // The following HTTP JSON to RPC mapping is enabled:
// //
// HTTP | gRPC // HTTP | gRPC
@ -384,6 +383,7 @@ type HttpRule struct {
// can be defined using the 'custom' field. // can be defined using the 'custom' field.
// //
// Types that are assignable to Pattern: // Types that are assignable to Pattern:
//
// *HttpRule_Get // *HttpRule_Get
// *HttpRule_Put // *HttpRule_Put
// *HttpRule_Post // *HttpRule_Post

@ -39,7 +39,6 @@ const (
// payload formats that can't be represented as JSON, such as raw binary or // payload formats that can't be represented as JSON, such as raw binary or
// an HTML page. // an HTML page.
// //
//
// This message can be used both in streaming and non-streaming API methods in // This message can be used both in streaming and non-streaming API methods in
// the request as well as the response. // the request as well as the response.
// //

@ -40,8 +40,7 @@
// It is functionally a tuple of the full name of the remote message type and // It is functionally a tuple of the full name of the remote message type and
// the serialized bytes of the remote message value. // 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: // An Any message containing another message value is constructed using New:
// //
@ -51,8 +50,7 @@
// } // }
// ... // make use of any // ... // make use of any
// //
// // # Unmarshaling an Any
// Unmarshaling an Any
// //
// With a populated Any message, the underlying message can be serialized into // With a populated Any message, the underlying message can be serialized into
// a remote concrete message value in a few ways. // 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 // listed in the case clauses are linked into the Go binary and therefore also
// registered in the global registry. // 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, // In order to type check whether an Any message represents some other message,
// then use the MessageIs method: // then use the MessageIs method:
@ -118,7 +115,6 @@
// } // }
// ... // make use of m // ... // make use of m
// } // }
//
package anypb package anypb
import ( import (
@ -192,8 +188,7 @@ const (
// in the type URL, for example "foo.bar.com/x/y.z" will yield type // in the type URL, for example "foo.bar.com/x/y.z" will yield type
// name "y.z". // name "y.z".
// //
// // # JSON
// JSON
// //
// The JSON representation of an `Any` value uses the regular // The JSON representation of an `Any` value uses the regular
// representation of the deserialized, embedded message, with an // representation of the deserialized, embedded message, with an
@ -220,7 +215,6 @@ const (
// "@type": "type.googleapis.com/google.protobuf.Duration", // "@type": "type.googleapis.com/google.protobuf.Duration",
// "value": "1.212s" // "value": "1.212s"
// } // }
//
type Any struct { type Any struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -238,10 +232,10 @@ type Any struct {
// scheme `http`, `https`, or no scheme, one can optionally set up a type // scheme `http`, `https`, or no scheme, one can optionally set up a type
// server that maps type URLs to message definitions as follows: // server that maps type URLs to message definitions as follows:
// //
// * If no scheme is provided, `https` is assumed. // - If no scheme is provided, `https` is assumed.
// * An HTTP GET on the URL must yield a [google.protobuf.Type][] // - An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error. // value in binary format, or produce an error.
// * Applications are allowed to cache lookup results based on the // - Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any // URL, or have them precompiled into a binary to avoid any
// lookup. Therefore, binary compatibility needs to be preserved // lookup. Therefore, binary compatibility needs to be preserved
// on changes to types. (Use versioned type names to manage // on changes to types. (Use versioned type names to manage
@ -253,7 +247,6 @@ type Any struct {
// //
// Schemes other than `http`, `https` (or the empty scheme) might be // Schemes other than `http`, `https` (or the empty scheme) might be
// used with implementation specific semantics. // used with implementation specific semantics.
//
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"` 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. // Must be a valid serialized protocol buffer of the above specified type.
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`

@ -92,8 +92,6 @@ type Api struct {
// `google.feature.v1`. For major versions 0 and 1, the suffix can // `google.feature.v1`. For major versions 0 and 1, the suffix can
// be omitted. Zero major versions must only be used for // be omitted. Zero major versions must only be used for
// experimental, non-GA interfaces. // experimental, non-GA interfaces.
//
//
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
// Source context for the protocol buffer service represented by this // Source context for the protocol buffer service represented by this
// message. // message.

@ -387,7 +387,9 @@ type CodeGeneratorResponse_File struct {
// produced by another code generator. The original generator may provide // produced by another code generator. The original generator may provide
// insertion points by placing special annotations in the file that look // insertion points by placing special annotations in the file that look
// like: // like:
//
// @@protoc_insertion_point(NAME) // @@protoc_insertion_point(NAME)
//
// The annotation can have arbitrary text before and after it on the line, // 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 // 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 // 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 // For example, the C++ code generator places the following line in the
// .pb.h files that it generates: // .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 // This line appears within the scope of the file's package namespace, but
// outside of any particular class. Another plugin can then specify the // outside of any particular class. Another plugin can then specify the
// insertion_point "namespace_scope" to generate additional classes or // insertion_point "namespace_scope" to generate additional classes or

@ -1679,10 +1679,12 @@ type MessageOptions struct {
// efficient, has fewer features, and is more complicated. // efficient, has fewer features, and is more complicated.
// //
// The message must be defined exactly as follows: // The message must be defined exactly as follows:
//
// message Foo { // message Foo {
// option message_set_wire_format = true; // option message_set_wire_format = true;
// extensions 4 to max; // extensions 4 to max;
// } // }
//
// Note that the message cannot have any defined fields; MessageSets only // Note that the message cannot have any defined fields; MessageSets only
// have extensions. // have extensions.
// //
@ -1705,8 +1707,11 @@ type MessageOptions struct {
// maps field. // maps field.
// //
// For maps fields: // For maps fields:
//
// map<KeyType, ValueType> map_field = 1; // map<KeyType, ValueType> map_field = 1;
//
// The parsed descriptor looks like: // The parsed descriptor looks like:
//
// message MapFieldEntry { // message MapFieldEntry {
// option map_entry = true; // option map_entry = true;
// optional KeyType key = 1; // optional KeyType key = 1;
@ -1847,7 +1852,6 @@ type FieldOptions struct {
// call from multiple threads concurrently, while non-const methods continue // call from multiple threads concurrently, while non-const methods continue
// to require exclusive access. // to require exclusive access.
// //
//
// Note that implementations may choose not to check required fields within // Note that implementations may choose not to check required fields within
// a lazy sub-message. That is, calling IsInitialized() on the outer message // a lazy sub-message. That is, calling IsInitialized() on the outer message
// may return true even if the inner message has missing required fields. // may return true even if the inner message has missing required fields.
@ -2426,14 +2430,19 @@ type SourceCodeInfo struct {
// tools. // tools.
// //
// For example, say we have a file like: // For example, say we have a file like:
//
// message Foo { // message Foo {
// optional string foo = 1; // optional string foo = 1;
// } // }
//
// Let's look at just the field definition: // Let's look at just the field definition:
//
// optional string foo = 1; // optional string foo = 1;
// ^ ^^ ^^ ^ ^^^ // ^ ^^ ^^ ^ ^^^
// a bc de f ghi // a bc de f ghi
//
// We have the following locations: // We have the following locations:
//
// span path represents // span path represents
// [a,i) [ 4, 0, 2, 0 ] The whole field definition. // [a,i) [ 4, 0, 2, 0 ] The whole field definition.
// [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional).
@ -2810,21 +2819,32 @@ type SourceCodeInfo_Location struct {
// Each element is a field number or an index. They form a path from // Each element is a field number or an index. They form a path from
// the root FileDescriptorProto to the place where the definition occurs. // the root FileDescriptorProto to the place where the definition occurs.
// For example, this path: // For example, this path:
//
// [ 4, 3, 2, 7, 1 ] // [ 4, 3, 2, 7, 1 ]
//
// refers to: // refers to:
//
// file.message_type(3) // 4, 3 // file.message_type(3) // 4, 3
// .field(7) // 2, 7 // .field(7) // 2, 7
// .name() // 1 // .name() // 1
//
// This is because FileDescriptorProto.message_type has field number 4: // 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: // and DescriptorProto.field has field number 2:
//
// repeated FieldDescriptorProto field = 2; // repeated FieldDescriptorProto field = 2;
//
// and FieldDescriptorProto.name has field number 1: // 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 // Thus, the above path gives the location of a field name. If we removed
// the last element: // the last element:
//
// [ 4, 3, 2, 7 ] // [ 4, 3, 2, 7 ]
//
// this path refers to the whole field declaration (from the beginning // this path refers to the whole field declaration (from the beginning
// of the label to the terminating semicolon). // of the label to the terminating semicolon).
Path []int32 `protobuf:"varint,1,rep,packed,name=path" json:"path,omitempty"` Path []int32 `protobuf:"varint,1,rep,packed,name=path" json:"path,omitempty"`

@ -38,8 +38,7 @@
// //
// The Duration message represents a signed span of time. // 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 // The AsDuration method can be used to convert a Duration message to a
// standard Go time.Duration value: // standard Go time.Duration value:
@ -68,15 +67,13 @@
// the resulting value to the closest representable value (e.g., math.MaxInt64 // the resulting value to the closest representable value (e.g., math.MaxInt64
// for positive overflow and math.MinInt64 for negative overflow). // 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 // The durationpb.New function can be used to construct a Duration message
// from a standard Go time.Duration value: // from a standard Go time.Duration value:
// //
// dur := durationpb.New(d) // dur := durationpb.New(d)
// ... // make use of d as a *durationpb.Duration // ... // make use of d as a *durationpb.Duration
//
package durationpb package durationpb
import ( import (
@ -153,8 +150,6 @@ const (
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should // 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 // be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s". // microsecond should be expressed in JSON format as "3.000001s".
//
//
type Duration struct { type Duration struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache

@ -40,8 +40,7 @@
// The paths are specific to some target message type, // The paths are specific to some target message type,
// which is not stored within the FieldMask message itself. // which is not stored within the FieldMask message itself.
// //
// // # Constructing a FieldMask
// Constructing a FieldMask
// //
// The New function is used construct a FieldMask: // The New function is used construct a FieldMask:
// //
@ -64,8 +63,7 @@
// ... // handle error // ... // handle error
// } // }
// //
// // # Type checking a FieldMask
// Type checking a FieldMask
// //
// In order to verify that a FieldMask represents a set of fields that are // In order to verify that a FieldMask represents a set of fields that are
// reachable from some target message type, use the IsValid method: // reachable from some target message type, use the IsValid method:
@ -131,7 +129,6 @@ const (
// (their value will be set to the default, and omitted in proto text // (their value will be set to the default, and omitted in proto text
// output): // output):
// //
//
// f { // f {
// a : 22 // a : 22
// b { // b {

@ -47,8 +47,7 @@
// "google.golang.org/protobuf/encoding/protojson" package // "google.golang.org/protobuf/encoding/protojson" package
// ensures that they will be serialized as their JSON equivalent. // 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 // The standard Go "encoding/json" package has functionality to serialize
// arbitrary types to a large degree. The Value.AsInterface, Struct.AsMap, and // 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, // forms back as Value, Struct, and ListValue messages, use the NewStruct,
// NewList, and NewValue constructor functions. // NewList, and NewValue constructor functions.
// //
// // # Example usage
// Example usage
// //
// Consider the following example JSON object: // Consider the following example JSON object:
// //
@ -121,7 +119,6 @@
// ... // handle error // ... // handle error
// } // }
// ... // make use of m as a *structpb.Value // ... // make use of m as a *structpb.Value
//
package structpb package structpb
import ( import (
@ -296,6 +293,7 @@ type Value struct {
// The kind of value. // The kind of value.
// //
// Types that are assignable to Kind: // Types that are assignable to Kind:
//
// *Value_NullValue // *Value_NullValue
// *Value_NumberValue // *Value_NumberValue
// *Value_StringValue // *Value_StringValue

@ -39,8 +39,7 @@
// The Timestamp message represents a timestamp, // The Timestamp message represents a timestamp,
// an instant in time since the Unix epoch (January 1st, 1970). // 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 // The AsTime method can be used to convert a Timestamp message to a
// standard Go time.Time value in UTC: // standard Go time.Time value in UTC:
@ -62,8 +61,7 @@
// ... // handle error // ... // handle error
// } // }
// //
// // # Conversion from a Go Time
// Conversion from a Go Time
// //
// The timestamppb.New function can be used to construct a Timestamp message // The timestamppb.New function can be used to construct a Timestamp message
// from a standard Go time.Time value: // from a standard Go time.Time value:
@ -75,7 +73,6 @@
// //
// ts := timestamppb.Now() // ts := timestamppb.Now()
// ... // make use of ts as a *timestamppb.Timestamp // ... // make use of ts as a *timestamppb.Timestamp
//
package timestamppb package timestamppb
import ( import (
@ -143,7 +140,6 @@ const (
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build(); // .setNanos((int) ((millis % 1000) * 1000000)).build();
// //
//
// Example 5: Compute Timestamp from Java `Instant.now()`. // Example 5: Compute Timestamp from Java `Instant.now()`.
// //
// Instant now = Instant.now(); // Instant now = Instant.now();
@ -152,7 +148,6 @@ const (
// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) // Timestamp.newBuilder().setSeconds(now.getEpochSecond())
// .setNanos(now.getNano()).build(); // .setNanos(now.getNano()).build();
// //
//
// Example 6: Compute Timestamp from current time in Python. // Example 6: Compute Timestamp from current time in Python.
// //
// timestamp = Timestamp() // timestamp = Timestamp()
@ -184,8 +179,6 @@ const (
// the Joda Time's [`ISODateTimeFormat.dateTime()`]( // the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D // 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. // ) to obtain a formatter capable of generating timestamps in this format.
//
//
type Timestamp struct { type Timestamp struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache

@ -1,54 +0,0 @@
//复制时倒入qs
// @ts-ignore
import qs from "qs";
const API_SERVER = process.env.NEXT_PUBLIC_WEB_SITE
export interface Config<T> extends RequestInit {
token?: string
data?: T
method?: 'POST' | 'GET'
}
interface IError {
status: number
details: [],
message: string
metadata: any
reason: string
}
const http = async <T, V>(endpoint: string, {data, token, headers, ...customConfig}: Config<T>) => {
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
}

@ -2,9 +2,9 @@ package user
import ( import (
"context" "context"
"git.diulo.com/mogfee/protoc-gen-kit/middleware" "git.diulo.com/mogfee/kit/middleware"
"git.diulo.com/mogfee/protoc-gen-kit/response" "git.diulo.com/mogfee/kit/response"
"git.diulo.com/mogfee/protoc-gen-kit/xerrors" "git.diulo.com/mogfee/kit/errors"
"github.com/gin-gonic/gin" "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 { if v, ok := out.(*LoginResponse); ok {
resp.Success(v) resp.Success(v)
} else { } 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 { if v, ok := out.(*LoginResponse); ok {
resp.Success(v) resp.Success(v)
} else { } else {
resp.Error(xerrors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse")) resp.Error(errors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse"))
} }
} }
} }

@ -12,7 +12,6 @@ import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
_ "google.golang.org/protobuf/types/descriptorpb" _ "google.golang.org/protobuf/types/descriptorpb"
_ "google.golang.org/protobuf/types/known/wrapperspb"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
) )
@ -29,7 +28,7 @@ type LoginRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
//用户名 // 用户名
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,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"` 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 sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
//密钥 // 密钥
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` 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, 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, 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, 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, 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, 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, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x0c, 0x6c, 0x6f, 0x67, 0x69, 0x6e,

@ -1,3 +1,4 @@
// @ts-ignore
import {Config,http} from "./http"; import {Config,http} from "./http";
export interface loginRequest { export interface loginRequest {
//用户名 //用户名

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.2.0 // - protoc-gen-go-grpc v1.3.0
// - protoc v3.18.1 // - protoc v3.18.1
// source: user.proto // source: user.proto
@ -18,13 +18,19 @@ import (
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7 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. // 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. // 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 { type UserClient interface {
//列表 // 列表
List(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) List(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
//等了 // 等了
Login(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) 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) { func (c *userClient) List(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
out := new(LoginResponse) 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 { if err != nil {
return nil, err 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) { func (c *userClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
out := new(LoginResponse) 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 { if err != nil {
return nil, err 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) { func (c *userClient) Delete(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
out := new(LoginResponse) 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 { if err != nil {
return nil, err return nil, err
} }
@ -68,9 +74,9 @@ func (c *userClient) Delete(ctx context.Context, in *LoginRequest, opts ...grpc.
// All implementations must embed UnimplementedUserServer // All implementations must embed UnimplementedUserServer
// for forward compatibility // for forward compatibility
type UserServer interface { type UserServer interface {
//列表 // 列表
List(context.Context, *LoginRequest) (*LoginResponse, error) List(context.Context, *LoginRequest) (*LoginResponse, error)
//等了 // 等了
Login(context.Context, *LoginRequest) (*LoginResponse, error) Login(context.Context, *LoginRequest) (*LoginResponse, error)
Delete(context.Context, *LoginRequest) (*LoginResponse, error) Delete(context.Context, *LoginRequest) (*LoginResponse, error)
mustEmbedUnimplementedUserServer() mustEmbedUnimplementedUserServer()
@ -112,7 +118,7 @@ func _User_List_Handler(srv interface{}, ctx context.Context, dec func(interface
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/com.web.api.user.user/list", FullMethod: User_List_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServer).List(ctx, req.(*LoginRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/com.web.api.user.user/login", FullMethod: User_Login_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServer).Login(ctx, req.(*LoginRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/com.web.api.user.user/delete", FullMethod: User_Delete_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServer).Delete(ctx, req.(*LoginRequest)) return srv.(UserServer).Delete(ctx, req.(*LoginRequest))

@ -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)
}
}

@ -94,6 +94,7 @@ type FieldRules struct {
Message *MessageRules `protobuf:"bytes,17,opt,name=message" json:"message,omitempty"` Message *MessageRules `protobuf:"bytes,17,opt,name=message" json:"message,omitempty"`
// Types that are assignable to Type: // Types that are assignable to Type:
//
// *FieldRules_Float // *FieldRules_Float
// *FieldRules_Double // *FieldRules_Double
// *FieldRules_Int32 // *FieldRules_Int32
@ -2000,6 +2001,7 @@ type StringRules struct {
// patterns // patterns
// //
// Types that are assignable to WellKnown: // Types that are assignable to WellKnown:
//
// *StringRules_Email // *StringRules_Email
// *StringRules_Hostname // *StringRules_Hostname
// *StringRules_Ip // *StringRules_Ip
@ -2371,6 +2373,7 @@ type BytesRules struct {
// patterns // patterns
// //
// Types that are assignable to WellKnown: // Types that are assignable to WellKnown:
//
// *BytesRules_Ip // *BytesRules_Ip
// *BytesRules_Ipv4 // *BytesRules_Ipv4
// *BytesRules_Ipv6 // *BytesRules_Ipv6

@ -2,7 +2,7 @@ package response
import ( import (
"encoding/json" "encoding/json"
"git.diulo.com/mogfee/protoc-gen-kit/xerrors" "git.diulo.com/mogfee/kit/errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/form" "github.com/go-playground/form"
"net/http" "net/http"
@ -53,7 +53,7 @@ func (s *result) Error(err error) {
}) })
return return
} }
gs := xerrors.FromError(err) gs := errors.FromError(err)
s.Result(gs.Status, gs.Reason, gs.Message, gs.Metadata) s.Result(gs.Status, gs.Reason, gs.Message, gs.Metadata)
} }

@ -2,13 +2,13 @@ package main
import ( import (
"fmt" "fmt"
protoc_gen_kit "git.diulo.com/mogfee/protoc-gen-kit" "git.diulo.com/mogfee/kit"
) )
func main() { func main() {
app := protoc_gen_kit.New( app := kit.New(
protoc_gen_kit.Name("user-server"), kit.Name("user-server"),
protoc_gen_kit.Server()) kit.Server())
fmt.Println("run start") fmt.Println("run start")
app.Run() app.Run()
fmt.Println("run end") fmt.Println("run end")

@ -2,8 +2,10 @@ package http
import "net/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 type FilterFunc func(http.Handler) http.Handler
// FilterChain returns a FilterFunc that specifies the chained handler for HTTP Router.
func FilterChain(filters ...FilterFunc) FilterFunc { func FilterChain(filters ...FilterFunc) FilterFunc {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
for i := len(filters) - 1; i >= 0; i-- { for i := len(filters) - 1; i >= 0; i-- {

@ -6,13 +6,19 @@ import (
"sync" "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 { type RouteInfo struct {
Path string Path string
Method 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 { type Router struct {
prefix string prefix string
pool sync.Pool pool sync.Pool
@ -26,18 +32,22 @@ func newRouter(prefix string, srv *Server, filters ...FilterFunc) *Router {
srv: srv, srv: srv,
filters: filters, filters: filters,
} }
r.pool.New = func() any { r.pool.New = func() interface{} {
return &wrapper{router: r} return &wrapper{router: r}
} }
return r return r
} }
// Group returns a new router group.
func (r *Router) Group(prefix string, filters ...FilterFunc) *Router { func (r *Router) Group(prefix string, filters ...FilterFunc) *Router {
var newFilters []FilterFunc var newFilters []FilterFunc
newFilters = append(newFilters, r.filters...) newFilters = append(newFilters, r.filters...)
newFilters = append(newFilters, filters...) newFilters = append(newFilters, filters...)
return newRouter(path.Join(r.prefix, prefix), r.srv, newFilters...) 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) { next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
ctx := r.pool.Get().(Context) ctx := r.pool.Get().(Context)
ctx.Reset(res, req) ctx.Reset(res, req)
@ -51,9 +61,48 @@ func (r *Router) Handle(method string, relativePath string, h HandlerFunc, filte
next = FilterChain(r.filters...)(next) next = FilterChain(r.filters...)(next)
r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method) 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) { func (r *Router) GET(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodGet, path, h, m...) 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) { func (r *Router) POST(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodPost, path, h, m...) 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...)
}

@ -3,14 +3,19 @@ package http
import ( import (
"context" "context"
"crypto/tls" "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/internal/matcher"
"git.diulo.com/mogfee/kit/log"
"git.diulo.com/mogfee/kit/middleware" "git.diulo.com/mogfee/kit/middleware"
"git.diulo.com/mogfee/kit/transport" "git.diulo.com/mogfee/kit/transport"
"github.com/gorilla/mux"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
"github.com/gorilla/mux"
) )
var ( var (
@ -19,7 +24,8 @@ var (
_ http.Handler = (*Server)(nil) _ http.Handler = (*Server)(nil)
) )
type ServerOption func(server *Server) // ServerOption is an HTTP server option.
type ServerOption func(*Server)
// Network with server network. // Network with server network.
func Network(network string) ServerOption { func Network(network string) ServerOption {
@ -127,6 +133,7 @@ func PathPrefix(prefix string) ServerOption {
} }
} }
// Server is an HTTP server wrapper.
type Server struct { type Server struct {
*http.Server *http.Server
lis net.Listener lis net.Listener
@ -147,6 +154,7 @@ type Server struct {
router *mux.Router router *mux.Router
} }
// NewServer creates an HTTP server by options.
func NewServer(opts ...ServerOption) *Server { func NewServer(opts ...ServerOption) *Server {
srv := &Server{ srv := &Server{
network: "tcp", network: "tcp",
@ -156,13 +164,14 @@ func NewServer(opts ...ServerOption) *Server {
decVars: DefaultRequestVars, decVars: DefaultRequestVars,
decQuery: DefaultRequestQuery, decQuery: DefaultRequestQuery,
decBody: DefaultRequestDecoder, decBody: DefaultRequestDecoder,
enc: DefaultResponseEncoder,
ene: DefaultErrorEncoder,
strictSlash: true, strictSlash: true,
router: mux.NewRouter(), router: mux.NewRouter(),
} }
for _, o := range opts { for _, o := range opts {
o(srv) o(srv)
} }
srv.router.StrictSlash(srv.strictSlash) srv.router.StrictSlash(srv.strictSlash)
srv.router.NotFoundHandler = http.DefaultServeMux srv.router.NotFoundHandler = http.DefaultServeMux
srv.router.MethodNotAllowedHandler = http.DefaultServeMux srv.router.MethodNotAllowedHandler = http.DefaultServeMux
@ -173,91 +182,157 @@ func NewServer(opts ...ServerOption) *Server {
} }
return srv 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) { func (s *Server) Use(selector string, m ...middleware.Middleware) {
s.middleware.Add(selector, m...) 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 { return s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
methods, err := route.GetMethods() methods, err := route.GetMethods()
if err != nil { if err != nil {
return err return nil // ignore no methods
} }
path, err := route.GetPathTemplate() path, err := route.GetPathTemplate()
if err != nil { if err != nil {
return err return err
} }
for _, method := range methods { for _, method := range methods {
if err = fn(RouteInfo{ if err := fn(RouteInfo{Method: method, Path: path}); err != nil {
Method: method,
Path: path,
}); err != nil {
return err return err
} }
} }
return nil 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 { func (s *Server) Route(prefix string, filters ...FilterFunc) *Router {
return newRouter(prefix, s, filters...) 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) { func (s *Server) Handle(path string, h http.Handler) {
s.router.Handle(path, h) 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) { func (s *Server) HandlePrefix(prefix string, h http.Handler) {
s.router.PathPrefix(prefix).Handler(h) 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) { func (s *Server) HandleFunc(path string, h http.HandlerFunc) {
s.router.HandleFunc(path, h) 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) 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) { func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
s.Handler.ServeHTTP(res, req) s.Handler.ServeHTTP(res, req)
} }
func (s *Server) filter() mux.MiddlewareFunc { func (s *Server) filter() mux.MiddlewareFunc {
return func(handler http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var ( var (
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
) )
if s.timeout > 0 { if s.timeout > 0 {
ctx, cancel = context.WithTimeout(request.Context(), s.timeout) ctx, cancel = context.WithTimeout(req.Context(), s.timeout)
} else { } else {
ctx, cancel = context.WithCancel(request.Context()) ctx, cancel = context.WithCancel(req.Context())
} }
defer cancel() defer cancel()
pathTemplate := request.URL.Path pathTemplate := req.URL.Path
if route := mux.CurrentRoute(request); route != nil { if route := mux.CurrentRoute(req); route != nil {
// /path/123 -> /path/{id}
pathTemplate, _ = route.GetPathTemplate() 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 { // Endpoint return a real address to registry endpoint.
//TODO implement me // examples:
panic("implement me") //
// 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 { // Start start the HTTP server.
//TODO implement me func (s *Server) Start(ctx context.Context) error {
panic("implement me") 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 { // Stop stop the HTTP server.
//TODO implement me func (s *Server) Stop(ctx context.Context) error {
panic("implement me") log.Info("[HTTP] server stopping")
return s.Shutdown(ctx)
} }
func (s *Server) ReplyHeader() transport.Header { func (s *Server) listenAndEndpoint() error {
//TODO implement me if s.lis == nil {
panic("implement me") 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
} }

@ -6,12 +6,16 @@ import (
"net/http" "net/http"
) )
var _ Transporter = (*Transport)(nil)
// Transporter is http Transporter
type Transporter interface { type Transporter interface {
transport.Transporter transport.Transporter
Request() *http.Request Request() *http.Request
PathTemplate() string PathTemplate() string
} }
// Transport is an HTTP transport.
type Transport struct { type Transport struct {
endpoint string endpoint string
operation string operation string
@ -21,33 +25,42 @@ type Transport struct {
pathTemplate string pathTemplate string
} }
func (t *Transport) Kind() transport.Kind { // Kind returns the transport kind.
func (tr *Transport) Kind() transport.Kind {
return transport.KindHTTP return transport.KindHTTP
} }
func (t *Transport) Endpoint() string { // Endpoint returns the transport endpoint.
return t.endpoint func (tr *Transport) Endpoint() string {
return tr.endpoint
} }
func (t *Transport) Operation() string { // Operation returns the transport operation.
return t.operation func (tr *Transport) Operation() string {
return tr.operation
} }
func (t *Transport) RequestHeader() transport.Header { // Request returns the HTTP request.
return t.reqHeader func (tr *Transport) Request() *http.Request {
return tr.request
} }
func (t *Transport) ReplyHeader() transport.Header { // RequestHeader returns the request header.
return t.replyHeader func (tr *Transport) RequestHeader() transport.Header {
return tr.reqHeader
} }
func (t *Transport) Request() *http.Request { // ReplyHeader returns the reply header.
return t.request func (tr *Transport) ReplyHeader() transport.Header {
return tr.replyHeader
} }
func (t *Transport) PathTemplate() string { // PathTemplate returns the http path template.
return t.pathTemplate func (tr *Transport) PathTemplate() string {
return tr.pathTemplate
} }
// SetOperation sets the transport operation.
func SetOperation(ctx context.Context, op string) { func SetOperation(ctx context.Context, op string) {
if tr, ok := transport.FromServerContext(ctx); ok { if tr, ok := transport.FromServerContext(ctx); ok {
if tr, ok := tr.(*Transport); 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) { func RequestFromServerContext(ctx context.Context) (*http.Request, bool) {
if tr, ok := transport.FromServerContext(ctx); ok { if tr, ok := transport.FromServerContext(ctx); ok {
if tr, ok := tr.(*Transport); ok { if tr, ok := tr.(*Transport); ok {
@ -63,3 +78,24 @@ func RequestFromServerContext(ctx context.Context) (*http.Request, bool) {
} }
return nil, false 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
}

Loading…
Cancel
Save