Compare commits

...

18 Commits

Author SHA1 Message Date
0d63723ed3 Merge pull request #164 from unistack-org/logger-clone-fix
logger: fix Clone
2023-01-06 22:44:26 +03:00
a7f84e0baa logger: fix Clone
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-06 22:41:57 +03:00
c209892ce8 Merge pull request #163 from unistack-org/logger_unwrap
logger/unwrap: fix Tagged option
2022-12-29 23:19:57 +03:00
421842315f logger/unwrap: fix Tagged option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-12-29 23:16:58 +03:00
25350a6531 Merge pull request #162 from unistack-org/util_http_method_not_allowed
util/http: trie support method not allowed
2022-12-27 23:49:36 +03:00
5e47cc7e8c util/http: trie support method not allowed
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-12-27 23:47:11 +03:00
1687b98b11 Merge pull request #161 from unistack-org/tracer
tracer: add labels method
2022-12-24 19:22:48 +03:00
a81649d2a2 tracer: add labels method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-12-24 19:20:22 +03:00
b48faa3b2b Merge pull request #160 from unistack-org/tracer
tracer: fix span options
2022-12-24 18:20:59 +03:00
0be584ef0d fix wrapper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-12-24 18:18:42 +03:00
26a2d18766 tracer: fix span options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-12-24 18:09:48 +03:00
25a796fe4f Merge pull request #158 from unistack-org/meter
meter/wrapper: fix naming
2022-11-28 14:39:45 +03:00
d23de14769 meter/wrapper: fix naming
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-28 14:36:54 +03:00
2fb108519c Merge pull request #157 from unistack-org/fixups
fix wrappers
2022-11-27 00:45:44 +03:00
c7ce238da3 fix wrappers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-27 00:43:30 +03:00
dependabot[bot]
67aa79f18a chore(deps): bump hmarr/auto-approve-action from 2 to 3 (#155)
Bumps [hmarr/auto-approve-action](https://github.com/hmarr/auto-approve-action) from 2 to 3.
- [Release notes](https://github.com/hmarr/auto-approve-action/releases)
- [Commits](https://github.com/hmarr/auto-approve-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: hmarr/auto-approve-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-19 21:45:49 +03:00
e6c3d734a3 Merge pull request #156 from unistack-org/take_unwrap
logger/unwrap: add tagged option
2022-11-19 15:23:22 +03:00
1374e27531 logger/unwrap: add tagged option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-19 15:20:42 +03:00
13 changed files with 222 additions and 151 deletions

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: approve
uses: hmarr/auto-approve-action@v2
uses: hmarr/auto-approve-action@v3
if: github.actor == 'vtolstov' || github.actor == 'dependabot[bot]'
id: approve
with:

View File

@@ -74,7 +74,7 @@ type Request interface {
type Response interface {
// Read the response
Codec() codec.Codec
// read the header
// Header data
Header() metadata.Metadata
// Read the undecoded response
Read() ([]byte, error)

View File

@@ -49,7 +49,7 @@ func (l *defaultLogger) Clone(opts ...Option) Logger {
oldopts.Wrappers = newopts.Wrappers
l.Lock()
cl := &defaultLogger{opts: oldopts, logFunc: l.logFunc, logfFunc: l.logfFunc}
cl := &defaultLogger{opts: oldopts, logFunc: l.logFunc, logfFunc: l.logfFunc, enc: json.NewEncoder(l.opts.Out)}
l.Unlock()
// wrap the Log func

View File

@@ -35,6 +35,7 @@ var (
nilAngleBytes = []byte("<nil>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
filteredBytes = []byte("<filtered>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
@@ -48,22 +49,25 @@ var (
type unwrap struct {
val interface{}
s fmt.State
depth int
pointers map[uintptr]int
opts *Options
depth int
ignoreNextType bool
}
// Options struct
type Options struct {
Codec codec.Codec
Indent string
UnwrapMethods bool
Codec codec.Codec
Indent string
Methods bool
Tagged bool
}
// NewOptions creates new Options struct via provided args
func NewOptions(opts ...Option) Options {
options := Options{
Indent: " ",
UnwrapMethods: false,
Indent: " ",
Methods: false,
}
for _, o := range opts {
o(&options)
@@ -71,26 +75,37 @@ func NewOptions(opts ...Option) Options {
return options
}
// Option func signature
type Option func(*Options)
func UnwrapIndent(f string) Option {
// Indent option specify indent level
func Indent(f string) Option {
return func(o *Options) {
o.Indent = f
}
}
func UnwrapMethods(b bool) Option {
// Methods option toggles fmt.Stringer methods
func Methods(b bool) Option {
return func(o *Options) {
o.UnwrapMethods = b
o.Methods = b
}
}
func UnwrapCodec(c codec.Codec) Option {
// Codec option automatic marshal arg via specified codec and write it to log
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}
// Tagged option toggles output only logger:"take" fields
func Tagged(b bool) Option {
return func(o *Options) {
o.Tagged = b
}
}
func Unwrap(val interface{}, opts ...Option) *unwrap {
options := NewOptions(opts...)
return &unwrap{val: val, opts: &options, pointers: make(map[uintptr]int)}
@@ -197,10 +212,8 @@ func (f *unwrap) formatPtr(v reflect.Value) {
switch {
case nilFound:
_, _ = f.s.Write(nilAngleBytes)
case cycleFound:
_, _ = f.s.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
@@ -256,7 +269,7 @@ func (f *unwrap) format(v reflect.Value) {
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.opts.UnwrapMethods {
if f.opts.Methods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.opts, f.s, v); handled {
return
@@ -335,13 +348,22 @@ func (f *unwrap) format(v reflect.Value) {
_, _ = f.s.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
numWritten := 0
_, _ = f.s.Write(openBraceBytes)
f.depth++
vt := v.Type()
prevSkip := false
for i := 0; i < numFields; i++ {
sv, ok := vt.Field(i).Tag.Lookup("logger")
if ok && sv == "omit" {
if ok {
switch sv {
case "omit":
prevSkip = true
continue
case "take":
break
}
} else if f.opts.Tagged {
prevSkip = true
continue
}
@@ -358,8 +380,12 @@ func (f *unwrap) format(v reflect.Value) {
_, _ = f.s.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
numWritten++
}
f.depth--
if numWritten == 0 && f.depth < 0 {
_, _ = f.s.Write(filteredBytes)
}
_, _ = f.s.Write(closeBraceBytes)
case reflect.Uintptr:
getHexPtr(f.s, uintptr(v.Uint()))

View File

@@ -1,24 +1,13 @@
package unwrap
import (
"fmt"
"strings"
"testing"
"go.unistack.org/micro/v3/codec"
)
func TestUnwrapOmit(t *testing.T) {
type val struct {
MP map[string]string `json:"mp" logger:"omit"`
STR string `json:"str"`
AR []string `json:"ar"`
}
v1 := &val{AR: []string{"string1", "string2"}, STR: "string", MP: map[string]string{"key": "val"}}
t.Logf("output: %#v", v1)
t.Logf("output: %#v", Unwrap(v1))
}
func TestUnwrap(t *testing.T) {
string1 := "string1"
string2 := "string2"
@@ -32,7 +21,10 @@ func TestUnwrap(t *testing.T) {
v1 := &val1{ar: []*string{&string1, &string2}, str: &string1, val: &val1{str: &string2}, mp: map[string]string{"key": "val"}}
t.Logf("output: %#v", Unwrap(v1))
buf := fmt.Sprintf("%#v", Unwrap(v1))
if strings.Compare(buf, `&unwrap.val1{mp:map[string]string{"key":"val"}, val:(*unwrap.val1){mp:map[string]string<nil>, val:(*unwrap.val1)<nil>, str:(*string)"string2", ar:[]*string<nil>}, str:(*string)"string1", ar:[]*string{<*><shown>, <*>"string2"}}`) != 0 {
t.Fatalf("not proper written %s", buf)
}
type val2 struct {
mp map[string]string
@@ -42,11 +34,11 @@ func TestUnwrap(t *testing.T) {
}
v2 := &val2{ar: []string{string1, string2}, str: string1, val: &val2{str: string2}, mp: map[string]string{"key": "val"}}
t.Logf("output: %#v", v2)
_ = v2
// t.Logf("output: %#v", v2)
}
func TestUnwrapCodec(t *testing.T) {
func TestCodec(t *testing.T) {
type val struct {
MP map[string]string `json:"mp"`
STR string `json:"str"`
@@ -55,5 +47,54 @@ func TestUnwrapCodec(t *testing.T) {
v1 := &val{AR: []string{"string1", "string2"}, STR: "string", MP: map[string]string{"key": "val"}}
t.Logf("output: %#v", Unwrap(v1, UnwrapCodec(codec.NewCodec())))
buf := fmt.Sprintf("%#v", Unwrap(v1, Codec(codec.NewCodec())))
if strings.Compare(buf, `{"mp":{"key":"val"},"str":"string","ar":["string1","string2"]}`) != 0 {
t.Fatalf("not proper written %s", buf)
}
}
func TestOmit(t *testing.T) {
type val struct {
Key1 string `logger:"omit"`
Key2 string `logger:"take"`
Key3 string
}
v1 := &val{Key1: "val1", Key2: "val2", Key3: "val3"}
buf := fmt.Sprintf("%#v", Unwrap(v1))
if strings.Compare(buf, `&unwrap.val{Key2:"val2", Key3:"val3"}`) != 0 {
t.Fatalf("not proper written %s", buf)
}
}
func TestTagged(t *testing.T) {
type val struct {
Key1 string `logger:"take"`
Key2 string
}
v1 := &val{Key1: "val1", Key2: "val2"}
buf := fmt.Sprintf("%#v", Unwrap(v1, Tagged(true)))
if strings.Compare(buf, `&unwrap.val{Key1:"val1"}`) != 0 {
t.Fatalf("not proper written %s", buf)
}
}
func TestTaggedNested(t *testing.T) {
type val struct {
key string `logger:"take"`
val string `logger:"omit"`
unk string
}
type str struct {
val *val `logger:"take"`
key string `logger:"omit"`
}
var iface interface{}
v := &str{key: "omit", val: &val{key: "test", val: "omit", unk: "unk"}}
iface = v
buf := fmt.Sprintf("%#v", Unwrap(iface, Tagged(true)))
if strings.Compare(buf, `&unwrap.str{val:(*unwrap.val){key:"test"}}`) != 0 {
t.Fatalf("not proper written %s", buf)
}
}

View File

@@ -66,7 +66,7 @@ var (
}
// DefaultSkipEndpoints wrapper not called for this endpoints
DefaultSkipEndpoints = []string{"Meter.Metrics"}
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
type lWrapper struct {
@@ -228,11 +228,7 @@ func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}
for _, o := range l.opts.ClientCallObservers {
labels = append(labels, o(ctx, req, rsp, opts, err)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).Log(ctx, l.opts.Level)
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
@@ -255,11 +251,7 @@ func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...clien
for _, o := range l.opts.ClientStreamObservers {
labels = append(labels, o(ctx, req, opts, stream, err)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).Log(ctx, l.opts.Level)
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return stream, err
}
@@ -282,11 +274,7 @@ func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...clie
for _, o := range l.opts.ClientPublishObservers {
labels = append(labels, o(ctx, msg, opts, err)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).Log(ctx, l.opts.Level)
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
@@ -309,11 +297,7 @@ func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp in
for _, o := range l.opts.ServerHandlerObservers {
labels = append(labels, o(ctx, req, rsp, err)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).Log(ctx, l.opts.Level)
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
@@ -336,11 +320,7 @@ func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) err
for _, o := range l.opts.ServerSubscriberObservers {
labels = append(labels, o(ctx, msg, err)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).Log(ctx, l.opts.Level)
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
@@ -387,11 +367,7 @@ func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.R
for _, o := range l.opts.ClientCallFuncObservers {
labels = append(labels, o(ctx, addr, req, rsp, opts, err)...)
}
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
l.opts.Logger.Fields(fields).Log(ctx, l.opts.Level)
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}

View File

@@ -50,7 +50,7 @@ var (
labelEndpoint = "endpoint"
// DefaultSkipEndpoints contains list of endpoints that not evaluted by wrapper
DefaultSkipEndpoints = []string{"Meter.Metrics"}
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
// Options struct
@@ -255,6 +255,7 @@ func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.
}
// NewHandlerWrapper create new server handler wrapper
// deprecated
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
@@ -262,6 +263,14 @@ func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
return handler.HandlerFunc
}
// NewServerHandlerWrapper create new server handler wrapper
func NewServerHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.HandlerFunc
}
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := req.Service() + "." + req.Endpoint()
@@ -295,6 +304,7 @@ func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
}
// NewSubscriberWrapper create server subscribe wrapper
// deprecated
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
@@ -302,6 +312,13 @@ func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
return handler.SubscriberFunc
}
func NewServerSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic()

View File

@@ -35,7 +35,7 @@ type noopSpan struct {
ctx context.Context
tracer Tracer
name string
labels []Label
opts SpanOptions
}
func (s *noopSpan) Finish(opts ...SpanOption) {
@@ -56,8 +56,12 @@ func (s *noopSpan) SetName(name string) {
s.name = name
}
func (s *noopSpan) SetLabels(labels ...Label) {
s.labels = labels
func (s *noopSpan) SetLabels(labels ...interface{}) {
s.opts.Labels = labels
}
func (s *noopSpan) AddLabels(labels ...interface{}) {
s.opts.Labels = append(s.opts.Labels, labels...)
}
// NewTracer returns new memory tracer

View File

@@ -3,7 +3,9 @@ package tracer
import "go.unistack.org/micro/v3/logger"
// SpanOptions contains span option
type SpanOptions struct{}
type SpanOptions struct {
Labels []interface{}
}
// SpanOption func signature
type SpanOption func(o *SpanOptions)
@@ -14,6 +16,12 @@ type EventOptions struct{}
// EventOption func signature
type EventOption func(o *EventOptions)
func SpanLabels(labels ...interface{}) SpanOption {
return func(o *SpanOptions) {
o.Labels = labels
}
}
// Options struct
type Options struct {
// Logger used for logging

View File

@@ -30,34 +30,7 @@ type Span interface {
// SetName set the span name
SetName(name string)
// SetLabels set the span labels
SetLabels(labels ...Label)
}
type Label struct {
val interface{}
key string
}
func LabelAny(k string, v interface{}) Label {
return Label{key: k, val: v}
}
func LabelString(k string, v string) Label {
return Label{key: k, val: v}
}
func LabelInt(k string, v int) Label {
return Label{key: k, val: v}
}
func LabelInt64(k string, v int64) Label {
return Label{key: k, val: v}
}
func LabelFloat64(k string, v float64) Label {
return Label{key: k, val: v}
}
func LabelBool(k string, v bool) Label {
return Label{key: k, val: v}
SetLabels(labels ...interface{})
// AddLabels append the span labels
AddLabels(labels ...interface{})
}

View File

@@ -14,95 +14,95 @@ import (
var (
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", true)
}
sp.SetLabels(labels...)
}
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", true)
}
sp.SetLabels(labels...)
}
DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Pub to %s", msg.Topic()))
var labels []tracer.Label
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", true)
}
sp.SetLabels(labels...)
}
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
var labels []interface{}
if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", true)
}
sp.SetLabels(labels...)
}
DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Sub from %s", msg.Topic()))
var labels []tracer.Label
var labels []interface{}
if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", true)
}
sp.SetLabels(labels...)
}
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", true)
}
sp.SetLabels(labels...)
}
DefaultSkipEndpoints = []string{"Meter.Metrics"}
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
type tWrapper struct {

View File

@@ -7,6 +7,7 @@ package http
// Modified by Unistack LLC to support interface{} type handler and parameters in map[string]string
import (
"errors"
"fmt"
"net/http"
"regexp"
@@ -15,6 +16,11 @@ import (
"strings"
)
var (
ErrNotFound = errors.New("route not found")
ErrMethodNotAllowed = errors.New("method not allowed")
)
type methodTyp uint
const (
@@ -399,16 +405,19 @@ func (n *Trie) setEndpoint(method methodTyp, handler interface{}, pattern string
}
// Search try to find element in tree with path and method
func (n *Trie) Search(method string, path string) (interface{}, map[string]string, bool) {
func (n *Trie) Search(method string, path string) (interface{}, map[string]string, error) {
params := &routeParams{}
// Find the routing handlers for the path
rn := n.findRoute(params, methodMap[method], path)
if rn == nil {
return nil, nil, false
if rn == nil && !params.methodNotAllowed {
return nil, nil, ErrNotFound
}
if params.methodNotAllowed {
return nil, nil, ErrMethodNotAllowed
}
ep, ok := rn.endpoints[methodMap[method]]
if !ok {
return nil, nil, false
return nil, nil, ErrMethodNotAllowed
}
eparams := make(map[string]string, len(params.keys))
@@ -416,12 +425,13 @@ func (n *Trie) Search(method string, path string) (interface{}, map[string]strin
eparams[key] = params.vals[idx]
}
return ep.handler, eparams, true
return ep.handler, eparams, nil
}
type routeParams struct {
keys []string
vals []string
keys []string
vals []string
methodNotAllowed bool
}
// Recursive edge traversal by checking all nodeTyp groups along the way.
@@ -495,6 +505,7 @@ func (n *Trie) findRoute(params *routeParams, method methodTyp, path string) *Tr
params.keys = append(params.keys, h.paramKeys...)
return xn
}
params.methodNotAllowed = true
}
}
@@ -530,6 +541,7 @@ func (n *Trie) findRoute(params *routeParams, method methodTyp, path string) *Tr
params.keys = append(params.keys, h.paramKeys...)
return xn
}
params.methodNotAllowed = true
}
}

View File

@@ -21,22 +21,22 @@ func TestTrieWildcardPathPrefix(t *testing.T) {
if err = tr.Insert([]string{http.MethodPost}, "/v1/*", &handler{name: "post_create"}); err != nil {
t.Fatal(err)
}
h, _, ok := tr.Search(http.MethodPost, "/v1/test/one")
if !ok {
h, _, err := tr.Search(http.MethodPost, "/v1/test/one")
if err != nil {
t.Fatalf("unexpected error handler not found")
}
if h.(*handler).name != "post_create" {
t.Fatalf("invalid handler %v", h)
}
h, _, ok = tr.Search(http.MethodPost, "/v1/update")
if !ok {
h, _, err = tr.Search(http.MethodPost, "/v1/update")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "post_update" {
t.Fatalf("invalid handler %v", h)
}
h, _, ok = tr.Search(http.MethodPost, "/v1/update/some/{x}")
if !ok {
h, _, err = tr.Search(http.MethodPost, "/v1/update/some/{x}")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "post_create" {
@@ -52,8 +52,8 @@ func TestTriePathPrefix(t *testing.T) {
_ = tr.Insert([]string{http.MethodPost}, "/v1/create/{id}", &handler{name: "post_create"})
_ = tr.Insert([]string{http.MethodPost}, "/v1/update/{id}", &handler{name: "post_update"})
_ = tr.Insert([]string{http.MethodPost}, "/", &handler{name: "post_wildcard"})
h, _, ok := tr.Search(http.MethodPost, "/")
if !ok {
h, _, err := tr.Search(http.MethodPost, "/")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "post_wildcard" {
@@ -68,8 +68,8 @@ func TestTrieFixedPattern(t *testing.T) {
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", &handler{name: "pattern"})
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/12", &handler{name: "fixed"})
h, _, ok := tr.Search(http.MethodPut, "/v1/create/12")
if !ok {
h, _, err := tr.Search(http.MethodPut, "/v1/create/12")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "fixed" {
@@ -80,8 +80,8 @@ func TestTrieFixedPattern(t *testing.T) {
func TestTrieNoMatchMethod(t *testing.T) {
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", nil)
_, _, ok := tr.Search(http.MethodPost, "/v1/create")
if ok {
_, _, err := tr.Search(http.MethodPost, "/v1/create")
if err == nil && err != ErrNotFound {
t.Fatalf("must be not found error")
}
}
@@ -90,9 +90,9 @@ func TestTrieMatchRegexp(t *testing.T) {
type handler struct{}
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{category}/{id:[0-9]+}", &handler{})
_, params, ok := tr.Search(http.MethodPut, "/v1/create/test_cat/12345")
_, params, err := tr.Search(http.MethodPut, "/v1/create/test_cat/12345")
switch {
case !ok:
case err != nil:
t.Fatalf("route not found")
case len(params) != 2:
t.Fatalf("param matching error %v", params)
@@ -105,8 +105,8 @@ func TestTrieMatchRegexpFail(t *testing.T) {
type handler struct{}
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id:[a-z]+}", &handler{})
_, _, ok := tr.Search(http.MethodPut, "/v1/create/12345")
if ok {
_, _, err := tr.Search(http.MethodPut, "/v1/create/12345")
if err != ErrNotFound {
t.Fatalf("route must not be not found")
}
}
@@ -118,14 +118,28 @@ func TestTrieMatchLongest(t *testing.T) {
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create", &handler{name: "first"})
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id:[0-9]+}", &handler{name: "second"})
if h, _, ok := tr.Search(http.MethodPut, "/v1/create/12345"); !ok {
if h, _, err := tr.Search(http.MethodPut, "/v1/create/12345"); err != nil {
t.Fatalf("route must be found")
} else if h.(*handler).name != "second" {
t.Fatalf("invalid handler found: %s != %s", h.(*handler).name, "second")
}
if h, _, ok := tr.Search(http.MethodPut, "/v1/create"); !ok {
if h, _, err := tr.Search(http.MethodPut, "/v1/create"); err != nil {
t.Fatalf("route must be found")
} else if h.(*handler).name != "first" {
t.Fatalf("invalid handler found: %s != %s", h.(*handler).name, "first")
}
}
func TestMethodNotAllowed(t *testing.T) {
type handler struct{}
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create", &handler{})
_, _, err := tr.Search(http.MethodPost, "/v1/create")
if err != ErrMethodNotAllowed {
t.Fatalf("route must be method not allowed: %v", err)
}
_, _, err = tr.Search(http.MethodPut, "/v1/create")
if err != nil {
t.Fatalf("route must be found: %v", err)
}
}