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

@ -1,9 +1,12 @@
kit:
go build -o ~/go/bin/protoc-gen-kit cmd/kit/main.go
gin-kit:
go build -o ~/go/bin/protoc-gen-gin-kit cmd/gin-kit/main.go
ts:
go build -o ~/go/bin/protoc-gen-ts cmd/ts/main.go
all:
make kit
make gin-kit
make ts
testts:
make ts

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

@ -0,0 +1,111 @@
package main
import (
protogen2 "git.diulo.com/mogfee/kit/protogen"
"google.golang.org/protobuf/compiler/protogen"
"strings"
)
func main() {
u := &Kit{
imports: map[string]string{},
}
protogen.Options{}.Run(u.Generate)
}
type Kit struct {
imports map[string]string
}
func (u *Kit) addImports(imp string) {
u.imports[imp] = imp
}
func (u *Kit) Generate(plugin *protogen.Plugin) error {
if len(plugin.Files) < 1 {
return nil
}
u.addImports("context")
u.addImports("git.diulo.com/mogfee/kit/transport/http")
for _, f := range plugin.Files {
if len(f.Services) == 0 {
continue
}
fname := f.GeneratedFilenamePrefix + "_http.pb.go"
t := plugin.NewGeneratedFile(fname, f.GoImportPath)
t.P("package " + f.Desc.Name())
t.P("import (")
for _, v := range u.imports {
t.P(`"` + v + `"`)
}
t.P(")")
for _, s := range f.Services {
t.P(`type `, s.GoName, `HTTPServer interface {`)
for _, m := range s.Methods {
t.P(m.GoName, `(context.Context, *`, m.Input.GoIdent.GoName, `) (*`, m.Output.GoIdent.GoName, `,error)`)
}
t.P(`}`)
}
for _, s := range f.Services {
serverName := s.GoName
t.P(`func Register`, serverName, `HTTPServer(s *http.Server,srv `, serverName, `Server) {`)
t.P(`r:=s.Route("/")`)
for _, m := range s.Methods {
method, path := protogen2.GetProtoMethod(m)
if method == "" {
continue
}
t.P(`r.`, method, `("`, path, `",_`, s.GoName, `_`, m.GoName, `0_HTTP_Handler(srv))`)
}
t.P(`}`)
}
for _, s := range f.Services {
for _, m := range s.Methods {
method, _ := protogen2.GetProtoMethod(m)
if method == "" {
continue
}
u.genGet(f, s, t, m)
}
}
}
return nil
}
func (u *Kit) genGet(f *protogen.File, s *protogen.Service, t *protogen.GeneratedFile, m *protogen.Method) {
method, path := protogen2.GetProtoMethod(m)
if method == "" {
return
}
t.P(`func _`, s.GoName, `_`, m.GoName, `0_HTTP_Handler(srv `, s.GoName, `HTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
var in `, m.Input.GoIdent.GoName)
if method == protogen2.METHOD_GET {
t.P(`if err := ctx.BindQuery(&in); err != nil {
return err
}`)
} else if method == protogen2.METHOD_POST {
t.P(`if err := ctx.Bind(&in); err != nil {
return err
}`)
}
if strings.LastIndexByte(path, '{') != -1 {
t.P(`if err := ctx.BindVars(&in); err != nil {
return err
}`)
}
t.P(`http.SetOperation(ctx, "/`, f.Desc.Package(), `.`, s.Desc.Name(), `/`, m.Desc.Name(), `")
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.`, m.GoName, `(ctx, req.(*`, m.Input.GoIdent.GoName, `))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*`, m.Output.GoIdent.GoName, `)
return ctx.Result(200, reply)
}
}`)
}

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

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

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

@ -1,15 +1,27 @@
package encoding
import "strings"
import (
"strings"
)
// Codec defines the interface Transport uses to encode and decode messages. Note
// that implementations of this interface must be thread safe; a Codec's
// methods can be called from concurrent goroutines.
type Codec interface {
Marshal(any) ([]byte, error)
Unmarshal([]byte, any) error
// Marshal returns the wire format of v.
Marshal(v interface{}) ([]byte, error)
// Unmarshal parses the wire format into v.
Unmarshal(data []byte, v interface{}) error
// Name returns the name of the Codec implementation. The returned string
// will be used as part of content type in transmission. The result must be
// static; the result cannot change between calls.
Name() string
}
var registeredCodecs = make(map[string]Codec)
// RegisterCodec registers the provided Codec for use with all Transport clients and
// servers.
func RegisterCodec(codec Codec) {
if codec == nil {
panic("cannot register a nil Codec")
@ -17,9 +29,14 @@ func RegisterCodec(codec Codec) {
if codec.Name() == "" {
panic("cannot register Codec with empty string result for Name()")
}
contentSubType := strings.ToLower(codec.Name())
registeredCodecs[contentSubType] = codec
contentSubtype := strings.ToLower(codec.Name())
registeredCodecs[contentSubtype] = codec
}
func GetCodec(contentSubType string) Codec {
return registeredCodecs[contentSubType]
// GetCodec gets a registered Codec by content-subtype, or nil if no Codec is
// registered for the content-subtype.
//
// The content-subtype is expected to be lowercase.
func GetCodec(contentSubtype string) Codec {
return registeredCodecs[contentSubtype]
}

@ -0,0 +1,88 @@
package encoding
import (
"encoding/xml"
"fmt"
"runtime/debug"
"testing"
)
type codec struct{}
func (c codec) Marshal(v interface{}) ([]byte, error) {
panic("implement me")
}
func (c codec) Unmarshal(data []byte, v interface{}) error {
panic("implement me")
}
func (c codec) Name() string {
return ""
}
// codec2 is a Codec implementation with xml.
type codec2 struct{}
func (codec2) Marshal(v interface{}) ([]byte, error) {
return xml.Marshal(v)
}
func (codec2) Unmarshal(data []byte, v interface{}) error {
return xml.Unmarshal(data, v)
}
func (codec2) Name() string {
return "xml"
}
func TestRegisterCodec(t *testing.T) {
f := func() { RegisterCodec(nil) }
funcDidPanic, panicValue, _ := didPanic(f)
if !funcDidPanic {
t.Fatalf(fmt.Sprintf("func should panic\n\tPanic value:\t%#v", panicValue))
}
if panicValue != "cannot register a nil Codec" {
t.Fatalf("panic error got %s want cannot register a nil Codec", panicValue)
}
f = func() {
RegisterCodec(codec{})
}
funcDidPanic, panicValue, _ = didPanic(f)
if !funcDidPanic {
t.Fatalf(fmt.Sprintf("func should panic\n\tPanic value:\t%#v", panicValue))
}
if panicValue != "cannot register Codec with empty string result for Name()" {
t.Fatalf("panic error got %s want cannot register Codec with empty string result for Name()", panicValue)
}
codec := codec2{}
RegisterCodec(codec)
got := GetCodec("xml")
if got != codec {
t.Fatalf("RegisterCodec(%v) want %v got %v", codec, codec, got)
}
}
// PanicTestFunc defines a func that should be passed to assert.Panics and assert.NotPanics
// methods, and represents a simple func that takes no arguments, and returns nothing.
type PanicTestFunc func()
// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
func didPanic(f PanicTestFunc) (bool, interface{}, string) {
didPanic := false
var message interface{}
var stack string
func() {
defer func() {
if message = recover(); message != nil {
didPanic = true
stack = string(debug.Stack())
}
}()
// call the target function
f()
}()
return didPanic, message, stack
}

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

@ -0,0 +1,211 @@
package form
import (
"encoding/base64"
"reflect"
"testing"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/go-kratos/kratos/v2/encoding"
bdtest "github.com/go-kratos/kratos/v2/internal/testdata/binding"
"github.com/go-kratos/kratos/v2/internal/testdata/complex"
ectest "github.com/go-kratos/kratos/v2/internal/testdata/encoding"
)
type LoginRequest struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
func TestFormCodecMarshal(t *testing.T) {
req := &LoginRequest{
Username: "kratos",
Password: "kratos_pwd",
}
content, err := encoding.GetCodec(Name).Marshal(req)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual([]byte("password=kratos_pwd&username=kratos"), content) {
t.Errorf("expect %s, got %s", "password=kratos_pwd&username=kratos", content)
}
req = &LoginRequest{
Username: "kratos",
Password: "",
}
content, err = encoding.GetCodec(Name).Marshal(req)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual([]byte("username=kratos"), content) {
t.Errorf("expect %s, got %s", "username=kratos", content)
}
m := struct {
ID int32 `json:"id"`
Name string `json:"name"`
}{
ID: 1,
Name: "kratos",
}
content, err = encoding.GetCodec(Name).Marshal(m)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual([]byte("id=1&name=kratos"), content) {
t.Errorf("expect %s, got %s", "id=1&name=kratos", content)
}
}
func TestFormCodecUnmarshal(t *testing.T) {
req := &LoginRequest{
Username: "kratos",
Password: "kratos_pwd",
}
content, err := encoding.GetCodec(Name).Marshal(req)
if err != nil {
t.Fatal(err)
}
bindReq := new(LoginRequest)
err = encoding.GetCodec(Name).Unmarshal(content, bindReq)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual("kratos", bindReq.Username) {
t.Errorf("expect %v, got %v", "kratos", bindReq.Username)
}
if !reflect.DeepEqual("kratos_pwd", bindReq.Password) {
t.Errorf("expect %v, got %v", "kratos_pwd", bindReq.Password)
}
}
func TestProtoEncodeDecode(t *testing.T) {
in := &complex.Complex{
Id: 2233,
NoOne: "2233",
Simple: &complex.Simple{Component: "5566"},
Simples: []string{"3344", "5566"},
B: true,
Sex: complex.Sex_woman,
Age: 18,
A: 19,
Count: 3,
Price: 11.23,
D: 22.22,
Byte: []byte("123"),
Map: map[string]string{"kratos": "https://go-kratos.dev/"},
Timestamp: &timestamppb.Timestamp{Seconds: 20, Nanos: 2},
Duration: &durationpb.Duration{Seconds: 120, Nanos: 22},
Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}},
Double: &wrapperspb.DoubleValue{Value: 12.33},
Float: &wrapperspb.FloatValue{Value: 12.34},
Int64: &wrapperspb.Int64Value{Value: 64},
Int32: &wrapperspb.Int32Value{Value: 32},
Uint64: &wrapperspb.UInt64Value{Value: 64},
Uint32: &wrapperspb.UInt32Value{Value: 32},
Bool: &wrapperspb.BoolValue{Value: false},
String_: &wrapperspb.StringValue{Value: "go-kratos"},
Bytes: &wrapperspb.BytesValue{Value: []byte("123")},
}
content, err := encoding.GetCodec(Name).Marshal(in)
if err != nil {
t.Fatal(err)
}
if "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration="+
"2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&"+
"numberOne=2233&price=11.23&sex=woman&simples=3344&simples=5566&string=go-kratos"+
"&timestamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" != string(content) {
t.Errorf("rawpath is not equal to %s", content)
}
in2 := &complex.Complex{}
err = encoding.GetCodec(Name).Unmarshal(content, in2)
if err != nil {
t.Fatal(err)
}
if int64(2233) != in2.Id {
t.Errorf("expect %v, got %v", int64(2233), in2.Id)
}
if "2233" != in2.NoOne {
t.Errorf("expect %v, got %v", "2233", in2.NoOne)
}
if in2.Simple == nil {
t.Errorf("expect %v, got %v", nil, in2.Simple)
}
if "5566" != in2.Simple.Component {
t.Errorf("expect %v, got %v", "5566", in2.Simple.Component)
}
if in2.Simples == nil {
t.Errorf("expect %v, got %v", nil, in2.Simples)
}
if len(in2.Simples) != 2 {
t.Errorf("expect %v, got %v", 2, len(in2.Simples))
}
if "3344" != in2.Simples[0] {
t.Errorf("expect %v, got %v", "3344", in2.Simples[0])
}
if "5566" != in2.Simples[1] {
t.Errorf("expect %v, got %v", "5566", in2.Simples[1])
}
}
func TestDecodeStructPb(t *testing.T) {
req := new(ectest.StructPb)
query := `data={"name":"kratos"}&data_list={"name1": "kratos"}&data_list={"name2": "go-kratos"}`
if err := encoding.GetCodec(Name).Unmarshal([]byte(query), req); err != nil {
t.Fatal(err)
}
if "kratos" != req.Data.GetFields()["name"].GetStringValue() {
t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name"].GetStringValue())
}
if len(req.DataList) != 2 {
t.Fatalf("execpt %v, got %v", 2, len(req.DataList))
}
if "kratos" != req.DataList[0].GetFields()["name1"].GetStringValue() {
t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name1"].GetStringValue())
}
if "go-kratos" != req.DataList[1].GetFields()["name2"].GetStringValue() {
t.Errorf("except %v, got %v", "go-kratos", req.Data.GetFields()["name2"].GetStringValue())
}
}
func TestDecodeBytesValuePb(t *testing.T) {
url := "https://example.com/xx/?a=1&b=2&c=3"
val := base64.URLEncoding.EncodeToString([]byte(url))
content := "bytes=" + val
in2 := &complex.Complex{}
if err := encoding.GetCodec(Name).Unmarshal([]byte(content), in2); err != nil {
t.Error(err)
}
if url != string(in2.Bytes.Value) {
t.Errorf("except %s, got %s", val, in2.Bytes.Value)
}
}
func TestEncodeFieldMask(t *testing.T) {
req := &bdtest.HelloRequest{
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"foo", "bar"}},
}
if v := EncodeFieldMask(req.ProtoReflect()); v != "updateMask=foo,bar" {
t.Errorf("got %s", v)
}
}
func TestOptional(t *testing.T) {
v := int32(100)
req := &bdtest.HelloRequest{
Name: "foo",
Sub: &bdtest.Sub{Name: "bar"},
OptInt32: &v,
}
query, _ := EncodeValues(req)
if query.Encode() != "name=foo&optInt32=100&sub.naming=bar" {
t.Fatalf("got %s", query.Encode())
}
}

@ -0,0 +1,333 @@
package form
import (
"encoding/base64"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
// DecodeValues decode url value into proto message.
func DecodeValues(msg proto.Message, values url.Values) error {
for key, values := range values {
if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), values); err != nil {
return err
}
}
return nil
}
func populateFieldValues(v protoreflect.Message, fieldPath []string, values []string) error {
if len(fieldPath) < 1 {
return errors.New("no field path")
}
if len(values) < 1 {
return errors.New("no value provided")
}
var fd protoreflect.FieldDescriptor
for i, fieldName := range fieldPath {
if fd = getFieldDescriptor(v, fieldName); fd == nil {
// ignore unexpected field.
return nil
}
if i == len(fieldPath)-1 {
break
}
if fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated {
if fd.IsMap() && len(fieldPath) > 1 {
// post subfield
return populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values)
}
return fmt.Errorf("invalid path: %q is not a message", fieldName)
}
v = v.Mutable(fd).Message()
}
if of := fd.ContainingOneof(); of != nil {
if f := v.WhichOneof(of); f != nil {
return fmt.Errorf("field already set for oneof %q", of.FullName().Name())
}
}
switch {
case fd.IsList():
return populateRepeatedField(fd, v.Mutable(fd).List(), values)
case fd.IsMap():
return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
}
if len(values) > 1 {
return fmt.Errorf("too many values for field %q: %s", fd.FullName().Name(), strings.Join(values, ", "))
}
return populateField(fd, v, values[0])
}
func getFieldDescriptor(v protoreflect.Message, fieldName string) protoreflect.FieldDescriptor {
fields := v.Descriptor().Fields()
var fd protoreflect.FieldDescriptor
if fd = getDescriptorByFieldAndName(fields, fieldName); fd == nil {
if v.Descriptor().FullName() == structMessageFullname {
fd = fields.ByNumber(structFieldsFieldNumber)
} else if len(fieldName) > 2 && strings.HasSuffix(fieldName, "[]") {
fd = getDescriptorByFieldAndName(fields, strings.TrimSuffix(fieldName, "[]"))
}
}
return fd
}
func getDescriptorByFieldAndName(fields protoreflect.FieldDescriptors, fieldName string) protoreflect.FieldDescriptor {
var fd protoreflect.FieldDescriptor
if fd = fields.ByName(protoreflect.Name(fieldName)); fd == nil {
fd = fields.ByJSONName(fieldName)
}
return fd
}
func populateField(fd protoreflect.FieldDescriptor, v protoreflect.Message, value string) error {
if value == "" {
return nil
}
val, err := parseField(fd, value)
if err != nil {
return fmt.Errorf("parsing field %q: %w", fd.FullName().Name(), err)
}
v.Set(fd, val)
return nil
}
func populateRepeatedField(fd protoreflect.FieldDescriptor, list protoreflect.List, values []string) error {
for _, value := range values {
v, err := parseField(fd, value)
if err != nil {
return fmt.Errorf("parsing list %q: %w", fd.FullName().Name(), err)
}
list.Append(v)
}
return nil
}
func populateMapField(fd protoreflect.FieldDescriptor, mp protoreflect.Map, fieldPath []string, values []string) error {
// post sub key.
nkey := len(fieldPath) - 1
key, err := parseField(fd.MapKey(), fieldPath[nkey])
if err != nil {
return fmt.Errorf("parsing map key %q: %w", fd.FullName().Name(), err)
}
vkey := len(values) - 1
value, err := parseField(fd.MapValue(), values[vkey])
if err != nil {
return fmt.Errorf("parsing map value %q: %w", fd.FullName().Name(), err)
}
mp.Set(key.MapKey(), value)
return nil
}
func parseField(fd protoreflect.FieldDescriptor, value string) (protoreflect.Value, error) {
switch fd.Kind() {
case protoreflect.BoolKind:
v, err := strconv.ParseBool(value)
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfBool(v), nil
case protoreflect.EnumKind:
enum, err := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName())
switch {
case errors.Is(err, protoregistry.NotFound):
return protoreflect.Value{}, fmt.Errorf("enum %q is not registered", fd.Enum().FullName())
case err != nil:
return protoreflect.Value{}, fmt.Errorf("failed to look up enum: %w", err)
}
v := enum.Descriptor().Values().ByName(protoreflect.Name(value))
if v == nil {
i, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value)
}
v = enum.Descriptor().Values().ByNumber(protoreflect.EnumNumber(i))
if v == nil {
return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value)
}
}
return protoreflect.ValueOfEnum(v.Number()), nil
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
v, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfInt32(int32(v)), nil
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
v, err := strconv.ParseInt(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfInt64(v), nil
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
v, err := strconv.ParseUint(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfUint32(uint32(v)), nil
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
v, err := strconv.ParseUint(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfUint64(v), nil
case protoreflect.FloatKind:
v, err := strconv.ParseFloat(value, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfFloat32(float32(v)), nil
case protoreflect.DoubleKind:
v, err := strconv.ParseFloat(value, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfFloat64(v), nil
case protoreflect.StringKind:
return protoreflect.ValueOfString(value), nil
case protoreflect.BytesKind:
v, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return protoreflect.Value{}, err
}
return protoreflect.ValueOfBytes(v), nil
case protoreflect.MessageKind, protoreflect.GroupKind:
return parseMessage(fd.Message(), value)
default:
panic(fmt.Sprintf("unknown field kind: %v", fd.Kind()))
}
}
func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect.Value, error) {
var msg proto.Message
switch md.FullName() {
case "google.protobuf.Timestamp":
if value == nullStr {
break
}
t, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return protoreflect.Value{}, err
}
msg = timestamppb.New(t)
case "google.protobuf.Duration":
if value == nullStr {
break
}
d, err := time.ParseDuration(value)
if err != nil {
return protoreflect.Value{}, err
}
msg = durationpb.New(d)
case "google.protobuf.DoubleValue":
v, err := strconv.ParseFloat(value, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Double(v)
case "google.protobuf.FloatValue":
v, err := strconv.ParseFloat(value, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Float(float32(v))
case "google.protobuf.Int64Value":
v, err := strconv.ParseInt(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Int64(v)
case "google.protobuf.Int32Value":
v, err := strconv.ParseInt(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Int32(int32(v))
case "google.protobuf.UInt64Value":
v, err := strconv.ParseUint(value, 10, 64) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.UInt64(v)
case "google.protobuf.UInt32Value":
v, err := strconv.ParseUint(value, 10, 32) //nolint:gomnd
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.UInt32(uint32(v))
case "google.protobuf.BoolValue":
v, err := strconv.ParseBool(value)
if err != nil {
return protoreflect.Value{}, err
}
msg = wrapperspb.Bool(v)
case "google.protobuf.StringValue":
msg = wrapperspb.String(value)
case "google.protobuf.BytesValue":
v, err := base64.StdEncoding.DecodeString(value)
if err != nil {
if v, err = base64.URLEncoding.DecodeString(value); err != nil {
return protoreflect.Value{}, err
}
}
msg = wrapperspb.Bytes(v)
case "google.protobuf.FieldMask":
fm := &fieldmaskpb.FieldMask{}
for _, fv := range strings.Split(value, ",") {
fm.Paths = append(fm.Paths, jsonSnakeCase(fv))
}
msg = fm
case "google.protobuf.Value":
fm, err := structpb.NewValue(value)
if err != nil {
return protoreflect.Value{}, err
}
msg = fm
case "google.protobuf.Struct":
var v structpb.Struct
if err := protojson.Unmarshal([]byte(value), &v); err != nil {
return protoreflect.Value{}, err
}
msg = &v
default:
return protoreflect.Value{}, fmt.Errorf("unsupported message type: %q", string(md.FullName()))
}
return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil
}
// jsonSnakeCase converts a camelCase identifier to a snake_case identifier,
// according to the protobuf JSON specification.
// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L864
func jsonSnakeCase(s string) string {
var b []byte
for i := 0; i < len(s); i++ { // proto identifiers are always ASCII
c := s[i]
if isASCIIUpper(c) {
b = append(b, '_')
c += 'a' - 'A' // convert to lowercase
}
b = append(b, c)
}
return string(b)
}
func isASCIIUpper(c byte) bool {
return 'A' <= c && c <= 'Z'
}

@ -0,0 +1,217 @@
package form
import (
"fmt"
"net/url"
"reflect"
"strconv"
"testing"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/go-kratos/kratos/v2/internal/testdata/complex"
)
func TestDecodeValues(t *testing.T) {
form, err := url.ParseQuery("a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=" +
"2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&numberOne=2233&price=11.23&sex=woman&simples=3344&" +
"simples=5566&string=go-kratos&timestamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566")
if err != nil {
t.Fatal(err)
}
comp := &complex.Complex{}
err = DecodeValues(comp, form)
if err != nil {
t.Fatal(err)
}
if comp.Id != int64(2233) {
t.Errorf("want %v, got %v", int64(2233), comp.Id)
}
if comp.NoOne != "2233" {
t.Errorf("want %v, got %v", "2233", comp.NoOne)
}
if comp.Simple == nil {
t.Fatalf("want %v, got %v", nil, comp.Simple)
}
if comp.Simple.Component != "5566" {
t.Errorf("want %v, got %v", "5566", comp.Simple.Component)
}
if len(comp.Simples) != 2 {
t.Fatalf("want %v, got %v", 2, len(comp.Simples))
}
if comp.Simples[0] != "3344" {
t.Errorf("want %v, got %v", "3344", comp.Simples[0])
}
if comp.Simples[1] != "5566" {
t.Errorf("want %v, got %v", "5566", comp.Simples[1])
}
}
func TestGetFieldDescriptor(t *testing.T) {
comp := &complex.Complex{}
field := getFieldDescriptor(comp.ProtoReflect(), "id")
if field.Kind() != protoreflect.Int64Kind {
t.Errorf("want: %d, got: %d", protoreflect.Int64Kind, field.Kind())
}
field = getFieldDescriptor(comp.ProtoReflect(), "simples")
if field.Kind() != protoreflect.StringKind {
t.Errorf("want: %d, got: %d", protoreflect.StringKind, field.Kind())
}
}
func TestPopulateRepeatedField(t *testing.T) {
query, err := url.ParseQuery("simples=3344&simples=5566")
if err != nil {
t.Fatal(err)
}
comp := &complex.Complex{}
field := getFieldDescriptor(comp.ProtoReflect(), "simples")
err = populateRepeatedField(field, comp.ProtoReflect().Mutable(field).List(), query["simples"])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual([]string{"3344", "5566"}, comp.GetSimples()) {
t.Errorf("want: %v, got: %v", []string{"3344", "5566"}, comp.GetSimples())
}
}
func TestPopulateMapField(t *testing.T) {
query, err := url.ParseQuery("map%5Bkratos%5D=https://go-kratos.dev/")
if err != nil {
t.Fatal(err)
}
comp := &complex.Complex{}
field := getFieldDescriptor(comp.ProtoReflect(), "map")
// Fill the comp map field with the url query values
err = populateMapField(field, comp.ProtoReflect().Mutable(field).Map(), []string{"kratos"}, query["map[kratos]"])
if err != nil {
t.Fatal(err)
}
// Get the comp map field value
if query["map[kratos]"][0] != comp.Map["kratos"] {
t.Errorf("want: %s, got: %s", query["map[kratos]"], comp.Map["kratos"])
}
}
func TestParseField(t *testing.T) {
tests := []struct {
name string
fieldName string
protoReflectKind protoreflect.Kind
value string
targetProtoReflectValue protoreflect.Value
targetErr error
}{
{
name: "BoolKind",
fieldName: "b",
protoReflectKind: protoreflect.BoolKind,
value: "true",
targetProtoReflectValue: protoreflect.ValueOfBool(true),
targetErr: nil,
},
{
name: "BoolKind",
fieldName: "b",
protoReflectKind: protoreflect.BoolKind,
value: "a",
targetProtoReflectValue: protoreflect.Value{},
targetErr: &strconv.NumError{Func: "ParseBool", Num: "a", Err: strconv.ErrSyntax},
},
{
name: "EnumKind",
fieldName: "sex",
protoReflectKind: protoreflect.EnumKind,
value: "1",
targetProtoReflectValue: protoreflect.ValueOfEnum(1),
targetErr: nil,
},
{
name: "EnumKind",
fieldName: "sex",
protoReflectKind: protoreflect.EnumKind,
value: "2",
targetProtoReflectValue: protoreflect.Value{},
targetErr: fmt.Errorf("%q is not a valid value", "2"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
comp := &complex.Complex{}
field := getFieldDescriptor(comp.ProtoReflect(), test.fieldName)
if test.protoReflectKind != field.Kind() {
t.Fatalf("want: %d, got: %d", test.protoReflectKind, field.Kind())
}
val, err := parseField(field, test.value)
if !reflect.DeepEqual(test.targetErr, err) {
t.Fatalf("want: %s, got: %s", test.targetErr, err)
}
if !reflect.DeepEqual(test.targetProtoReflectValue, val) {
t.Errorf("want: %s, got: %s", test.targetProtoReflectValue, val)
}
})
}
}
func TestJsonSnakeCase(t *testing.T) {
tests := []struct {
camelCase string
snakeCase string
}{
{
"userId", "user_id",
},
{
"user", "user",
},
{
"userIdAndUsername", "user_id_and_username",
},
{
"", "",
},
}
for _, test := range tests {
t.Run(test.camelCase, func(t *testing.T) {
snake := jsonSnakeCase(test.camelCase)
if snake != test.snakeCase {
t.Errorf("want: %s, got: %s", test.snakeCase, snake)
}
})
}
}
func TestIsASCIIUpper(t *testing.T) {
tests := []struct {
b byte
upper bool
}{
{
'A', true,
},
{
'a', false,
},
{
',', false,
},
{
'1', false,
},
{
' ', false,
},
}
for _, test := range tests {
t.Run(string(test.b), func(t *testing.T) {
upper := isASCIIUpper(test.b)
if test.upper != upper {
t.Errorf("'%s' is not ascii upper", string(test.b))
}
})
}
}

@ -0,0 +1,224 @@
package form
import (
"encoding/base64"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/fieldmaskpb"
)
// EncodeValues encode a message into url values.
func EncodeValues(msg interface{}) (url.Values, error) {
if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) {
return url.Values{}, nil
}
if v, ok := msg.(proto.Message); ok {
u := make(url.Values)
err := encodeByField(u, "", v.ProtoReflect())
if err != nil {
return nil, err
}
return u, nil
}
return encoder.Encode(msg)
}
func encodeByField(u url.Values, path string, m protoreflect.Message) (finalErr error) {
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
var (
key string
newPath string
)
if fd.HasJSONName() {
key = fd.JSONName()
} else {
key = fd.TextName()
}
if path == "" {
newPath = key
} else {
newPath = path + "." + key
}
if of := fd.ContainingOneof(); of != nil {
if f := m.WhichOneof(of); f != nil && f != fd {
return true
}
}
switch {
case fd.IsList():
if v.List().Len() > 0 {
list, err := encodeRepeatedField(fd, v.List())
if err != nil {
finalErr = err
return false
}
for _, item := range list {
u.Add(newPath, item)
}
}
case fd.IsMap():
if v.Map().Len() > 0 {
m, err := encodeMapField(fd, v.Map())
if err != nil {
finalErr = err
return false
}
for k, value := range m {
u.Set(fmt.Sprintf("%s[%s]", newPath, k), value)
}
}
case (fd.Kind() == protoreflect.MessageKind) || (fd.Kind() == protoreflect.GroupKind):
value, err := encodeMessage(fd.Message(), v)
if err == nil {
u.Set(newPath, value)
return true
}
if err = encodeByField(u, newPath, v.Message()); err != nil {
finalErr = err
return false
}
default:
value, err := EncodeField(fd, v)
if err != nil {
finalErr = err
return false
}
u.Set(newPath, value)
}
return true
})
return
}
func encodeRepeatedField(fieldDescriptor protoreflect.FieldDescriptor, list protoreflect.List) ([]string, error) {
var values []string
for i := 0; i < list.Len(); i++ {
value, err := EncodeField(fieldDescriptor, list.Get(i))
if err != nil {
return nil, err
}
values = append(values, value)
}
return values, nil
}
func encodeMapField(fieldDescriptor protoreflect.FieldDescriptor, mp protoreflect.Map) (map[string]string, error) {
m := make(map[string]string)
mp.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
key, err := EncodeField(fieldDescriptor.MapValue(), k.Value())
if err != nil {
return false
}
value, err := EncodeField(fieldDescriptor.MapValue(), v)
if err != nil {
return false
}
m[key] = value
return true
})
return m, nil
}
// EncodeField encode proto message filed
func EncodeField(fieldDescriptor protoreflect.FieldDescriptor, value protoreflect.Value) (string, error) {
switch fieldDescriptor.Kind() {
case protoreflect.BoolKind:
return strconv.FormatBool(value.Bool()), nil
case protoreflect.EnumKind:
if fieldDescriptor.Enum().FullName() == "google.protobuf.NullValue" {
return nullStr, nil
}
desc := fieldDescriptor.Enum().Values().ByNumber(value.Enum())
return string(desc.Name()), nil
case protoreflect.StringKind:
return value.String(), nil
case protoreflect.BytesKind:
return base64.URLEncoding.EncodeToString(value.Bytes()), nil
case protoreflect.MessageKind, protoreflect.GroupKind:
return encodeMessage(fieldDescriptor.Message(), value)
default:
return fmt.Sprint(value.Interface()), nil
}
}
// encodeMessage marshals the fields in the given protoreflect.Message.
// If the typeURL is non-empty, then a synthetic "@type" field is injected
// containing the URL as the value.
func encodeMessage(msgDescriptor protoreflect.MessageDescriptor, value protoreflect.Value) (string, error) {
switch msgDescriptor.FullName() {
case timestampMessageFullname:
return marshalTimestamp(value.Message())
case durationMessageFullname:
return marshalDuration(value.Message())
case bytesMessageFullname:
return marshalBytes(value.Message())
case "google.protobuf.DoubleValue", "google.protobuf.FloatValue", "google.protobuf.Int64Value", "google.protobuf.Int32Value",
"google.protobuf.UInt64Value", "google.protobuf.UInt32Value", "google.protobuf.BoolValue", "google.protobuf.StringValue":
fd := msgDescriptor.Fields()
v := value.Message().Get(fd.ByName("value"))
return fmt.Sprint(v.Interface()), nil
case fieldMaskFullName:
m, ok := value.Message().Interface().(*fieldmaskpb.FieldMask)
if !ok || m == nil {
return "", nil
}
for i, v := range m.Paths {
m.Paths[i] = jsonCamelCase(v)
}
return strings.Join(m.Paths, ","), nil
default:
return "", fmt.Errorf("unsupported message type: %q", string(msgDescriptor.FullName()))
}
}
// EncodeFieldMask return field mask name=paths
func EncodeFieldMask(m protoreflect.Message) (query string) {
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if fd.Kind() == protoreflect.MessageKind {
if msg := fd.Message(); msg.FullName() == fieldMaskFullName {
value, err := encodeMessage(msg, v)
if err != nil {
return false
}
if fd.HasJSONName() {
query = fd.JSONName() + "=" + value
} else {
query = fd.TextName() + "=" + value
}
return false
}
}
return true
})
return
}
// JSONCamelCase converts a snake_case identifier to a camelCase identifier,
// according to the protobuf JSON specification.
// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L842
func jsonCamelCase(s string) string {
var b []byte
var wasUnderscore bool
for i := 0; i < len(s); i++ { // proto identifiers are always ASCII
c := s[i]
if c != '_' {
if wasUnderscore && isASCIILower(c) {
c -= 'a' - 'A' // convert to uppercase
}
b = append(b, c)
}
wasUnderscore = c == '_'
}
return string(b)
}
func isASCIILower(c byte) bool {
return 'a' <= c && c <= 'z'
}

@ -0,0 +1,110 @@
package form
import (
"testing"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/go-kratos/kratos/v2/internal/testdata/complex"
)
func TestEncodeValues(t *testing.T) {
in := &complex.Complex{
Id: 2233,
NoOne: "2233",
Simple: &complex.Simple{Component: "5566"},
Simples: []string{"3344", "5566"},
B: true,
Sex: complex.Sex_woman,
Age: 18,
A: 19,
Count: 3,
Price: 11.23,
D: 22.22,
Byte: []byte("123"),
Map: map[string]string{"kratos": "https://go-kratos.dev/", "kratos_start": "https://go-kratos.dev/en/docs/getting-started/start/"},
Timestamp: &timestamppb.Timestamp{Seconds: 20, Nanos: 2},
Duration: &durationpb.Duration{Seconds: 120, Nanos: 22},
Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}},
Double: &wrapperspb.DoubleValue{Value: 12.33},
Float: &wrapperspb.FloatValue{Value: 12.34},
Int64: &wrapperspb.Int64Value{Value: 64},
Int32: &wrapperspb.Int32Value{Value: 32},
Uint64: &wrapperspb.UInt64Value{Value: 64},
Uint32: &wrapperspb.UInt32Value{Value: 32},
Bool: &wrapperspb.BoolValue{Value: false},
String_: &wrapperspb.StringValue{Value: "go-kratos"},
Bytes: &wrapperspb.BytesValue{Value: []byte("123")},
}
query, err := EncodeValues(in)
if err != nil {
t.Fatal(err)
}
want := "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&map%5Bkratos_start%5D=https%3A%2F%2Fgo-kratos.dev%2Fen%2Fdocs%2Fgetting-started%2Fstart%2F&numberOne=2233&price=11.23&sex=woman&simples=3344&simples=5566&string=go-kratos&timestamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" // nolint:lll
if got := query.Encode(); want != got {
t.Errorf("want: %s, got: %s", want, got)
}
}
func TestJsonCamelCase(t *testing.T) {
tests := []struct {
camelCase string
snakeCase string
}{
{
"userId", "user_id",
},
{
"user", "user",
},
{
"userIdAndUsername", "user_id_and_username",
},
{
"", "",
},
}
for _, test := range tests {
t.Run(test.snakeCase, func(t *testing.T) {
camel := jsonCamelCase(test.snakeCase)
if camel != test.camelCase {
t.Errorf("want: %s, got: %s", test.camelCase, camel)
}
})
}
}
func TestIsASCIILower(t *testing.T) {
tests := []struct {
b byte
lower bool
}{
{
'A', false,
},
{
'a', true,
},
{
',', false,
},
{
'1', false,
},
{
' ', false,
},
}
for _, test := range tests {
t.Run(string(test.b), func(t *testing.T) {
lower := isASCIILower(test.b)
if test.lower != lower {
t.Errorf("'%s' is not ascii lower", string(test.b))
}
})
}
}

@ -0,0 +1,94 @@
package form
import (
"encoding/base64"
"fmt"
"math"
"strings"
"time"
"google.golang.org/protobuf/reflect/protoreflect"
)
const (
// timestamp
timestampMessageFullname protoreflect.FullName = "google.protobuf.Timestamp"
maxTimestampSeconds = 253402300799
minTimestampSeconds = -6213559680013
timestampSecondsFieldNumber protoreflect.FieldNumber = 1
timestampNanosFieldNumber protoreflect.FieldNumber = 2
// duration
durationMessageFullname protoreflect.FullName = "google.protobuf.Duration"
secondsInNanos = 999999999
durationSecondsFieldNumber protoreflect.FieldNumber = 1
durationNanosFieldNumber protoreflect.FieldNumber = 2
// bytes
bytesMessageFullname protoreflect.FullName = "google.protobuf.BytesValue"
bytesValueFieldNumber protoreflect.FieldNumber = 1
// google.protobuf.Struct.
structMessageFullname protoreflect.FullName = "google.protobuf.Struct"
structFieldsFieldNumber protoreflect.FieldNumber = 1
fieldMaskFullName protoreflect.FullName = "google.protobuf.FieldMask"
)
func marshalTimestamp(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(timestampSecondsFieldNumber)
fdNanos := fds.ByNumber(timestampNanosFieldNumber)
secsVal := m.Get(fdSeconds)
nanosVal := m.Get(fdNanos)
secs := secsVal.Int()
nanos := nanosVal.Int()
if secs < minTimestampSeconds || secs > maxTimestampSeconds {
return "", fmt.Errorf("%s: seconds out of range %v", timestampMessageFullname, secs)
}
if nanos < 0 || nanos > secondsInNanos {
return "", fmt.Errorf("%s: nanos out of range %v", timestampMessageFullname, nanos)
}
// Uses RFC 3339, where generated output will be Z-normalized and uses 0, 3,
// 6 or 9 fractional digits.
t := time.Unix(secs, nanos).UTC()
x := t.Format("2006-01-02T15:04:05.000000000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, ".000")
return x + "Z", nil
}
func marshalDuration(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdSeconds := fds.ByNumber(durationSecondsFieldNumber)
fdNanos := fds.ByNumber(durationNanosFieldNumber)
secsVal := m.Get(fdSeconds)
nanosVal := m.Get(fdNanos)
secs := secsVal.Int()
nanos := nanosVal.Int()
d := time.Duration(secs) * time.Second
overflow := d/time.Second != time.Duration(secs)
d += time.Duration(nanos) * time.Nanosecond
overflow = overflow || (secs < 0 && nanos < 0 && d > 0)
overflow = overflow || (secs > 0 && nanos > 0 && d < 0)
if overflow {
switch {
case secs < 0:
return time.Duration(math.MinInt64).String(), nil
case secs > 0:
return time.Duration(math.MaxInt64).String(), nil
}
}
return d.String(), nil
}
func marshalBytes(m protoreflect.Message) (string, error) {
fds := m.Descriptor().Fields()
fdBytes := fds.ByNumber(bytesValueFieldNumber)
bytesVal := m.Get(fdBytes)
val := bytesVal.Bytes()
return base64.StdEncoding.EncodeToString(val), nil
}

@ -0,0 +1,95 @@
package form
import (
"encoding/base64"
"testing"
"time"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func TestMarshalTimeStamp(t *testing.T) {
tests := []struct {
input *timestamppb.Timestamp
expect string
}{
{
input: timestamppb.New(time.Date(2022, 1, 2, 3, 4, 5, 6, time.UTC)),
expect: "2022-01-02T03:04:05.000000006Z",
},
{
input: timestamppb.New(time.Date(2022, 13, 1, 13, 61, 61, 100, time.UTC)),
expect: "2023-01-01T14:02:01.000000100Z",
},
}
for _, v := range tests {
got, err := marshalTimestamp(v.input.ProtoReflect())
if err != nil {
t.Fatal(err)
}
if want := v.expect; got != want {
t.Errorf("expect %v, got %v", want, got)
}
}
}
func TestMarshalDuration(t *testing.T) {
tests := []struct {
input *durationpb.Duration
expect string
}{
{
input: durationpb.New(time.Duration(1<<63 - 1)),
expect: "2562047h47m16.854775807s",
},
{
input: durationpb.New(time.Duration(-1 << 63)),
expect: "-2562047h47m16.854775808s",
},
{
input: durationpb.New(100 * time.Second),
expect: "1m40s",
},
{
input: durationpb.New(-100 * time.Second),
expect: "-1m40s",
},
}
for _, v := range tests {
got, err := marshalDuration(v.input.ProtoReflect())
if err != nil {
t.Fatal(err)
}
if want := v.expect; got != want {
t.Errorf("expect %s, got %s", want, got)
}
}
}
func TestMarshalBytes(t *testing.T) {
tests := []struct {
input protoreflect.Message
expect string
}{
{
input: wrapperspb.Bytes([]byte("abc123!?$*&()'-=@~")).ProtoReflect(),
expect: base64.StdEncoding.EncodeToString([]byte("abc123!?$*&()'-=@~")),
},
{
input: wrapperspb.Bytes([]byte("kratos")).ProtoReflect(),
expect: base64.StdEncoding.EncodeToString([]byte("kratos")),
},
}
for _, v := range tests {
got, err := marshalBytes(v.input)
if err != nil {
t.Fatal(err)
}
if want := v.expect; got != want {
t.Errorf("expect %v, got %v", want, got)
}
}
}

@ -0,0 +1,67 @@
package json
import (
"encoding/json"
"git.diulo.com/mogfee/kit/encoding"
"reflect"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// Name is the name registered for the json codec.
const Name = "json"
var (
// MarshalOptions is a configurable JSON format marshaller.
MarshalOptions = protojson.MarshalOptions{
EmitUnpopulated: true,
}
// UnmarshalOptions is a configurable JSON format parser.
UnmarshalOptions = protojson.UnmarshalOptions{
DiscardUnknown: true,
}
)
func init() {
encoding.RegisterCodec(codec{})
}
// codec is a Codec implementation with json.
type codec struct{}
func (codec) Marshal(v interface{}) ([]byte, error) {
switch m := v.(type) {
case json.Marshaler:
return m.MarshalJSON()
case proto.Message:
return MarshalOptions.Marshal(m)
default:
return json.Marshal(m)
}
}
func (codec) Unmarshal(data []byte, v interface{}) error {
switch m := v.(type) {
case json.Unmarshaler:
return m.UnmarshalJSON(data)
case proto.Message:
return UnmarshalOptions.Unmarshal(data, m)
default:
rv := reflect.ValueOf(v)
for rv := rv; rv.Kind() == reflect.Ptr; {
if rv.IsNil() {
rv.Set(reflect.New(rv.Type().Elem()))
}
rv = rv.Elem()
}
if m, ok := reflect.Indirect(rv).Interface().(proto.Message); ok {
return UnmarshalOptions.Unmarshal(data, m)
}
return json.Unmarshal(data, m)
}
}
func (codec) Name() string {
return Name
}

@ -0,0 +1,147 @@
package json
import (
"encoding/json"
"reflect"
"strings"
"testing"
testData "github.com/go-kratos/kratos/v2/internal/testdata/encoding"
)
type testEmbed struct {
Level1a int `json:"a"`
Level1b int `json:"b"`
Level1c int `json:"c"`
}
type testMessage struct {
Field1 string `json:"a"`
Field2 string `json:"b"`
Field3 string `json:"c"`
Embed *testEmbed `json:"embed,omitempty"`
}
type mock struct {
value int
}
const (
Unknown = iota
Gopher
Zebra
)
func (a *mock) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
switch strings.ToLower(s) {
default:
a.value = Unknown
case "gopher":
a.value = Gopher
case "zebra":
a.value = Zebra
}
return nil
}
func (a *mock) MarshalJSON() ([]byte, error) {
var s string
switch a.value {
default:
s = "unknown"
case Gopher:
s = "gopher"
case Zebra:
s = "zebra"
}
return json.Marshal(s)
}
func TestJSON_Marshal(t *testing.T) {
tests := []struct {
input interface{}
expect string
}{
{
input: &testMessage{},
expect: `{"a":"","b":"","c":""}`,
},
{
input: &testMessage{Field1: "a", Field2: "b", Field3: "c"},
expect: `{"a":"a","b":"b","c":"c"}`,
},
{
input: &testData.TestModel{Id: 1, Name: "go-kratos", Hobby: []string{"1", "2"}},
expect: `{"id":"1","name":"go-kratos","hobby":["1","2"],"attrs":{}}`,
},
{
input: &mock{value: Gopher},
expect: `"gopher"`,
},
}
for _, v := range tests {
data, err := (codec{}).Marshal(v.input)
if err != nil {
t.Errorf("marshal(%#v): %s", v.input, err)
}
if got, want := string(data), v.expect; strings.ReplaceAll(got, " ", "") != want {
if strings.Contains(want, "\n") {
t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", v.input, got, want)
} else {
t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want)
}
}
}
}
func TestJSON_Unmarshal(t *testing.T) {
p := testMessage{}
p2 := testData.TestModel{}
p3 := &testData.TestModel{}
p4 := &mock{}
tests := []struct {
input string
expect interface{}
}{
{
input: `{"a":"","b":"","c":""}`,
expect: &testMessage{},
},
{
input: `{"a":"a","b":"b","c":"c"}`,
expect: &p,
},
{
input: `{"id":"1","name":"go-kratos","hobby":["1","2"],"attrs":{}}`,
expect: &p2,
},
{
input: `{"id":1,"name":"go-kratos","hobby":["1","2"]}`,
expect: &p3,
},
{
input: `"zebra"`,
expect: p4,
},
}
for _, v := range tests {
want := []byte(v.input)
err := (codec{}).Unmarshal(want, v.expect)
if err != nil {
t.Errorf("marshal(%#v): %s", v.input, err)
}
got, err := codec{}.Marshal(v.expect)
if err != nil {
t.Errorf("marshal(%#v): %s", v.input, err)
}
if !reflect.DeepEqual(strings.ReplaceAll(string(got), " ", ""), strings.ReplaceAll(string(want), " ", "")) {
t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want)
}
}
}

@ -0,0 +1,31 @@
// Package proto defines the protobuf codec. Importing this package will
// register the codec.
package proto
import (
"google.golang.org/protobuf/proto"
"github.com/go-kratos/kratos/v2/encoding"
)
// Name is the name registered for the proto compressor.
const Name = "proto"
func init() {
encoding.RegisterCodec(codec{})
}
// codec is a Codec implementation with protobuf. It is the default codec for Transport.
type codec struct{}
func (codec) Marshal(v interface{}) ([]byte, error) {
return proto.Marshal(v.(proto.Message))
}
func (codec) Unmarshal(data []byte, v interface{}) error {
return proto.Unmarshal(data, v.(proto.Message))
}
func (codec) Name() string {
return Name
}

@ -0,0 +1,46 @@
package proto
import (
"reflect"
"testing"
testData "github.com/go-kratos/kratos/v2/internal/testdata/encoding"
)
func TestName(t *testing.T) {
c := new(codec)
if !reflect.DeepEqual(c.Name(), "proto") {
t.Errorf("no expect float_key value: %v, but got: %v", c.Name(), "proto")
}
}
func TestCodec(t *testing.T) {
c := new(codec)
model := testData.TestModel{
Id: 1,
Name: "kratos",
Hobby: []string{"study", "eat", "play"},
}
m, err := c.Marshal(&model)
if err != nil {
t.Errorf("Marshal() should be nil, but got %s", err)
}
var res testData.TestModel
err = c.Unmarshal(m, &res)
if err != nil {
t.Errorf("Unmarshal() should be nil, but got %s", err)
}
if !reflect.DeepEqual(res.Id, model.Id) {
t.Errorf("ID should be %d, but got %d", res.Id, model.Id)
}
if !reflect.DeepEqual(res.Name, model.Name) {
t.Errorf("Name should be %s, but got %s", res.Name, model.Name)
}
if !reflect.DeepEqual(res.Hobby, model.Hobby) {
t.Errorf("Hobby should be %s, but got %s", res.Hobby, model.Hobby)
}
}

@ -0,0 +1,29 @@
package xml
import (
"encoding/xml"
"github.com/go-kratos/kratos/v2/encoding"
)
// Name is the name registered for the xml codec.
const Name = "xml"
func init() {
encoding.RegisterCodec(codec{})
}
// codec is a Codec implementation with xml.
type codec struct{}
func (codec) Marshal(v interface{}) ([]byte, error) {
return xml.Marshal(v)
}
func (codec) Unmarshal(data []byte, v interface{}) error {
return xml.Unmarshal(data, v)
}
func (codec) Name() string {
return Name
}

@ -0,0 +1,117 @@
package xml
import (
"reflect"
"strings"
"testing"
)
type Plain struct {
V interface{}
}
type NestedOrder struct {
XMLName struct{} `xml:"result"`
Field1 string `xml:"parent>c"`
Field2 string `xml:"parent>b"`
Field3 string `xml:"parent>a"`
}
func TestCodec_Marshal(t *testing.T) {
tests := []struct {
Value interface{}
ExpectXML string
}{
// Test value types
{Value: &Plain{true}, ExpectXML: `<Plain><V>true</V></Plain>`},
{Value: &Plain{false}, ExpectXML: `<Plain><V>false</V></Plain>`},
{Value: &Plain{42}, ExpectXML: `<Plain><V>42</V></Plain>`},
{
Value: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"},
ExpectXML: `<result>` +
`<parent>` +
`<c>C</c>` +
`<b>B</b>` +
`<a>A</a>` +
`</parent>` +
`</result>`,
},
}
for _, tt := range tests {
data, err := (codec{}).Marshal(tt.Value)
if err != nil {
t.Errorf("marshal(%#v): %s", tt.Value, err)
}
if got, want := string(data), tt.ExpectXML; got != want {
if strings.Contains(want, "\n") {
t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", tt.Value, got, want)
} else {
t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", tt.Value, got, want)
}
}
}
}
func TestCodec_Unmarshal(t *testing.T) {
tests := []struct {
want interface{}
InputXML string
}{
{
want: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"},
InputXML: `<result>` +
`<parent>` +
`<c>C</c>` +
`<b>B</b>` +
`<a>A</a>` +
`</parent>` +
`</result>`,
},
}
for _, tt := range tests {
vt := reflect.TypeOf(tt.want)
dest := reflect.New(vt.Elem()).Interface()
data := []byte(tt.InputXML)
err := (codec{}).Unmarshal(data, dest)
if err != nil {
t.Errorf("unmarshal(%#v, %#v): %s", tt.InputXML, dest, err)
}
if got, want := dest, tt.want; !reflect.DeepEqual(got, want) {
t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", tt.InputXML, got, want)
}
}
}
func TestCodec_NilUnmarshal(t *testing.T) {
tests := []struct {
want interface{}
InputXML string
}{
{
want: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"},
InputXML: `<result>` +
`<parent>` +
`<c>C</c>` +
`<b>B</b>` +
`<a>A</a>` +
`</parent>` +
`</result>`,
},
}
for _, tt := range tests {
s := struct {
A string `xml:"a"`
B *NestedOrder
}{A: "a"}
data := []byte(tt.InputXML)
err := (codec{}).Unmarshal(data, &s.B)
if err != nil {
t.Errorf("unmarshal(%#v, %#v): %s", tt.InputXML, s.B, err)
}
if got, want := s.B, tt.want; !reflect.DeepEqual(got, want) {
t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", tt.InputXML, got, want)
}
}
}

@ -0,0 +1,29 @@
package yaml
import (
"gopkg.in/yaml.v3"
"github.com/go-kratos/kratos/v2/encoding"
)
// Name is the name registered for the yaml codec.
const Name = "yaml"
func init() {
encoding.RegisterCodec(codec{})
}
// codec is a Codec implementation with yaml.
type codec struct{}
func (codec) Marshal(v interface{}) ([]byte, error) {
return yaml.Marshal(v)
}
func (codec) Unmarshal(data []byte, v interface{}) error {
return yaml.Unmarshal(data, v)
}
func (codec) Name() string {
return Name
}

@ -0,0 +1,104 @@
package yaml
import (
"math"
"reflect"
"testing"
)
func TestCodec_Unmarshal(t *testing.T) {
tests := []struct {
data string
value interface{}
}{
{
"",
(*struct{})(nil),
},
{
"{}", &struct{}{},
},
{
"v: hi",
map[string]string{"v": "hi"},
},
{
"v: hi", map[string]interface{}{"v": "hi"},
},
{
"v: true",
map[string]string{"v": "true"},
},
{
"v: true",
map[string]interface{}{"v": true},
},
{
"v: 10",
map[string]interface{}{"v": 10},
},
{
"v: 0b10",
map[string]interface{}{"v": 2},
},
{
"v: 0xA",
map[string]interface{}{"v": 10},
},
{
"v: 4294967296",
map[string]int64{"v": 4294967296},
},
{
"v: 0.1",
map[string]interface{}{"v": 0.1},
},
{
"v: .1",
map[string]interface{}{"v": 0.1},
},
{
"v: .Inf",
map[string]interface{}{"v": math.Inf(+1)},
},
{
"v: -.Inf",
map[string]interface{}{"v": math.Inf(-1)},
},
{
"v: -10",
map[string]interface{}{"v": -10},
},
{
"v: -.1",
map[string]interface{}{"v": -0.1},
},
}
for _, tt := range tests {
v := reflect.ValueOf(tt.value).Type()
value := reflect.New(v)
err := (codec{}).Unmarshal([]byte(tt.data), value.Interface())
if err != nil {
t.Fatalf("(codec{}).Unmarshal should not return err")
}
}
spec := struct {
A string
B map[string]interface{}
}{A: "a"}
err := (codec{}).Unmarshal([]byte("v: hi"), &spec.B)
if err != nil {
t.Fatalf("(codec{}).Unmarshal should not return err")
}
}
func TestCodec_Marshal(t *testing.T) {
value := map[string]string{"v": "hi"}
got, err := (codec{}).Marshal(value)
if err != nil {
t.Fatalf("should not return err")
}
if string(got) != "v: hi\n" {
t.Fatalf("want \"v: hi\n\" return \"%s\"", string(got))
}
}

@ -1,20 +1,93 @@
package main
import (
"context"
"fmt"
"git.diulo.com/mogfee/protoc-gen-kit/example/service"
"git.diulo.com/mogfee/protoc-gen-kit/log"
"git.diulo.com/mogfee/protoc-gen-kit/middleware"
user "git.diulo.com/mogfee/protoc-gen-kit/proto/v1"
"github.com/gin-gonic/gin"
"git.diulo.com/mogfee/kit"
"git.diulo.com/mogfee/kit/errors"
"git.diulo.com/mogfee/kit/example/service"
"git.diulo.com/mogfee/kit/middleware"
"git.diulo.com/mogfee/kit/middleware/logging"
user "git.diulo.com/mogfee/kit/proto/v1"
"git.diulo.com/mogfee/kit/transport/http"
"strings"
)
func main() {
gin.SetMode(gin.ReleaseMode)
app := gin.Default()
srv := service.UserService{}
logger := log.With(log.DefaultLogger)
user.RegisterUserHandler(app, &srv, middleware.Logger(logger), middleware.Validate())
fmt.Println("http://localhost:8888")
app.Run("localhost:8888")
hs := http.NewServer(
http.Address("localhost:9093"),
http.Middleware(
logging.Server(),
func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, a any) (any, error) {
if v, ok := a.(interface {
Validate() error
}); ok {
if err := v.Validate(); err != nil {
if a, ok := err.(interface {
Field() string
Reason() string
}); ok {
field := a.Field()
return nil, errors.New(400, "InvalidArgument", "").WithMetadata(map[string]string{
strings.ToLower(field[0:1]) + field[1:]: a.Reason(),
})
}
//if vv, ok := err.(ValidateError); ok {
// s.Result(http.StatusBadRequest, "InvalidArgument", vv.ErrorName(), map[string]string{
// lcfirst(vv.Field()): vv.Reason(),
// })
// return
//}
}
}
return handler(ctx, a)
}
},
),
)
route := hs.Route("/")
route.GET("/api/abc", func(ctx http.Context) error {
in := UserAddRequest{Name: "tom"}
http.SetOperation(ctx, "/api/abc")
h := ctx.Middleware(func(ctx context.Context, a any) (any, error) {
return AddUser(ctx, a.(*UserAddRequest))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply, _ := out.(*UserAddResponse)
return ctx.Result(200, reply)
})
user.RegisterUserHTTPServer(hs, &service.UserService{})
app := kit.New(
kit.Name("kit-server"),
kit.Version("v1.0"),
kit.Server(hs),
)
fmt.Println(app.Run())
fmt.Println(app.Stop())
}
func ma1in() {
//gin.SetMode(gin.ReleaseMode)
//app := gin.Default()
//srv := service.UserService{}
//logger := log.With(log.DefaultLogger)
//user.RegisterUserHandler(app, &srv, middleware.Logger(logger), middleware.Validate())
//fmt.Println("http://localhost:8888")
//app.Run("localhost:8888")
}
type UserAddRequest struct {
Name string
}
type UserAddResponse struct {
Id string
}
func AddUser(ctx context.Context, request *UserAddRequest) (*UserAddResponse, error) {
return &UserAddResponse{Id: request.Name}, errors.New(500, "xx", "")
}

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

@ -0,0 +1,34 @@
package endpoint
import (
"net/url"
)
// NewEndpoint new an Endpoint URL.
func NewEndpoint(scheme, host string) *url.URL {
return &url.URL{Scheme: scheme, Host: host}
}
// ParseEndpoint parses an Endpoint URL.
func ParseEndpoint(endpoints []string, scheme string) (string, error) {
for _, e := range endpoints {
u, err := url.Parse(e)
if err != nil {
return "", err
}
if u.Scheme == scheme {
return u.Host, nil
}
}
return "", nil
}
// Scheme is the scheme of endpoint url.
// examples: scheme="http",isSecure=true get "https"
func Scheme(scheme string, isSecure bool) string {
if isSecure {
return scheme + "s"
}
return scheme
}

@ -0,0 +1,126 @@
package endpoint
import (
"net/url"
"reflect"
"testing"
)
func TestNewEndpoint(t *testing.T) {
type args struct {
scheme string
host string
}
tests := []struct {
name string
args args
want *url.URL
}{
{
name: "https://github.com/go-kratos/kratos/",
args: args{"https", "github.com/go-kratos/kratos/"},
want: &url.URL{Scheme: "https", Host: "github.com/go-kratos/kratos/"},
},
{
name: "https://go-kratos.dev/",
args: args{"https", "go-kratos.dev/"},
want: &url.URL{Scheme: "https", Host: "go-kratos.dev/"},
},
{
name: "https://www.google.com/",
args: args{"https", "www.google.com/"},
want: &url.URL{Scheme: "https", Host: "www.google.com/"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewEndpoint(tt.args.scheme, tt.args.host); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewEndpoint() = %v, want %v", got, tt.want)
}
})
}
}
func TestParseEndpoint(t *testing.T) {
type args struct {
endpoints []string
scheme string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "kratos",
args: args{endpoints: []string{"https://github.com/go-kratos/kratos"}, scheme: "https"},
want: "github.com",
wantErr: false,
},
{
name: "test",
args: args{endpoints: []string{"http://go-kratos.dev/"}, scheme: "https"},
want: "",
wantErr: false,
},
{
name: "localhost:8080",
args: args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpcs"},
want: "localhost:8080",
wantErr: false,
},
{
name: "localhost:8081",
args: args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpc"},
want: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseEndpoint(tt.args.endpoints, tt.args.scheme)
if (err != nil) != tt.wantErr {
t.Errorf("ParseEndpoint() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ParseEndpoint() got = %v, want %v", got, tt.want)
}
})
}
}
func TestSchema(t *testing.T) {
tests := []struct {
schema string
secure bool
want string
}{
{
schema: "http",
secure: true,
want: "https",
},
{
schema: "http",
secure: false,
want: "http",
},
{
schema: "grpc",
secure: true,
want: "grpcs",
},
{
schema: "grpc",
secure: false,
want: "grpc",
},
}
for _, tt := range tests {
if got := Scheme(tt.schema, tt.secure); got != tt.want {
t.Errorf("Schema() = %v, want %v", got, tt.want)
}
}
}

@ -0,0 +1,92 @@
package host
import (
"fmt"
"net"
"strconv"
)
// ExtractHostPort from address
func ExtractHostPort(addr string) (host string, port uint64, err error) {
var ports string
host, ports, err = net.SplitHostPort(addr)
if err != nil {
return
}
port, err = strconv.ParseUint(ports, 10, 16) //nolint:gomnd
return
}
func isValidIP(addr string) bool {
ip := net.ParseIP(addr)
return ip.IsGlobalUnicast() && !ip.IsInterfaceLocalMulticast()
}
// Port return a real port.
func Port(lis net.Listener) (int, bool) {
if addr, ok := lis.Addr().(*net.TCPAddr); ok {
return addr.Port, true
}
return 0, false
}
// Extract returns a private addr and port.
func Extract(hostPort string, lis net.Listener) (string, error) {
addr, port, err := net.SplitHostPort(hostPort)
if err != nil && lis == nil {
return "", err
}
if lis != nil {
p, ok := Port(lis)
if !ok {
return "", fmt.Errorf("failed to extract port: %v", lis.Addr())
}
port = strconv.Itoa(p)
}
if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]" && addr != "::") {
return net.JoinHostPort(addr, port), nil
}
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
minIndex := int(^uint(0) >> 1)
ips := make([]net.IP, 0)
for _, iface := range ifaces {
if (iface.Flags & net.FlagUp) == 0 {
continue
}
if iface.Index >= minIndex && len(ips) != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for i, rawAddr := range addrs {
var ip net.IP
switch addr := rawAddr.(type) {
case *net.IPAddr:
ip = addr.IP
case *net.IPNet:
ip = addr.IP
default:
continue
}
if isValidIP(ip.String()) {
minIndex = iface.Index
if i == 0 {
ips = make([]net.IP, 0, 1)
}
ips = append(ips, ip)
if ip.To4() != nil {
break
}
}
}
}
if len(ips) != 0 {
return net.JoinHostPort(ips[len(ips)-1].String(), port), nil
}
return "", nil
}

@ -0,0 +1,149 @@
package host
import (
"net"
"reflect"
"testing"
)
func TestValidIP(t *testing.T) {
tests := []struct {
addr string
expect bool
}{
{"127.0.0.1", false},
{"255.255.255.255", false},
{"0.0.0.0", false},
{"localhost", false},
{"10.1.0.1", true},
{"172.16.0.1", true},
{"192.168.1.1", true},
{"8.8.8.8", true},
{"1.1.1.1", true},
{"9.255.255.255", true},
{"10.0.0.0", true},
{"10.255.255.255", true},
{"11.0.0.0", true},
{"172.15.255.255", true},
{"172.16.0.0", true},
{"172.16.255.255", true},
{"172.23.18.255", true},
{"172.31.255.255", true},
{"172.31.0.0", true},
{"172.32.0.0", true},
{"192.167.255.255", true},
{"192.168.0.0", true},
{"192.168.255.255", true},
{"192.169.0.0", true},
{"fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true},
{"fc00::", true},
{"fcff:1200:0:44::", true},
{"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true},
{"fe00::", true},
}
for _, test := range tests {
t.Run(test.addr, func(t *testing.T) {
res := isValidIP(test.addr)
if res != test.expect {
t.Fatalf("expected %t got %t", test.expect, res)
}
})
}
}
func TestExtract(t *testing.T) {
tests := []struct {
addr string
expect string
}{
{"127.0.0.1:80", "127.0.0.1:80"},
{"10.0.0.1:80", "10.0.0.1:80"},
{"172.16.0.1:80", "172.16.0.1:80"},
{"192.168.1.1:80", "192.168.1.1:80"},
{"0.0.0.0:80", ""},
{"[::]:80", ""},
{":80", ""},
}
for _, test := range tests {
t.Run(test.addr, func(t *testing.T) {
res, err := Extract(test.addr, nil)
if err != nil {
t.Fatal(err)
}
if res != test.expect && (test.expect == "" && test.addr == test.expect) {
t.Fatalf("expected %s got %s", test.expect, res)
}
})
}
lis, err := net.Listen("tcp", ":12345")
if err != nil {
t.Errorf("expected: %v got %v", nil, err)
}
res, err := Extract("", lis)
if err != nil {
t.Errorf("expected: %v got %v", nil, err)
}
expect, err := Extract(lis.Addr().String(), nil)
if err != nil {
t.Errorf("expected: %v got %v", nil, err)
}
if !reflect.DeepEqual(res, expect) {
t.Errorf("expected %s got %s", expect, res)
}
}
func TestExtract2(t *testing.T) {
addr := "localhost:9001"
lis, err := net.Listen("tcp", addr)
if err != nil {
t.Errorf("expected: %v got %v", nil, err)
}
res, err := Extract(addr, lis)
if err != nil {
t.Errorf("expected: %v got %v", nil, err)
}
if !reflect.DeepEqual(res, "localhost:9001") {
t.Errorf("expected %s got %s", "localhost:9001", res)
}
}
func TestPort(t *testing.T) {
lis, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
}
port, ok := Port(lis)
if !ok || port == 0 {
t.Fatalf("expected: %s got %d", lis.Addr().String(), port)
}
}
func TestExtractHostPort(t *testing.T) {
host, port, err := ExtractHostPort("127.0.0.1:8000")
if err != nil {
t.Fatalf("expected: %v got %v", nil, err)
}
t.Logf("host port: %s, %d", host, port)
host, port, err = ExtractHostPort("www.bilibili.com:80")
if err != nil {
t.Fatalf("expected: %v got %v", nil, err)
}
t.Logf("host port: %s, %d", host, port)
host, port, err = ExtractHostPort("consul://2/33")
if err == nil {
t.Fatalf("expected: not nil got %v", nil)
}
t.Logf("host port: %s, %d", host, port)
}
func TestIpIsUp(t *testing.T) {
interfaces, err := net.Interfaces()
if err != nil {
t.Fail()
}
for i := range interfaces {
println(interfaces[i].Name, interfaces[i].Flags&net.FlagUp)
}
}

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

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

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

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

@ -0,0 +1,14 @@
package logging
import (
"context"
"git.diulo.com/mogfee/kit/middleware"
)
func Server() middleware.Middleware {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, a any) (any, error) {
return handler(ctx, a)
}
}
}

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

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

Binary file not shown.

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

@ -68,10 +68,10 @@ var (
//
// Example:
//
// service Foo {
// option (google.api.default_host) = "foo.googleapi.com";
// ...
// }
// service Foo {
// option (google.api.default_host) = "foo.googleapi.com";
// ...
// }
//
// optional string default_host = 1049;
E_DefaultHost = &file_google_api_client_proto_extTypes[0]
@ -79,22 +79,22 @@ var (
//
// Example:
//
// service Foo {
// option (google.api.oauth_scopes) = \
// "https://www.googleapis.com/auth/cloud-platform";
// ...
// }
// service Foo {
// option (google.api.oauth_scopes) = \
// "https://www.googleapis.com/auth/cloud-platform";
// ...
// }
//
// If there is more than one scope, use a comma-separated string:
//
// Example:
//
// service Foo {
// option (google.api.oauth_scopes) = \
// "https://www.googleapis.com/auth/cloud-platform,"
// "https://www.googleapis.com/auth/monitoring";
// ...
// }
// service Foo {
// option (google.api.oauth_scopes) = \
// "https://www.googleapis.com/auth/cloud-platform,"
// "https://www.googleapis.com/auth/monitoring";
// ...
// }
//
// optional string oauth_scopes = 1050;
E_OauthScopes = &file_google_api_client_proto_extTypes[1]
@ -116,26 +116,26 @@ var (
//
// For example, the proto RPC and annotation:
//
// rpc CreateSubscription(CreateSubscriptionRequest)
// returns (Subscription) {
// option (google.api.method_signature) = "name,topic";
// }
// rpc CreateSubscription(CreateSubscriptionRequest)
// returns (Subscription) {
// option (google.api.method_signature) = "name,topic";
// }
//
// Would add the following Java overload (in addition to the method accepting
// the request object):
//
// public final Subscription createSubscription(String name, String topic)
// public final Subscription createSubscription(String name, String topic)
//
// The following backwards-compatibility guidelines apply:
//
// * Adding this annotation to an unannotated method is backwards
// - Adding this annotation to an unannotated method is backwards
// compatible.
// * Adding this annotation to a method which already has existing
// - Adding this annotation to a method which already has existing
// method signature annotations is backwards compatible if and only if
// the new method signature annotation is last in the sequence.
// * Modifying or removing an existing method signature annotation is
// - Modifying or removing an existing method signature annotation is
// a breaking change.
// * Re-ordering existing method signature annotations is a breaking
// - Re-ordering existing method signature annotations is a breaking
// change.
//
// repeated string method_signature = 1051;

@ -134,13 +134,13 @@ var (
//
// Examples:
//
// string name = 1 [(google.api.field_behavior) = REQUIRED];
// State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
// google.protobuf.Duration ttl = 1
// [(google.api.field_behavior) = INPUT_ONLY];
// google.protobuf.Timestamp expire_time = 1
// [(google.api.field_behavior) = OUTPUT_ONLY,
// (google.api.field_behavior) = IMMUTABLE];
// string name = 1 [(google.api.field_behavior) = REQUIRED];
// State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
// google.protobuf.Duration ttl = 1
// [(google.api.field_behavior) = INPUT_ONLY];
// google.protobuf.Timestamp expire_time = 1
// [(google.api.field_behavior) = OUTPUT_ONLY,
// (google.api.field_behavior) = IMMUTABLE];
//
// repeated google.api.FieldBehavior field_behavior = 1052;
E_FieldBehavior = &file_google_api_field_behavior_proto_extTypes[0]

@ -126,19 +126,19 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
//
// Example:
//
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get: "/v1/{name=messages/*}"
// };
// }
// }
// message GetMessageRequest {
// string name = 1; // Mapped to URL path.
// }
// message Message {
// string text = 1; // The resource content.
// }
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get: "/v1/{name=messages/*}"
// };
// }
// }
// message GetMessageRequest {
// string name = 1; // Mapped to URL path.
// }
// message Message {
// string text = 1; // The resource content.
// }
//
// This enables an HTTP REST to gRPC mapping as below:
//
@ -150,21 +150,21 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
// automatically become HTTP query parameters if there is no HTTP request body.
// For example:
//
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get:"/v1/messages/{message_id}"
// };
// }
// }
// message GetMessageRequest {
// message SubMessage {
// string subfield = 1;
// }
// string message_id = 1; // Mapped to URL path.
// int64 revision = 2; // Mapped to URL query parameter `revision`.
// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`.
// }
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get:"/v1/messages/{message_id}"
// };
// }
// }
// message GetMessageRequest {
// message SubMessage {
// string subfield = 1;
// }
// string message_id = 1; // Mapped to URL path.
// int64 revision = 2; // Mapped to URL query parameter `revision`.
// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`.
// }
//
// This enables a HTTP JSON to RPC mapping as below:
//
@ -185,18 +185,18 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
// specifies the mapping. Consider a REST update method on the
// message resource collection:
//
// service Messaging {
// rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
// option (google.api.http) = {
// patch: "/v1/messages/{message_id}"
// body: "message"
// };
// }
// }
// message UpdateMessageRequest {
// string message_id = 1; // mapped to the URL
// Message message = 2; // mapped to the body
// }
// service Messaging {
// rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
// option (google.api.http) = {
// patch: "/v1/messages/{message_id}"
// body: "message"
// };
// }
// }
// message UpdateMessageRequest {
// string message_id = 1; // mapped to the URL
// Message message = 2; // mapped to the body
// }
//
// The following HTTP JSON to RPC mapping is enabled, where the
// representation of the JSON in the request body is determined by
@ -212,19 +212,18 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
// request body. This enables the following alternative definition of
// the update method:
//
// service Messaging {
// rpc UpdateMessage(Message) returns (Message) {
// option (google.api.http) = {
// patch: "/v1/messages/{message_id}"
// body: "*"
// };
// }
// }
// message Message {
// string message_id = 1;
// string text = 2;
// }
//
// service Messaging {
// rpc UpdateMessage(Message) returns (Message) {
// option (google.api.http) = {
// patch: "/v1/messages/{message_id}"
// body: "*"
// };
// }
// }
// message Message {
// string message_id = 1;
// string text = 2;
// }
//
// The following HTTP JSON to RPC mapping is enabled:
//
@ -242,20 +241,20 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
// It is possible to define multiple HTTP methods for one RPC by using
// the `additional_bindings` option. Example:
//
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get: "/v1/messages/{message_id}"
// additional_bindings {
// get: "/v1/users/{user_id}/messages/{message_id}"
// }
// };
// }
// }
// message GetMessageRequest {
// string message_id = 1;
// string user_id = 2;
// }
// service Messaging {
// rpc GetMessage(GetMessageRequest) returns (Message) {
// option (google.api.http) = {
// get: "/v1/messages/{message_id}"
// additional_bindings {
// get: "/v1/users/{user_id}/messages/{message_id}"
// }
// };
// }
// }
// message GetMessageRequest {
// string message_id = 1;
// string user_id = 2;
// }
//
// This enables the following two alternative HTTP JSON to RPC mappings:
//
@ -267,15 +266,15 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
//
// ## Rules for HTTP mapping
//
// 1. Leaf request fields (recursive expansion nested messages in the request
// message) are classified into three categories:
// - Fields referred by the path template. They are passed via the URL path.
// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP
// request body.
// - All other fields are passed via the URL query parameters, and the
// parameter name is the field path in the request message. A repeated
// field can be represented as multiple query parameters under the same
// name.
// 1. Leaf request fields (recursive expansion nested messages in the request
// message) are classified into three categories:
// - Fields referred by the path template. They are passed via the URL path.
// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP
// request body.
// - All other fields are passed via the URL query parameters, and the
// parameter name is the field path in the request message. A repeated
// field can be represented as multiple query parameters under the same
// name.
// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields
// are passed via URL path and HTTP request body.
// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all
@ -283,12 +282,12 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
//
// ### Path template syntax
//
// Template = "/" Segments [ Verb ] ;
// Segments = Segment { "/" Segment } ;
// Segment = "*" | "**" | LITERAL | Variable ;
// Variable = "{" FieldPath [ "=" Segments ] "}" ;
// FieldPath = IDENT { "." IDENT } ;
// Verb = ":" LITERAL ;
// Template = "/" Segments [ Verb ] ;
// Segments = Segment { "/" Segment } ;
// Segment = "*" | "**" | LITERAL | Variable ;
// Variable = "{" FieldPath [ "=" Segments ] "}" ;
// FieldPath = IDENT { "." IDENT } ;
// Verb = ":" LITERAL ;
//
// The syntax `*` matches a single URL path segment. The syntax `**` matches
// zero or more URL path segments, which must be the last part of the URL path
@ -337,11 +336,11 @@ func (x *Http) GetFullyDecodeReservedExpansion() bool {
//
// Example:
//
// http:
// rules:
// # Selects a gRPC method and applies HttpRule to it.
// - selector: example.v1.Messaging.GetMessage
// get: /v1/messages/{message_id}/{sub.subfield}
// http:
// rules:
// # Selects a gRPC method and applies HttpRule to it.
// - selector: example.v1.Messaging.GetMessage
// get: /v1/messages/{message_id}/{sub.subfield}
//
// ## Special notes
//
@ -384,6 +383,7 @@ type HttpRule struct {
// can be defined using the 'custom' field.
//
// Types that are assignable to Pattern:
//
// *HttpRule_Get
// *HttpRule_Put
// *HttpRule_Post

@ -39,7 +39,6 @@ const (
// payload formats that can't be represented as JSON, such as raw binary or
// an HTML page.
//
//
// This message can be used both in streaming and non-streaming API methods in
// the request as well as the response.
//
@ -49,28 +48,28 @@ const (
//
// Example:
//
// message GetResourceRequest {
// // A unique request id.
// string request_id = 1;
// message GetResourceRequest {
// // A unique request id.
// string request_id = 1;
//
// // The raw HTTP body is bound to this field.
// google.api.HttpBody http_body = 2;
// }
// // The raw HTTP body is bound to this field.
// google.api.HttpBody http_body = 2;
// }
//
// service ResourceService {
// rpc GetResource(GetResourceRequest) returns (google.api.HttpBody);
// rpc UpdateResource(google.api.HttpBody) returns
// (google.protobuf.Empty);
// }
// service ResourceService {
// rpc GetResource(GetResourceRequest) returns (google.api.HttpBody);
// rpc UpdateResource(google.api.HttpBody) returns
// (google.protobuf.Empty);
// }
//
// Example with streaming methods:
//
// service CaldavService {
// rpc GetCalendar(stream google.api.HttpBody)
// returns (stream google.api.HttpBody);
// rpc UpdateCalendar(stream google.api.HttpBody)
// returns (stream google.api.HttpBody);
// }
// service CaldavService {
// rpc GetCalendar(stream google.api.HttpBody)
// returns (stream google.api.HttpBody);
// rpc UpdateCalendar(stream google.api.HttpBody)
// returns (stream google.api.HttpBody);
// }
//
// Use of this type only changes how the request and response bodies are
// handled, all other features will continue to work unchanged.

@ -40,8 +40,7 @@
// It is functionally a tuple of the full name of the remote message type and
// the serialized bytes of the remote message value.
//
//
// Constructing an Any
// # Constructing an Any
//
// An Any message containing another message value is constructed using New:
//
@ -51,8 +50,7 @@
// }
// ... // make use of any
//
//
// Unmarshaling an Any
// # Unmarshaling an Any
//
// With a populated Any message, the underlying message can be serialized into
// a remote concrete message value in a few ways.
@ -98,8 +96,7 @@
// listed in the case clauses are linked into the Go binary and therefore also
// registered in the global registry.
//
//
// Type checking an Any
// # Type checking an Any
//
// In order to type check whether an Any message represents some other message,
// then use the MessageIs method:
@ -118,7 +115,6 @@
// }
// ... // make use of m
// }
//
package anypb
import (
@ -146,45 +142,45 @@ const (
//
// Example 1: Pack and unpack a message in C++.
//
// Foo foo = ...;
// Any any;
// any.PackFrom(foo);
// ...
// if (any.UnpackTo(&foo)) {
// ...
// }
// Foo foo = ...;
// Any any;
// any.PackFrom(foo);
// ...
// if (any.UnpackTo(&foo)) {
// ...
// }
//
// Example 2: Pack and unpack a message in Java.
//
// Foo foo = ...;
// Any any = Any.pack(foo);
// ...
// if (any.is(Foo.class)) {
// foo = any.unpack(Foo.class);
// }
// Foo foo = ...;
// Any any = Any.pack(foo);
// ...
// if (any.is(Foo.class)) {
// foo = any.unpack(Foo.class);
// }
//
// Example 3: Pack and unpack a message in Python.
//
// foo = Foo(...)
// any = Any()
// any.Pack(foo)
// ...
// if any.Is(Foo.DESCRIPTOR):
// any.Unpack(foo)
// ...
// foo = Foo(...)
// any = Any()
// any.Pack(foo)
// ...
// if any.Is(Foo.DESCRIPTOR):
// any.Unpack(foo)
// ...
//
// Example 4: Pack and unpack a message in Go
//
// foo := &pb.Foo{...}
// any, err := anypb.New(foo)
// if err != nil {
// ...
// }
// ...
// foo := &pb.Foo{}
// if err := any.UnmarshalTo(foo); err != nil {
// ...
// }
// foo := &pb.Foo{...}
// any, err := anypb.New(foo)
// if err != nil {
// ...
// }
// ...
// foo := &pb.Foo{}
// if err := any.UnmarshalTo(foo); err != nil {
// ...
// }
//
// The pack methods provided by protobuf library will by default use
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
@ -192,35 +188,33 @@ const (
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
// name "y.z".
//
//
// JSON
// # JSON
//
// The JSON representation of an `Any` value uses the regular
// representation of the deserialized, embedded message, with an
// additional field `@type` which contains the type URL. Example:
//
// package google.profile;
// message Person {
// string first_name = 1;
// string last_name = 2;
// }
// package google.profile;
// message Person {
// string first_name = 1;
// string last_name = 2;
// }
//
// {
// "@type": "type.googleapis.com/google.profile.Person",
// "firstName": <string>,
// "lastName": <string>
// }
// {
// "@type": "type.googleapis.com/google.profile.Person",
// "firstName": <string>,
// "lastName": <string>
// }
//
// If the embedded message type is well-known and has a custom JSON
// representation, that representation will be embedded adding a field
// `value` which holds the custom JSON in addition to the `@type`
// field. Example (for message [google.protobuf.Duration][]):
//
// {
// "@type": "type.googleapis.com/google.protobuf.Duration",
// "value": "1.212s"
// }
//
// {
// "@type": "type.googleapis.com/google.protobuf.Duration",
// "value": "1.212s"
// }
type Any struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -238,14 +232,14 @@ type Any struct {
// scheme `http`, `https`, or no scheme, one can optionally set up a type
// server that maps type URLs to message definitions as follows:
//
// * If no scheme is provided, `https` is assumed.
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error.
// * Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any
// lookup. Therefore, binary compatibility needs to be preserved
// on changes to types. (Use versioned type names to manage
// breaking changes.)
// - If no scheme is provided, `https` is assumed.
// - An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error.
// - Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any
// lookup. Therefore, binary compatibility needs to be preserved
// on changes to types. (Use versioned type names to manage
// breaking changes.)
//
// Note: this functionality is not currently available in the official
// protobuf release, and it is not used for type URLs beginning with
@ -253,7 +247,6 @@ type Any struct {
//
// Schemes other than `http`, `https` (or the empty scheme) might be
// used with implementation specific semantics.
//
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
// Must be a valid serialized protocol buffer of the above specified type.
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`

@ -92,8 +92,6 @@ type Api struct {
// `google.feature.v1`. For major versions 0 and 1, the suffix can
// be omitted. Zero major versions must only be used for
// experimental, non-GA interfaces.
//
//
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
// Source context for the protocol buffer service represented by this
// message.
@ -292,45 +290,45 @@ func (x *Method) GetSyntax() typepb.Syntax {
// interface must redeclare all the methods from the included interface, but
// documentation and options are inherited as follows:
//
// - If after comment and whitespace stripping, the documentation
// string of the redeclared method is empty, it will be inherited
// from the original method.
// - If after comment and whitespace stripping, the documentation
// string of the redeclared method is empty, it will be inherited
// from the original method.
//
// - Each annotation belonging to the service config (http,
// visibility) which is not set in the redeclared method will be
// inherited.
// - Each annotation belonging to the service config (http,
// visibility) which is not set in the redeclared method will be
// inherited.
//
// - If an http annotation is inherited, the path pattern will be
// modified as follows. Any version prefix will be replaced by the
// version of the including interface plus the [root][] path if
// specified.
// - If an http annotation is inherited, the path pattern will be
// modified as follows. Any version prefix will be replaced by the
// version of the including interface plus the [root][] path if
// specified.
//
// Example of a simple mixin:
//
// package google.acl.v1;
// service AccessControl {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v1/{resource=**}:getAcl";
// }
// }
// package google.acl.v1;
// service AccessControl {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v1/{resource=**}:getAcl";
// }
// }
//
// package google.storage.v2;
// service Storage {
// rpc GetAcl(GetAclRequest) returns (Acl);
// package google.storage.v2;
// service Storage {
// rpc GetAcl(GetAclRequest) returns (Acl);
//
// // Get a data record.
// rpc GetData(GetDataRequest) returns (Data) {
// option (google.api.http).get = "/v2/{resource=**}";
// }
// }
// // Get a data record.
// rpc GetData(GetDataRequest) returns (Data) {
// option (google.api.http).get = "/v2/{resource=**}";
// }
// }
//
// Example of a mixin configuration:
//
// apis:
// - name: google.storage.v2.Storage
// mixins:
// - name: google.acl.v1.AccessControl
// apis:
// - name: google.storage.v2.Storage
// mixins:
// - name: google.acl.v1.AccessControl
//
// The mixin construct implies that all methods in `AccessControl` are
// also declared with same name and request/response types in
@ -338,34 +336,34 @@ func (x *Method) GetSyntax() typepb.Syntax {
// see the effective `Storage.GetAcl` method after inheriting
// documentation and annotations as follows:
//
// service Storage {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v2/{resource=**}:getAcl";
// }
// ...
// }
// service Storage {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v2/{resource=**}:getAcl";
// }
// ...
// }
//
// Note how the version in the path pattern changed from `v1` to `v2`.
//
// If the `root` field in the mixin is specified, it should be a
// relative path under which inherited HTTP paths are placed. Example:
//
// apis:
// - name: google.storage.v2.Storage
// mixins:
// - name: google.acl.v1.AccessControl
// root: acls
// apis:
// - name: google.storage.v2.Storage
// mixins:
// - name: google.acl.v1.AccessControl
// root: acls
//
// This implies the following inherited HTTP annotation:
//
// service Storage {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl";
// }
// ...
// }
// service Storage {
// // Get the underlying ACL object.
// rpc GetAcl(GetAclRequest) returns (Acl) {
// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl";
// }
// ...
// }
type Mixin struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache

@ -387,7 +387,9 @@ type CodeGeneratorResponse_File struct {
// produced by another code generator. The original generator may provide
// insertion points by placing special annotations in the file that look
// like:
// @@protoc_insertion_point(NAME)
//
// @@protoc_insertion_point(NAME)
//
// The annotation can have arbitrary text before and after it on the line,
// which allows it to be placed in a comment. NAME should be replaced with
// an identifier naming the point -- this is what other generators will use
@ -399,7 +401,9 @@ type CodeGeneratorResponse_File struct {
//
// For example, the C++ code generator places the following line in the
// .pb.h files that it generates:
// // @@protoc_insertion_point(namespace_scope)
//
// // @@protoc_insertion_point(namespace_scope)
//
// This line appears within the scope of the file's package namespace, but
// outside of any particular class. Another plugin can then specify the
// insertion_point "namespace_scope" to generate additional classes or

@ -1679,10 +1679,12 @@ type MessageOptions struct {
// efficient, has fewer features, and is more complicated.
//
// The message must be defined exactly as follows:
// message Foo {
// option message_set_wire_format = true;
// extensions 4 to max;
// }
//
// message Foo {
// option message_set_wire_format = true;
// extensions 4 to max;
// }
//
// Note that the message cannot have any defined fields; MessageSets only
// have extensions.
//
@ -1705,14 +1707,17 @@ type MessageOptions struct {
// maps field.
//
// For maps fields:
// map<KeyType, ValueType> map_field = 1;
//
// map<KeyType, ValueType> map_field = 1;
//
// The parsed descriptor looks like:
// message MapFieldEntry {
// option map_entry = true;
// optional KeyType key = 1;
// optional ValueType value = 2;
// }
// repeated MapFieldEntry map_field = 1;
//
// message MapFieldEntry {
// option map_entry = true;
// optional KeyType key = 1;
// optional ValueType value = 2;
// }
// repeated MapFieldEntry map_field = 1;
//
// Implementations may choose not to generate the map_entry=true message, but
// use a native map in the target language to hold the keys and values.
@ -1847,7 +1852,6 @@ type FieldOptions struct {
// call from multiple threads concurrently, while non-const methods continue
// to require exclusive access.
//
//
// Note that implementations may choose not to check required fields within
// a lazy sub-message. That is, calling IsInitialized() on the outer message
// may return true even if the inner message has missing required fields.
@ -2426,43 +2430,48 @@ type SourceCodeInfo struct {
// tools.
//
// For example, say we have a file like:
// message Foo {
// optional string foo = 1;
// }
//
// message Foo {
// optional string foo = 1;
// }
//
// Let's look at just the field definition:
// optional string foo = 1;
// ^ ^^ ^^ ^ ^^^
// a bc de f ghi
//
// optional string foo = 1;
// ^ ^^ ^^ ^ ^^^
// a bc de f ghi
//
// We have the following locations:
// span path represents
// [a,i) [ 4, 0, 2, 0 ] The whole field definition.
// [a,b) [ 4, 0, 2, 0, 4 ] The label (optional).
// [c,d) [ 4, 0, 2, 0, 5 ] The type (string).
// [e,f) [ 4, 0, 2, 0, 1 ] The name (foo).
// [g,h) [ 4, 0, 2, 0, 3 ] The number (1).
//
// span path represents
// [a,i) [ 4, 0, 2, 0 ] The whole field definition.
// [a,b) [ 4, 0, 2, 0, 4 ] The label (optional).
// [c,d) [ 4, 0, 2, 0, 5 ] The type (string).
// [e,f) [ 4, 0, 2, 0, 1 ] The name (foo).
// [g,h) [ 4, 0, 2, 0, 3 ] The number (1).
//
// Notes:
// - A location may refer to a repeated field itself (i.e. not to any
// particular index within it). This is used whenever a set of elements are
// logically enclosed in a single code segment. For example, an entire
// extend block (possibly containing multiple extension definitions) will
// have an outer location whose path refers to the "extensions" repeated
// field without an index.
// - Multiple locations may have the same path. This happens when a single
// logical declaration is spread out across multiple places. The most
// obvious example is the "extend" block again -- there may be multiple
// extend blocks in the same scope, each of which will have the same path.
// - A location's span is not always a subset of its parent's span. For
// example, the "extendee" of an extension declaration appears at the
// beginning of the "extend" block and is shared by all extensions within
// the block.
// - Just because a location's span is a subset of some other location's span
// does not mean that it is a descendant. For example, a "group" defines
// both a type and a field in a single declaration. Thus, the locations
// corresponding to the type and field and their components will overlap.
// - Code which tries to interpret locations should probably be designed to
// ignore those that it doesn't understand, as more types of locations could
// be recorded in the future.
// - A location may refer to a repeated field itself (i.e. not to any
// particular index within it). This is used whenever a set of elements are
// logically enclosed in a single code segment. For example, an entire
// extend block (possibly containing multiple extension definitions) will
// have an outer location whose path refers to the "extensions" repeated
// field without an index.
// - Multiple locations may have the same path. This happens when a single
// logical declaration is spread out across multiple places. The most
// obvious example is the "extend" block again -- there may be multiple
// extend blocks in the same scope, each of which will have the same path.
// - A location's span is not always a subset of its parent's span. For
// example, the "extendee" of an extension declaration appears at the
// beginning of the "extend" block and is shared by all extensions within
// the block.
// - Just because a location's span is a subset of some other location's span
// does not mean that it is a descendant. For example, a "group" defines
// both a type and a field in a single declaration. Thus, the locations
// corresponding to the type and field and their components will overlap.
// - Code which tries to interpret locations should probably be designed to
// ignore those that it doesn't understand, as more types of locations could
// be recorded in the future.
Location []*SourceCodeInfo_Location `protobuf:"bytes,1,rep,name=location" json:"location,omitempty"`
}
@ -2810,21 +2819,32 @@ type SourceCodeInfo_Location struct {
// Each element is a field number or an index. They form a path from
// the root FileDescriptorProto to the place where the definition occurs.
// For example, this path:
// [ 4, 3, 2, 7, 1 ]
//
// [ 4, 3, 2, 7, 1 ]
//
// refers to:
// file.message_type(3) // 4, 3
// .field(7) // 2, 7
// .name() // 1
//
// file.message_type(3) // 4, 3
// .field(7) // 2, 7
// .name() // 1
//
// This is because FileDescriptorProto.message_type has field number 4:
// repeated DescriptorProto message_type = 4;
//
// repeated DescriptorProto message_type = 4;
//
// and DescriptorProto.field has field number 2:
// repeated FieldDescriptorProto field = 2;
//
// repeated FieldDescriptorProto field = 2;
//
// and FieldDescriptorProto.name has field number 1:
// optional string name = 1;
//
// optional string name = 1;
//
// Thus, the above path gives the location of a field name. If we removed
// the last element:
// [ 4, 3, 2, 7 ]
//
// [ 4, 3, 2, 7 ]
//
// this path refers to the whole field declaration (from the beginning
// of the label to the terminating semicolon).
Path []int32 `protobuf:"varint,1,rep,packed,name=path" json:"path,omitempty"`
@ -2853,34 +2873,34 @@ type SourceCodeInfo_Location struct {
//
// Examples:
//
// optional int32 foo = 1; // Comment attached to foo.
// // Comment attached to bar.
// optional int32 bar = 2;
// optional int32 foo = 1; // Comment attached to foo.
// // Comment attached to bar.
// optional int32 bar = 2;
//
// optional string baz = 3;
// // Comment attached to baz.
// // Another line attached to baz.
// optional string baz = 3;
// // Comment attached to baz.
// // Another line attached to baz.
//
// // Comment attached to qux.
// //
// // Another line attached to qux.
// optional double qux = 4;
// // Comment attached to qux.
// //
// // Another line attached to qux.
// optional double qux = 4;
//
// // Detached comment for corge. This is not leading or trailing comments
// // to qux or corge because there are blank lines separating it from
// // both.
// // Detached comment for corge. This is not leading or trailing comments
// // to qux or corge because there are blank lines separating it from
// // both.
//
// // Detached comment for corge paragraph 2.
// // Detached comment for corge paragraph 2.
//
// optional string corge = 5;
// /* Block comment attached
// * to corge. Leading asterisks
// * will be removed. */
// /* Block comment attached to
// * grault. */
// optional int32 grault = 6;
// optional string corge = 5;
// /* Block comment attached
// * to corge. Leading asterisks
// * will be removed. */
// /* Block comment attached to
// * grault. */
// optional int32 grault = 6;
//
// // ignored detached comments.
// // ignored detached comments.
LeadingComments *string `protobuf:"bytes,3,opt,name=leading_comments,json=leadingComments" json:"leading_comments,omitempty"`
TrailingComments *string `protobuf:"bytes,4,opt,name=trailing_comments,json=trailingComments" json:"trailing_comments,omitempty"`
LeadingDetachedComments []string `protobuf:"bytes,6,rep,name=leading_detached_comments,json=leadingDetachedComments" json:"leading_detached_comments,omitempty"`

@ -38,8 +38,7 @@
//
// The Duration message represents a signed span of time.
//
//
// Conversion to a Go Duration
// # Conversion to a Go Duration
//
// The AsDuration method can be used to convert a Duration message to a
// standard Go time.Duration value:
@ -68,15 +67,13 @@
// the resulting value to the closest representable value (e.g., math.MaxInt64
// for positive overflow and math.MinInt64 for negative overflow).
//
//
// Conversion from a Go Duration
// # Conversion from a Go Duration
//
// The durationpb.New function can be used to construct a Duration message
// from a standard Go time.Duration value:
//
// dur := durationpb.New(d)
// ... // make use of d as a *durationpb.Duration
//
package durationpb
import (
@ -106,43 +103,43 @@ const (
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
// Example 3: Compute Duration from datetime.timedelta in Python.
//
// td = datetime.timedelta(days=3, minutes=10)
// duration = Duration()
// duration.FromTimedelta(td)
// td = datetime.timedelta(days=3, minutes=10)
// duration = Duration()
// duration.FromTimedelta(td)
//
// # JSON Mapping
//
@ -153,8 +150,6 @@ const (
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
//
//
type Duration struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache

@ -54,9 +54,9 @@ const (
// empty messages in your APIs. A typical example is to use it as the request
// or the response type of an API method. For instance:
//
// service Foo {
// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
// }
// service Foo {
// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
// }
//
// The JSON representation for `Empty` is empty JSON object `{}`.
type Empty struct {

@ -40,8 +40,7 @@
// The paths are specific to some target message type,
// which is not stored within the FieldMask message itself.
//
//
// Constructing a FieldMask
// # Constructing a FieldMask
//
// The New function is used construct a FieldMask:
//
@ -64,8 +63,7 @@
// ... // handle error
// }
//
//
// Type checking a FieldMask
// # Type checking a FieldMask
//
// In order to verify that a FieldMask represents a set of fields that are
// reachable from some target message type, use the IsValid method:
@ -99,8 +97,8 @@ const (
// `FieldMask` represents a set of symbolic field paths, for example:
//
// paths: "f.a"
// paths: "f.b.d"
// paths: "f.a"
// paths: "f.b.d"
//
// Here `f` represents a field in some root message, `a` and `b`
// fields in the message found in `f`, and `d` a field found in the
@ -117,27 +115,26 @@ const (
// specified in the mask. For example, if the mask in the previous
// example is applied to a response message as follows:
//
// f {
// a : 22
// b {
// d : 1
// x : 2
// }
// y : 13
// }
// z: 8
// f {
// a : 22
// b {
// d : 1
// x : 2
// }
// y : 13
// }
// z: 8
//
// The result will not contain specific values for fields x,y and z
// (their value will be set to the default, and omitted in proto text
// output):
//
//
// f {
// a : 22
// b {
// d : 1
// }
// }
// f {
// a : 22
// b {
// d : 1
// }
// }
//
// A repeated field is not allowed except at the last position of a
// paths string.
@ -175,36 +172,36 @@ const (
//
// For example, given the target message:
//
// f {
// b {
// d: 1
// x: 2
// }
// c: [1]
// }
// f {
// b {
// d: 1
// x: 2
// }
// c: [1]
// }
//
// And an update message:
//
// f {
// b {
// d: 10
// }
// c: [2]
// }
// f {
// b {
// d: 10
// }
// c: [2]
// }
//
// then if the field mask is:
//
// paths: ["f.b", "f.c"]
// paths: ["f.b", "f.c"]
//
// then the result will be:
//
// f {
// b {
// d: 10
// x: 2
// }
// c: [1, 2]
// }
// f {
// b {
// d: 10
// x: 2
// }
// c: [1, 2]
// }
//
// An implementation may provide options to override this default behavior for
// repeated and message fields.
@ -242,51 +239,51 @@ const (
//
// As an example, consider the following message declarations:
//
// message Profile {
// User user = 1;
// Photo photo = 2;
// }
// message User {
// string display_name = 1;
// string address = 2;
// }
// message Profile {
// User user = 1;
// Photo photo = 2;
// }
// message User {
// string display_name = 1;
// string address = 2;
// }
//
// In proto a field mask for `Profile` may look as such:
//
// mask {
// paths: "user.display_name"
// paths: "photo"
// }
// mask {
// paths: "user.display_name"
// paths: "photo"
// }
//
// In JSON, the same mask is represented as below:
//
// {
// mask: "user.displayName,photo"
// }
// {
// mask: "user.displayName,photo"
// }
//
// # Field Masks and Oneof Fields
//
// Field masks treat fields in oneofs just as regular fields. Consider the
// following message:
//
// message SampleMessage {
// oneof test_oneof {
// string name = 4;
// SubMessage sub_message = 9;
// }
// }
// message SampleMessage {
// oneof test_oneof {
// string name = 4;
// SubMessage sub_message = 9;
// }
// }
//
// The field mask can be:
//
// mask {
// paths: "name"
// }
// mask {
// paths: "name"
// }
//
// Or:
//
// mask {
// paths: "sub_message"
// }
// mask {
// paths: "sub_message"
// }
//
// Note that oneof type names ("test_oneof" in this case) cannot be used in
// paths.

@ -47,8 +47,7 @@
// "google.golang.org/protobuf/encoding/protojson" package
// ensures that they will be serialized as their JSON equivalent.
//
//
// Conversion to and from a Go interface
// # Conversion to and from a Go interface
//
// The standard Go "encoding/json" package has functionality to serialize
// arbitrary types to a large degree. The Value.AsInterface, Struct.AsMap, and
@ -61,8 +60,7 @@
// forms back as Value, Struct, and ListValue messages, use the NewStruct,
// NewList, and NewValue constructor functions.
//
//
// Example usage
// # Example usage
//
// Consider the following example JSON object:
//
@ -121,7 +119,6 @@
// ... // handle error
// }
// ... // make use of m as a *structpb.Value
//
package structpb
import (
@ -145,7 +142,7 @@ const (
// `NullValue` is a singleton enumeration to represent the null value for the
// `Value` type union.
//
// The JSON representation for `NullValue` is JSON `null`.
// The JSON representation for `NullValue` is JSON `null`.
type NullValue int32
const (
@ -296,6 +293,7 @@ type Value struct {
// The kind of value.
//
// Types that are assignable to Kind:
//
// *Value_NullValue
// *Value_NumberValue
// *Value_StringValue

@ -39,8 +39,7 @@
// The Timestamp message represents a timestamp,
// an instant in time since the Unix epoch (January 1st, 1970).
//
//
// Conversion to a Go Time
// # Conversion to a Go Time
//
// The AsTime method can be used to convert a Timestamp message to a
// standard Go time.Time value in UTC:
@ -62,8 +61,7 @@
// ... // handle error
// }
//
//
// Conversion from a Go Time
// # Conversion from a Go Time
//
// The timestamppb.New function can be used to construct a Timestamp message
// from a standard Go time.Time value:
@ -75,7 +73,6 @@
//
// ts := timestamppb.Now()
// ... // make use of ts as a *timestamppb.Timestamp
//
package timestamppb
import (
@ -111,52 +108,50 @@ const (
//
// Example 1: Compute Timestamp from POSIX `time()`.
//
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
//
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
//
// struct timeval tv;
// gettimeofday(&tv, NULL);
// struct timeval tv;
// gettimeofday(&tv, NULL);
//
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
//
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
//
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
//
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
//
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
//
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
//
// Example 5: Compute Timestamp from Java `Instant.now()`.
//
// Instant now = Instant.now();
//
// Timestamp timestamp =
// Timestamp.newBuilder().setSeconds(now.getEpochSecond())
// .setNanos(now.getNano()).build();
// Instant now = Instant.now();
//
// Timestamp timestamp =
// Timestamp.newBuilder().setSeconds(now.getEpochSecond())
// .setNanos(now.getNano()).build();
//
// Example 6: Compute Timestamp from current time in Python.
//
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
//
// # JSON Mapping
//
@ -184,8 +179,6 @@ const (
// the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format.
//
//
type Timestamp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache

@ -1,54 +0,0 @@
//复制时倒入qs
// @ts-ignore
import qs from "qs";
const API_SERVER = process.env.NEXT_PUBLIC_WEB_SITE
export interface Config<T> extends RequestInit {
token?: string
data?: T
method?: 'POST' | 'GET'
}
interface IError {
status: number
details: [],
message: string
metadata: any
reason: string
}
const http = async <T, V>(endpoint: string, {data, token, headers, ...customConfig}: Config<T>) => {
const config = {
method: "GET",
headers: {
Authorization: token ? `Bearer ${token}` : '',
'Content-type': data ? 'application/json' : '',
},
...customConfig
}
if (config.method.toUpperCase() === "GET") {
endpoint += `?${qs.stringify(data)}`
} else {
config.body = JSON.stringify(data || {})
}
console.log(API_SERVER + endpoint, token)
return fetch(API_SERVER + endpoint, config).then(async response => {
if (response.status == 401) {
if (typeof window != 'undefined') {
// window.location.reload();
}
return Promise.reject({message: "请重新登录", status: 401})
}
const data = await response.json();
if (response.ok) {
return data as V;
} else {
return Promise.reject(data as IError);
}
})
}
export {
http
}

@ -2,9 +2,9 @@ package user
import (
"context"
"git.diulo.com/mogfee/protoc-gen-kit/middleware"
"git.diulo.com/mogfee/protoc-gen-kit/response"
"git.diulo.com/mogfee/protoc-gen-kit/xerrors"
"git.diulo.com/mogfee/kit/middleware"
"git.diulo.com/mogfee/kit/response"
"git.diulo.com/mogfee/kit/errors"
"github.com/gin-gonic/gin"
)
@ -30,7 +30,7 @@ func httpListHandler(srv UserServer, m ...middleware.Middleware) func(c *gin.Con
if v, ok := out.(*LoginResponse); ok {
resp.Success(v)
} else {
resp.Error(xerrors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse"))
resp.Error(errors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse"))
}
}
}
@ -53,7 +53,7 @@ func httpLoginHandler(srv UserServer, m ...middleware.Middleware) func(c *gin.Co
if v, ok := out.(*LoginResponse); ok {
resp.Success(v)
} else {
resp.Error(xerrors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse"))
resp.Error(errors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse"))
}
}
}

@ -12,7 +12,6 @@ import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
_ "google.golang.org/protobuf/types/descriptorpb"
_ "google.golang.org/protobuf/types/known/wrapperspb"
reflect "reflect"
sync "sync"
)
@ -29,7 +28,7 @@ type LoginRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
//用户名
// 用户名
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
FirstName string `protobuf:"bytes,3,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"`
@ -93,7 +92,7 @@ type LoginResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
//密钥
// 密钥
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
}
@ -144,8 +143,6 @@ var file_user_proto_rawDesc = []byte{
0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f,
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x0c, 0x6c, 0x6f, 0x67, 0x69, 0x6e,

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

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc-gen-go-grpc v1.3.0
// - protoc v3.18.1
// source: user.proto
@ -18,13 +18,19 @@ import (
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
User_List_FullMethodName = "/com.web.api.user.user/list"
User_Login_FullMethodName = "/com.web.api.user.user/login"
User_Delete_FullMethodName = "/com.web.api.user.user/delete"
)
// UserClient is the client API for User service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type UserClient interface {
//列表
// 列表
List(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
//等了
// 等了
Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
Delete(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
}
@ -39,7 +45,7 @@ func NewUserClient(cc grpc.ClientConnInterface) UserClient {
func (c *userClient) List(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
out := new(LoginResponse)
err := c.cc.Invoke(ctx, "/com.web.api.user.user/list", in, out, opts...)
err := c.cc.Invoke(ctx, User_List_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -48,7 +54,7 @@ func (c *userClient) List(ctx context.Context, in *LoginRequest, opts ...grpc.Ca
func (c *userClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
out := new(LoginResponse)
err := c.cc.Invoke(ctx, "/com.web.api.user.user/login", in, out, opts...)
err := c.cc.Invoke(ctx, User_Login_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -57,7 +63,7 @@ func (c *userClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.C
func (c *userClient) Delete(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
out := new(LoginResponse)
err := c.cc.Invoke(ctx, "/com.web.api.user.user/delete", in, out, opts...)
err := c.cc.Invoke(ctx, User_Delete_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -68,9 +74,9 @@ func (c *userClient) Delete(ctx context.Context, in *LoginRequest, opts ...grpc.
// All implementations must embed UnimplementedUserServer
// for forward compatibility
type UserServer interface {
//列表
// 列表
List(context.Context, *LoginRequest) (*LoginResponse, error)
//等了
// 等了
Login(context.Context, *LoginRequest) (*LoginResponse, error)
Delete(context.Context, *LoginRequest) (*LoginResponse, error)
mustEmbedUnimplementedUserServer()
@ -112,7 +118,7 @@ func _User_List_Handler(srv interface{}, ctx context.Context, dec func(interface
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/com.web.api.user.user/list",
FullMethod: User_List_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServer).List(ctx, req.(*LoginRequest))
@ -130,7 +136,7 @@ func _User_Login_Handler(srv interface{}, ctx context.Context, dec func(interfac
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/com.web.api.user.user/login",
FullMethod: User_Login_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServer).Login(ctx, req.(*LoginRequest))
@ -148,7 +154,7 @@ func _User_Delete_Handler(srv interface{}, ctx context.Context, dec func(interfa
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/com.web.api.user.user/delete",
FullMethod: User_Delete_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServer).Delete(ctx, req.(*LoginRequest))

@ -0,0 +1,54 @@
package user
import (
"context"
"git.diulo.com/mogfee/kit/transport/http"
)
type UserHTTPServer interface {
List(context.Context, *LoginRequest) (*LoginResponse, error)
Login(context.Context, *LoginRequest) (*LoginResponse, error)
Delete(context.Context, *LoginRequest) (*LoginResponse, error)
}
func RegisterUserHTTPServer(s *http.Server, srv UserServer) {
r := s.Route("/")
r.GET("/api/v1/user/list", _User_List0_HTTP_Handler(srv))
r.POST("/api/v1/user/login", _User_Login0_HTTP_Handler(srv))
}
func _User_List0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
var in LoginRequest
if err := ctx.BindQuery(&in); err != nil {
return err
}
http.SetOperation(ctx, "/com.web.api.user.user/list")
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.List(ctx, req.(*LoginRequest))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*LoginResponse)
return ctx.Result(200, reply)
}
}
func _User_Login0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
var in LoginRequest
if err := ctx.Bind(&in); err != nil {
return err
}
http.SetOperation(ctx, "/com.web.api.user.user/login")
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.Login(ctx, req.(*LoginRequest))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*LoginResponse)
return ctx.Result(200, reply)
}
}

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

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

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

@ -2,8 +2,10 @@ package http
import "net/http"
// FilterFunc is a function which receives a http.Handler and returns another http.Handler.
type FilterFunc func(http.Handler) http.Handler
// FilterChain returns a FilterFunc that specifies the chained handler for HTTP Router.
func FilterChain(filters ...FilterFunc) FilterFunc {
return func(next http.Handler) http.Handler {
for i := len(filters) - 1; i >= 0; i-- {

@ -6,13 +6,19 @@ import (
"sync"
)
type WalkRoutFunc func(RouteInfo) error
// WalkRouteFunc is the type of the function called for each route visited by Walk.
type WalkRouteFunc func(RouteInfo) error
// RouteInfo is an HTTP route info.
type RouteInfo struct {
Path string
Method string
}
type HandlerFunc func(Context) error
// HandlerFunc defines a function to serve HTTP requests.
type HandlerFunc func(ctx Context) error
// Router is an HTTP router.
type Router struct {
prefix string
pool sync.Pool
@ -26,18 +32,22 @@ func newRouter(prefix string, srv *Server, filters ...FilterFunc) *Router {
srv: srv,
filters: filters,
}
r.pool.New = func() any {
r.pool.New = func() interface{} {
return &wrapper{router: r}
}
return r
}
// Group returns a new router group.
func (r *Router) Group(prefix string, filters ...FilterFunc) *Router {
var newFilters []FilterFunc
newFilters = append(newFilters, r.filters...)
newFilters = append(newFilters, filters...)
return newRouter(path.Join(r.prefix, prefix), r.srv, newFilters...)
}
func (r *Router) Handle(method string, relativePath string, h HandlerFunc, filters ...FilterFunc) {
// Handle registers a new route with a matcher for the URL path and method.
func (r *Router) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) {
next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
ctx := r.pool.Get().(Context)
ctx.Reset(res, req)
@ -51,9 +61,48 @@ func (r *Router) Handle(method string, relativePath string, h HandlerFunc, filte
next = FilterChain(r.filters...)(next)
r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method)
}
// GET registers a new GET route for a path with matching handler in the router.
func (r *Router) GET(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodGet, path, h, m...)
}
// HEAD registers a new HEAD route for a path with matching handler in the router.
func (r *Router) HEAD(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodHead, path, h, m...)
}
// POST registers a new POST route for a path with matching handler in the router.
func (r *Router) POST(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodPost, path, h, m...)
}
// PUT registers a new PUT route for a path with matching handler in the router.
func (r *Router) PUT(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodPut, path, h, m...)
}
// PATCH registers a new PATCH route for a path with matching handler in the router.
func (r *Router) PATCH(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodPatch, path, h, m...)
}
// DELETE registers a new DELETE route for a path with matching handler in the router.
func (r *Router) DELETE(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodDelete, path, h, m...)
}
// CONNECT registers a new CONNECT route for a path with matching handler in the router.
func (r *Router) CONNECT(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodConnect, path, h, m...)
}
// OPTIONS registers a new OPTIONS route for a path with matching handler in the router.
func (r *Router) OPTIONS(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodOptions, path, h, m...)
}
// TRACE registers a new TRACE route for a path with matching handler in the router.
func (r *Router) TRACE(path string, h HandlerFunc, m ...FilterFunc) {
r.Handle(http.MethodTrace, path, h, m...)
}

@ -3,14 +3,19 @@ package http
import (
"context"
"crypto/tls"
"errors"
"git.diulo.com/mogfee/kit/internal/endpoint"
"git.diulo.com/mogfee/kit/internal/host"
"git.diulo.com/mogfee/kit/internal/matcher"
"git.diulo.com/mogfee/kit/log"
"git.diulo.com/mogfee/kit/middleware"
"git.diulo.com/mogfee/kit/transport"
"github.com/gorilla/mux"
"net"
"net/http"
"net/url"
"time"
"github.com/gorilla/mux"
)
var (
@ -19,7 +24,8 @@ var (
_ http.Handler = (*Server)(nil)
)
type ServerOption func(server *Server)
// ServerOption is an HTTP server option.
type ServerOption func(*Server)
// Network with server network.
func Network(network string) ServerOption {
@ -127,6 +133,7 @@ func PathPrefix(prefix string) ServerOption {
}
}
// Server is an HTTP server wrapper.
type Server struct {
*http.Server
lis net.Listener
@ -147,6 +154,7 @@ type Server struct {
router *mux.Router
}
// NewServer creates an HTTP server by options.
func NewServer(opts ...ServerOption) *Server {
srv := &Server{
network: "tcp",
@ -156,13 +164,14 @@ func NewServer(opts ...ServerOption) *Server {
decVars: DefaultRequestVars,
decQuery: DefaultRequestQuery,
decBody: DefaultRequestDecoder,
enc: DefaultResponseEncoder,
ene: DefaultErrorEncoder,
strictSlash: true,
router: mux.NewRouter(),
}
for _, o := range opts {
o(srv)
}
srv.router.StrictSlash(srv.strictSlash)
srv.router.NotFoundHandler = http.DefaultServeMux
srv.router.MethodNotAllowedHandler = http.DefaultServeMux
@ -173,91 +182,157 @@ func NewServer(opts ...ServerOption) *Server {
}
return srv
}
// Use uses a service middleware with selector.
// selector:
// - '/*'
// - '/helloworld.v1.Greeter/*'
// - '/helloworld.v1.Greeter/SayHello'
func (s *Server) Use(selector string, m ...middleware.Middleware) {
s.middleware.Add(selector, m...)
}
func (s *Server) WalkRoute(fn WalkRoutFunc) error {
// WalkRoute walks the router and all its sub-routers, calling walkFn for each route in the tree.
func (s *Server) WalkRoute(fn WalkRouteFunc) error {
return s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
methods, err := route.GetMethods()
if err != nil {
return err
return nil // ignore no methods
}
path, err := route.GetPathTemplate()
if err != nil {
return err
}
for _, method := range methods {
if err = fn(RouteInfo{
Method: method,
Path: path,
}); err != nil {
if err := fn(RouteInfo{Method: method, Path: path}); err != nil {
return err
}
}
return nil
})
}
func (s *Server) Kind() transport.Kind {
return transport.htt
}
// Route registers an HTTP router.
func (s *Server) Route(prefix string, filters ...FilterFunc) *Router {
return newRouter(prefix, s, filters...)
}
// Handle registers a new route with a matcher for the URL path.
func (s *Server) Handle(path string, h http.Handler) {
s.router.Handle(path, h)
}
// HandlePrefix registers a new route with a matcher for the URL path prefix.
func (s *Server) HandlePrefix(prefix string, h http.Handler) {
s.router.PathPrefix(prefix).Handler(h)
}
// HandleFunc registers a new route with a matcher for the URL path.
func (s *Server) HandleFunc(path string, h http.HandlerFunc) {
s.router.HandleFunc(path, h)
}
func (s *Server) HandleHeader(key, val string, h http.Handler) {
// HandleHeader registers a new route with a matcher for the header.
func (s *Server) HandleHeader(key, val string, h http.HandlerFunc) {
s.router.Headers(key, val).Handler(h)
}
// ServeHTTP should write reply headers and data to the ResponseWriter and then return.
func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
s.Handler.ServeHTTP(res, req)
}
func (s *Server) filter() mux.MiddlewareFunc {
return func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var (
ctx context.Context
cancel context.CancelFunc
)
if s.timeout > 0 {
ctx, cancel = context.WithTimeout(request.Context(), s.timeout)
ctx, cancel = context.WithTimeout(req.Context(), s.timeout)
} else {
ctx, cancel = context.WithCancel(request.Context())
ctx, cancel = context.WithCancel(req.Context())
}
defer cancel()
pathTemplate := request.URL.Path
if route := mux.CurrentRoute(request); route != nil {
pathTemplate := req.URL.Path
if route := mux.CurrentRoute(req); route != nil {
// /path/123 -> /path/{id}
pathTemplate, _ = route.GetPathTemplate()
}
tr := &Transport{}
tr := &Transport{
operation: pathTemplate,
pathTemplate: pathTemplate,
reqHeader: headerCarrier(req.Header),
replyHeader: headerCarrier(w.Header()),
request: req,
}
if s.endpoint != nil {
tr.endpoint = s.endpoint.String()
}
tr.request = req.WithContext(transport.NewServerContext(ctx, tr))
next.ServeHTTP(w, tr.request)
})
}
}
func (s *Server) Endpoint() string {
//TODO implement me
panic("implement me")
// Endpoint return a real address to registry endpoint.
// examples:
//
// https://127.0.0.1:8000
// Legacy: http://127.0.0.1:8000?isSecure=false
func (s *Server) Endpoint() (*url.URL, error) {
if err := s.listenAndEndpoint(); err != nil {
return nil, err
}
return s.endpoint, nil
}
func (s *Server) Operation() string {
//TODO implement me
panic("implement me")
// Start start the HTTP server.
func (s *Server) Start(ctx context.Context) error {
if err := s.listenAndEndpoint(); err != nil {
return err
}
s.BaseContext = func(net.Listener) context.Context {
return ctx
}
log.Infof("[HTTP] server listening on: %s", s.lis.Addr().String())
var err error
if s.tlsConf != nil {
err = s.ServeTLS(s.lis, "", "")
} else {
err = s.Serve(s.lis)
}
if !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
func (s *Server) RequestHeader() transport.Header {
//TODO implement me
panic("implement me")
// Stop stop the HTTP server.
func (s *Server) Stop(ctx context.Context) error {
log.Info("[HTTP] server stopping")
return s.Shutdown(ctx)
}
func (s *Server) ReplyHeader() transport.Header {
//TODO implement me
panic("implement me")
func (s *Server) listenAndEndpoint() error {
if s.lis == nil {
lis, err := net.Listen(s.network, s.address)
if err != nil {
s.err = err
return err
}
s.lis = lis
}
if s.endpoint == nil {
addr, err := host.Extract(s.address, s.lis)
if err != nil {
s.err = err
return err
}
s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("http", s.tlsConf != nil), addr)
}
return s.err
}

@ -6,12 +6,16 @@ import (
"net/http"
)
var _ Transporter = (*Transport)(nil)
// Transporter is http Transporter
type Transporter interface {
transport.Transporter
Request() *http.Request
PathTemplate() string
}
// Transport is an HTTP transport.
type Transport struct {
endpoint string
operation string
@ -21,33 +25,42 @@ type Transport struct {
pathTemplate string
}
func (t *Transport) Kind() transport.Kind {
// Kind returns the transport kind.
func (tr *Transport) Kind() transport.Kind {
return transport.KindHTTP
}
func (t *Transport) Endpoint() string {
return t.endpoint
// Endpoint returns the transport endpoint.
func (tr *Transport) Endpoint() string {
return tr.endpoint
}
func (t *Transport) Operation() string {
return t.operation
// Operation returns the transport operation.
func (tr *Transport) Operation() string {
return tr.operation
}
func (t *Transport) RequestHeader() transport.Header {
return t.reqHeader
// Request returns the HTTP request.
func (tr *Transport) Request() *http.Request {
return tr.request
}
func (t *Transport) ReplyHeader() transport.Header {
return t.replyHeader
// RequestHeader returns the request header.
func (tr *Transport) RequestHeader() transport.Header {
return tr.reqHeader
}
func (t *Transport) Request() *http.Request {
return t.request
// ReplyHeader returns the reply header.
func (tr *Transport) ReplyHeader() transport.Header {
return tr.replyHeader
}
func (t *Transport) PathTemplate() string {
return t.pathTemplate
// PathTemplate returns the http path template.
func (tr *Transport) PathTemplate() string {
return tr.pathTemplate
}
// SetOperation sets the transport operation.
func SetOperation(ctx context.Context, op string) {
if tr, ok := transport.FromServerContext(ctx); ok {
if tr, ok := tr.(*Transport); ok {
@ -55,6 +68,8 @@ func SetOperation(ctx context.Context, op string) {
}
}
}
// RequestFromServerContext returns request from context.
func RequestFromServerContext(ctx context.Context) (*http.Request, bool) {
if tr, ok := transport.FromServerContext(ctx); ok {
if tr, ok := tr.(*Transport); ok {
@ -63,3 +78,24 @@ func RequestFromServerContext(ctx context.Context) (*http.Request, bool) {
}
return nil, false
}
type headerCarrier http.Header
// Get returns the value associated with the passed key.
func (hc headerCarrier) Get(key string) string {
return http.Header(hc).Get(key)
}
// Set stores the key-value pair.
func (hc headerCarrier) Set(key string, value string) {
http.Header(hc).Set(key, value)
}
// Keys lists the keys stored in this carrier.
func (hc headerCarrier) Keys() []string {
keys := make([]string, 0, len(hc))
for k := range http.Header(hc) {
keys = append(keys, k)
}
return keys
}

Loading…
Cancel
Save