master
parent
75c9c1c909
commit
3a77d1a930
65 changed files with 3408 additions and 663 deletions
@ -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
|
@ -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) |
||||||
|
} |
||||||
|
}`) |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# encoding |
||||||
|
|
||||||
|
## msgpack |
||||||
|
|
||||||
|
```shell |
||||||
|
go get -u github.com/go-kratos/kratos/contrib/encoding/msgpack/v2 |
||||||
|
``` |
@ -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 |
||||||
|
} |
@ -0,0 +1,211 @@ |
|||||||
|
package form |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/base64" |
||||||
|
"reflect" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb" |
||||||
|
"google.golang.org/protobuf/types/known/fieldmaskpb" |
||||||
|
"google.golang.org/protobuf/types/known/timestamppb" |
||||||
|
"google.golang.org/protobuf/types/known/wrapperspb" |
||||||
|
|
||||||
|
"github.com/go-kratos/kratos/v2/encoding" |
||||||
|
bdtest "github.com/go-kratos/kratos/v2/internal/testdata/binding" |
||||||
|
"github.com/go-kratos/kratos/v2/internal/testdata/complex" |
||||||
|
ectest "github.com/go-kratos/kratos/v2/internal/testdata/encoding" |
||||||
|
) |
||||||
|
|
||||||
|
type LoginRequest struct { |
||||||
|
Username string `json:"username,omitempty"` |
||||||
|
Password string `json:"password,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func TestFormCodecMarshal(t *testing.T) { |
||||||
|
req := &LoginRequest{ |
||||||
|
Username: "kratos", |
||||||
|
Password: "kratos_pwd", |
||||||
|
} |
||||||
|
content, err := encoding.GetCodec(Name).Marshal(req) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if !reflect.DeepEqual([]byte("password=kratos_pwd&username=kratos"), content) { |
||||||
|
t.Errorf("expect %s, got %s", "password=kratos_pwd&username=kratos", content) |
||||||
|
} |
||||||
|
|
||||||
|
req = &LoginRequest{ |
||||||
|
Username: "kratos", |
||||||
|
Password: "", |
||||||
|
} |
||||||
|
content, err = encoding.GetCodec(Name).Marshal(req) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if !reflect.DeepEqual([]byte("username=kratos"), content) { |
||||||
|
t.Errorf("expect %s, got %s", "username=kratos", content) |
||||||
|
} |
||||||
|
|
||||||
|
m := struct { |
||||||
|
ID int32 `json:"id"` |
||||||
|
Name string `json:"name"` |
||||||
|
}{ |
||||||
|
ID: 1, |
||||||
|
Name: "kratos", |
||||||
|
} |
||||||
|
content, err = encoding.GetCodec(Name).Marshal(m) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if !reflect.DeepEqual([]byte("id=1&name=kratos"), content) { |
||||||
|
t.Errorf("expect %s, got %s", "id=1&name=kratos", content) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestFormCodecUnmarshal(t *testing.T) { |
||||||
|
req := &LoginRequest{ |
||||||
|
Username: "kratos", |
||||||
|
Password: "kratos_pwd", |
||||||
|
} |
||||||
|
content, err := encoding.GetCodec(Name).Marshal(req) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
bindReq := new(LoginRequest) |
||||||
|
err = encoding.GetCodec(Name).Unmarshal(content, bindReq) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if !reflect.DeepEqual("kratos", bindReq.Username) { |
||||||
|
t.Errorf("expect %v, got %v", "kratos", bindReq.Username) |
||||||
|
} |
||||||
|
if !reflect.DeepEqual("kratos_pwd", bindReq.Password) { |
||||||
|
t.Errorf("expect %v, got %v", "kratos_pwd", bindReq.Password) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestProtoEncodeDecode(t *testing.T) { |
||||||
|
in := &complex.Complex{ |
||||||
|
Id: 2233, |
||||||
|
NoOne: "2233", |
||||||
|
Simple: &complex.Simple{Component: "5566"}, |
||||||
|
Simples: []string{"3344", "5566"}, |
||||||
|
B: true, |
||||||
|
Sex: complex.Sex_woman, |
||||||
|
Age: 18, |
||||||
|
A: 19, |
||||||
|
Count: 3, |
||||||
|
Price: 11.23, |
||||||
|
D: 22.22, |
||||||
|
Byte: []byte("123"), |
||||||
|
Map: map[string]string{"kratos": "https://go-kratos.dev/"}, |
||||||
|
|
||||||
|
Timestamp: ×tamppb.Timestamp{Seconds: 20, Nanos: 2}, |
||||||
|
Duration: &durationpb.Duration{Seconds: 120, Nanos: 22}, |
||||||
|
Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}}, |
||||||
|
Double: &wrapperspb.DoubleValue{Value: 12.33}, |
||||||
|
Float: &wrapperspb.FloatValue{Value: 12.34}, |
||||||
|
Int64: &wrapperspb.Int64Value{Value: 64}, |
||||||
|
Int32: &wrapperspb.Int32Value{Value: 32}, |
||||||
|
Uint64: &wrapperspb.UInt64Value{Value: 64}, |
||||||
|
Uint32: &wrapperspb.UInt32Value{Value: 32}, |
||||||
|
Bool: &wrapperspb.BoolValue{Value: false}, |
||||||
|
String_: &wrapperspb.StringValue{Value: "go-kratos"}, |
||||||
|
Bytes: &wrapperspb.BytesValue{Value: []byte("123")}, |
||||||
|
} |
||||||
|
content, err := encoding.GetCodec(Name).Marshal(in) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration="+ |
||||||
|
"2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&"+ |
||||||
|
"numberOne=2233&price=11.23&sex=woman&simples=3344&simples=5566&string=go-kratos"+ |
||||||
|
"×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" != string(content) { |
||||||
|
t.Errorf("rawpath is not equal to %s", content) |
||||||
|
} |
||||||
|
in2 := &complex.Complex{} |
||||||
|
err = encoding.GetCodec(Name).Unmarshal(content, in2) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if int64(2233) != in2.Id { |
||||||
|
t.Errorf("expect %v, got %v", int64(2233), in2.Id) |
||||||
|
} |
||||||
|
if "2233" != in2.NoOne { |
||||||
|
t.Errorf("expect %v, got %v", "2233", in2.NoOne) |
||||||
|
} |
||||||
|
if in2.Simple == nil { |
||||||
|
t.Errorf("expect %v, got %v", nil, in2.Simple) |
||||||
|
} |
||||||
|
if "5566" != in2.Simple.Component { |
||||||
|
t.Errorf("expect %v, got %v", "5566", in2.Simple.Component) |
||||||
|
} |
||||||
|
if in2.Simples == nil { |
||||||
|
t.Errorf("expect %v, got %v", nil, in2.Simples) |
||||||
|
} |
||||||
|
if len(in2.Simples) != 2 { |
||||||
|
t.Errorf("expect %v, got %v", 2, len(in2.Simples)) |
||||||
|
} |
||||||
|
if "3344" != in2.Simples[0] { |
||||||
|
t.Errorf("expect %v, got %v", "3344", in2.Simples[0]) |
||||||
|
} |
||||||
|
if "5566" != in2.Simples[1] { |
||||||
|
t.Errorf("expect %v, got %v", "5566", in2.Simples[1]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestDecodeStructPb(t *testing.T) { |
||||||
|
req := new(ectest.StructPb) |
||||||
|
query := `data={"name":"kratos"}&data_list={"name1": "kratos"}&data_list={"name2": "go-kratos"}` |
||||||
|
if err := encoding.GetCodec(Name).Unmarshal([]byte(query), req); err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if "kratos" != req.Data.GetFields()["name"].GetStringValue() { |
||||||
|
t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name"].GetStringValue()) |
||||||
|
} |
||||||
|
if len(req.DataList) != 2 { |
||||||
|
t.Fatalf("execpt %v, got %v", 2, len(req.DataList)) |
||||||
|
} |
||||||
|
if "kratos" != req.DataList[0].GetFields()["name1"].GetStringValue() { |
||||||
|
t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name1"].GetStringValue()) |
||||||
|
} |
||||||
|
if "go-kratos" != req.DataList[1].GetFields()["name2"].GetStringValue() { |
||||||
|
t.Errorf("except %v, got %v", "go-kratos", req.Data.GetFields()["name2"].GetStringValue()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestDecodeBytesValuePb(t *testing.T) { |
||||||
|
url := "https://example.com/xx/?a=1&b=2&c=3" |
||||||
|
val := base64.URLEncoding.EncodeToString([]byte(url)) |
||||||
|
content := "bytes=" + val |
||||||
|
in2 := &complex.Complex{} |
||||||
|
if err := encoding.GetCodec(Name).Unmarshal([]byte(content), in2); err != nil { |
||||||
|
t.Error(err) |
||||||
|
} |
||||||
|
if url != string(in2.Bytes.Value) { |
||||||
|
t.Errorf("except %s, got %s", val, in2.Bytes.Value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestEncodeFieldMask(t *testing.T) { |
||||||
|
req := &bdtest.HelloRequest{ |
||||||
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"foo", "bar"}}, |
||||||
|
} |
||||||
|
if v := EncodeFieldMask(req.ProtoReflect()); v != "updateMask=foo,bar" { |
||||||
|
t.Errorf("got %s", v) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestOptional(t *testing.T) { |
||||||
|
v := int32(100) |
||||||
|
req := &bdtest.HelloRequest{ |
||||||
|
Name: "foo", |
||||||
|
Sub: &bdtest.Sub{Name: "bar"}, |
||||||
|
OptInt32: &v, |
||||||
|
} |
||||||
|
query, _ := EncodeValues(req) |
||||||
|
if query.Encode() != "name=foo&optInt32=100&sub.naming=bar" { |
||||||
|
t.Fatalf("got %s", query.Encode()) |
||||||
|
} |
||||||
|
} |
@ -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×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
comp := &complex.Complex{} |
||||||
|
err = DecodeValues(comp, form) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if comp.Id != int64(2233) { |
||||||
|
t.Errorf("want %v, got %v", int64(2233), comp.Id) |
||||||
|
} |
||||||
|
if comp.NoOne != "2233" { |
||||||
|
t.Errorf("want %v, got %v", "2233", comp.NoOne) |
||||||
|
} |
||||||
|
if comp.Simple == nil { |
||||||
|
t.Fatalf("want %v, got %v", nil, comp.Simple) |
||||||
|
} |
||||||
|
if comp.Simple.Component != "5566" { |
||||||
|
t.Errorf("want %v, got %v", "5566", comp.Simple.Component) |
||||||
|
} |
||||||
|
if len(comp.Simples) != 2 { |
||||||
|
t.Fatalf("want %v, got %v", 2, len(comp.Simples)) |
||||||
|
} |
||||||
|
if comp.Simples[0] != "3344" { |
||||||
|
t.Errorf("want %v, got %v", "3344", comp.Simples[0]) |
||||||
|
} |
||||||
|
if comp.Simples[1] != "5566" { |
||||||
|
t.Errorf("want %v, got %v", "5566", comp.Simples[1]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestGetFieldDescriptor(t *testing.T) { |
||||||
|
comp := &complex.Complex{} |
||||||
|
|
||||||
|
field := getFieldDescriptor(comp.ProtoReflect(), "id") |
||||||
|
if field.Kind() != protoreflect.Int64Kind { |
||||||
|
t.Errorf("want: %d, got: %d", protoreflect.Int64Kind, field.Kind()) |
||||||
|
} |
||||||
|
|
||||||
|
field = getFieldDescriptor(comp.ProtoReflect(), "simples") |
||||||
|
if field.Kind() != protoreflect.StringKind { |
||||||
|
t.Errorf("want: %d, got: %d", protoreflect.StringKind, field.Kind()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestPopulateRepeatedField(t *testing.T) { |
||||||
|
query, err := url.ParseQuery("simples=3344&simples=5566") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
comp := &complex.Complex{} |
||||||
|
field := getFieldDescriptor(comp.ProtoReflect(), "simples") |
||||||
|
|
||||||
|
err = populateRepeatedField(field, comp.ProtoReflect().Mutable(field).List(), query["simples"]) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if !reflect.DeepEqual([]string{"3344", "5566"}, comp.GetSimples()) { |
||||||
|
t.Errorf("want: %v, got: %v", []string{"3344", "5566"}, comp.GetSimples()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestPopulateMapField(t *testing.T) { |
||||||
|
query, err := url.ParseQuery("map%5Bkratos%5D=https://go-kratos.dev/") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
comp := &complex.Complex{} |
||||||
|
field := getFieldDescriptor(comp.ProtoReflect(), "map") |
||||||
|
// Fill the comp map field with the url query values
|
||||||
|
err = populateMapField(field, comp.ProtoReflect().Mutable(field).Map(), []string{"kratos"}, query["map[kratos]"]) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
// Get the comp map field value
|
||||||
|
if query["map[kratos]"][0] != comp.Map["kratos"] { |
||||||
|
t.Errorf("want: %s, got: %s", query["map[kratos]"], comp.Map["kratos"]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseField(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
name string |
||||||
|
fieldName string |
||||||
|
protoReflectKind protoreflect.Kind |
||||||
|
value string |
||||||
|
targetProtoReflectValue protoreflect.Value |
||||||
|
targetErr error |
||||||
|
}{ |
||||||
|
{ |
||||||
|
name: "BoolKind", |
||||||
|
fieldName: "b", |
||||||
|
protoReflectKind: protoreflect.BoolKind, |
||||||
|
value: "true", |
||||||
|
targetProtoReflectValue: protoreflect.ValueOfBool(true), |
||||||
|
targetErr: nil, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "BoolKind", |
||||||
|
fieldName: "b", |
||||||
|
protoReflectKind: protoreflect.BoolKind, |
||||||
|
value: "a", |
||||||
|
targetProtoReflectValue: protoreflect.Value{}, |
||||||
|
targetErr: &strconv.NumError{Func: "ParseBool", Num: "a", Err: strconv.ErrSyntax}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "EnumKind", |
||||||
|
fieldName: "sex", |
||||||
|
protoReflectKind: protoreflect.EnumKind, |
||||||
|
value: "1", |
||||||
|
targetProtoReflectValue: protoreflect.ValueOfEnum(1), |
||||||
|
targetErr: nil, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "EnumKind", |
||||||
|
fieldName: "sex", |
||||||
|
protoReflectKind: protoreflect.EnumKind, |
||||||
|
value: "2", |
||||||
|
targetProtoReflectValue: protoreflect.Value{}, |
||||||
|
targetErr: fmt.Errorf("%q is not a valid value", "2"), |
||||||
|
}, |
||||||
|
} |
||||||
|
for _, test := range tests { |
||||||
|
t.Run(test.name, func(t *testing.T) { |
||||||
|
comp := &complex.Complex{} |
||||||
|
field := getFieldDescriptor(comp.ProtoReflect(), test.fieldName) |
||||||
|
if test.protoReflectKind != field.Kind() { |
||||||
|
t.Fatalf("want: %d, got: %d", test.protoReflectKind, field.Kind()) |
||||||
|
} |
||||||
|
val, err := parseField(field, test.value) |
||||||
|
if !reflect.DeepEqual(test.targetErr, err) { |
||||||
|
t.Fatalf("want: %s, got: %s", test.targetErr, err) |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(test.targetProtoReflectValue, val) { |
||||||
|
t.Errorf("want: %s, got: %s", test.targetProtoReflectValue, val) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestJsonSnakeCase(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
camelCase string |
||||||
|
snakeCase string |
||||||
|
}{ |
||||||
|
{ |
||||||
|
"userId", "user_id", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"user", "user", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"userIdAndUsername", "user_id_and_username", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"", "", |
||||||
|
}, |
||||||
|
} |
||||||
|
for _, test := range tests { |
||||||
|
t.Run(test.camelCase, func(t *testing.T) { |
||||||
|
snake := jsonSnakeCase(test.camelCase) |
||||||
|
if snake != test.snakeCase { |
||||||
|
t.Errorf("want: %s, got: %s", test.snakeCase, snake) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestIsASCIIUpper(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
b byte |
||||||
|
upper bool |
||||||
|
}{ |
||||||
|
{ |
||||||
|
'A', true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
'a', false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
',', false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
'1', false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
' ', false, |
||||||
|
}, |
||||||
|
} |
||||||
|
for _, test := range tests { |
||||||
|
t.Run(string(test.b), func(t *testing.T) { |
||||||
|
upper := isASCIIUpper(test.b) |
||||||
|
if test.upper != upper { |
||||||
|
t.Errorf("'%s' is not ascii upper", string(test.b)) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -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: ×tamppb.Timestamp{Seconds: 20, Nanos: 2}, |
||||||
|
Duration: &durationpb.Duration{Seconds: 120, Nanos: 22}, |
||||||
|
Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}}, |
||||||
|
Double: &wrapperspb.DoubleValue{Value: 12.33}, |
||||||
|
Float: &wrapperspb.FloatValue{Value: 12.34}, |
||||||
|
Int64: &wrapperspb.Int64Value{Value: 64}, |
||||||
|
Int32: &wrapperspb.Int32Value{Value: 32}, |
||||||
|
Uint64: &wrapperspb.UInt64Value{Value: 64}, |
||||||
|
Uint32: &wrapperspb.UInt32Value{Value: 32}, |
||||||
|
Bool: &wrapperspb.BoolValue{Value: false}, |
||||||
|
String_: &wrapperspb.StringValue{Value: "go-kratos"}, |
||||||
|
Bytes: &wrapperspb.BytesValue{Value: []byte("123")}, |
||||||
|
} |
||||||
|
query, err := EncodeValues(in) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
want := "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&map%5Bkratos_start%5D=https%3A%2F%2Fgo-kratos.dev%2Fen%2Fdocs%2Fgetting-started%2Fstart%2F&numberOne=2233&price=11.23&sex=woman&simples=3344&simples=5566&string=go-kratos×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" // nolint:lll
|
||||||
|
if got := query.Encode(); want != got { |
||||||
|
t.Errorf("want: %s, got: %s", want, got) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestJsonCamelCase(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
camelCase string |
||||||
|
snakeCase string |
||||||
|
}{ |
||||||
|
{ |
||||||
|
"userId", "user_id", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"user", "user", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"userIdAndUsername", "user_id_and_username", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"", "", |
||||||
|
}, |
||||||
|
} |
||||||
|
for _, test := range tests { |
||||||
|
t.Run(test.snakeCase, func(t *testing.T) { |
||||||
|
camel := jsonCamelCase(test.snakeCase) |
||||||
|
if camel != test.camelCase { |
||||||
|
t.Errorf("want: %s, got: %s", test.camelCase, camel) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestIsASCIILower(t *testing.T) { |
||||||
|
tests := []struct { |
||||||
|
b byte |
||||||
|
lower bool |
||||||
|
}{ |
||||||
|
{ |
||||||
|
'A', false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
'a', true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
',', false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
'1', false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
' ', false, |
||||||
|
}, |
||||||
|
} |
||||||
|
for _, test := range tests { |
||||||
|
t.Run(string(test.b), func(t *testing.T) { |
||||||
|
lower := isASCIILower(test.b) |
||||||
|
if test.lower != lower { |
||||||
|
t.Errorf("'%s' is not ascii lower", string(test.b)) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -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", "") |
||||||
} |
} |
||||||
|
@ -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) |
||||||
|
} |
||||||
|
} |
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Binary file not shown.
@ -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 |
|
||||||
} |
|
@ -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) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue