Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
927ca879b2 | |||
00450c9cc7 | |||
534bce2d20 | |||
53949be0cc | |||
d8fe2ff8b4 | |||
53b5ee2c6f | |||
dfd85cd871 | |||
52182261af | |||
1f3834e187 | |||
0354873c3a | |||
8e5e2167cd | |||
c26a7db47c | |||
74765b4c5f | |||
8bd7323af1 | |||
|
899dc8b3bc | ||
6e6c31b5dd | |||
94929878fe | |||
8ce469a09e | |||
88788776d2 | |||
e143e2b547 | |||
a36f99e30b | |||
326ee53333 | |||
1244c5bb4d | |||
4ccc8a9c85 | |||
8a2e84d489 | |||
d29363b78d | |||
734f751055 | |||
55d8a9ee20 | |||
07c93042ba | |||
b9bbfdf159 | |||
fbad257acc | |||
1829febb6e | |||
7838fa62a8 | |||
332803d8de | |||
11c868d476 | |||
38d6e482d7 | |||
07d4085201 | |||
45f30c0be3 | |||
6af837fd25 |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
|
21
.github/workflows/codeql-analysis.yml
vendored
21
.github/workflows/codeql-analysis.yml
vendored
@@ -9,7 +9,7 @@
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
name: "codeql"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
@@ -17,16 +17,16 @@ on:
|
||||
types:
|
||||
- completed
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ master, v3 ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
branches: [ master, v3 ]
|
||||
schedule:
|
||||
- cron: '34 1 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
name: analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -42,11 +42,14 @@ jobs:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: setup
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
- name: init
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
@@ -57,7 +60,7 @@ jobs:
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
- name: autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
@@ -71,5 +74,5 @@ jobs:
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
- name: analyze
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
75
.github/workflows/dependabot-automerge.yml
vendored
75
.github/workflows/dependabot-automerge.yml
vendored
@@ -1,66 +1,31 @@
|
||||
name: "prautomerge"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["prbuild"]
|
||||
types:
|
||||
- completed
|
||||
pull_request_target:
|
||||
types: [assigned, opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
Dependabot-Automerge:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
# Contains workaround to execute if dependabot updates the PR by checking for the base branch in the linked PR
|
||||
# The the github.event.workflow_run.event value is 'push' and not 'pull_request'
|
||||
# dont work with multiple workflows when last returns success
|
||||
if: >-
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
&& github.actor == 'dependabot[bot]'
|
||||
&& github.event.sender.login == 'dependabot[bot]'
|
||||
&& github.event.sender.type == 'Bot'
|
||||
&& (github.event.workflow_run.event == 'pull_request'
|
||||
|| (github.event.workflow_run.event == 'push' && github.event.workflow_run.pull_requests[0].base.ref == github.event.repository.default_branch ))
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Approve Changes and Merge changes if label 'dependencies' is set
|
||||
uses: actions/github-script@v5
|
||||
- name: metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v1.1.1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
console.log(context.payload.workflow_run);
|
||||
|
||||
var labelNames = await github.paginate(
|
||||
github.issues.listLabelsOnIssue,
|
||||
{
|
||||
repo: context.repo.repo,
|
||||
owner: context.repo.owner,
|
||||
issue_number: context.payload.workflow_run.pull_requests[0].number,
|
||||
},
|
||||
(response) => response.data.map(
|
||||
(label) => label.name
|
||||
)
|
||||
);
|
||||
|
||||
console.log(labelNames);
|
||||
|
||||
if (labelNames.includes('dependencies')) {
|
||||
console.log('Found label');
|
||||
|
||||
await github.pulls.createReview({
|
||||
repo: context.repo.repo,
|
||||
owner: context.repo.owner,
|
||||
pull_number: context.payload.workflow_run.pull_requests[0].number,
|
||||
event: 'APPROVE'
|
||||
});
|
||||
console.log('Approved PR');
|
||||
|
||||
await github.pulls.merge({
|
||||
repo: context.repo.repo,
|
||||
owner: context.repo.owner,
|
||||
pull_number: context.payload.workflow_run.pull_requests[0].number,
|
||||
});
|
||||
|
||||
console.log('Merged PR');
|
||||
}
|
||||
github-token: "${{ secrets.TOKEN }}"
|
||||
- name: approve
|
||||
run: gh pr review --approve "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.TOKEN}}
|
||||
- name: merge
|
||||
if: ${{contains(steps.metadata.outputs.dependency-names, 'go.unistack.org')}}
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.TOKEN}}
|
||||
|
1
.github/workflows/pr.yml
vendored
1
.github/workflows/pr.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Micro [](https://opensource.org/licenses/Apache-2.0) [](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [](https://goreportcard.com/report/github.com/unistack-org/micro) [](https://unistack-org.slack.com/messages/default)
|
||||
# Micro [](https://opensource.org/licenses/Apache-2.0) [](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [](https://goreportcard.com/report/go.unistack.org/micro/v3) [](https://unistack-org.slack.com/messages/default)
|
||||
|
||||
Micro is a standard library for microservices.
|
||||
|
||||
|
@@ -49,6 +49,7 @@ type Message interface {
|
||||
Topic() string
|
||||
Payload() interface{}
|
||||
ContentType() string
|
||||
Metadata() metadata.Metadata
|
||||
}
|
||||
|
||||
// Request is the interface for a synchronous request used by Call or Stream
|
||||
@@ -91,10 +92,16 @@ type Stream interface {
|
||||
Send(msg interface{}) error
|
||||
// Recv will decode and read a response
|
||||
Recv(msg interface{}) error
|
||||
// SendMsg will encode and send a request
|
||||
SendMsg(msg interface{}) error
|
||||
// RecvMsg will decode and read a response
|
||||
RecvMsg(msg interface{}) error
|
||||
// Error returns the stream error
|
||||
Error() error
|
||||
// Close closes the stream
|
||||
Close() error
|
||||
// CloseSend closes the send direction of the stream
|
||||
CloseSend() error
|
||||
}
|
||||
|
||||
// Option used by the Client
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
type LookupFunc func(context.Context, Request, CallOptions) ([]string, error)
|
||||
|
||||
// LookupRoute for a request using the router and then choose one using the selector
|
||||
func LookupRoute(ctx context.Context, req Request, opts CallOptions) ([]string, error) {
|
||||
func LookupRoute(_ context.Context, req Request, opts CallOptions) ([]string, error) {
|
||||
// check to see if an address was provided as a call option
|
||||
if len(opts.Address) > 0 {
|
||||
return opts.Address, nil
|
||||
|
@@ -119,6 +119,14 @@ func (n *noopStream) Recv(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noopStream) SendMsg(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noopStream) RecvMsg(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noopStream) Error() error {
|
||||
return nil
|
||||
}
|
||||
@@ -127,6 +135,10 @@ func (n *noopStream) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noopStream) CloseSend() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noopMessage) Topic() string {
|
||||
return n.topic
|
||||
}
|
||||
@@ -139,6 +151,10 @@ func (n *noopMessage) ContentType() string {
|
||||
return n.opts.ContentType
|
||||
}
|
||||
|
||||
func (n *noopMessage) Metadata() metadata.Metadata {
|
||||
return n.opts.Metadata
|
||||
}
|
||||
|
||||
func (n *noopClient) newCodec(contentType string) (codec.Codec, error) {
|
||||
if cf, ok := n.opts.Codecs[contentType]; ok {
|
||||
return cf, nil
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"go.unistack.org/micro/v3/broker"
|
||||
"go.unistack.org/micro/v3/codec"
|
||||
"go.unistack.org/micro/v3/logger"
|
||||
"go.unistack.org/micro/v3/metadata"
|
||||
"go.unistack.org/micro/v3/meter"
|
||||
"go.unistack.org/micro/v3/network/transport"
|
||||
"go.unistack.org/micro/v3/register"
|
||||
@@ -128,7 +129,7 @@ type PublishOptions struct {
|
||||
|
||||
// NewMessageOptions creates message options struct
|
||||
func NewMessageOptions(opts ...MessageOption) MessageOptions {
|
||||
options := MessageOptions{}
|
||||
options := MessageOptions{Metadata: metadata.New(1)}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
@@ -137,7 +138,10 @@ func NewMessageOptions(opts ...MessageOption) MessageOptions {
|
||||
|
||||
// MessageOptions holds client message options
|
||||
type MessageOptions struct {
|
||||
// Metadata additional metadata
|
||||
Metadata metadata.Metadata
|
||||
// ContentType specify content-type of message
|
||||
// deprecated
|
||||
ContentType string
|
||||
}
|
||||
|
||||
@@ -517,6 +521,7 @@ func WithSelectOptions(sops ...selector.SelectOption) CallOption {
|
||||
// Deprecated
|
||||
func WithMessageContentType(ct string) MessageOption {
|
||||
return func(o *MessageOptions) {
|
||||
o.Metadata.Set(metadata.HeaderContentType, ct)
|
||||
o.ContentType = ct
|
||||
}
|
||||
}
|
||||
@@ -524,10 +529,18 @@ func WithMessageContentType(ct string) MessageOption {
|
||||
// MessageContentType sets the message content type
|
||||
func MessageContentType(ct string) MessageOption {
|
||||
return func(o *MessageOptions) {
|
||||
o.Metadata.Set(metadata.HeaderContentType, ct)
|
||||
o.ContentType = ct
|
||||
}
|
||||
}
|
||||
|
||||
// MessageMetadata sets the message metadata
|
||||
func MessageMetadata(k, v string) MessageOption {
|
||||
return func(o *MessageOptions) {
|
||||
o.Metadata.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// StreamingRequest specifies that request is streaming
|
||||
func StreamingRequest(b bool) RequestOption {
|
||||
return func(o *RequestOptions) {
|
||||
|
@@ -20,7 +20,7 @@ func RetryNever(ctx context.Context, req Request, retryCount int, err error) (bo
|
||||
}
|
||||
|
||||
// RetryOnError retries a request on a 500 or timeout error
|
||||
func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
|
||||
func RetryOnError(_ context.Context, _ Request, _ int, err error) (bool, error) {
|
||||
if err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
@@ -5,29 +5,40 @@ type Frame struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// NewFrame returns new frame with data
|
||||
func NewFrame(data []byte) *Frame {
|
||||
return &Frame{Data: data}
|
||||
}
|
||||
|
||||
// MarshalJSON returns frame data
|
||||
func (m *Frame) MarshalJSON() ([]byte, error) {
|
||||
return m.Data, nil
|
||||
return m.Marshal()
|
||||
}
|
||||
|
||||
// UnmarshalJSON set frame data
|
||||
func (m *Frame) UnmarshalJSON(data []byte) error {
|
||||
m.Data = data
|
||||
return nil
|
||||
return m.Unmarshal(data)
|
||||
}
|
||||
|
||||
// ProtoMessage noop func
|
||||
func (m *Frame) ProtoMessage() {}
|
||||
|
||||
// Reset resets frame
|
||||
func (m *Frame) Reset() {
|
||||
*m = Frame{}
|
||||
}
|
||||
|
||||
// String returns frame as string
|
||||
func (m *Frame) String() string {
|
||||
return string(m.Data)
|
||||
}
|
||||
|
||||
// Marshal returns frame data
|
||||
func (m *Frame) Marshal() ([]byte, error) {
|
||||
return m.Data, nil
|
||||
}
|
||||
|
||||
// Unmarshal set frame data
|
||||
func (m *Frame) Unmarshal(data []byte) error {
|
||||
m.Data = data
|
||||
return nil
|
||||
|
@@ -23,6 +23,8 @@ var (
|
||||
ErrInvalidStruct = errors.New("invalid struct specified")
|
||||
// ErrWatcherStopped is returned when source watcher has been stopped
|
||||
ErrWatcherStopped = errors.New("watcher stopped")
|
||||
// ErrWatcherNotImplemented returned when config does not implement watch
|
||||
ErrWatcherNotImplemented = errors.New("watcher not implemented")
|
||||
)
|
||||
|
||||
// Config is an interface abstraction for dynamic configuration
|
||||
|
@@ -32,3 +32,33 @@ func SetOption(k, v interface{}) Option {
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSaveOption returns a function to setup a context with given value
|
||||
func SetSaveOption(k, v interface{}) SaveOption {
|
||||
return func(o *SaveOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLoadOption returns a function to setup a context with given value
|
||||
func SetLoadOption(k, v interface{}) LoadOption {
|
||||
return func(o *LoadOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetWatchOption returns a function to setup a context with given value
|
||||
func SetWatchOption(k, v interface{}) WatchOption {
|
||||
return func(o *WatchOptions) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -271,7 +270,7 @@ func (c *defaultConfig) Name() string {
|
||||
}
|
||||
|
||||
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
return nil, ErrWatcherNotImplemented
|
||||
}
|
||||
|
||||
// NewConfig returns new default config source
|
||||
|
@@ -66,6 +66,7 @@ type LoadOptions struct {
|
||||
Struct interface{}
|
||||
Override bool
|
||||
Append bool
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
func NewLoadOptions(opts ...LoadOption) LoadOptions {
|
||||
@@ -102,7 +103,8 @@ type SaveOption func(o *SaveOptions)
|
||||
|
||||
// SaveOptions struct
|
||||
type SaveOptions struct {
|
||||
Struct interface{}
|
||||
Struct interface{}
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// SaveStruct override struct for save to config
|
||||
|
127
errors/errors.go
127
errors/errors.go
@@ -6,6 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -53,6 +55,22 @@ func (e *Error) Error() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
/*
|
||||
// Generator struct holds id of error
|
||||
type Generator struct {
|
||||
id string
|
||||
}
|
||||
|
||||
// Generator can emit new error with static id
|
||||
func NewGenerator(id string) *Generator {
|
||||
return &Generator{id: id}
|
||||
}
|
||||
|
||||
func (g *Generator) BadRequest(format string, args ...interface{}) error {
|
||||
return BadRequest(g.id, format, args...)
|
||||
}
|
||||
*/
|
||||
|
||||
// New generates a custom error
|
||||
func New(id, detail string, code int32) error {
|
||||
return &Error{
|
||||
@@ -75,121 +93,121 @@ func Parse(err string) *Error {
|
||||
}
|
||||
|
||||
// BadRequest generates a 400 error.
|
||||
func BadRequest(id, format string, a ...interface{}) error {
|
||||
func BadRequest(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 400,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(400),
|
||||
}
|
||||
}
|
||||
|
||||
// Unauthorized generates a 401 error.
|
||||
func Unauthorized(id, format string, a ...interface{}) error {
|
||||
func Unauthorized(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 401,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(401),
|
||||
}
|
||||
}
|
||||
|
||||
// Forbidden generates a 403 error.
|
||||
func Forbidden(id, format string, a ...interface{}) error {
|
||||
func Forbidden(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 403,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(403),
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound generates a 404 error.
|
||||
func NotFound(id, format string, a ...interface{}) error {
|
||||
func NotFound(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 404,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(404),
|
||||
}
|
||||
}
|
||||
|
||||
// MethodNotAllowed generates a 405 error.
|
||||
func MethodNotAllowed(id, format string, a ...interface{}) error {
|
||||
func MethodNotAllowed(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 405,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(405),
|
||||
}
|
||||
}
|
||||
|
||||
// Timeout generates a 408 error.
|
||||
func Timeout(id, format string, a ...interface{}) error {
|
||||
func Timeout(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 408,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(408),
|
||||
}
|
||||
}
|
||||
|
||||
// Conflict generates a 409 error.
|
||||
func Conflict(id, format string, a ...interface{}) error {
|
||||
func Conflict(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 409,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(409),
|
||||
}
|
||||
}
|
||||
|
||||
// InternalServerError generates a 500 error.
|
||||
func InternalServerError(id, format string, a ...interface{}) error {
|
||||
func InternalServerError(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 500,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(500),
|
||||
}
|
||||
}
|
||||
|
||||
// NotImplemented generates a 501 error
|
||||
func NotImplemented(id, format string, a ...interface{}) error {
|
||||
func NotImplemented(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 501,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(501),
|
||||
}
|
||||
}
|
||||
|
||||
// BadGateway generates a 502 error
|
||||
func BadGateway(id, format string, a ...interface{}) error {
|
||||
func BadGateway(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 502,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(502),
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceUnavailable generates a 503 error
|
||||
func ServiceUnavailable(id, format string, a ...interface{}) error {
|
||||
func ServiceUnavailable(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 503,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(503),
|
||||
}
|
||||
}
|
||||
|
||||
// GatewayTimeout generates a 504 error
|
||||
func GatewayTimeout(id, format string, a ...interface{}) error {
|
||||
func GatewayTimeout(id, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
ID: id,
|
||||
Code: 504,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Detail: fmt.Sprintf(format, args...),
|
||||
Status: http.StatusText(504),
|
||||
}
|
||||
}
|
||||
@@ -222,3 +240,64 @@ func FromError(err error) *Error {
|
||||
|
||||
return Parse(err.Error())
|
||||
}
|
||||
|
||||
// MarshalJSON returns error data
|
||||
func (e *Error) MarshalJSON() ([]byte, error) {
|
||||
return e.Marshal()
|
||||
}
|
||||
|
||||
// UnmarshalJSON set error data
|
||||
func (e *Error) UnmarshalJSON(data []byte) error {
|
||||
return e.Unmarshal(data)
|
||||
}
|
||||
|
||||
// ProtoMessage noop func
|
||||
func (e *Error) ProtoMessage() {}
|
||||
|
||||
// Reset resets error
|
||||
func (e *Error) Reset() {
|
||||
*e = Error{}
|
||||
}
|
||||
|
||||
// String returns error as string
|
||||
func (e *Error) String() string {
|
||||
return fmt.Sprintf(`{"id":"%s","detail":"%s","status":"%s","code":%d}`, e.ID, e.Detail, e.Status, e.Code)
|
||||
}
|
||||
|
||||
// Marshal returns error data
|
||||
func (e *Error) Marshal() ([]byte, error) {
|
||||
return []byte(e.String()), nil
|
||||
}
|
||||
|
||||
// Unmarshal set error data
|
||||
func (e *Error) Unmarshal(data []byte) error {
|
||||
str := string(data)
|
||||
if len(data) < 41 {
|
||||
return fmt.Errorf("invalid data")
|
||||
}
|
||||
parts := strings.FieldsFunc(str[1:len(str)-1], func(r rune) bool {
|
||||
return r == ','
|
||||
})
|
||||
for _, part := range parts {
|
||||
nparts := strings.FieldsFunc(part, func(r rune) bool {
|
||||
return r == ':'
|
||||
})
|
||||
for idx := 0; idx < len(nparts); idx++ {
|
||||
switch {
|
||||
case nparts[idx] == `"id"`:
|
||||
e.ID = nparts[idx+1][1 : len(nparts[idx+1])-1]
|
||||
case nparts[idx] == `"detail"`:
|
||||
e.Detail = nparts[idx+1][1 : len(nparts[idx+1])-1]
|
||||
case nparts[idx] == `"status"`:
|
||||
e.Status = nparts[idx+1][1 : len(nparts[idx+1])-1]
|
||||
case nparts[idx] == `"code"`:
|
||||
c, err := strconv.ParseInt(nparts[idx+1], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Code = int32(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
31
errors/errors.proto
Normal file
31
errors/errors.proto
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 Unistack LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package micro.errors;
|
||||
|
||||
option cc_enable_arenas = true;
|
||||
option go_package = "go.unistack.org/micro/v3/errors;errors";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "MicroErrors";
|
||||
option java_package = "micro.errors";
|
||||
option objc_class_prefix = "MERRORS";
|
||||
|
||||
message Error {
|
||||
string id = 1;
|
||||
string detail = 2;
|
||||
string status = 3;
|
||||
uint32 code = 4;
|
||||
}
|
@@ -349,6 +349,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
|
||||
return eid, err
|
||||
}
|
||||
|
||||
// NewFlow create new flow
|
||||
func NewFlow(opts ...Option) Flow {
|
||||
options := NewOptions(opts...)
|
||||
return µFlow{opts: options}
|
||||
@@ -574,11 +575,13 @@ func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...Ex
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewCallStep create new step with client.Call
|
||||
func NewCallStep(service string, name string, method string, opts ...StepOption) Step {
|
||||
options := NewStepOptions(opts...)
|
||||
return µCallStep{service: service, method: name + "." + method, opts: options}
|
||||
}
|
||||
|
||||
// NewPublishStep create new step with client.Publish
|
||||
func NewPublishStep(topic string, opts ...StepOption) Step {
|
||||
options := NewStepOptions(opts...)
|
||||
return µPublishStep{topic: topic, opts: options}
|
||||
|
@@ -70,7 +70,7 @@ func Client(c client.Client) Option {
|
||||
|
||||
// Context specifies a context for the service.
|
||||
// Can be used to signal shutdown of the flow
|
||||
// Can be used for extra option values.
|
||||
// or can be used for extra option values.
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = ctx
|
||||
@@ -91,7 +91,7 @@ func Store(s store.Store) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WorflowOption signature
|
||||
// WorflowOption func signature
|
||||
type WorkflowOption func(*WorkflowOptions)
|
||||
|
||||
// WorkflowOptions holds workflow options
|
||||
@@ -107,6 +107,7 @@ func WorkflowID(id string) WorkflowOption {
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteOptions holds execute options
|
||||
type ExecuteOptions struct {
|
||||
// Client holds the client.Client
|
||||
Client client.Client
|
||||
@@ -128,56 +129,66 @@ type ExecuteOptions struct {
|
||||
Async bool
|
||||
}
|
||||
|
||||
// ExecuteOption func signature
|
||||
type ExecuteOption func(*ExecuteOptions)
|
||||
|
||||
// ExecuteClient pass client.Client to ExecuteOption
|
||||
func ExecuteClient(c client.Client) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Client = c
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTracer pass tracer.Tracer to ExecuteOption
|
||||
func ExecuteTracer(t tracer.Tracer) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteLogger pass logger.Logger to ExecuteOption
|
||||
func ExecuteLogger(l logger.Logger) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteMeter pass meter.Meter to ExecuteOption
|
||||
func ExecuteMeter(m meter.Meter) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteContext pass context.Context ot ExecuteOption
|
||||
func ExecuteContext(ctx context.Context) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteReverse says that dag must be run in reverse order
|
||||
func ExecuteReverse(b bool) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Reverse = b
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTimeout pass timeout time.Duration for execution
|
||||
func ExecuteTimeout(td time.Duration) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Timeout = td
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteAsync says that caller does not wait for execution complete
|
||||
func ExecuteAsync(b bool) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Async = b
|
||||
}
|
||||
}
|
||||
|
||||
// NewExecuteOptions create new ExecuteOptions struct
|
||||
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
|
||||
options := ExecuteOptions{
|
||||
Client: client.DefaultClient,
|
||||
@@ -192,6 +203,7 @@ func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// StepOptions holds step options
|
||||
type StepOptions struct {
|
||||
Context context.Context
|
||||
Fallback string
|
||||
@@ -199,8 +211,10 @@ type StepOptions struct {
|
||||
Requires []string
|
||||
}
|
||||
|
||||
// StepOption func signature
|
||||
type StepOption func(*StepOptions)
|
||||
|
||||
// NewStepOptions create new StepOptions struct
|
||||
func NewStepOptions(opts ...StepOption) StepOptions {
|
||||
options := StepOptions{
|
||||
Context: context.Background(),
|
||||
@@ -211,18 +225,21 @@ func NewStepOptions(opts ...StepOption) StepOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// StepID sets the step id for dag
|
||||
func StepID(id string) StepOption {
|
||||
return func(o *StepOptions) {
|
||||
o.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
// StepRequires specifies required steps
|
||||
func StepRequires(steps ...string) StepOption {
|
||||
return func(o *StepOptions) {
|
||||
o.Requires = steps
|
||||
}
|
||||
}
|
||||
|
||||
// StepFallback set the step to run on error
|
||||
func StepFallback(step string) StepOption {
|
||||
return func(o *StepOptions) {
|
||||
o.Fallback = step
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package micro
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package micro
|
||||
|
4
go.mod
4
go.mod
@@ -4,11 +4,11 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/ef-ds/deque v1.0.4
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4
|
||||
go.unistack.org/micro-proto/v3 v3.1.0
|
||||
go.unistack.org/micro-proto/v3 v3.1.1
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
|
8
go.sum
8
go.sum
@@ -1,7 +1,7 @@
|
||||
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
|
||||
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -11,8 +11,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4 h1:fOH64AB0C3ixGf9emky61STvPJL3smxJg+1Zwx1oCdg=
|
||||
github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
|
||||
go.unistack.org/micro-proto/v3 v3.1.0 h1:q39FwjFiRZn+Ux/tt+d3bJTmDtsQQWa+3SLYVo1vLfA=
|
||||
go.unistack.org/micro-proto/v3 v3.1.0/go.mod h1:DpRhYCBXlmSJ/AAXTmntvlh7kQkYU6eFvlmYAx4BQS8=
|
||||
go.unistack.org/micro-proto/v3 v3.1.1 h1:78qRmltwGek5kSQ9tNmDZ9TCRvZM7YDIOgzriKvabjA=
|
||||
go.unistack.org/micro-proto/v3 v3.1.1/go.mod h1:DpRhYCBXlmSJ/AAXTmntvlh7kQkYU6eFvlmYAx4BQS8=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@@ -81,6 +81,8 @@ func (l *defaultLogger) Fields(fields ...interface{}) Logger {
|
||||
} else if len(fields)%2 != 0 {
|
||||
fields = fields[:len(fields)-1]
|
||||
}
|
||||
nl.logFunc = l.logFunc
|
||||
nl.logfFunc = l.logfFunc
|
||||
nl.opts.Fields = append(nl.opts.Fields, fields...)
|
||||
return nl
|
||||
}
|
||||
|
@@ -7,6 +7,37 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
|
||||
if err := l.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nl, ok := FromContext(NewContext(ctx, l.Fields("key", "val")))
|
||||
if !ok {
|
||||
t.Fatal("context without logger")
|
||||
}
|
||||
nl.Info(ctx, "message")
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
|
||||
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFields(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
|
||||
if err := l.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l.Fields("key", "val").Info(ctx, "message")
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
|
||||
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
@@ -10,6 +10,7 @@ type stdLogger struct {
|
||||
level Level
|
||||
}
|
||||
|
||||
// NewStdLogger returns new *log.Logger baked by logger.Logger implementation
|
||||
func NewStdLogger(l Logger, level Level) *log.Logger {
|
||||
return log.New(&stdLogger{l: l, level: level}, "" /* prefix */, 0 /* flags */)
|
||||
}
|
||||
@@ -20,6 +21,7 @@ func (sl *stdLogger) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// RedirectStdLogger replace *log.Logger with logger.Logger implementation
|
||||
func RedirectStdLogger(l Logger, level Level) func() {
|
||||
flags := log.Flags()
|
||||
prefix := log.Prefix()
|
||||
|
@@ -20,104 +20,104 @@ type Wrapper interface {
|
||||
Logf(LogfFunc) LogfFunc
|
||||
}
|
||||
|
||||
var _ Logger = &OmitLogger{}
|
||||
var _ Logger = &omitLogger{}
|
||||
|
||||
type OmitLogger struct {
|
||||
type omitLogger struct {
|
||||
l Logger
|
||||
}
|
||||
|
||||
func NewOmitLogger(l Logger) Logger {
|
||||
return &OmitLogger{l: l}
|
||||
return &omitLogger{l: l}
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Init(opts ...Option) error {
|
||||
func (w *omitLogger) Init(opts ...Option) error {
|
||||
return w.l.Init(append(opts, WrapLogger(NewOmitWrapper()))...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) V(level Level) bool {
|
||||
func (w *omitLogger) V(level Level) bool {
|
||||
return w.l.V(level)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Level(level Level) {
|
||||
func (w *omitLogger) Level(level Level) {
|
||||
w.l.Level(level)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Clone(opts ...Option) Logger {
|
||||
func (w *omitLogger) Clone(opts ...Option) Logger {
|
||||
return w.l.Clone(opts...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Options() Options {
|
||||
func (w *omitLogger) Options() Options {
|
||||
return w.l.Options()
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Fields(fields ...interface{}) Logger {
|
||||
func (w *omitLogger) Fields(fields ...interface{}) Logger {
|
||||
return w.l.Fields(fields...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
|
||||
func (w *omitLogger) Info(ctx context.Context, args ...interface{}) {
|
||||
w.l.Info(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Trace(ctx context.Context, args ...interface{}) {
|
||||
func (w *omitLogger) Trace(ctx context.Context, args ...interface{}) {
|
||||
w.l.Trace(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Debug(ctx context.Context, args ...interface{}) {
|
||||
func (w *omitLogger) Debug(ctx context.Context, args ...interface{}) {
|
||||
w.l.Debug(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Warn(ctx context.Context, args ...interface{}) {
|
||||
func (w *omitLogger) Warn(ctx context.Context, args ...interface{}) {
|
||||
w.l.Warn(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Error(ctx context.Context, args ...interface{}) {
|
||||
func (w *omitLogger) Error(ctx context.Context, args ...interface{}) {
|
||||
w.l.Error(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Fatal(ctx context.Context, args ...interface{}) {
|
||||
func (w *omitLogger) Fatal(ctx context.Context, args ...interface{}) {
|
||||
w.l.Fatal(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
|
||||
func (w *omitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Infof(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
|
||||
func (w *omitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Tracef(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
|
||||
func (w *omitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Debugf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
|
||||
func (w *omitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Warnf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
|
||||
func (w *omitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Errorf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
|
||||
func (w *omitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Fatalf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
|
||||
func (w *omitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
|
||||
w.l.Log(ctx, level, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
|
||||
func (w *omitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
|
||||
w.l.Logf(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) String() string {
|
||||
func (w *omitLogger) String() string {
|
||||
return w.l.String()
|
||||
}
|
||||
|
||||
type OmitWrapper struct{}
|
||||
type omitWrapper struct{}
|
||||
|
||||
func NewOmitWrapper() Wrapper {
|
||||
return &OmitWrapper{}
|
||||
return &omitWrapper{}
|
||||
}
|
||||
|
||||
func getArgs(args []interface{}) []interface{} {
|
||||
@@ -153,13 +153,13 @@ func getArgs(args []interface{}) []interface{} {
|
||||
return nargs
|
||||
}
|
||||
|
||||
func (w *OmitWrapper) Log(fn LogFunc) LogFunc {
|
||||
func (w *omitWrapper) Log(fn LogFunc) LogFunc {
|
||||
return func(ctx context.Context, level Level, args ...interface{}) {
|
||||
fn(ctx, level, getArgs(args)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
|
||||
func (w *omitWrapper) Logf(fn LogfFunc) LogfFunc {
|
||||
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
|
||||
fn(ctx, level, msg, getArgs(args)...)
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// protoc-gen-go-micro version: v3.5.2
|
||||
// protoc-gen-go-micro version: v3.5.3
|
||||
// source: handler.proto
|
||||
|
||||
package handler
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// protoc-gen-go-micro version: v3.5.2
|
||||
// protoc-gen-go-micro version: v3.5.3
|
||||
// source: handler.proto
|
||||
|
||||
package handler
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
// Option powers the configuration for metrics implementations:
|
||||
type Option func(*Options)
|
||||
|
||||
// Options for metrics implementations:
|
||||
// Options for metrics implementations
|
||||
type Options struct {
|
||||
// Logger used for logging
|
||||
Logger logger.Logger
|
||||
@@ -102,6 +102,7 @@ func Logger(l logger.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Labels sets the meter labels
|
||||
func Labels(ls ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.Labels = append(o.Labels, ls...)
|
||||
|
@@ -11,25 +11,38 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ClientRequestDurationSeconds = "client_request_duration_seconds"
|
||||
// ClientRequestDurationSeconds specifies meter metric name
|
||||
ClientRequestDurationSeconds = "client_request_duration_seconds"
|
||||
// ClientRequestLatencyMicroseconds specifies meter metric name
|
||||
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
|
||||
ClientRequestTotal = "client_request_total"
|
||||
ClientRequestInflight = "client_request_inflight"
|
||||
|
||||
ServerRequestDurationSeconds = "server_request_duration_seconds"
|
||||
// ClientRequestTotal specifies meter metric name
|
||||
ClientRequestTotal = "client_request_total"
|
||||
// ClientRequestInflight specifies meter metric name
|
||||
ClientRequestInflight = "client_request_inflight"
|
||||
// ServerRequestDurationSeconds specifies meter metric name
|
||||
ServerRequestDurationSeconds = "server_request_duration_seconds"
|
||||
// ServerRequestLatencyMicroseconds specifies meter metric name
|
||||
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
|
||||
ServerRequestTotal = "server_request_total"
|
||||
ServerRequestInflight = "server_request_inflight"
|
||||
|
||||
PublishMessageDurationSeconds = "publish_message_duration_seconds"
|
||||
// ServerRequestTotal specifies meter metric name
|
||||
ServerRequestTotal = "server_request_total"
|
||||
// ServerRequestInflight specifies meter metric name
|
||||
ServerRequestInflight = "server_request_inflight"
|
||||
// PublishMessageDurationSeconds specifies meter metric name
|
||||
PublishMessageDurationSeconds = "publish_message_duration_seconds"
|
||||
// PublishMessageLatencyMicroseconds specifies meter metric name
|
||||
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
|
||||
PublishMessageTotal = "publish_message_total"
|
||||
PublishMessageInflight = "publish_message_inflight"
|
||||
|
||||
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
|
||||
// PublishMessageTotal specifies meter metric name
|
||||
PublishMessageTotal = "publish_message_total"
|
||||
// PublishMessageInflight specifies meter metric name
|
||||
PublishMessageInflight = "publish_message_inflight"
|
||||
// SubscribeMessageDurationSeconds specifies meter metric name
|
||||
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
|
||||
// SubscribeMessageLatencyMicroseconds specifies meter metric name
|
||||
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
|
||||
SubscribeMessageTotal = "subscribe_message_total"
|
||||
SubscribeMessageInflight = "subscribe_message_inflight"
|
||||
// SubscribeMessageTotal specifies meter metric name
|
||||
SubscribeMessageTotal = "subscribe_message_total"
|
||||
// SubscribeMessageInflight specifies meter metric name
|
||||
SubscribeMessageInflight = "subscribe_message_inflight"
|
||||
|
||||
labelSuccess = "success"
|
||||
labelFailure = "failure"
|
||||
@@ -40,14 +53,17 @@ var (
|
||||
DefaultSkipEndpoints = []string{"Meter.Metrics"}
|
||||
)
|
||||
|
||||
// Options struct
|
||||
type Options struct {
|
||||
Meter meter.Meter
|
||||
lopts []meter.Option
|
||||
SkipEndpoints []string
|
||||
}
|
||||
|
||||
// Option func signature
|
||||
type Option func(*Options)
|
||||
|
||||
// NewOptions creates new Options struct
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Meter: meter.DefaultMeter,
|
||||
@@ -60,30 +76,35 @@ func NewOptions(opts ...Option) Options {
|
||||
return options
|
||||
}
|
||||
|
||||
// ServiceName passes service name to meter label
|
||||
func ServiceName(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.lopts = append(o.lopts, meter.Labels("name", name))
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceVersion passes service version to meter label
|
||||
func ServiceVersion(version string) Option {
|
||||
return func(o *Options) {
|
||||
o.lopts = append(o.lopts, meter.Labels("version", version))
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceID passes service id to meter label
|
||||
func ServiceID(id string) Option {
|
||||
return func(o *Options) {
|
||||
o.lopts = append(o.lopts, meter.Labels("id", id))
|
||||
}
|
||||
}
|
||||
|
||||
// Meter passes meter
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// SkipEndpoint add endpoint to skip
|
||||
func SkipEndoints(eps ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
|
||||
@@ -96,6 +117,7 @@ type wrapper struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
// NewClientWrapper create new client wrapper
|
||||
func NewClientWrapper(opts ...Option) client.Wrapper {
|
||||
return func(c client.Client) client.Client {
|
||||
handler := &wrapper{
|
||||
@@ -106,6 +128,7 @@ func NewClientWrapper(opts ...Option) client.Wrapper {
|
||||
}
|
||||
}
|
||||
|
||||
// NewCallWrapper create new call wrapper
|
||||
func NewCallWrapper(opts ...Option) client.CallWrapper {
|
||||
return func(fn client.CallFunc) client.CallFunc {
|
||||
handler := &wrapper{
|
||||
@@ -231,6 +254,7 @@ func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.
|
||||
return err
|
||||
}
|
||||
|
||||
// NewHandlerWrapper create new server handler wrapper
|
||||
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
|
||||
handler := &wrapper{
|
||||
opts: NewOptions(opts...),
|
||||
@@ -240,7 +264,7 @@ func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
|
||||
|
||||
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
endpoint := req.Endpoint()
|
||||
endpoint := req.Service() + "." + req.Endpoint()
|
||||
for _, ep := range w.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return fn(ctx, req, rsp)
|
||||
@@ -270,6 +294,7 @@ func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// NewSubscribeWrapper create server subscribe wrapper
|
||||
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
|
||||
handler := &wrapper{
|
||||
opts: NewOptions(opts...),
|
||||
|
@@ -51,6 +51,4 @@ func TestExtractEndpoint(t *testing.T) {
|
||||
if endpoints[0].Response != "TestResponse" {
|
||||
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response)
|
||||
}
|
||||
|
||||
t.Logf("XXX %#+v\n", endpoints[0])
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// protoc-gen-go-micro version: v3.5.2
|
||||
// protoc-gen-go-micro version: v3.5.3
|
||||
// source: health.proto
|
||||
|
||||
package health
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// protoc-gen-go-micro version: v3.5.2
|
||||
// protoc-gen-go-micro version: v3.5.3
|
||||
// source: health.proto
|
||||
|
||||
package health
|
||||
|
@@ -16,6 +16,7 @@ import (
|
||||
"go.unistack.org/micro/v3/network/transport"
|
||||
"go.unistack.org/micro/v3/register"
|
||||
"go.unistack.org/micro/v3/tracer"
|
||||
"go.unistack.org/micro/v3/util/id"
|
||||
)
|
||||
|
||||
// Option func
|
||||
@@ -106,7 +107,7 @@ func NewOptions(opts ...Option) Options {
|
||||
Address: DefaultAddress,
|
||||
Name: DefaultName,
|
||||
Version: DefaultVersion,
|
||||
ID: DefaultID,
|
||||
ID: id.Must(),
|
||||
Namespace: DefaultNamespace,
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,6 @@ import (
|
||||
"go.unistack.org/micro/v3/codec"
|
||||
"go.unistack.org/micro/v3/metadata"
|
||||
"go.unistack.org/micro/v3/register"
|
||||
"go.unistack.org/micro/v3/util/id"
|
||||
)
|
||||
|
||||
// DefaultServer default server
|
||||
@@ -21,8 +20,6 @@ var (
|
||||
DefaultName = "server"
|
||||
// DefaultVersion will be used if no version passed
|
||||
DefaultVersion = "latest"
|
||||
// DefaultID will be used if no id passed
|
||||
DefaultID = id.Must()
|
||||
// DefaultRegisterCheck holds func that run before register server
|
||||
DefaultRegisterCheck = func(context.Context) error { return nil }
|
||||
// DefaultRegisterInterval holds interval for register
|
||||
@@ -126,11 +123,21 @@ type Response interface {
|
||||
// The last error will be left in Error().
|
||||
// EOF indicates end of the stream.
|
||||
type Stream interface {
|
||||
// Context for the stream
|
||||
Context() context.Context
|
||||
// Request returns request
|
||||
Request() Request
|
||||
// Send will encode and send a request
|
||||
Send(msg interface{}) error
|
||||
// Recv will decode and read a response
|
||||
Recv(msg interface{}) error
|
||||
// SendMsg will encode and send a request
|
||||
SendMsg(msg interface{}) error
|
||||
// RecvMsg will decode and read a response
|
||||
RecvMsg(msg interface{}) error
|
||||
// Error returns stream error
|
||||
Error() error
|
||||
// Close closes the stream
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
@@ -18,11 +18,11 @@ var (
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
labels = make([]tracer.Label, 0, len(md))
|
||||
for k, v := range md {
|
||||
labels = append(labels, tracer.String(k, v))
|
||||
labels = append(labels, tracer.LabelString(k, v))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
labels = append(labels, tracer.Bool("error", true))
|
||||
labels = append(labels, tracer.LabelBool("error", true))
|
||||
}
|
||||
sp.SetLabels(labels...)
|
||||
}
|
||||
@@ -33,11 +33,11 @@ var (
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
labels = make([]tracer.Label, 0, len(md))
|
||||
for k, v := range md {
|
||||
labels = append(labels, tracer.String(k, v))
|
||||
labels = append(labels, tracer.LabelString(k, v))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
labels = append(labels, tracer.Bool("error", true))
|
||||
labels = append(labels, tracer.LabelBool("error", true))
|
||||
}
|
||||
sp.SetLabels(labels...)
|
||||
}
|
||||
@@ -48,11 +48,11 @@ var (
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
labels = make([]tracer.Label, 0, len(md))
|
||||
for k, v := range md {
|
||||
labels = append(labels, tracer.String(k, v))
|
||||
labels = append(labels, tracer.LabelString(k, v))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
labels = append(labels, tracer.Bool("error", true))
|
||||
labels = append(labels, tracer.LabelBool("error", true))
|
||||
}
|
||||
sp.SetLabels(labels...)
|
||||
}
|
||||
@@ -63,11 +63,11 @@ var (
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
labels = make([]tracer.Label, 0, len(md))
|
||||
for k, v := range md {
|
||||
labels = append(labels, tracer.String(k, v))
|
||||
labels = append(labels, tracer.LabelString(k, v))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
labels = append(labels, tracer.Bool("error", true))
|
||||
labels = append(labels, tracer.LabelBool("error", true))
|
||||
}
|
||||
sp.SetLabels(labels...)
|
||||
}
|
||||
@@ -78,11 +78,11 @@ var (
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
labels = make([]tracer.Label, 0, len(md))
|
||||
for k, v := range md {
|
||||
labels = append(labels, tracer.String(k, v))
|
||||
labels = append(labels, tracer.LabelString(k, v))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
labels = append(labels, tracer.Bool("error", true))
|
||||
labels = append(labels, tracer.LabelBool("error", true))
|
||||
}
|
||||
sp.SetLabels(labels...)
|
||||
}
|
||||
@@ -93,11 +93,11 @@ var (
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
labels = make([]tracer.Label, 0, len(md))
|
||||
for k, v := range md {
|
||||
labels = append(labels, tracer.String(k, v))
|
||||
labels = append(labels, tracer.LabelString(k, v))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
labels = append(labels, tracer.Bool("error", true))
|
||||
labels = append(labels, tracer.LabelBool("error", true))
|
||||
}
|
||||
sp.SetLabels(labels...)
|
||||
}
|
||||
@@ -229,7 +229,10 @@ func (ot *tWrapper) Call(ctx context.Context, req client.Request, rsp interface{
|
||||
}
|
||||
}
|
||||
|
||||
sp := tracer.SpanFromContext(ctx)
|
||||
sp, ok := tracer.SpanFromContext(ctx)
|
||||
if !ok {
|
||||
ctx, sp = ot.opts.Tracer.Start(ctx, endpoint)
|
||||
}
|
||||
defer sp.Finish()
|
||||
|
||||
err := ot.Client.Call(ctx, req, rsp, opts...)
|
||||
@@ -249,7 +252,10 @@ func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...clie
|
||||
}
|
||||
}
|
||||
|
||||
sp := tracer.SpanFromContext(ctx)
|
||||
sp, ok := tracer.SpanFromContext(ctx)
|
||||
if !ok {
|
||||
ctx, sp = ot.opts.Tracer.Start(ctx, endpoint)
|
||||
}
|
||||
defer sp.Finish()
|
||||
|
||||
stream, err := ot.Client.Stream(ctx, req, opts...)
|
||||
@@ -262,7 +268,10 @@ func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...clie
|
||||
}
|
||||
|
||||
func (ot *tWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
|
||||
sp := tracer.SpanFromContext(ctx)
|
||||
sp, ok := tracer.SpanFromContext(ctx)
|
||||
if !ok {
|
||||
ctx, sp = ot.opts.Tracer.Start(ctx, msg.Topic())
|
||||
}
|
||||
defer sp.Finish()
|
||||
|
||||
err := ot.Client.Publish(ctx, msg, opts...)
|
||||
@@ -282,7 +291,10 @@ func (ot *tWrapper) ServerHandler(ctx context.Context, req server.Request, rsp i
|
||||
}
|
||||
}
|
||||
|
||||
sp := tracer.SpanFromContext(ctx)
|
||||
sp, ok := tracer.SpanFromContext(ctx)
|
||||
if !ok {
|
||||
ctx, sp = ot.opts.Tracer.Start(ctx, fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
|
||||
}
|
||||
defer sp.Finish()
|
||||
|
||||
err := ot.serverHandler(ctx, req, rsp)
|
||||
@@ -295,7 +307,10 @@ func (ot *tWrapper) ServerHandler(ctx context.Context, req server.Request, rsp i
|
||||
}
|
||||
|
||||
func (ot *tWrapper) ServerSubscriber(ctx context.Context, msg server.Message) error {
|
||||
sp := tracer.SpanFromContext(ctx)
|
||||
sp, ok := tracer.SpanFromContext(ctx)
|
||||
if !ok {
|
||||
ctx, sp = ot.opts.Tracer.Start(ctx, msg.Topic())
|
||||
}
|
||||
defer sp.Finish()
|
||||
|
||||
err := ot.serverSubscriber(ctx, msg)
|
||||
@@ -339,7 +354,10 @@ func (ot *tWrapper) ClientCallFunc(ctx context.Context, addr string, req client.
|
||||
}
|
||||
}
|
||||
|
||||
sp := tracer.SpanFromContext(ctx)
|
||||
sp, ok := tracer.SpanFromContext(ctx)
|
||||
if !ok {
|
||||
ctx, sp = ot.opts.Tracer.Start(ctx, endpoint)
|
||||
}
|
||||
defer sp.Finish()
|
||||
|
||||
err := ot.clientCallFunc(ctx, addr, req, rsp, opts)
|
||||
|
@@ -2,8 +2,12 @@ package buf // import "go.unistack.org/micro/v3/util/buf"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
var _ io.Closer = &Buffer{}
|
||||
|
||||
// Buffer bytes.Buffer wrapper to satisfie io.Closer interface
|
||||
type Buffer struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package http
|
||||
|
@@ -1,239 +1,708 @@
|
||||
package http
|
||||
|
||||
// Radix tree implementation below is a based on the original work by
|
||||
// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go
|
||||
// (MIT licensed). It's been heavily modified for use as a HTTP routing tree.
|
||||
// Copied from chi mux tree.go https://raw.githubusercontent.com/go-chi/chi/master/tree.go
|
||||
// Modified by Unistack LLC to support interface{} type handler and parameters in map[string]string
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TrieOptions contains search options
|
||||
type TrieOptions struct {
|
||||
IgnoreCase bool
|
||||
}
|
||||
|
||||
// TrieOption func signature
|
||||
type TrieOption func(*TrieOptions)
|
||||
|
||||
// IgnoreCase says that search must be case insensitive
|
||||
func IgnoreCase(b bool) TrieOption {
|
||||
return func(o *TrieOptions) {
|
||||
o.IgnoreCase = b
|
||||
}
|
||||
}
|
||||
|
||||
// Tree is a trie tree.
|
||||
type Trie struct {
|
||||
node *node
|
||||
rcache map[string]*regexp.Regexp
|
||||
rmu sync.RWMutex
|
||||
}
|
||||
|
||||
// node is a node of tree
|
||||
type node struct {
|
||||
actions map[string]interface{} // key is method, val is handler interface
|
||||
children map[string]*node // key is label of next nodes
|
||||
label string
|
||||
}
|
||||
type methodTyp uint
|
||||
|
||||
const (
|
||||
pathRoot string = "/"
|
||||
pathDelimiter string = "/"
|
||||
paramDelimiter string = ":"
|
||||
leftPtnDelimiter string = "{"
|
||||
rightPtnDelimiter string = "}"
|
||||
ptnWildcard string = "(.+)"
|
||||
mSTUB methodTyp = 1 << iota
|
||||
mCONNECT
|
||||
mDELETE
|
||||
mGET
|
||||
mHEAD
|
||||
mOPTIONS
|
||||
mPATCH
|
||||
mPOST
|
||||
mPUT
|
||||
mTRACE
|
||||
)
|
||||
|
||||
// NewTree creates a new trie tree.
|
||||
var mALL = mCONNECT | mDELETE | mGET | mHEAD |
|
||||
mOPTIONS | mPATCH | mPOST | mPUT | mTRACE
|
||||
|
||||
var methodMap = map[string]methodTyp{
|
||||
http.MethodConnect: mCONNECT,
|
||||
http.MethodDelete: mDELETE,
|
||||
http.MethodGet: mGET,
|
||||
http.MethodHead: mHEAD,
|
||||
http.MethodOptions: mOPTIONS,
|
||||
http.MethodPatch: mPATCH,
|
||||
http.MethodPost: mPOST,
|
||||
http.MethodPut: mPUT,
|
||||
http.MethodTrace: mTRACE,
|
||||
}
|
||||
|
||||
// RegisterMethod adds support for custom HTTP method handlers
|
||||
func RegisterMethod(method string) error {
|
||||
if method == "" {
|
||||
return nil
|
||||
}
|
||||
method = strings.ToUpper(method)
|
||||
if _, ok := methodMap[method]; ok {
|
||||
return nil
|
||||
}
|
||||
n := len(methodMap)
|
||||
if n > strconv.IntSize-2 {
|
||||
return fmt.Errorf("max number of methods reached (%d)", strconv.IntSize)
|
||||
}
|
||||
mt := methodTyp(2 << n)
|
||||
methodMap[method] = mt
|
||||
mALL |= mt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type nodeTyp uint8
|
||||
|
||||
const (
|
||||
ntStatic nodeTyp = iota // /home
|
||||
ntRegexp // /{id:[0-9]+}
|
||||
ntParam // /{user}
|
||||
ntCatchAll // /api/v1/*
|
||||
)
|
||||
|
||||
// NewTrie create new tree
|
||||
func NewTrie() *Trie {
|
||||
return &Trie{
|
||||
node: &node{
|
||||
label: pathRoot,
|
||||
actions: make(map[string]interface{}),
|
||||
children: make(map[string]*node),
|
||||
},
|
||||
rcache: make(map[string]*regexp.Regexp),
|
||||
return &Trie{}
|
||||
}
|
||||
|
||||
// Trie holds nodes for path based tree search
|
||||
type Trie struct {
|
||||
// regexp matcher for regexp nodes
|
||||
rex *regexp.Regexp
|
||||
|
||||
// HTTP handler endpoints on the leaf node
|
||||
endpoints endpoints
|
||||
|
||||
// prefix is the common prefix we ignore
|
||||
prefix string
|
||||
|
||||
// child nodes should be stored in-order for iteration,
|
||||
// in groups of the node type.
|
||||
children [ntCatchAll + 1]nodes
|
||||
|
||||
// first byte of the child prefix
|
||||
tail byte
|
||||
|
||||
// node type: static, regexp, param, catchAll
|
||||
typ nodeTyp
|
||||
|
||||
// first byte of the prefix
|
||||
label byte
|
||||
}
|
||||
|
||||
// endpoints is a mapping of http method constants to handlers
|
||||
// for a given route.
|
||||
type endpoints map[methodTyp]*endpoint
|
||||
|
||||
type endpoint struct {
|
||||
// endpoint handler
|
||||
handler interface{}
|
||||
// pattern is the routing pattern for handler nodes
|
||||
pattern string
|
||||
// parameters keys recorded on handler nodes
|
||||
paramKeys []string
|
||||
}
|
||||
|
||||
func (s endpoints) Value(method methodTyp) *endpoint {
|
||||
mh, ok := s[method]
|
||||
if !ok {
|
||||
mh = &endpoint{}
|
||||
s[method] = mh
|
||||
}
|
||||
|
||||
return mh
|
||||
}
|
||||
|
||||
// Insert add elemenent to tree
|
||||
func (n *Trie) Insert(methods []string, pattern string, handler interface{}) error {
|
||||
var err error
|
||||
for _, method := range methods {
|
||||
if err = n.insert(methodMap[method], pattern, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Trie) insert(method methodTyp, pattern string, handler interface{}) error {
|
||||
var parent *Trie
|
||||
search := pattern
|
||||
|
||||
for {
|
||||
// Handle key exhaustion
|
||||
if len(search) == 0 {
|
||||
// Insert or update the node's leaf handler
|
||||
return n.setEndpoint(method, handler, pattern)
|
||||
}
|
||||
|
||||
// We're going to be searching for a wild node next,
|
||||
// in this case, we need to get the tail
|
||||
label := search[0]
|
||||
var segTail byte
|
||||
var segEndIdx int
|
||||
var segTyp nodeTyp
|
||||
var segRexpat string
|
||||
var err error
|
||||
if label == '{' || label == '*' {
|
||||
segTyp, _, segRexpat, segTail, _, segEndIdx, err = patNextSegment(search)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var prefix string
|
||||
if segTyp == ntRegexp {
|
||||
prefix = segRexpat
|
||||
}
|
||||
|
||||
// Look for the edge to attach to
|
||||
parent = n
|
||||
n = n.getEdge(segTyp, label, segTail, prefix)
|
||||
|
||||
// No edge, create one
|
||||
if n == nil {
|
||||
child := &Trie{typ: ntStatic, label: label, tail: segTail, prefix: search}
|
||||
var hn *Trie
|
||||
hn, err = parent.addChild(child, search)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hn.setEndpoint(method, handler, pattern)
|
||||
}
|
||||
|
||||
// Found an edge to match the pattern
|
||||
|
||||
if n.typ > ntStatic {
|
||||
// We found a param node, trim the param from the search path and continue.
|
||||
// This param/wild pattern segment would already be on the tree from a previous
|
||||
// call to addChild when creating a new node.
|
||||
search = search[segEndIdx:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Static nodes fall below here.
|
||||
// Determine longest prefix of the search key on match.
|
||||
commonPrefix := longestPrefix(search, n.prefix)
|
||||
if commonPrefix == len(n.prefix) {
|
||||
// the common prefix is as long as the current node's prefix we're attempting to insert.
|
||||
// keep the search going.
|
||||
search = search[commonPrefix:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Split the node
|
||||
child := &Trie{
|
||||
typ: ntStatic,
|
||||
prefix: search[:commonPrefix],
|
||||
}
|
||||
if err = parent.replaceChild(search[0], segTail, child); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore the existing node
|
||||
n.label = n.prefix[commonPrefix]
|
||||
n.prefix = n.prefix[commonPrefix:]
|
||||
if _, err = child.addChild(n, n.prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the new key is a subset, set the method/handler on this node and finish.
|
||||
search = search[commonPrefix:]
|
||||
if len(search) == 0 {
|
||||
return child.setEndpoint(method, handler, pattern)
|
||||
}
|
||||
|
||||
// Create a new edge for the node
|
||||
subchild := &Trie{
|
||||
typ: ntStatic,
|
||||
label: search[0],
|
||||
prefix: search,
|
||||
}
|
||||
var hn *Trie
|
||||
hn, err = child.addChild(subchild, search)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hn.setEndpoint(method, handler, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts a route definition to tree.
|
||||
func (t *Trie) Insert(methods []string, path string, handler interface{}) {
|
||||
curNode := t.node
|
||||
if path == pathRoot {
|
||||
curNode.label = path
|
||||
for _, method := range methods {
|
||||
curNode.actions[method] = handler
|
||||
}
|
||||
return
|
||||
// addChild appends the new `child` node to the tree using the `pattern` as the trie key.
|
||||
// For a URL router like chi's, we split the static, param, regexp and wildcard segments
|
||||
// into different nodes. In addition, addChild will recursively call itself until every
|
||||
// pattern segment is added to the url pattern tree as individual nodes, depending on type.
|
||||
func (n *Trie) addChild(child *Trie, prefix string) (*Trie, error) {
|
||||
search := prefix
|
||||
|
||||
// handler leaf node added to the tree is the child.
|
||||
// this may be overridden later down the flow
|
||||
hn := child
|
||||
|
||||
// Parse next segment
|
||||
segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx, err := patNextSegment(search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ep := splitPath(path)
|
||||
for i, p := range ep {
|
||||
nextNode, ok := curNode.children[p]
|
||||
if ok {
|
||||
curNode = nextNode
|
||||
}
|
||||
// Create a new node.
|
||||
if !ok {
|
||||
curNode.children[p] = &node{
|
||||
label: p,
|
||||
actions: make(map[string]interface{}),
|
||||
children: make(map[string]*node),
|
||||
// Add child depending on next up segment
|
||||
switch segTyp {
|
||||
|
||||
case ntStatic:
|
||||
// Search prefix is all static (that is, has no params in path)
|
||||
// noop
|
||||
|
||||
default:
|
||||
// Search prefix contains a param, regexp or wildcard
|
||||
|
||||
if segTyp == ntRegexp {
|
||||
rex, err := regexp.Compile(segRexpat)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regexp pattern '%s' in route param", segRexpat)
|
||||
}
|
||||
curNode = curNode.children[p]
|
||||
child.prefix = segRexpat
|
||||
child.rex = rex
|
||||
}
|
||||
// last loop.
|
||||
// If there is already registered data, overwrite it.
|
||||
if i == len(ep)-1 {
|
||||
curNode.label = p
|
||||
for _, method := range methods {
|
||||
curNode.actions[method] = handler
|
||||
|
||||
switch {
|
||||
case segStartIdx == 0:
|
||||
// Route starts with a param
|
||||
child.typ = segTyp
|
||||
|
||||
if segTyp == ntCatchAll {
|
||||
segStartIdx = -1
|
||||
} else {
|
||||
segStartIdx = segEndIdx
|
||||
}
|
||||
if segStartIdx < 0 {
|
||||
segStartIdx = len(search)
|
||||
}
|
||||
child.tail = segTail // for params, we set the tail
|
||||
|
||||
if segStartIdx != len(search) {
|
||||
// add static edge for the remaining part, split the end.
|
||||
// its not possible to have adjacent param nodes, so its certainly
|
||||
// going to be a static node next.
|
||||
|
||||
search = search[segStartIdx:] // advance search position
|
||||
|
||||
nn := &Trie{
|
||||
typ: ntStatic,
|
||||
label: search[0],
|
||||
prefix: search,
|
||||
}
|
||||
var err error
|
||||
hn, err = child.addChild(nn, search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
case segStartIdx > 0:
|
||||
// Route has some param
|
||||
|
||||
// starts with a static segment
|
||||
child.typ = ntStatic
|
||||
child.prefix = search[:segStartIdx]
|
||||
child.rex = nil
|
||||
|
||||
// add the param edge node
|
||||
search = search[segStartIdx:]
|
||||
|
||||
nn := &Trie{
|
||||
typ: segTyp,
|
||||
label: search[0],
|
||||
tail: segTail,
|
||||
}
|
||||
var err error
|
||||
hn, err = child.addChild(nn, search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
n.children[child.typ] = append(n.children[child.typ], child)
|
||||
n.children[child.typ].Sort()
|
||||
return hn, nil
|
||||
}
|
||||
|
||||
// Search searches a path from a tree.
|
||||
func (t *Trie) Search(method string, path string, opts ...TrieOption) (interface{}, map[string]string, bool) {
|
||||
params := make(map[string]string)
|
||||
|
||||
options := TrieOptions{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
curNode := t.node
|
||||
|
||||
nodeLoop:
|
||||
for _, p := range splitPath(path) {
|
||||
nextNode, ok := curNode.children[p]
|
||||
if ok {
|
||||
curNode = nextNode
|
||||
continue nodeLoop
|
||||
}
|
||||
if options.IgnoreCase {
|
||||
// additional loop for case insensitive matching
|
||||
for k, v := range curNode.children {
|
||||
if literalEqual(k, p, true) {
|
||||
curNode = v
|
||||
continue nodeLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(curNode.children) == 0 {
|
||||
if !literalEqual(curNode.label, p, options.IgnoreCase) {
|
||||
// no matching path was found
|
||||
return nil, nil, false
|
||||
}
|
||||
break
|
||||
}
|
||||
isParamMatch := false
|
||||
for c := range curNode.children {
|
||||
if string([]rune(c)[0]) == leftPtnDelimiter {
|
||||
ptn := getPattern(c)
|
||||
t.rmu.RLock()
|
||||
reg, ok := t.rcache[ptn]
|
||||
t.rmu.RUnlock()
|
||||
if !ok {
|
||||
var err error
|
||||
reg, err = regexp.Compile(ptn)
|
||||
if err != nil {
|
||||
return nil, nil, false
|
||||
}
|
||||
t.rmu.Lock()
|
||||
t.rcache[ptn] = reg
|
||||
t.rmu.Unlock()
|
||||
}
|
||||
if reg.Match([]byte(p)) {
|
||||
pn := getParamName(c)
|
||||
params[pn] = p
|
||||
curNode = curNode.children[c]
|
||||
isParamMatch = true
|
||||
break
|
||||
}
|
||||
// no matching param was found.
|
||||
return nil, nil, false
|
||||
}
|
||||
}
|
||||
if !isParamMatch {
|
||||
return nil, nil, false
|
||||
func (n *Trie) replaceChild(label, tail byte, child *Trie) error {
|
||||
for i := 0; i < len(n.children[child.typ]); i++ {
|
||||
if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail {
|
||||
n.children[child.typ][i] = child
|
||||
n.children[child.typ][i].label = label
|
||||
n.children[child.typ][i].tail = tail
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if path == pathRoot {
|
||||
if len(curNode.actions) == 0 {
|
||||
return nil, nil, false
|
||||
return fmt.Errorf("replacing missing child")
|
||||
}
|
||||
|
||||
func (n *Trie) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *Trie {
|
||||
nds := n.children[ntyp]
|
||||
for i := 0; i < len(nds); i++ {
|
||||
if nds[i].label == label && nds[i].tail == tail {
|
||||
if ntyp == ntRegexp && nds[i].prefix != prefix {
|
||||
continue
|
||||
}
|
||||
return nds[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
handler, ok := curNode.actions[method]
|
||||
if !ok || handler == nil {
|
||||
func (n *Trie) setEndpoint(method methodTyp, handler interface{}, pattern string) error {
|
||||
// Set the handler for the method type on the node
|
||||
if n.endpoints == nil {
|
||||
n.endpoints = make(endpoints)
|
||||
}
|
||||
|
||||
paramKeys, err := patParamKeys(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if method&mSTUB == mSTUB {
|
||||
n.endpoints.Value(mSTUB).handler = handler
|
||||
}
|
||||
if method&mALL == mALL {
|
||||
h := n.endpoints.Value(mALL)
|
||||
h.handler = handler
|
||||
h.pattern = pattern
|
||||
h.paramKeys = paramKeys
|
||||
for _, m := range methodMap {
|
||||
h := n.endpoints.Value(m)
|
||||
h.handler = handler
|
||||
h.pattern = pattern
|
||||
h.paramKeys = paramKeys
|
||||
}
|
||||
} else {
|
||||
h := n.endpoints.Value(method)
|
||||
h.handler = handler
|
||||
h.pattern = pattern
|
||||
h.paramKeys = paramKeys
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Search try to find element in tree with path and method
|
||||
func (n *Trie) Search(method string, path string) (interface{}, map[string]string, bool) {
|
||||
params := &routeParams{}
|
||||
// Find the routing handlers for the path
|
||||
rn := n.findRoute(params, methodMap[method], path)
|
||||
if rn == nil {
|
||||
return nil, nil, false
|
||||
}
|
||||
return handler, params, true
|
||||
}
|
||||
|
||||
// getPattern gets a pattern from a label
|
||||
// {id:[^\d+$]} -> ^\d+$
|
||||
// {id} -> (.+)
|
||||
func getPattern(label string) string {
|
||||
leftI := strings.Index(label, leftPtnDelimiter)
|
||||
rightI := strings.Index(label, paramDelimiter)
|
||||
// if label doesn't have any pattern, return wild card pattern as default.
|
||||
if leftI == -1 || rightI == -1 {
|
||||
return ptnWildcard
|
||||
ep, ok := rn.endpoints[methodMap[method]]
|
||||
if !ok {
|
||||
return nil, nil, false
|
||||
}
|
||||
return label[rightI+1 : len(label)-1]
|
||||
|
||||
eparams := make(map[string]string, len(params.keys))
|
||||
for idx, key := range params.keys {
|
||||
eparams[key] = params.vals[idx]
|
||||
}
|
||||
|
||||
return ep.handler, eparams, true
|
||||
}
|
||||
|
||||
// getParamName gets a parameter from a label
|
||||
// {id:[^\d+$]} -> id
|
||||
// {id} -> id
|
||||
func getParamName(label string) string {
|
||||
leftI := strings.Index(label, leftPtnDelimiter)
|
||||
rightI := func(l string) int {
|
||||
r := []rune(l)
|
||||
type routeParams struct {
|
||||
keys []string
|
||||
vals []string
|
||||
}
|
||||
|
||||
var n int
|
||||
// Recursive edge traversal by checking all nodeTyp groups along the way.
|
||||
// It's like searching through a multi-dimensional radix trie.
|
||||
func (n *Trie) findRoute(params *routeParams, method methodTyp, path string) *Trie {
|
||||
nn := n
|
||||
search := path
|
||||
|
||||
loop:
|
||||
for i := 0; i < len(r); i++ {
|
||||
n = i
|
||||
switch string(r[i]) {
|
||||
case paramDelimiter:
|
||||
n = i
|
||||
break loop
|
||||
case rightPtnDelimiter:
|
||||
n = i
|
||||
break loop
|
||||
for t, nds := range nn.children {
|
||||
ntyp := nodeTyp(t)
|
||||
if len(nds) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var xn *Trie
|
||||
xsearch := search
|
||||
|
||||
var label byte
|
||||
if search != "" {
|
||||
label = search[0]
|
||||
}
|
||||
|
||||
switch ntyp {
|
||||
case ntStatic:
|
||||
xn = nds.findEdge(label)
|
||||
if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) {
|
||||
continue
|
||||
}
|
||||
xsearch = xsearch[len(xn.prefix):]
|
||||
|
||||
case ntParam, ntRegexp:
|
||||
// short-circuit and return no matching route for empty param values
|
||||
if xsearch == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if i == len(r)-1 {
|
||||
n = i + 1
|
||||
break loop
|
||||
// serially loop through each node grouped by the tail delimiter
|
||||
for idx := 0; idx < len(nds); idx++ {
|
||||
xn = nds[idx]
|
||||
|
||||
// label for param nodes is the delimiter byte
|
||||
p := strings.IndexByte(xsearch, xn.tail)
|
||||
|
||||
if p < 0 {
|
||||
if xn.tail == '/' {
|
||||
p = len(xsearch)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if ntyp == ntRegexp && p == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if ntyp == ntRegexp && xn.rex != nil {
|
||||
if !xn.rex.MatchString(xsearch[:p]) {
|
||||
continue
|
||||
}
|
||||
} else if strings.IndexByte(xsearch[:p], '/') != -1 {
|
||||
// avoid a match across path segments
|
||||
continue
|
||||
}
|
||||
|
||||
prevlen := len(params.vals)
|
||||
params.vals = append(params.vals, xsearch[:p])
|
||||
xsearch = xsearch[p:]
|
||||
|
||||
if len(xsearch) == 0 {
|
||||
if xn.isLeaf() {
|
||||
h := xn.endpoints[method]
|
||||
if h != nil && h.handler != nil {
|
||||
params.keys = append(params.keys, h.paramKeys...)
|
||||
return xn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recursively find the next node on this branch
|
||||
fin := xn.findRoute(params, method, xsearch)
|
||||
if fin != nil {
|
||||
return fin
|
||||
}
|
||||
|
||||
// not found on this branch, reset vars
|
||||
params.vals = params.vals[:prevlen]
|
||||
xsearch = search
|
||||
}
|
||||
|
||||
params.vals = append(params.vals, "")
|
||||
|
||||
default:
|
||||
// catch-all nodes
|
||||
params.vals = append(params.vals, search)
|
||||
xn = nds[0]
|
||||
xsearch = ""
|
||||
}
|
||||
|
||||
if xn == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// did we find it yet?
|
||||
if len(xsearch) == 0 {
|
||||
if xn.isLeaf() {
|
||||
h := xn.endpoints[method]
|
||||
if h != nil && h.handler != nil {
|
||||
params.keys = append(params.keys, h.paramKeys...)
|
||||
return xn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}(label)
|
||||
// recursively find the next node..
|
||||
fin := xn.findRoute(params, method, xsearch)
|
||||
if fin != nil {
|
||||
return fin
|
||||
}
|
||||
|
||||
return label[leftI+1 : rightI]
|
||||
// Did not find final handler, let's remove the param here if it was set
|
||||
if xn.typ > ntStatic {
|
||||
if len(params.vals) > 0 {
|
||||
params.vals = params.vals[:len(params.vals)-1]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// splitPath removes an empty value in slice.
|
||||
func splitPath(path string) []string {
|
||||
s := strings.Split(path, pathDelimiter)
|
||||
var r []string
|
||||
for _, str := range s {
|
||||
if str != "" {
|
||||
r = append(r, str)
|
||||
func (n *Trie) isLeaf() bool {
|
||||
return n.endpoints != nil
|
||||
}
|
||||
|
||||
// patNextSegment returns the next segment details from a pattern:
|
||||
// node type, param key, regexp string, param tail byte, param starting index, param ending index
|
||||
func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int, error) {
|
||||
ps := strings.Index(pattern, "{")
|
||||
ws := strings.Index(pattern, "*")
|
||||
|
||||
if ps < 0 && ws < 0 {
|
||||
return ntStatic, "", "", 0, 0, len(pattern), nil // we return the entire thing
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if ps >= 0 && ws >= 0 && ws < ps {
|
||||
return ntStatic, "", "", 0, 0, 0, fmt.Errorf("wildcard '*' must be the last pattern in a route, otherwise use a '{param}'")
|
||||
}
|
||||
|
||||
var tail byte = '/' // Default endpoint tail to / byte
|
||||
|
||||
if ps < 0 {
|
||||
// Wildcard pattern as finale
|
||||
if ws < len(pattern)-1 {
|
||||
return ntStatic, "", "", 0, 0, 0, fmt.Errorf("wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead")
|
||||
}
|
||||
|
||||
return ntCatchAll, "*", "", 0, ws, len(pattern), nil
|
||||
}
|
||||
|
||||
// Param/Regexp pattern is next
|
||||
nt := ntParam
|
||||
|
||||
// Read to closing } taking into account opens and closes in curl count (cc)
|
||||
cc := 0
|
||||
pe := ps
|
||||
for i, c := range pattern[ps:] {
|
||||
if c == '{' {
|
||||
cc++
|
||||
} else if c == '}' {
|
||||
cc--
|
||||
if cc == 0 {
|
||||
pe = ps + i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
|
||||
if pe == ps {
|
||||
return ntStatic, "", "", 0, 0, 0, fmt.Errorf("route param closing delimiter '}' is missing")
|
||||
}
|
||||
|
||||
key := pattern[ps+1 : pe]
|
||||
pe++ // set end to next position
|
||||
|
||||
if pe < len(pattern) {
|
||||
tail = pattern[pe]
|
||||
}
|
||||
|
||||
var rexpat string
|
||||
if idx := strings.Index(key, ":"); idx >= 0 {
|
||||
nt = ntRegexp
|
||||
rexpat = key[idx+1:]
|
||||
key = key[:idx]
|
||||
}
|
||||
|
||||
if len(rexpat) > 0 {
|
||||
if rexpat[0] != '^' {
|
||||
rexpat = "^" + rexpat
|
||||
}
|
||||
if rexpat[len(rexpat)-1] != '$' {
|
||||
rexpat += "$"
|
||||
}
|
||||
}
|
||||
|
||||
return nt, key, rexpat, tail, ps, pe, nil
|
||||
}
|
||||
|
||||
func literalEqual(component, literal string, ignoreCase bool) bool {
|
||||
if ignoreCase {
|
||||
return strings.EqualFold(component, literal)
|
||||
func patParamKeys(pattern string) ([]string, error) {
|
||||
pat := pattern
|
||||
paramKeys := []string{}
|
||||
for {
|
||||
ptyp, paramKey, _, _, _, e, err := patNextSegment(pat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ptyp == ntStatic {
|
||||
return paramKeys, nil
|
||||
}
|
||||
for i := 0; i < len(paramKeys); i++ {
|
||||
if paramKeys[i] == paramKey {
|
||||
return nil, fmt.Errorf("routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey)
|
||||
}
|
||||
}
|
||||
paramKeys = append(paramKeys, paramKey)
|
||||
pat = pat[e:]
|
||||
}
|
||||
return component == literal
|
||||
}
|
||||
|
||||
// longestPrefix finds the length of the shared prefix
|
||||
// of two strings
|
||||
func longestPrefix(k1, k2 string) int {
|
||||
max := len(k1)
|
||||
if l := len(k2); l < max {
|
||||
max = l
|
||||
}
|
||||
var i int
|
||||
for i = 0; i < max; i++ {
|
||||
if k1[i] != k2[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
type nodes []*Trie
|
||||
|
||||
// Sort the list of nodes by label
|
||||
func (ns nodes) Sort() { sort.Sort(ns); ns.tailSort() }
|
||||
func (ns nodes) Len() int { return len(ns) }
|
||||
func (ns nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
||||
func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label }
|
||||
|
||||
// tailSort pushes nodes with '/' as the tail to the end of the list for param nodes.
|
||||
// The list order determines the traversal order.
|
||||
func (ns nodes) tailSort() {
|
||||
for i := len(ns) - 1; i >= 0; i-- {
|
||||
if ns[i].typ > ntStatic && ns[i].tail == '/' {
|
||||
ns.Swap(i, len(ns)-1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ns nodes) findEdge(label byte) *Trie {
|
||||
num := len(ns)
|
||||
idx := 0
|
||||
i, j := 0, num-1
|
||||
for i <= j {
|
||||
idx = i + (j-i)/2
|
||||
if label > ns[idx].label {
|
||||
i = idx + 1
|
||||
} else if label < ns[idx].label {
|
||||
j = idx - 1
|
||||
} else {
|
||||
i = num // breaks cond
|
||||
}
|
||||
}
|
||||
|
||||
if ns[idx].label != label {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ns[idx]
|
||||
}
|
||||
|
@@ -5,14 +5,70 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTrieBackwards(t *testing.T) {
|
||||
_ = &Trie{}
|
||||
}
|
||||
|
||||
func TestTrieWildcardPathPrefix(t *testing.T) {
|
||||
var err error
|
||||
type handler struct {
|
||||
name string
|
||||
}
|
||||
tr := NewTrie()
|
||||
if err = tr.Insert([]string{http.MethodPost}, "/v1/update", &handler{name: "post_update"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
if h.(*handler).name != "post_create" {
|
||||
t.Fatalf("invalid handler %v", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriePathPrefix(t *testing.T) {
|
||||
type handler struct {
|
||||
name string
|
||||
}
|
||||
tr := NewTrie()
|
||||
_ = 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 {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
if h.(*handler).name != "post_wildcard" {
|
||||
t.Fatalf("invalid handler %v", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrieFixedPattern(t *testing.T) {
|
||||
type handler struct {
|
||||
name string
|
||||
}
|
||||
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", IgnoreCase(false))
|
||||
_ = 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 {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
@@ -21,43 +77,9 @@ func TestTrieFixedPattern(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrieIgnoreCase(t *testing.T) {
|
||||
type handler struct {
|
||||
name string
|
||||
}
|
||||
tr := NewTrie()
|
||||
tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", &handler{name: "test"})
|
||||
|
||||
_, _, ok := tr.Search(http.MethodPut, "/v1/CREATE/12", IgnoreCase(true))
|
||||
if !ok {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrieContentType(t *testing.T) {
|
||||
type handler struct {
|
||||
name string
|
||||
}
|
||||
tr := NewTrie()
|
||||
tr.Insert([]string{"application/json"}, "/v1/create/{id}", &handler{name: "test"})
|
||||
|
||||
h, _, ok := tr.Search("application/json", "/v1/create/12")
|
||||
if !ok {
|
||||
t.Fatalf("must be found error")
|
||||
}
|
||||
if h.(*handler).name != "test" {
|
||||
t.Fatalf("invalid handler %v", h)
|
||||
}
|
||||
_, _, ok = tr.Search("text/xml", "/v1/create/12")
|
||||
if ok {
|
||||
t.Fatalf("must be not found error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrieNoMatchMethod(t *testing.T) {
|
||||
tr := NewTrie()
|
||||
tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", nil)
|
||||
|
||||
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", nil)
|
||||
_, _, ok := tr.Search(http.MethodPost, "/v1/create")
|
||||
if ok {
|
||||
t.Fatalf("must be not found error")
|
||||
@@ -67,8 +89,7 @@ func TestTrieNoMatchMethod(t *testing.T) {
|
||||
func TestTrieMatchRegexp(t *testing.T) {
|
||||
type handler struct{}
|
||||
tr := NewTrie()
|
||||
tr.Insert([]string{http.MethodPut}, "/v1/create/{category}/{id:[0-9]+}", &handler{})
|
||||
|
||||
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{category}/{id:[0-9]+}", &handler{})
|
||||
_, params, ok := tr.Search(http.MethodPut, "/v1/create/test_cat/12345")
|
||||
switch {
|
||||
case !ok:
|
||||
@@ -83,8 +104,7 @@ func TestTrieMatchRegexp(t *testing.T) {
|
||||
func TestTrieMatchRegexpFail(t *testing.T) {
|
||||
type handler struct{}
|
||||
tr := NewTrie()
|
||||
tr.Insert([]string{http.MethodPut}, "/v1/create/{id:[a-z]+}", &handler{})
|
||||
|
||||
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id:[a-z]+}", &handler{})
|
||||
_, _, ok := tr.Search(http.MethodPut, "/v1/create/12345")
|
||||
if ok {
|
||||
t.Fatalf("route must not be not found")
|
||||
@@ -96,8 +116,8 @@ func TestTrieMatchLongest(t *testing.T) {
|
||||
name string
|
||||
}
|
||||
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"})
|
||||
_ = 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 {
|
||||
t.Fatalf("route must be found")
|
||||
} else if h.(*handler).name != "second" {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package pool
|
||||
|
@@ -10,16 +10,19 @@ type Rand struct {
|
||||
buf [8]byte
|
||||
}
|
||||
|
||||
// Int31 function implementation
|
||||
func (r *Rand) Int31() int32 {
|
||||
_, _ = crand.Read(r.buf[:4])
|
||||
return int32(binary.BigEndian.Uint32(r.buf[:4]) & ^uint32(1<<31))
|
||||
}
|
||||
|
||||
// Int function implementation
|
||||
func (r *Rand) Int() int {
|
||||
u := uint(r.Int63())
|
||||
return int(u << 1 >> 1) // clear sign bit if int == int32
|
||||
}
|
||||
|
||||
// Float64 function implementation
|
||||
func (r *Rand) Float64() float64 {
|
||||
again:
|
||||
f := float64(r.Int63()) / (1 << 63)
|
||||
@@ -29,6 +32,7 @@ again:
|
||||
return f
|
||||
}
|
||||
|
||||
// Float32 function implementation
|
||||
func (r *Rand) Float32() float32 {
|
||||
again:
|
||||
f := float32(r.Float64())
|
||||
@@ -38,14 +42,17 @@ again:
|
||||
return f
|
||||
}
|
||||
|
||||
// Uint32 function implementation
|
||||
func (r *Rand) Uint32() uint32 {
|
||||
return uint32(r.Int63() >> 31)
|
||||
}
|
||||
|
||||
// Uint64 function implementation
|
||||
func (r *Rand) Uint64() uint64 {
|
||||
return uint64(r.Int63())>>31 | uint64(r.Int63())<<32
|
||||
}
|
||||
|
||||
// Intn function implementation
|
||||
func (r *Rand) Intn(n int) int {
|
||||
if n <= 1<<31-1 {
|
||||
return int(r.Int31n(int32(n)))
|
||||
@@ -53,12 +60,13 @@ func (r *Rand) Intn(n int) int {
|
||||
return int(r.Int63n(int64(n)))
|
||||
}
|
||||
|
||||
// Int63 function implementation
|
||||
func (r *Rand) Int63() int64 {
|
||||
_, _ = crand.Read(r.buf[:])
|
||||
return int64(binary.BigEndian.Uint64(r.buf[:]) & ^uint64(1<<63))
|
||||
}
|
||||
|
||||
// Int31n copied from the standard library math/rand implementation of Int31n
|
||||
// Int31n function implementation copied from the standard library math/rand implementation of Int31n
|
||||
func (r *Rand) Int31n(n int32) int32 {
|
||||
if n&(n-1) == 0 { // n is power of two, can mask
|
||||
return r.Int31() & (n - 1)
|
||||
@@ -71,7 +79,7 @@ func (r *Rand) Int31n(n int32) int32 {
|
||||
return v % n
|
||||
}
|
||||
|
||||
// Int63n copied from the standard library math/rand implementation of Int63n
|
||||
// Int63n function implementation copied from the standard library math/rand implementation of Int63n
|
||||
func (r *Rand) Int63n(n int64) int64 {
|
||||
if n&(n-1) == 0 { // n is power of two, can mask
|
||||
return r.Int63() & (n - 1)
|
||||
@@ -84,7 +92,7 @@ func (r *Rand) Int63n(n int64) int64 {
|
||||
return v % n
|
||||
}
|
||||
|
||||
// Shuffle copied from the standard library math/rand implementation of Shuffle
|
||||
// Shuffle function implementation copied from the standard library math/rand implementation of Shuffle
|
||||
func (r *Rand) Shuffle(n int, swap func(i, j int)) {
|
||||
if n < 0 {
|
||||
panic("invalid argument to Shuffle")
|
||||
|
@@ -10,30 +10,40 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidStruct happens when passed not struct and not struct pointer
|
||||
ErrInvalidStruct = errors.New("invalid struct specified")
|
||||
ErrInvalidValue = errors.New("invalid value specified")
|
||||
ErrNotFound = errors.New("struct field not found")
|
||||
// ErrInvalidValue happens when passed invalid value for field
|
||||
ErrInvalidValue = errors.New("invalid value specified")
|
||||
// ErrNotFound happens when struct field not found
|
||||
ErrNotFound = errors.New("struct field not found")
|
||||
)
|
||||
|
||||
// Option func signature
|
||||
type Option func(*Options)
|
||||
|
||||
// Options for merge
|
||||
type Options struct {
|
||||
Tags []string
|
||||
// Tags specifies tags to lookup
|
||||
Tags []string
|
||||
// SliceAppend controls slice appending
|
||||
SliceAppend bool
|
||||
}
|
||||
|
||||
// Tags sets the merge tags for lookup
|
||||
func Tags(t []string) Option {
|
||||
return func(o *Options) {
|
||||
o.Tags = t
|
||||
}
|
||||
}
|
||||
|
||||
// SliceAppend sets the option
|
||||
func SliceAppend(b bool) Option {
|
||||
return func(o *Options) {
|
||||
o.SliceAppend = b
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges map[string]interface{} to destination struct
|
||||
func Merge(dst interface{}, mp map[string]interface{}, opts ...Option) error {
|
||||
var err error
|
||||
var sval reflect.Value
|
||||
@@ -91,7 +101,10 @@ func Merge(dst interface{}, mp map[string]interface{}, opts ...Option) error {
|
||||
|
||||
val, ok := mp[fname]
|
||||
if !ok {
|
||||
continue
|
||||
val, ok = mp[dfld.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
sval = reflect.ValueOf(val)
|
||||
@@ -478,6 +491,7 @@ func IsEmpty(v reflect.Value) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// FieldName returns map field name that can be looked up in struct field
|
||||
func FieldName(name string) string {
|
||||
newstr := make([]rune, 0, len(name))
|
||||
for idx, chr := range name {
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrInvalidParam specifies invalid url query params
|
||||
@@ -72,9 +73,10 @@ func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, e
|
||||
// ZeroFieldByPath clean struct field by its path
|
||||
func ZeroFieldByPath(src interface{}, path string) error {
|
||||
var err error
|
||||
var val reflect.Value
|
||||
val := reflect.ValueOf(src)
|
||||
|
||||
for _, p := range strings.Split(path, ".") {
|
||||
val, err = structValueByName(reflect.ValueOf(src), p)
|
||||
val, err = structValueByName(val, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,6 +95,27 @@ func ZeroFieldByPath(src interface{}, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFieldByPath set struct field by its path
|
||||
func SetFieldByPath(src interface{}, dst interface{}, path string) error {
|
||||
var err error
|
||||
val := reflect.ValueOf(src)
|
||||
|
||||
for _, p := range strings.Split(path, ".") {
|
||||
val, err = structValueByName(val, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !val.CanSet() {
|
||||
return ErrInvalidStruct
|
||||
}
|
||||
|
||||
val.Set(reflect.ValueOf(dst))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// structValueByName get struct field by its name
|
||||
func structValueByName(sv reflect.Value, tkey string) (reflect.Value, error) {
|
||||
if sv.Kind() == reflect.Ptr {
|
||||
@@ -160,9 +183,6 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
|
||||
continue
|
||||
}
|
||||
if fld.Name == tkey || strings.EqualFold(strings.ToLower(fld.Name), strings.ToLower(tkey)) {
|
||||
if val.Kind() != reflect.Ptr && val.CanAddr() {
|
||||
val = val.Addr()
|
||||
}
|
||||
return val.Interface(), nil
|
||||
}
|
||||
|
||||
@@ -214,10 +234,30 @@ func StructFields(src interface{}) ([]StructField, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch val.Interface().(type) {
|
||||
case time.Time, *time.Time:
|
||||
fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
|
||||
continue
|
||||
case time.Duration, *time.Duration:
|
||||
fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
|
||||
continue
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
// case timeKind:
|
||||
// fmt.Printf("GGG\n")
|
||||
// fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
|
||||
case reflect.Ptr:
|
||||
// if !val.IsValid()
|
||||
if reflect.Indirect(val).Kind() == reflect.Struct {
|
||||
infields, err := StructFields(val.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, infield := range infields {
|
||||
infield.Path = fmt.Sprintf("%s.%s", fld.Name, infield.Path)
|
||||
fields = append(fields, infield)
|
||||
}
|
||||
} else {
|
||||
fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
|
||||
}
|
||||
case reflect.Struct:
|
||||
infields, err := StructFields(val.Interface())
|
||||
if err != nil {
|
||||
|
@@ -4,33 +4,88 @@ import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
rutil "go.unistack.org/micro/v3/util/reflect"
|
||||
)
|
||||
|
||||
func TestZeroFieldByPath(t *testing.T) {
|
||||
func TestStructfields(t *testing.T) {
|
||||
type Config struct {
|
||||
Wait time.Duration
|
||||
Time time.Time
|
||||
Metadata map[string]int
|
||||
Broker string
|
||||
Addr []string
|
||||
Verbose bool
|
||||
Nested *Config
|
||||
}
|
||||
cfg := &Config{Nested: &Config{}}
|
||||
fields, err := rutil.StructFields(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(fields) != 13 {
|
||||
t.Fatalf("invalid fields number: %v", fields)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFieldByPath(t *testing.T) {
|
||||
type NestedStr struct {
|
||||
BBB string
|
||||
CCC int
|
||||
BBB string `json:"bbb"`
|
||||
CCC int `json:"ccc"`
|
||||
}
|
||||
type Str1 struct {
|
||||
Name []string `json:"name" codec:"flatten"`
|
||||
XXX string `json:"xxx"`
|
||||
Nested NestedStr
|
||||
Name []string `json:"name" codec:"flatten"`
|
||||
XXX string `json:"xxx"`
|
||||
Nested NestedStr `json:"nested"`
|
||||
}
|
||||
type Str2 struct {
|
||||
XXX string `json:"xxx"`
|
||||
Nested *NestedStr
|
||||
Name []string `json:"name" codec:"flatten"`
|
||||
XXX string `json:"xxx"`
|
||||
Nested *NestedStr `json:"nested"`
|
||||
Name []string `json:"name" codec:"flatten"`
|
||||
}
|
||||
var err error
|
||||
val1 := &Str1{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
|
||||
val2 := &Str2{Name: []string{"first", "second"}, XXX: "ttt", Nested: &NestedStr{BBB: "ddd", CCC: 9}}
|
||||
err = rutil.SetFieldByPath(val1, "xxx", "Nested.BBB")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if val1.Nested.BBB != "xxx" {
|
||||
t.Fatalf("SetFieldByPath not works: %#+v", val1)
|
||||
}
|
||||
err = rutil.SetFieldByPath(val2, "xxx", "Nested.BBB")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if val2.Nested.BBB != "xxx" {
|
||||
t.Fatalf("SetFieldByPath not works: %#+v", val1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroFieldByPath(t *testing.T) {
|
||||
type NestedStr struct {
|
||||
BBB string `json:"bbb"`
|
||||
CCC int `json:"ccc"`
|
||||
}
|
||||
type Str1 struct {
|
||||
Name []string `json:"name" codec:"flatten"`
|
||||
XXX string `json:"xxx"`
|
||||
Nested NestedStr `json:"nested"`
|
||||
}
|
||||
type Str2 struct {
|
||||
XXX string `json:"xxx"`
|
||||
Nested *NestedStr `json:"nested"`
|
||||
Name []string `json:"name" codec:"flatten"`
|
||||
}
|
||||
var err error
|
||||
val1 := &Str1{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
|
||||
|
||||
err = rutil.ZeroFieldByPath(val1, "name.nested.bbb")
|
||||
err = rutil.ZeroFieldByPath(val1, "Nested.BBB")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = rutil.ZeroFieldByPath(val1, "name.nested")
|
||||
err = rutil.ZeroFieldByPath(val1, "Nested")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -39,7 +94,7 @@ func TestZeroFieldByPath(t *testing.T) {
|
||||
}
|
||||
|
||||
val2 := &Str2{Name: []string{"first", "second"}, XXX: "ttt", Nested: &NestedStr{BBB: "ddd", CCC: 9}}
|
||||
err = rutil.ZeroFieldByPath(val2, "name.nested")
|
||||
err = rutil.ZeroFieldByPath(val2, "Nested")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -148,9 +203,9 @@ func TestStructByName(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v, ok := iface.(*[]string); !ok {
|
||||
t.Fatalf("not *[]string %v", iface)
|
||||
} else if len(*v) != 2 {
|
||||
if v, ok := iface.([]string); !ok {
|
||||
t.Fatalf("not []string %v", iface)
|
||||
} else if len(v) != 2 {
|
||||
t.Fatalf("invalid number %v", iface)
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package basic
|
||||
|
Reference in New Issue
Block a user