Compare commits

...

39 Commits

Author SHA1 Message Date
927ca879b2 Merge pull request #78 from unistack-org/master
merge master
2022-01-21 00:51:14 +03:00
00450c9cc7 Merge pull request #77 from unistack-org/errors
errors: add proto
2022-01-21 00:50:32 +03:00
534bce2d20 errors: add proto
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-21 00:50:16 +03:00
53949be0cc Merge pull request #76 from unistack-org/logger_test
add logger context test
2022-01-20 15:29:44 +03:00
d8fe2ff8b4 add logger context test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-20 15:29:21 +03:00
53b5ee2c6f Merge pull request #75 from unistack-org/logger_test
logger: add logger Fields test
2022-01-20 00:31:23 +03:00
dfd85cd871 logger: add logger Fields test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-20 00:28:09 +03:00
52182261af Merge pull request #74 from unistack-org/master
logger: fix Fields
2022-01-19 19:55:21 +03:00
1f3834e187 Merge pull request #73 from unistack-org/logger
logger: fix fields
2022-01-19 19:54:50 +03:00
0354873c3a logger: fix fields
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-19 19:54:33 +03:00
8e5e2167cd Merge pull request #72 from unistack-org/master
lint fixes
2022-01-10 16:48:27 +03:00
c26a7db47c Merge pull request #71 from unistack-org/lint
many lint fixes
2022-01-10 16:47:56 +03:00
74765b4c5f many lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-10 16:47:37 +03:00
8bd7323af1 Merge pull request #70 from unistack-org/dependabot/go_modules/go.unistack.org/micro-proto/v3-3.1.1
chore(deps): bump go.unistack.org/micro-proto/v3 from 3.1.0 to 3.1.1
2021-12-31 09:11:36 +03:00
dependabot[bot]
899dc8b3bc chore(deps): bump go.unistack.org/micro-proto/v3 from 3.1.0 to 3.1.1
Bumps [go.unistack.org/micro-proto/v3](https://github.com/unistack-org/micro-proto) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v3.1.0...v3.1.1)

---
updated-dependencies:
- dependency-name: go.unistack.org/micro-proto/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-31 06:11:19 +00:00
6e6c31b5dd Merge pull request #69 from unistack-org/master
merge master
2021-12-28 09:30:34 +03:00
94929878fe Merge pull request #68 from unistack-org/improvements
improvements
2021-12-28 09:23:45 +03:00
8ce469a09e tracer: fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-12-28 09:18:52 +03:00
88788776d2 Merge branch 'master' into v3 2021-12-16 15:04:08 +03:00
e143e2b547 client: allow to set metadata for message
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-12-16 15:03:42 +03:00
a36f99e30b Merge pull request #66 from unistack-org/minor_changes
config: add new error type
2021-11-30 07:35:27 +03:00
326ee53333 config: add new error type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-30 07:34:49 +03:00
1244c5bb4d Merge pull request #65 from unistack-org/master
merge changes from master
2021-11-24 00:59:00 +03:00
4ccc8a9c85 Merge pull request #64 from unistack-org/minor_changes
minor changes
2021-11-24 00:58:21 +03:00
8a2e84d489 minor changes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-24 00:57:59 +03:00
d29363b78d codec: add NewFrame helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-19 09:22:13 +03:00
734f751055 Merge pull request #63 from unistack-org/master
util/http: add type alias
2021-11-19 03:04:55 +03:00
55d8a9ee20 util/http: add type alias
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-19 03:04:08 +03:00
07c93042ba Merge pull request #62 from unistack-org/master
merge stable
2021-11-18 16:01:10 +03:00
b9bbfdf159 config: add watch option helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-18 15:57:14 +03:00
fbad257acc config: add helpers to load/save options (#60) 2021-11-18 15:46:30 +03:00
1829febb6e util/http: fix lint issues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-09 17:07:52 +03:00
7838fa62a8 util/trie: import some code from chi router
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-09 16:34:05 +03:00
332803d8de update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-27 22:52:48 +03:00
11c868d476 Merge branch 'v3' 2021-10-27 22:51:58 +03:00
38d6e482d7 util/reflect: fix StructFields
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-27 22:51:35 +03:00
07d4085201 util/reflect: fix reflect methods
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-27 12:55:48 +03:00
45f30c0be3 util/reflect: ZeroFieldByPath and SetFieldByPath
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-26 14:12:37 +03:00
6af837fd25 fixup workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-02 18:42:20 +03:00
47 changed files with 1323 additions and 443 deletions

View File

@@ -3,6 +3,7 @@ on:
push:
branches:
- master
- v3
jobs:
test:
name: test

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ on:
pull_request:
branches:
- master
- v3
jobs:
test:
name: test

View File

@@ -1,4 +1,4 @@
# Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/badge/github.com/unistack-org/micro)](https://goreportcard.com/report/github.com/unistack-org/micro) [![Slack](https://img.shields.io/static/v1?label=micro&message=slack&color=blueviolet)](https://unistack-org.slack.com/messages/default)
# Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/report/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3) [![Slack](https://img.shields.io/static/v1?label=micro&message=slack&color=blueviolet)](https://unistack-org.slack.com/messages/default)
Micro is a standard library for microservices.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 &microFlow{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 &microCallStep{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 &microPublishStep{topic: topic, opts: options}

View File

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

View File

@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore
package micro

View File

@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore
package micro

4
go.mod
View File

@@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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...),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore
package http

View File

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

View File

@@ -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" {

View File

@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore
package pool

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore
package basic