Compare commits

...

97 Commits

Author SHA1 Message Date
3805d0f067 Merge pull request #107 from unistack-org/retries
client: determenistic retry backoff
2022-03-27 00:19:06 +03:00
680ac11ef9 client: determenistic retry backoff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-27 00:16:22 +03:00
35ab6ae84e Merge pull request #106 from unistack-org/jitter
jitter: add NewTickerContext
2022-03-26 18:01:31 +03:00
c6c2b0884e jitter: add NewTickerContext
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-26 17:59:19 +03:00
297a80da84 Merge pull request #105 from unistack-org/improve
small fixes
2022-03-25 14:27:29 +03:00
2d292db7bd small fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-25 14:24:20 +03:00
54c4287fab Update README.md 2022-03-22 15:00:01 +03:00
9c074e5741 Merge pull request #103 from unistack-org/dependabot/github_actions/actions/cache-3
chore(deps): bump actions/cache from 2 to 3
2022-03-22 14:57:20 +03:00
290975eaf5 Merge pull request #104 from unistack-org/small_changes
config: add Validate func, small lint fixes
2022-03-22 14:57:03 +03:00
c64218d52c config: add Validate func, small lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-22 14:54:43 +03:00
dependabot[bot]
46c266a4a9 chore(deps): bump actions/cache from 2 to 3
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-22 06:14:47 +00:00
5527b16cd8 Merge pull request #102 from unistack-org/cleanup
server: remove unparsed body from request and message
2022-03-21 15:26:20 +03:00
4904cad8ef server: remove unparsed body from request and message
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-21 15:23:41 +03:00
74633f4290 Merge pull request #101 from unistack-org/dependabot/go_modules/github.com/golang-jwt/jwt/v4-4.4.0
chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.3.0 to 4.4.0
2022-03-18 16:29:26 +03:00
dependabot[bot]
c8ad4d772b chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.3.0 to 4.4.0
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.3.0...v4.4.0)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-17 08:24:50 +00:00
91bd0f7efe Merge branch 'master' into v3 2022-03-17 11:23:08 +03:00
00dc7e1bb5 update go version
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-07 13:44:18 +03:00
5a5165a003 Merge pull request #99 from unistack-org/dependabot/go_modules/github.com/golang-jwt/jwt/v4-4.3.0
chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.2.0 to 4.3.0
2022-03-07 13:16:48 +03:00
382e3d554b Merge pull request #98 from unistack-org/dependabot/github_actions/golangci/golangci-lint-action-3.1.0
chore(deps): bump golangci/golangci-lint-action from 2 to 3.1.0
2022-03-07 13:16:37 +03:00
05a0c97fc6 Merge pull request #100 from unistack-org/dependabot/go_modules/go.unistack.org/micro-proto/v3-3.2.7
chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.1 to 3.2.7
2022-03-07 13:14:31 +03:00
dependabot[bot]
5e06ae1a42 chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.1 to 3.2.7
Bumps [go.unistack.org/micro-proto/v3](https://github.com/unistack-org/micro-proto) from 3.2.1 to 3.2.7.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v3.2.1...v3.2.7)

---
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>
2022-03-07 10:11:59 +00:00
dependabot[bot]
7ac4ad4efa chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.2.0 to 4.3.0
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 10:11:54 +00:00
dependabot[bot]
01348bd9b2 chore(deps): bump golangci/golangci-lint-action from 2 to 3.1.0
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 2 to 3.1.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v2...v3.1.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 10:11:45 +00:00
2287c65118 update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-05 19:07:51 +03:00
b34bc7ffff Merge pull request #97 from unistack-org/dependabot/go_modules/go.unistack.org/micro-proto/v3-3.2.7
chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.5 to 3.2.7
2022-03-02 09:42:24 +03:00
dependabot[bot]
2a0bf03d0a chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.5 to 3.2.7
Bumps [go.unistack.org/micro-proto/v3](https://github.com/unistack-org/micro-proto) from 3.2.5 to 3.2.7.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v3.2.5...v3.2.7)

---
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>
2022-03-02 06:42:04 +00:00
89114c291c Merge pull request #94 from unistack-org/dependabot/go_modules/go.unistack.org/micro-proto/v3-3.2.5
chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.3 to 3.2.5
2022-02-28 09:12:18 +03:00
dependabot[bot]
b4b4320fac chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.3 to 3.2.5
Bumps [go.unistack.org/micro-proto/v3](https://github.com/unistack-org/micro-proto) from 3.2.3 to 3.2.5.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v3.2.3...v3.2.5)

---
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>
2022-02-28 06:12:01 +00:00
7b0d69115c Merge pull request #90 from unistack-org/dependabot/go_modules/github.com/golang-jwt/jwt/v4-4.3.0
chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.2.0 to 4.3.0
2022-02-25 10:00:00 +03:00
f054beb6e8 Merge pull request #92 from unistack-org/dependabot/github_actions/dependabot/fetch-metadata-1.2.1
chore(deps): bump dependabot/fetch-metadata from 1.1.1 to 1.2.1
2022-02-25 09:44:23 +03:00
9fb346594e Merge pull request #93 from unistack-org/dependabot/github_actions/golangci/golangci-lint-action-3
chore(deps): bump golangci/golangci-lint-action from 2 to 3
2022-02-25 09:33:47 +03:00
dependabot[bot]
cbf6fbd780 chore(deps): bump golangci/golangci-lint-action from 2 to 3
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 2 to 3.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 06:18:58 +00:00
dependabot[bot]
0392bff282 chore(deps): bump dependabot/fetch-metadata from 1.1.1 to 1.2.1
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.1.1 to 1.2.1.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.1.1...v1.2.1)

---
updated-dependencies:
- dependency-name: dependabot/fetch-metadata
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-23 06:14:10 +00:00
dependabot[bot]
75b1fe5dc6 chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.2.0 to 4.3.0
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-10 06:31:09 +00:00
1f232ffba8 Merge pull request #89 from unistack-org/dependabot/go_modules/go.unistack.org/micro-proto/v3-3.2.3
chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.1 to 3.2.3
2022-02-02 09:15:27 +03:00
dependabot[bot]
7f43b64fc2 chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.1 to 3.2.3
Bumps [go.unistack.org/micro-proto/v3](https://github.com/unistack-org/micro-proto) from 3.2.1 to 3.2.3.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v3.2.1...v3.2.3)

---
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>
2022-02-02 06:15:10 +00:00
d0d04a840a Merge pull request #88 from unistack-org/master
merge master
2022-01-30 17:05:41 +03:00
1dda3f0dcc Merge pull request #87 from unistack-org/register
register: DefaultDomain as var not as const
2022-01-30 17:05:19 +03:00
1abf5e7647 register: DefaultDomain as var not as const
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-30 17:04:52 +03:00
f06610c9c2 Merge pull request #86 from unistack-org/master
update micro-proto
2022-01-26 00:48:11 +03:00
df8560bb6f update micro-proto
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-26 00:47:22 +03:00
0257eae936 Merge pull request #85 from unistack-org/master
merge master
2022-01-25 00:41:46 +03:00
58f03d05e7 Merge pull request #84 from unistack-org/errors_marshal
errors: fix MarshalJSON func
2022-01-25 00:41:10 +03:00
60340a749b errors: fix MarshalJSON func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-25 00:40:50 +03:00
56b0df5b7a Merge pull request #83 from unistack-org/dependabot/go_modules/go.unistack.org/micro-proto/v3-3.2.0
chore(deps): bump go.unistack.org/micro-proto/v3 from 3.1.1 to 3.2.0
2022-01-24 09:15:46 +03:00
dependabot[bot]
bb59d5a2fd chore(deps): bump go.unistack.org/micro-proto/v3 from 3.1.1 to 3.2.0
Bumps [go.unistack.org/micro-proto/v3](https://github.com/unistack-org/micro-proto) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v3.1.1...v3.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-24 06:14:48 +00:00
67d5dc7e28 Merge pull request #82 from unistack-org/master
errors: fix parsing
2022-01-21 19:14:08 +03:00
797c0f822d Merge pull request #81 from unistack-org/errors
errors: fix parsing
2022-01-21 19:13:39 +03:00
8546140e22 errors: fix parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-21 19:13:21 +03:00
92b125c1ce Merge pull request #80 from unistack-org/master
merge master
2022-01-21 18:21:47 +03:00
8f7eebc24f Merge pull request #79 from unistack-org/errors
errors: fix FromError and errors.Unmarshal
2022-01-21 18:21:13 +03:00
b0def96d14 errors: fix FromError and errors.Unmarshal
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-01-21 18:20:56 +03:00
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
bcaea675a7 util/reflect: add method to zero struct field
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-25 14:41:19 +03:00
3087ba1d73 regen
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-23 23:45:53 +03:00
3f5b19497c meter: add Clone method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-09 23:50:57 +03:00
37d937d7ae meter: add missing options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-09 19:16:45 +03:00
7d68f2396e tracer: dont return noop from context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-07 22:46:47 +03:00
0854a7ea72 micro: add simple test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-07 20:59:27 +03:00
6af837fd25 fixup workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-02 18:42:20 +03:00
79 changed files with 2138 additions and 650 deletions

20
.github/workflows/autoapprove.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: "autoapprove"
on:
pull_request_target:
types: [assigned, opened, synchronize, reopened]
permissions:
pull-requests: write
contents: write
jobs:
autoapprove:
runs-on: ubuntu-latest
steps:
- name: approve
uses: hmarr/auto-approve-action@v2
if: github.actor == 'vtolstov' || github.actor == 'dependabot[bot]'
id: approve
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

21
.github/workflows/automerge.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: "automerge"
on:
pull_request_target:
types: [assigned, opened, synchronize, reopened]
permissions:
pull-requests: write
contents: write
jobs:
automerge:
runs-on: ubuntu-latest
if: github.actor == 'vtolstov'
steps:
- name: merge
id: merge
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.TOKEN}}

View File

@@ -1,8 +1,9 @@
name: build name: build
on: on:
push: push:
branches: branches:
- master - master
- v3
jobs: jobs:
test: test:
name: test name: test
@@ -11,49 +12,33 @@ jobs:
- name: setup - name: setup
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.16 go-version: 1.17
- name: checkout
uses: actions/checkout@v3
- name: cache - name: cache
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go- restore-keys: ${{ runner.os }}-go-
- name: sdk checkout - name: deps
uses: actions/checkout@v2
- name: sdk deps
run: go get -v -t -d ./... run: go get -v -t -d ./...
- name: sdk test - name: test
env: env:
INTEGRATION_TESTS: yes INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./... run: go test -mod readonly -v ./...
- name: tests checkout
uses: actions/checkout@v2
with:
repository: unistack-org/micro-tests
ref: refs/heads/master
path: micro-tests
fetch-depth: 1
- name: tests deps
run: |
cd micro-tests
go mod edit -replace="github.com/unistack-org/micro/v3=../"
go get -v -t -d ./...
- name: tests test
env:
INTEGRATION_TESTS: yes
run: cd micro-tests && go test -mod readonly -v ./...
lint: lint:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: lint - name: lint
uses: golangci/golangci-lint-action@v2 uses: golangci/golangci-lint-action@v3.1.0
continue-on-error: true continue-on-error: true
with: with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.39 version: v1.30
# Optional: working directory, useful for monorepos # Optional: working directory, useful for monorepos
# working-directory: somedir # working-directory: somedir
# Optional: golangci-lint command line arguments. # Optional: golangci-lint command line arguments.

View File

@@ -9,7 +9,7 @@
# the `language` matrix defined below to confirm you have the correct set of # the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages. # supported CodeQL languages.
# #
name: "CodeQL" name: "codeql"
on: on:
workflow_run: workflow_run:
@@ -17,16 +17,16 @@ on:
types: types:
- completed - completed
push: push:
branches: [ master ] branches: [ master, v3 ]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ master ] branches: [ master, v3 ]
schedule: schedule:
- cron: '34 1 * * 0' - cron: '34 1 * * 0'
jobs: jobs:
analyze: analyze:
name: Analyze name: analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
actions: read 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 # 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: steps:
- name: Checkout repository - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: setup
uses: actions/setup-go@v2
with:
go-version: 1.17
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: init
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
@@ -57,7 +60,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # 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 uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
@@ -71,5 +74,5 @@ jobs:
# make bootstrap # make bootstrap
# make release # make release
- name: Perform CodeQL Analysis - name: analyze
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v1

View File

@@ -1,66 +1,27 @@
name: "prautomerge" name: "dependabot-automerge"
on: on:
workflow_run: pull_request_target:
workflows: ["prbuild"] types: [assigned, opened, synchronize, reopened]
types:
- completed
permissions: permissions:
contents: write
pull-requests: write pull-requests: write
contents: write
jobs: jobs:
Dependabot-Automerge: automerge:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Contains workaround to execute if dependabot updates the PR by checking for the base branch in the linked PR if: github.actor == 'dependabot[bot]'
# 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 ))
steps: steps:
- name: Approve Changes and Merge changes if label 'dependencies' is set - name: metadata
uses: actions/github-script@v5 id: metadata
uses: dependabot/fetch-metadata@v1.3.0
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: "${{ secrets.TOKEN }}"
script: | - name: merge
console.log(context.payload.workflow_run); id: merge
if: ${{contains(steps.metadata.outputs.dependency-names, 'go.unistack.org')}}
var labelNames = await github.paginate( run: gh pr merge --auto --merge "$PR_URL"
github.issues.listLabelsOnIssue, env:
{ PR_URL: ${{github.event.pull_request.html_url}}
repo: context.repo.repo, GITHUB_TOKEN: ${{secrets.TOKEN}}
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');
}

View File

@@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- v3
jobs: jobs:
test: test:
name: test name: test
@@ -11,49 +12,33 @@ jobs:
- name: setup - name: setup
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.16 go-version: 1.17
- name: checkout
uses: actions/checkout@v3
- name: cache - name: cache
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: ~/go/pkg path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go- restore-keys: ${{ runner.os }}-go-
- name: sdk checkout - name: deps
uses: actions/checkout@v2
- name: sdk deps
run: go get -v -t -d ./... run: go get -v -t -d ./...
- name: sdk test - name: test
env: env:
INTEGRATION_TESTS: yes INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./... run: go test -mod readonly -v ./...
- name: tests checkout
uses: actions/checkout@v2
with:
repository: unistack-org/micro-tests
ref: refs/heads/master
path: micro-tests
fetch-depth: 1
- name: tests deps
run: |
cd micro-tests
go mod edit -replace="github.com/unistack-org/micro/v3=../"
go get -v -t -d ./...
- name: tests test
env:
INTEGRATION_TESTS: yes
run: cd micro-tests && go test -mod readonly -v ./...
lint: lint:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: lint - name: lint
uses: golangci/golangci-lint-action@v2 uses: golangci/golangci-lint-action@v3.1.0
continue-on-error: true continue-on-error: true
with: with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.39 version: v1.30
# Optional: working directory, useful for monorepos # Optional: working directory, useful for monorepos
# working-directory: somedir # working-directory: somedir
# Optional: golangci-lint command line arguments. # Optional: golangci-lint command line arguments.

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/badge/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. Micro is a standard library for microservices.

View File

@@ -50,6 +50,7 @@ type Handler func(Event) error
// Events contains multiple events // Events contains multiple events
type Events []Event type Events []Event
// Ack try to ack all events and return
func (evs Events) Ack() error { func (evs Events) Ack() error {
var err error var err error
for _, ev := range evs { for _, ev := range evs {
@@ -60,6 +61,7 @@ func (evs Events) Ack() error {
return nil return nil
} }
// SetError sets error on event
func (evs Events) SetError(err error) { func (evs Events) SetError(err error) {
for _, ev := range evs { for _, ev := range evs {
ev.SetError(err) ev.SetError(err)

View File

@@ -2,6 +2,7 @@ package client
import ( import (
"context" "context"
"math"
"time" "time"
"go.unistack.org/micro/v3/util/backoff" "go.unistack.org/micro/v3/util/backoff"
@@ -10,6 +11,20 @@ import (
// BackoffFunc is the backoff call func // BackoffFunc is the backoff call func
type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error)
func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) { // BackoffExp using exponential backoff func
func BackoffExp(_ context.Context, _ Request, attempts int) (time.Duration, error) {
return backoff.Do(attempts), nil return backoff.Do(attempts), nil
} }
// BackoffInterval specifies randomization interval for backoff func
func BackoffInterval(min time.Duration, max time.Duration) BackoffFunc {
return func(_ context.Context, _ Request, attempts int) (time.Duration, error) {
td := time.Duration(time.Duration(math.Pow(float64(attempts), math.E)) * time.Millisecond * 100)
if td < min {
return min, nil
} else if td > max {
return max, nil
}
return td, nil
}
}

View File

@@ -22,7 +22,7 @@ func TestBackoff(t *testing.T) {
} }
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
d, err := exponentialBackoff(context.TODO(), r, i) d, err := BackoffExp(context.TODO(), r, i)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -14,8 +14,8 @@ var (
DefaultClient Client = NewClient() DefaultClient Client = NewClient()
// DefaultContentType is the default content-type if not specified // DefaultContentType is the default content-type if not specified
DefaultContentType = "application/json" DefaultContentType = "application/json"
// DefaultBackoff is the default backoff function for retries // DefaultBackoff is the default backoff function for retries (minimum 10 millisecond and maximum 5 second)
DefaultBackoff = exponentialBackoff DefaultBackoff = BackoffInterval(10*time.Millisecond, 5*time.Second)
// DefaultRetry is the default check-for-retry function for retries // DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryNever DefaultRetry = RetryNever
// DefaultRetries is the default number of times a request is tried // DefaultRetries is the default number of times a request is tried
@@ -49,6 +49,7 @@ type Message interface {
Topic() string Topic() string
Payload() interface{} Payload() interface{}
ContentType() string ContentType() string
Metadata() metadata.Metadata
} }
// Request is the interface for a synchronous request used by Call or Stream // Request is the interface for a synchronous request used by Call or Stream
@@ -91,10 +92,16 @@ type Stream interface {
Send(msg interface{}) error Send(msg interface{}) error
// Recv will decode and read a response // Recv will decode and read a response
Recv(msg interface{}) error 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 returns the stream error
Error() error Error() error
// Close closes the stream // Close closes the stream
Close() error Close() error
// CloseSend closes the send direction of the stream
CloseSend() error
} }
// Option used by the Client // Option used by the Client

View File

@@ -12,7 +12,7 @@ import (
type LookupFunc func(context.Context, Request, CallOptions) ([]string, error) type LookupFunc func(context.Context, Request, CallOptions) ([]string, error)
// LookupRoute for a request using the router and then choose one using the selector // 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 // check to see if an address was provided as a call option
if len(opts.Address) > 0 { if len(opts.Address) > 0 {
return opts.Address, nil return opts.Address, nil

View File

@@ -119,6 +119,14 @@ func (n *noopStream) Recv(interface{}) error {
return nil return nil
} }
func (n *noopStream) SendMsg(interface{}) error {
return nil
}
func (n *noopStream) RecvMsg(interface{}) error {
return nil
}
func (n *noopStream) Error() error { func (n *noopStream) Error() error {
return nil return nil
} }
@@ -127,6 +135,10 @@ func (n *noopStream) Close() error {
return nil return nil
} }
func (n *noopStream) CloseSend() error {
return nil
}
func (n *noopMessage) Topic() string { func (n *noopMessage) Topic() string {
return n.topic return n.topic
} }
@@ -139,6 +151,10 @@ func (n *noopMessage) ContentType() string {
return n.opts.ContentType return n.opts.ContentType
} }
func (n *noopMessage) Metadata() metadata.Metadata {
return n.opts.Metadata
}
func (n *noopClient) newCodec(contentType string) (codec.Codec, error) { func (n *noopClient) newCodec(contentType string) (codec.Codec, error) {
if cf, ok := n.opts.Codecs[contentType]; ok { if cf, ok := n.opts.Codecs[contentType]; ok {
return cf, nil return cf, nil

View File

@@ -8,6 +8,7 @@ import (
"go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport" "go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
@@ -128,7 +129,7 @@ type PublishOptions struct {
// NewMessageOptions creates message options struct // NewMessageOptions creates message options struct
func NewMessageOptions(opts ...MessageOption) MessageOptions { func NewMessageOptions(opts ...MessageOption) MessageOptions {
options := MessageOptions{} options := MessageOptions{Metadata: metadata.New(1)}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
@@ -137,7 +138,10 @@ func NewMessageOptions(opts ...MessageOption) MessageOptions {
// MessageOptions holds client message options // MessageOptions holds client message options
type MessageOptions struct { type MessageOptions struct {
// Metadata additional metadata
Metadata metadata.Metadata
// ContentType specify content-type of message // ContentType specify content-type of message
// deprecated
ContentType string ContentType string
} }
@@ -517,6 +521,7 @@ func WithSelectOptions(sops ...selector.SelectOption) CallOption {
// Deprecated // Deprecated
func WithMessageContentType(ct string) MessageOption { func WithMessageContentType(ct string) MessageOption {
return func(o *MessageOptions) { return func(o *MessageOptions) {
o.Metadata.Set(metadata.HeaderContentType, ct)
o.ContentType = ct o.ContentType = ct
} }
} }
@@ -524,10 +529,18 @@ func WithMessageContentType(ct string) MessageOption {
// MessageContentType sets the message content type // MessageContentType sets the message content type
func MessageContentType(ct string) MessageOption { func MessageContentType(ct string) MessageOption {
return func(o *MessageOptions) { return func(o *MessageOptions) {
o.Metadata.Set(metadata.HeaderContentType, ct)
o.ContentType = 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 // StreamingRequest specifies that request is streaming
func StreamingRequest(b bool) RequestOption { func StreamingRequest(b bool) RequestOption {
return func(o *RequestOptions) { return func(o *RequestOptions) {

View File

@@ -19,18 +19,32 @@ func RetryNever(ctx context.Context, req Request, retryCount int, err error) (bo
return false, nil return false, nil
} }
// RetryOnError retries a request on a 500 or timeout error // RetryOnError retries a request on a 500 or 408 (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 { if err == nil {
return false, nil return false, nil
} }
me := errors.FromError(err) me := errors.FromError(err)
switch me.Code { switch me.Code {
// retry on timeout or internal server error // retry on timeout or internal server error
case 408, 500: case 408, 500:
return true, nil return true, nil
} }
return false, nil return false, nil
} }
// RetryOnErrors retries a request on specified error codes
func RetryOnErrors(codes ...int32) RetryFunc {
return func(_ context.Context, _ Request, _ int, err error) (bool, error) {
if err == nil {
return false, nil
}
me := errors.FromError(err)
for _, code := range codes {
if me.Code == code {
return true, nil
}
}
return false, nil
}
}

View File

@@ -5,29 +5,40 @@ type Frame struct {
Data []byte 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) { func (m *Frame) MarshalJSON() ([]byte, error) {
return m.Data, nil return m.Marshal()
} }
// UnmarshalJSON set frame data
func (m *Frame) UnmarshalJSON(data []byte) error { func (m *Frame) UnmarshalJSON(data []byte) error {
m.Data = data return m.Unmarshal(data)
return nil
} }
// ProtoMessage noop func
func (m *Frame) ProtoMessage() {} func (m *Frame) ProtoMessage() {}
// Reset resets frame
func (m *Frame) Reset() { func (m *Frame) Reset() {
*m = Frame{} *m = Frame{}
} }
// String returns frame as string
func (m *Frame) String() string { func (m *Frame) String() string {
return string(m.Data) return string(m.Data)
} }
// Marshal returns frame data
func (m *Frame) Marshal() ([]byte, error) { func (m *Frame) Marshal() ([]byte, error) {
return m.Data, nil return m.Data, nil
} }
// Unmarshal set frame data
func (m *Frame) Unmarshal(data []byte) error { func (m *Frame) Unmarshal(data []byte) error {
m.Data = data m.Data = data
return nil return nil

View File

@@ -17,7 +17,7 @@ syntax = "proto3";
package micro.codec; package micro.codec;
option cc_enable_arenas = true; option cc_enable_arenas = true;
option go_package = "github.com/unistack-org/micro/v3/codec;codec"; option go_package = "go.unistack.org/micro/v3/codec;codec";
option java_multiple_files = true; option java_multiple_files = true;
option java_outer_classname = "MicroCodec"; option java_outer_classname = "MicroCodec";
option java_package = "micro.codec"; option java_package = "micro.codec";

View File

@@ -4,11 +4,16 @@ package config // import "go.unistack.org/micro/v3/config"
import ( import (
"context" "context"
"errors" "errors"
"reflect"
"time" "time"
) )
type Validator interface {
Validate() error
}
// DefaultConfig default config // DefaultConfig default config
var DefaultConfig Config = NewConfig() var DefaultConfig = NewConfig()
// DefaultWatcherMinInterval default min interval for poll changes // DefaultWatcherMinInterval default min interval for poll changes
var DefaultWatcherMinInterval = 5 * time.Second var DefaultWatcherMinInterval = 5 * time.Second
@@ -23,6 +28,8 @@ var (
ErrInvalidStruct = errors.New("invalid struct specified") ErrInvalidStruct = errors.New("invalid struct specified")
// ErrWatcherStopped is returned when source watcher has been stopped // ErrWatcherStopped is returned when source watcher has been stopped
ErrWatcherStopped = errors.New("watcher 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 // Config is an interface abstraction for dynamic configuration
@@ -65,7 +72,59 @@ func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
return nil return nil
} }
// Validate runs Validate() error func for each struct field
func Validate(ctx context.Context, cfg interface{}) error {
if cfg == nil {
return nil
}
if v, ok := cfg.(Validator); ok {
if err := v.Validate(); err != nil {
return err
}
}
sv := reflect.ValueOf(cfg)
if sv.Kind() == reflect.Ptr {
sv = sv.Elem()
}
if sv.Kind() != reflect.Struct {
return nil
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.IsValid() || len(fld.PkgPath) != 0 {
continue
}
if v, ok := val.Interface().(Validator); ok {
if err := v.Validate(); err != nil {
return err
}
}
switch val.Kind() {
case reflect.Ptr:
if reflect.Indirect(val).Kind() == reflect.Struct {
if err := Validate(ctx, val.Interface()); err != nil {
return err
}
}
case reflect.Struct:
if err := Validate(ctx, val.Interface()); err != nil {
return err
}
}
}
return nil
}
var ( var (
// DefaultAfterLoad default func that runs after config load
DefaultAfterLoad = func(ctx context.Context, c Config) error { DefaultAfterLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterLoad { for _, fn := range c.Options().AfterLoad {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
@@ -77,7 +136,7 @@ var (
} }
return nil return nil
} }
// DefaultAfterSave default func that runs after config save
DefaultAfterSave = func(ctx context.Context, c Config) error { DefaultAfterSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterSave { for _, fn := range c.Options().AfterSave {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
@@ -89,7 +148,7 @@ var (
} }
return nil return nil
} }
// DefaultBeforeLoad default func that runs before config load
DefaultBeforeLoad = func(ctx context.Context, c Config) error { DefaultBeforeLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeLoad { for _, fn := range c.Options().BeforeLoad {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
@@ -101,11 +160,11 @@ var (
} }
return nil return nil
} }
// DefaultBeforeSave default func that runs befora config save
DefaultBeforeSave = func(ctx context.Context, c Config) error { DefaultBeforeSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeSave { for _, fn := range c.Options().BeforeSave {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s BeforeSavec err: %v", c.String(), err) c.Options().Logger.Errorf(ctx, "%s BeforeSave err: %v", c.String(), err)
if !c.Options().AllowFail { if !c.Options().AllowFail {
return err return err
} }

View File

@@ -32,3 +32,33 @@ func SetOption(k, v interface{}) Option {
o.Context = context.WithValue(o.Context, k, v) 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 ( import (
"context" "context"
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@@ -271,7 +270,7 @@ func (c *defaultConfig) Name() string {
} }
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) { 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 // NewConfig returns new default config source

View File

@@ -8,30 +8,46 @@ import (
"go.unistack.org/micro/v3/config" "go.unistack.org/micro/v3/config"
) )
type Cfg struct { type cfg struct {
StringValue string `default:"string_value"` StringValue string `default:"string_value"`
IgnoreValue string `json:"-"` IgnoreValue string `json:"-"`
StructValue struct { StructValue *cfgStructValue
StringValue string `default:"string_value"` IntValue int `default:"99"`
}
type cfgStructValue struct {
StringValue string `default:"string_value"`
}
func (c *cfg) Validate() error {
if c.IntValue != 10 {
return fmt.Errorf("invalid IntValue %d != %d", 10, c.IntValue)
} }
IntValue int `default:"99"` return nil
}
func (c *cfgStructValue) Validate() error {
if c.StringValue != "string_value" {
return fmt.Errorf("invalid StringValue %s != %s", "string_value", c.StringValue)
}
return nil
} }
func TestDefault(t *testing.T) { func TestDefault(t *testing.T) {
ctx := context.Background() ctx := context.Background()
conf := &Cfg{IntValue: 10} conf := &cfg{IntValue: 10}
blfn := func(ctx context.Context, cfg config.Config) error { blfn := func(_ context.Context, c config.Config) error {
nconf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := c.Options().Struct.(*cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", c.Options())
} }
nconf.StringValue = "before_load" nconf.StringValue = "before_load"
return nil return nil
} }
alfn := func(ctx context.Context, cfg config.Config) error { alfn := func(_ context.Context, c config.Config) error {
nconf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := c.Options().Struct.(*cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", c.Options())
} }
nconf.StringValue = "after_load" nconf.StringValue = "after_load"
return nil return nil
@@ -50,3 +66,19 @@ func TestDefault(t *testing.T) {
_ = conf _ = conf
// t.Logf("%#+v\n", conf) // t.Logf("%#+v\n", conf)
} }
func TestValidate(t *testing.T) {
ctx := context.Background()
conf := &cfg{IntValue: 10}
cfg := config.NewConfig(config.Struct(conf))
if err := cfg.Init(); err != nil {
t.Fatal(err)
}
if err := cfg.Load(ctx); err != nil {
t.Fatal(err)
}
if err := config.Validate(ctx, conf); err != nil {
t.Fatal(err)
}
}

View File

@@ -66,8 +66,10 @@ type LoadOptions struct {
Struct interface{} Struct interface{}
Override bool Override bool
Append bool Append bool
Context context.Context
} }
// NewLoadOptions create LoadOptions struct with provided opts
func NewLoadOptions(opts ...LoadOption) LoadOptions { func NewLoadOptions(opts ...LoadOption) LoadOptions {
options := LoadOptions{} options := LoadOptions{}
for _, o := range opts { for _, o := range opts {
@@ -102,7 +104,8 @@ type SaveOption func(o *SaveOptions)
// SaveOptions struct // SaveOptions struct
type SaveOptions struct { type SaveOptions struct {
Struct interface{} Struct interface{}
Context context.Context
} }
// SaveStruct override struct for save to config // SaveStruct override struct for save to config
@@ -219,8 +222,10 @@ type WatchOptions struct {
Coalesce bool Coalesce bool
} }
// WatchOption func signature
type WatchOption func(*WatchOptions) type WatchOption func(*WatchOptions)
// NewWatchOptions create WatchOptions struct with provided opts
func NewWatchOptions(opts ...WatchOption) WatchOptions { func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{ options := WatchOptions{
Context: context.Background(), Context: context.Background(),

View File

@@ -3,9 +3,12 @@
package errors // import "go.unistack.org/micro/v3/errors" package errors // import "go.unistack.org/micro/v3/errors"
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings"
) )
var ( var (
@@ -53,6 +56,22 @@ func (e *Error) Error() string {
return string(b) 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 // New generates a custom error
func New(id, detail string, code int32) error { func New(id, detail string, code int32) error {
return &Error{ return &Error{
@@ -66,130 +85,130 @@ func New(id, detail string, code int32) error {
// Parse tries to parse a JSON string into an error. If that // Parse tries to parse a JSON string into an error. If that
// fails, it will set the given string as the error detail. // fails, it will set the given string as the error detail.
func Parse(err string) *Error { func Parse(err string) *Error {
e := new(Error) e := &Error{}
errr := json.Unmarshal([]byte(err), e) nerr := json.Unmarshal([]byte(err), e)
if errr != nil { if nerr != nil {
e.Detail = err e.Detail = err
} }
return e return e
} }
// BadRequest generates a 400 error. // BadRequest generates a 400 error.
func BadRequest(id, format string, a ...interface{}) error { func BadRequest(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 400, Code: 400,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(400), Status: http.StatusText(400),
} }
} }
// Unauthorized generates a 401 error. // Unauthorized generates a 401 error.
func Unauthorized(id, format string, a ...interface{}) error { func Unauthorized(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 401, Code: 401,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(401), Status: http.StatusText(401),
} }
} }
// Forbidden generates a 403 error. // Forbidden generates a 403 error.
func Forbidden(id, format string, a ...interface{}) error { func Forbidden(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 403, Code: 403,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(403), Status: http.StatusText(403),
} }
} }
// NotFound generates a 404 error. // NotFound generates a 404 error.
func NotFound(id, format string, a ...interface{}) error { func NotFound(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 404, Code: 404,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(404), Status: http.StatusText(404),
} }
} }
// MethodNotAllowed generates a 405 error. // MethodNotAllowed generates a 405 error.
func MethodNotAllowed(id, format string, a ...interface{}) error { func MethodNotAllowed(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 405, Code: 405,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(405), Status: http.StatusText(405),
} }
} }
// Timeout generates a 408 error. // Timeout generates a 408 error.
func Timeout(id, format string, a ...interface{}) error { func Timeout(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 408, Code: 408,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(408), Status: http.StatusText(408),
} }
} }
// Conflict generates a 409 error. // Conflict generates a 409 error.
func Conflict(id, format string, a ...interface{}) error { func Conflict(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 409, Code: 409,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(409), Status: http.StatusText(409),
} }
} }
// InternalServerError generates a 500 error. // InternalServerError generates a 500 error.
func InternalServerError(id, format string, a ...interface{}) error { func InternalServerError(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 500, Code: 500,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(500), Status: http.StatusText(500),
} }
} }
// NotImplemented generates a 501 error // NotImplemented generates a 501 error
func NotImplemented(id, format string, a ...interface{}) error { func NotImplemented(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 501, Code: 501,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(501), Status: http.StatusText(501),
} }
} }
// BadGateway generates a 502 error // BadGateway generates a 502 error
func BadGateway(id, format string, a ...interface{}) error { func BadGateway(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 502, Code: 502,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(502), Status: http.StatusText(502),
} }
} }
// ServiceUnavailable generates a 503 error // ServiceUnavailable generates a 503 error
func ServiceUnavailable(id, format string, a ...interface{}) error { func ServiceUnavailable(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 503, Code: 503,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(503), Status: http.StatusText(503),
} }
} }
// GatewayTimeout generates a 504 error // GatewayTimeout generates a 504 error
func GatewayTimeout(id, format string, a ...interface{}) error { func GatewayTimeout(id, format string, args ...interface{}) error {
return &Error{ return &Error{
ID: id, ID: id,
Code: 504, Code: 504,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(504), Status: http.StatusText(504),
} }
} }
@@ -222,3 +241,81 @@ func FromError(err error) *Error {
return Parse(err.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}`, addslashes(e.ID), addslashes(e.Detail), addslashes(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)/2; idx += 2 {
val := strings.Trim(nparts[idx+1], `"`)
if len(val) == 0 {
continue
}
switch {
case nparts[idx] == `"id"`:
e.ID = val
case nparts[idx] == `"detail"`:
e.Detail = val
case nparts[idx] == `"status"`:
e.Status = val
case nparts[idx] == `"code"`:
c, err := strconv.ParseInt(val, 10, 32)
if err != nil {
return err
}
e.Code = int32(c)
}
idx++
}
}
return nil
}
func addslashes(str string) string {
var buf bytes.Buffer
for _, char := range str {
switch char {
case '\'', '"', '\\':
buf.WriteRune('\\')
}
buf.WriteRune(char)
}
return buf.String()
}

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

@@ -1,11 +1,34 @@
package errors package errors
import ( import (
"encoding/json"
er "errors" er "errors"
"fmt"
"net/http" "net/http"
"testing" "testing"
) )
func TestMarshalJSON(t *testing.T) {
e := InternalServerError("id", "err: %v", fmt.Errorf("err: %v", `xxx: "UNIX_TIMESTAMP": invalid identifier`))
_, err := json.Marshal(e)
if err != nil {
t.Fatal(err)
}
}
func TestEmpty(t *testing.T) {
msg := "test"
var err *Error
err = FromError(fmt.Errorf(msg))
if err.Detail != msg {
t.Fatalf("invalid error %v", err)
}
err = FromError(fmt.Errorf(`{"id":"","detail":"%s","status":"%s","code":0}`, msg, msg))
if err.Detail != msg || err.Status != msg {
t.Fatalf("invalid error %#+v", err)
}
}
func TestFromError(t *testing.T) { func TestFromError(t *testing.T) {
err := NotFound("go.micro.test", "%s", "example") err := NotFound("go.micro.test", "%s", "example")
merr := FromError(err) merr := FromError(err)

View File

@@ -349,6 +349,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return eid, err return eid, err
} }
// NewFlow create new flow
func NewFlow(opts ...Option) Flow { func NewFlow(opts ...Option) Flow {
options := NewOptions(opts...) options := NewOptions(opts...)
return &microFlow{opts: options} return &microFlow{opts: options}
@@ -574,11 +575,13 @@ func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...Ex
return nil, nil return nil, nil
} }
// NewCallStep create new step with client.Call
func NewCallStep(service string, name string, method string, opts ...StepOption) Step { func NewCallStep(service string, name string, method string, opts ...StepOption) Step {
options := NewStepOptions(opts...) options := NewStepOptions(opts...)
return &microCallStep{service: service, method: name + "." + method, opts: options} return &microCallStep{service: service, method: name + "." + method, opts: options}
} }
// NewPublishStep create new step with client.Publish
func NewPublishStep(topic string, opts ...StepOption) Step { func NewPublishStep(topic string, opts ...StepOption) Step {
options := NewStepOptions(opts...) options := NewStepOptions(opts...)
return &microPublishStep{topic: topic, opts: options} return &microPublishStep{topic: topic, opts: options}

View File

@@ -11,7 +11,9 @@ import (
) )
var ( var (
// ErrStepNotExists returns when step not found
ErrStepNotExists = errors.New("step not exists") ErrStepNotExists = errors.New("step not exists")
// ErrMissingClient returns when client.Client is missing
ErrMissingClient = errors.New("client not set") ErrMissingClient = errors.New("client not set")
) )
@@ -36,6 +38,7 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// Message used to transfer data between steps
type Message struct { type Message struct {
Header metadata.Metadata Header metadata.Metadata
Body RawMessage Body RawMessage
@@ -67,6 +70,7 @@ type Step interface {
Response() *Message Response() *Message
} }
// Status contains step current status
type Status int type Status int
func (status Status) String() string { func (status Status) String() string {
@@ -74,15 +78,22 @@ func (status Status) String() string {
} }
const ( const (
// StatusPending step waiting to start
StatusPending Status = iota StatusPending Status = iota
// StatusRunning step is running
StatusRunning StatusRunning
// StatusFailure step competed with error
StatusFailure StatusFailure
// StatusSuccess step completed without error
StatusSuccess StatusSuccess
// StatusAborted step aborted while it running
StatusAborted StatusAborted
// StatusSuspend step suspended
StatusSuspend StatusSuspend
) )
var ( var (
// StatusString contains map status => string
StatusString = map[Status]string{ StatusString = map[Status]string{
StatusPending: "StatusPending", StatusPending: "StatusPending",
StatusRunning: "StatusRunning", StatusRunning: "StatusRunning",
@@ -91,6 +102,7 @@ var (
StatusAborted: "StatusAborted", StatusAborted: "StatusAborted",
StatusSuspend: "StatusSuspend", StatusSuspend: "StatusSuspend",
} }
// StringStatus contains map string => status
StringStatus = map[string]Status{ StringStatus = map[string]Status{
"StatusPending": StatusPending, "StatusPending": StatusPending,
"StatusRunning": StatusRunning, "StatusRunning": StatusRunning,
@@ -144,6 +156,7 @@ var (
atomicSteps atomic.Value atomicSteps atomic.Value
) )
// RegisterStep register own step with workflow
func RegisterStep(step Step) { func RegisterStep(step Step) {
flowMu.Lock() flowMu.Lock()
steps, _ := atomicSteps.Load().([]Step) steps, _ := atomicSteps.Load().([]Step)

View File

@@ -70,7 +70,7 @@ func Client(c client.Client) Option {
// Context specifies a context for the service. // Context specifies a context for the service.
// Can be used to signal shutdown of the flow // 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 { func Context(ctx context.Context) Option {
return func(o *Options) { return func(o *Options) {
o.Context = ctx o.Context = ctx
@@ -91,7 +91,7 @@ func Store(s store.Store) Option {
} }
} }
// WorflowOption signature // WorkflowOption func signature
type WorkflowOption func(*WorkflowOptions) type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options // WorkflowOptions holds workflow options
@@ -107,6 +107,7 @@ func WorkflowID(id string) WorkflowOption {
} }
} }
// ExecuteOptions holds execute options
type ExecuteOptions struct { type ExecuteOptions struct {
// Client holds the client.Client // Client holds the client.Client
Client client.Client Client client.Client
@@ -128,56 +129,66 @@ type ExecuteOptions struct {
Async bool Async bool
} }
// ExecuteOption func signature
type ExecuteOption func(*ExecuteOptions) type ExecuteOption func(*ExecuteOptions)
// ExecuteClient pass client.Client to ExecuteOption
func ExecuteClient(c client.Client) ExecuteOption { func ExecuteClient(c client.Client) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Client = c o.Client = c
} }
} }
// ExecuteTracer pass tracer.Tracer to ExecuteOption
func ExecuteTracer(t tracer.Tracer) ExecuteOption { func ExecuteTracer(t tracer.Tracer) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Tracer = t o.Tracer = t
} }
} }
// ExecuteLogger pass logger.Logger to ExecuteOption
func ExecuteLogger(l logger.Logger) ExecuteOption { func ExecuteLogger(l logger.Logger) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Logger = l o.Logger = l
} }
} }
// ExecuteMeter pass meter.Meter to ExecuteOption
func ExecuteMeter(m meter.Meter) ExecuteOption { func ExecuteMeter(m meter.Meter) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Meter = m o.Meter = m
} }
} }
// ExecuteContext pass context.Context ot ExecuteOption
func ExecuteContext(ctx context.Context) ExecuteOption { func ExecuteContext(ctx context.Context) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Context = ctx o.Context = ctx
} }
} }
// ExecuteReverse says that dag must be run in reverse order
func ExecuteReverse(b bool) ExecuteOption { func ExecuteReverse(b bool) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Reverse = b o.Reverse = b
} }
} }
// ExecuteTimeout pass timeout time.Duration for execution
func ExecuteTimeout(td time.Duration) ExecuteOption { func ExecuteTimeout(td time.Duration) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Timeout = td o.Timeout = td
} }
} }
// ExecuteAsync says that caller does not wait for execution complete
func ExecuteAsync(b bool) ExecuteOption { func ExecuteAsync(b bool) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Async = b o.Async = b
} }
} }
// NewExecuteOptions create new ExecuteOptions struct
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions { func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
options := ExecuteOptions{ options := ExecuteOptions{
Client: client.DefaultClient, Client: client.DefaultClient,
@@ -192,6 +203,7 @@ func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
return options return options
} }
// StepOptions holds step options
type StepOptions struct { type StepOptions struct {
Context context.Context Context context.Context
Fallback string Fallback string
@@ -199,8 +211,10 @@ type StepOptions struct {
Requires []string Requires []string
} }
// StepOption func signature
type StepOption func(*StepOptions) type StepOption func(*StepOptions)
// NewStepOptions create new StepOptions struct
func NewStepOptions(opts ...StepOption) StepOptions { func NewStepOptions(opts ...StepOption) StepOptions {
options := StepOptions{ options := StepOptions{
Context: context.Background(), Context: context.Background(),
@@ -211,18 +225,21 @@ func NewStepOptions(opts ...StepOption) StepOptions {
return options return options
} }
// StepID sets the step id for dag
func StepID(id string) StepOption { func StepID(id string) StepOption {
return func(o *StepOptions) { return func(o *StepOptions) {
o.ID = id o.ID = id
} }
} }
// StepRequires specifies required steps
func StepRequires(steps ...string) StepOption { func StepRequires(steps ...string) StepOption {
return func(o *StepOptions) { return func(o *StepOptions) {
o.Requires = steps o.Requires = steps
} }
} }
// StepFallback set the step to run on error
func StepFallback(step string) StepOption { func StepFallback(step string) StepOption {
return func(o *StepOptions) { return func(o *StepOptions) {
o.Fallback = step o.Fallback = step

View File

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

View File

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

7
go.mod
View File

@@ -4,11 +4,10 @@ go 1.16
require ( require (
github.com/ef-ds/deque v1.0.4 github.com/ef-ds/deque v1.0.4
github.com/golang-jwt/jwt/v4 v4.1.0 github.com/golang-jwt/jwt/v4 v4.4.0
github.com/imdario/mergo v0.3.12 github.com/imdario/mergo v0.3.12
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4 github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35
github.com/unistack-org/micro-proto v0.0.9 go.unistack.org/micro-proto/v3 v3.2.7
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
) )

147
go.sum
View File

@@ -1,37 +1,162 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= 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/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/golang-jwt/jwt/v4 v4.4.0 h1:EmVIxB5jzbllGIjiCV5JG4VylbK3KE400tLGLI1cdfU=
github.com/golang-jwt/jwt/v4 v4.4.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/gnostic v0.6.6 h1:MVSM2r2j9aRUvYNym66JGW96Ddd5MN4sTi59yktb6yk=
github.com/google/gnostic v0.6.6/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 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= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4 h1:fOH64AB0C3ixGf9emky61STvPJL3smxJg+1Zwx1oCdg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35 h1:4mohWoM/UGg1BvFFiqSPRl5uwJY3rVV0HQX0ETqauqQ=
github.com/unistack-org/micro-proto v0.0.9 h1:KrWLS4FUX7UAWNAilQf70uad6ZPf/0EudeddCXllRVc= github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
github.com/unistack-org/micro-proto v0.0.9/go.mod h1:Cckwmzd89gvS7ThxzZp9kQR/EOdksFQcsTAtDDyKwrg= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.unistack.org/micro-proto/v3 v3.2.7 h1:zG6d69kHc+oij2lwQ3AfrCgdjiEVRG2A7TlsxjusWs4=
go.unistack.org/micro-proto/v3 v3.2.7/go.mod h1:ZltVWNECD5yK+40+OCONzGw4OtmSdTpVi8/KFgo9dqM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo= 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/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -12,11 +12,11 @@ import (
) )
type defaultLogger struct { type defaultLogger struct {
sync.RWMutex
enc *json.Encoder enc *json.Encoder
logFunc LogFunc logFunc LogFunc
logfFunc LogfFunc logfFunc LogfFunc
opts Options opts Options
sync.RWMutex
} }
// Init(opts...) should only overwrite provided options // Init(opts...) should only overwrite provided options
@@ -81,6 +81,8 @@ func (l *defaultLogger) Fields(fields ...interface{}) Logger {
} else if len(fields)%2 != 0 { } else if len(fields)%2 != 0 {
fields = fields[:len(fields)-1] fields = fields[:len(fields)-1]
} }
nl.logFunc = l.logFunc
nl.logfFunc = l.logfFunc
nl.opts.Fields = append(nl.opts.Fields, fields...) nl.opts.Fields = append(nl.opts.Fields, fields...)
return nl return nl
} }

View File

@@ -7,6 +7,37 @@ import (
"testing" "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) { func TestClone(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)

View File

@@ -10,6 +10,7 @@ type stdLogger struct {
level Level level Level
} }
// NewStdLogger returns new *log.Logger baked by logger.Logger implementation
func NewStdLogger(l Logger, level Level) *log.Logger { func NewStdLogger(l Logger, level Level) *log.Logger {
return log.New(&stdLogger{l: l, level: level}, "" /* prefix */, 0 /* flags */) 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 return len(p), nil
} }
// RedirectStdLogger replace *log.Logger with logger.Logger implementation
func RedirectStdLogger(l Logger, level Level) func() { func RedirectStdLogger(l Logger, level Level) func() {
flags := log.Flags() flags := log.Flags()
prefix := log.Prefix() prefix := log.Prefix()

View File

@@ -20,104 +20,104 @@ type Wrapper interface {
Logf(LogfFunc) LogfFunc Logf(LogfFunc) LogfFunc
} }
var _ Logger = &OmitLogger{} var _ Logger = &omitLogger{}
type OmitLogger struct { type omitLogger struct {
l Logger l Logger
} }
func NewOmitLogger(l Logger) 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()))...) 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) return w.l.V(level)
} }
func (w *OmitLogger) Level(level Level) { func (w *omitLogger) Level(level Level) {
w.l.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...) return w.l.Clone(opts...)
} }
func (w *OmitLogger) Options() Options { func (w *omitLogger) Options() Options {
return w.l.Options() return w.l.Options()
} }
func (w *OmitLogger) Fields(fields ...interface{}) Logger { func (w *omitLogger) Fields(fields ...interface{}) Logger {
return w.l.Fields(fields...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) 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...) w.l.Logf(ctx, level, msg, args...)
} }
func (w *OmitLogger) String() string { func (w *omitLogger) String() string {
return w.l.String() return w.l.String()
} }
type OmitWrapper struct{} type omitWrapper struct{}
func NewOmitWrapper() Wrapper { func NewOmitWrapper() Wrapper {
return &OmitWrapper{} return &omitWrapper{}
} }
func getArgs(args []interface{}) []interface{} { func getArgs(args []interface{}) []interface{} {
@@ -153,13 +153,13 @@ func getArgs(args []interface{}) []interface{} {
return nargs 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{}) { return func(ctx context.Context, level Level, args ...interface{}) {
fn(ctx, level, getArgs(args)...) 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{}) { return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...) fn(ctx, level, msg, getArgs(args)...)
} }

View File

@@ -11,6 +11,7 @@ import (
) )
var ( var (
// DefaultClientCallObserver called by wrapper in client Call
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, err error) []string { DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@@ -19,6 +20,7 @@ var (
return labels return labels
} }
// DefaultClientStreamObserver called by wrapper in client Stream
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, err error) []string { DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@@ -27,6 +29,7 @@ var (
return labels return labels
} }
// DefaultClientPublishObserver called by wrapper in client Publish
DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, err error) []string { DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, err error) []string {
labels := []string{"endpoint", msg.Topic()} labels := []string{"endpoint", msg.Topic()}
if err != nil { if err != nil {
@@ -35,6 +38,7 @@ var (
return labels return labels
} }
// DefaultServerHandlerObserver called by wrapper in server Handler
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, err error) []string { DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@@ -43,6 +47,7 @@ var (
return labels return labels
} }
// DefaultServerSubscriberObserver called by wrapper in server Subscriber
DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, err error) []string { DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, err error) []string {
labels := []string{"endpoint", msg.Topic()} labels := []string{"endpoint", msg.Topic()}
if err != nil { if err != nil {
@@ -51,6 +56,7 @@ var (
return labels return labels
} }
// DefaultClientCallFuncObserver called by wrapper in client CallFunc
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, err error) []string { DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@@ -59,6 +65,7 @@ var (
return labels return labels
} }
// DefaultSkipEndpoints wrapper not called for this endpoints
DefaultSkipEndpoints = []string{"Meter.Metrics"} DefaultSkipEndpoints = []string{"Meter.Metrics"}
) )
@@ -71,11 +78,17 @@ type lWrapper struct {
} }
type ( type (
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string // ClientCallObserver func signature
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string // ClientStreamObserver func signature
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string // ClientPublishObserver func signature
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
// ClientCallFuncObserver func signature
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
// ServerHandlerObserver func signature
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
// ServerSubscriberObserver func signature
ServerSubscriberObserver func(context.Context, server.Message, error) []string ServerSubscriberObserver func(context.Context, server.Message, error) []string
) )

View File

@@ -1,12 +1,12 @@
package meter package meter
//go:generate sh -c "protoc -I./handler -I../ -I$(go list -f '{{ .Dir }}' -m github.com/unistack-org/micro-proto) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto" //go:generate sh -c "protoc -I./handler -I../ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v3) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto"
import ( import (
// import required packages // import required packages
_ "github.com/unistack-org/micro-proto/api" _ "go.unistack.org/micro-proto/v3/api"
// import required packages // import required packages
_ "github.com/unistack-org/micro-proto/openapiv2" _ "go.unistack.org/micro-proto/v3/openapiv3"
) )

View File

@@ -1,24 +1,20 @@
syntax = "proto3"; syntax = "proto3";
package micro.meter.handler; package micro.meter.handler;
option go_package = "github.com/unistack-org/micro/v3/meter/handler;handler"; option go_package = "go.unistack.org/micro/v3/meter/handler;handler";
import "api/annotations.proto"; import "api/annotations.proto";
import "openapiv2/annotations.proto"; import "openapiv3/annotations.proto";
import "codec/frame.proto"; import "codec/frame.proto";
service Meter { service Meter {
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) { rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = { option (micro.openapiv3.openapiv3_operation) = {
operation_id: "Metrics"; operation_id: "Metrics";
responses: { responses: {
response_code: { default: {
name: "default"; reference: {
value: { _ref: "micro.codec.Frame";
json_reference: {
description: "Error response";
_ref: "micro.codec.Frame";
};
}; };
}; };
}; };

View File

@@ -1,12 +1,11 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.4.2 // protoc-gen-go-micro version: v3.5.3
// source: handler.proto // source: handler.proto
package handler package handler
import ( import (
context "context" context "context"
api "go.unistack.org/micro/v3/api" api "go.unistack.org/micro/v3/api"
codec "go.unistack.org/micro/v3/codec" codec "go.unistack.org/micro/v3/codec"
) )

View File

@@ -1,12 +1,11 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.4.2 // protoc-gen-go-micro version: v3.5.3
// source: handler.proto // source: handler.proto
package handler package handler
import ( import (
context "context" context "context"
api "go.unistack.org/micro/v3/api" api "go.unistack.org/micro/v3/api"
codec "go.unistack.org/micro/v3/codec" codec "go.unistack.org/micro/v3/codec"
server "go.unistack.org/micro/v3/server" server "go.unistack.org/micro/v3/server"

View File

@@ -28,17 +28,31 @@ var (
// Meter is an interface for collecting and instrumenting metrics // Meter is an interface for collecting and instrumenting metrics
type Meter interface { type Meter interface {
// Name returns meter name
Name() string Name() string
// Init initialize meter
Init(opts ...Option) error Init(opts ...Option) error
// Clone create meter copy with new options
Clone(opts ...Option) Meter
// Counter get or create counter
Counter(name string, labels ...string) Counter Counter(name string, labels ...string) Counter
// FloatCounter get or create float counter
FloatCounter(name string, labels ...string) FloatCounter FloatCounter(name string, labels ...string) FloatCounter
// Gauge get or create gauge
Gauge(name string, fn func() float64, labels ...string) Gauge Gauge(name string, fn func() float64, labels ...string) Gauge
// Set create new meter metrics set
Set(opts ...Option) Meter Set(opts ...Option) Meter
// Histogram get or create histogram
Histogram(name string, labels ...string) Histogram Histogram(name string, labels ...string) Histogram
// Summary get or create summary
Summary(name string, labels ...string) Summary Summary(name string, labels ...string) Summary
// SummaryExt get or create summary with spcified quantiles and window time
SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
// Write writes metrics to io.Writer
Write(w io.Writer, opts ...Option) error Write(w io.Writer, opts ...Option) error
// Options returns meter options
Options() Options Options() Options
// String return meter type
String() string String() string
} }
@@ -88,7 +102,7 @@ func (k byKey) Swap(i, j int) {
k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1] k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1]
} }
// BuildLables used to sort labels and delete duplicates. // BuildLabels used to sort labels and delete duplicates.
// Last value wins in case of duplicate label keys. // Last value wins in case of duplicate label keys.
func BuildLabels(labels ...string) []string { func BuildLabels(labels ...string) []string {
if len(labels)%2 == 1 { if len(labels)%2 == 1 {

View File

@@ -15,6 +15,15 @@ func NewMeter(opts ...Option) Meter {
return &noopMeter{opts: NewOptions(opts...)} return &noopMeter{opts: NewOptions(opts...)}
} }
// Clone return old meter with new options
func (r *noopMeter) Clone(opts ...Option) Meter {
options := r.opts
for _, o := range opts {
o(&options)
}
return &noopMeter{opts: options}
}
func (r *noopMeter) Name() string { func (r *noopMeter) Name() string {
return r.opts.Name return r.opts.Name
} }

View File

@@ -9,7 +9,7 @@ import (
// Option powers the configuration for metrics implementations: // Option powers the configuration for metrics implementations:
type Option func(*Options) type Option func(*Options)
// Options for metrics implementations: // Options for metrics implementations
type Options struct { type Options struct {
// Logger used for logging // Logger used for logging
Logger logger.Logger Logger logger.Logger
@@ -51,6 +51,20 @@ func NewOptions(opt ...Option) Options {
return opts return opts
} }
// LabelPrefix sets the labels prefix
func LabelPrefix(pref string) Option {
return func(o *Options) {
o.LabelPrefix = pref
}
}
// MetricPrefix sets the metric prefix
func MetricPrefix(pref string) Option {
return func(o *Options) {
o.MetricPrefix = pref
}
}
// Context sets the metrics context // Context sets the metrics context
func Context(ctx context.Context) Option { func Context(ctx context.Context) Option {
return func(o *Options) { return func(o *Options) {
@@ -88,6 +102,7 @@ func Logger(l logger.Logger) Option {
} }
} }
// Labels sets the meter labels
func Labels(ls ...string) Option { func Labels(ls ...string) Option {
return func(o *Options) { return func(o *Options) {
o.Labels = append(o.Labels, ls...) o.Labels = append(o.Labels, ls...)

View File

@@ -11,25 +11,38 @@ import (
) )
var ( 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" ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
ClientRequestTotal = "client_request_total" // ClientRequestTotal specifies meter metric name
ClientRequestInflight = "client_request_inflight" ClientRequestTotal = "client_request_total"
// ClientRequestInflight specifies meter metric name
ServerRequestDurationSeconds = "server_request_duration_seconds" ClientRequestInflight = "client_request_inflight"
// ServerRequestDurationSeconds specifies meter metric name
ServerRequestDurationSeconds = "server_request_duration_seconds"
// ServerRequestLatencyMicroseconds specifies meter metric name
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds" ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
ServerRequestTotal = "server_request_total" // ServerRequestTotal specifies meter metric name
ServerRequestInflight = "server_request_inflight" ServerRequestTotal = "server_request_total"
// ServerRequestInflight specifies meter metric name
PublishMessageDurationSeconds = "publish_message_duration_seconds" ServerRequestInflight = "server_request_inflight"
// PublishMessageDurationSeconds specifies meter metric name
PublishMessageDurationSeconds = "publish_message_duration_seconds"
// PublishMessageLatencyMicroseconds specifies meter metric name
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds" PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
PublishMessageTotal = "publish_message_total" // PublishMessageTotal specifies meter metric name
PublishMessageInflight = "publish_message_inflight" PublishMessageTotal = "publish_message_total"
// PublishMessageInflight specifies meter metric name
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds" PublishMessageInflight = "publish_message_inflight"
// SubscribeMessageDurationSeconds specifies meter metric name
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
// SubscribeMessageLatencyMicroseconds specifies meter metric name
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds" SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
SubscribeMessageTotal = "subscribe_message_total" // SubscribeMessageTotal specifies meter metric name
SubscribeMessageInflight = "subscribe_message_inflight" SubscribeMessageTotal = "subscribe_message_total"
// SubscribeMessageInflight specifies meter metric name
SubscribeMessageInflight = "subscribe_message_inflight"
labelSuccess = "success" labelSuccess = "success"
labelFailure = "failure" labelFailure = "failure"
@@ -40,14 +53,17 @@ var (
DefaultSkipEndpoints = []string{"Meter.Metrics"} DefaultSkipEndpoints = []string{"Meter.Metrics"}
) )
// Options struct
type Options struct { type Options struct {
Meter meter.Meter Meter meter.Meter
lopts []meter.Option lopts []meter.Option
SkipEndpoints []string SkipEndpoints []string
} }
// Option func signature
type Option func(*Options) type Option func(*Options)
// NewOptions creates new Options struct
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
@@ -60,30 +76,35 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// ServiceName passes service name to meter label
func ServiceName(name string) Option { func ServiceName(name string) Option {
return func(o *Options) { return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("name", name)) o.lopts = append(o.lopts, meter.Labels("name", name))
} }
} }
// ServiceVersion passes service version to meter label
func ServiceVersion(version string) Option { func ServiceVersion(version string) Option {
return func(o *Options) { return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("version", version)) o.lopts = append(o.lopts, meter.Labels("version", version))
} }
} }
// ServiceID passes service id to meter label
func ServiceID(id string) Option { func ServiceID(id string) Option {
return func(o *Options) { return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("id", id)) o.lopts = append(o.lopts, meter.Labels("id", id))
} }
} }
// Meter passes meter
func Meter(m meter.Meter) Option { func Meter(m meter.Meter) Option {
return func(o *Options) { return func(o *Options) {
o.Meter = m o.Meter = m
} }
} }
// SkipEndoints add endpoint to skip
func SkipEndoints(eps ...string) Option { func SkipEndoints(eps ...string) Option {
return func(o *Options) { return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...) o.SkipEndpoints = append(o.SkipEndpoints, eps...)
@@ -96,6 +117,7 @@ type wrapper struct {
opts Options opts Options
} }
// NewClientWrapper create new client wrapper
func NewClientWrapper(opts ...Option) client.Wrapper { func NewClientWrapper(opts ...Option) client.Wrapper {
return func(c client.Client) client.Client { return func(c client.Client) client.Client {
handler := &wrapper{ handler := &wrapper{
@@ -106,6 +128,7 @@ func NewClientWrapper(opts ...Option) client.Wrapper {
} }
} }
// NewCallWrapper create new call wrapper
func NewCallWrapper(opts ...Option) client.CallWrapper { func NewCallWrapper(opts ...Option) client.CallWrapper {
return func(fn client.CallFunc) client.CallFunc { return func(fn client.CallFunc) client.CallFunc {
handler := &wrapper{ handler := &wrapper{
@@ -231,6 +254,7 @@ func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.
return err return err
} }
// NewHandlerWrapper create new server handler wrapper
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper { func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{ handler := &wrapper{
opts: NewOptions(opts...), opts: NewOptions(opts...),
@@ -240,7 +264,7 @@ func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc { func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error { 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 { for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint { if ep == endpoint {
return fn(ctx, req, rsp) return fn(ctx, req, rsp)
@@ -270,6 +294,7 @@ func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
} }
} }
// NewSubscriberWrapper create server subscribe wrapper
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper { func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{ handler := &wrapper{
opts: NewOptions(opts...), opts: NewOptions(opts...),

View File

@@ -51,6 +51,4 @@ func TestExtractEndpoint(t *testing.T) {
if endpoints[0].Response != "TestResponse" { if endpoints[0].Response != "TestResponse" {
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response) t.Fatalf("Expected TestResponse got %s", endpoints[0].Response)
} }
t.Logf("XXX %#+v\n", endpoints[0])
} }

View File

@@ -2,7 +2,6 @@ package register
import ( import (
"context" "context"
"errors"
"sync" "sync"
"time" "time"
@@ -438,7 +437,7 @@ func (m *watcher) Next() (*Result, error) {
return r, nil return r, nil
} }
case <-m.exit: case <-m.exit:
return nil, errors.New("watcher stopped") return nil, ErrWatcherStopped
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"sync"
"testing" "testing"
"time" "time"
) )
@@ -284,29 +285,39 @@ func TestMemoryWildcard(t *testing.T) {
} }
func TestWatcher(t *testing.T) { func TestWatcher(t *testing.T) {
w := &watcher{ testSrv := &Service{Name: "foo", Version: "1.0.0"}
id: "test",
res: make(chan *Result),
exit: make(chan bool),
wo: WatchOptions{
Domain: WildcardDomain,
},
}
ctx := context.TODO()
m := NewRegister()
m.Init()
m.Connect(ctx)
wc, err := m.Watch(ctx)
if err != nil {
t.Fatalf("cant watch: %v", err)
}
defer wc.Stop()
var wg sync.WaitGroup
wg.Add(1)
go func() { go func() {
w.res <- &Result{ for {
Service: &Service{Name: "foo"}, ch, err := wc.Next()
if err != nil {
t.Fatal("unexpected err", err)
}
t.Logf("changes %#+v", ch.Service)
wc.Stop()
wg.Done()
return
} }
}() }()
_, err := w.Next() if err := m.Register(ctx, testSrv); err != nil {
if err != nil { t.Fatalf("Register err: %v", err)
t.Fatal("unexpected err", err)
} }
w.Stop() wg.Wait()
if _, err := wc.Next(); err == nil {
if _, err := w.Next(); err == nil {
t.Fatal("expected error on Next()") t.Fatal("expected error on Next()")
} }
} }

View File

@@ -44,9 +44,8 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// nolint: golint,revive
// RegisterOptions holds options for register method // RegisterOptions holds options for register method
type RegisterOptions struct { type RegisterOptions struct { // nolint: golint,revive
Context context.Context Context context.Context
Domain string Domain string
TTL time.Duration TTL time.Duration
@@ -197,33 +196,29 @@ func TLSConfig(t *tls.Config) Option {
} }
} }
// nolint: golint,revive
// RegisterAttempts specifies register atempts count // RegisterAttempts specifies register atempts count
func RegisterAttempts(t int) RegisterOption { func RegisterAttempts(t int) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.Attempts = t o.Attempts = t
} }
} }
// nolint: golint,revive
// RegisterTTL specifies register ttl // RegisterTTL specifies register ttl
func RegisterTTL(t time.Duration) RegisterOption { func RegisterTTL(t time.Duration) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.TTL = t o.TTL = t
} }
} }
// nolint: golint,revive
// RegisterContext sets the register context // RegisterContext sets the register context
func RegisterContext(ctx context.Context) RegisterOption { func RegisterContext(ctx context.Context) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.Context = ctx o.Context = ctx
} }
} }
// nolint: golint,revive
// RegisterDomain secifies register domain // RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption { func RegisterDomain(d string) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.Domain = d o.Domain = d
} }

View File

@@ -11,10 +11,11 @@ import (
const ( const (
// WildcardDomain indicates any domain // WildcardDomain indicates any domain
WildcardDomain = "*" WildcardDomain = "*"
// DefaultDomain to use if none was provided in options
DefaultDomain = "micro"
) )
// DefaultDomain to use if none was provided in options
var DefaultDomain = "micro"
var ( var (
// DefaultRegister is the global default register // DefaultRegister is the global default register
DefaultRegister Register = NewRegister() DefaultRegister Register = NewRegister()
@@ -68,9 +69,8 @@ type Endpoint struct {
// Option func signature // Option func signature
type Option func(*Options) type Option func(*Options)
// nolint: golint,revive
// RegisterOption option is used to register service // RegisterOption option is used to register service
type RegisterOption func(*RegisterOptions) type RegisterOption func(*RegisterOptions) // nolint: golint,revive
// WatchOption option is used to watch service changes // WatchOption option is used to watch service changes
type WatchOption func(*WatchOptions) type WatchOption func(*WatchOptions)

View File

@@ -1,12 +1,12 @@
package server package server
//go:generate sh -c "protoc -I./health -I../ -I$(go list -f '{{ .Dir }}' -m github.com/unistack-org/micro-proto) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./health health/health.proto" //go:generate sh -c "protoc -I./health -I../ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v3) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./health health/health.proto"
import ( import (
// import required packages // import required packages
_ "github.com/unistack-org/micro-proto/api" _ "go.unistack.org/micro-proto/v3/api"
// import required packages // import required packages
_ "github.com/unistack-org/micro-proto/openapiv2" _ "go.unistack.org/micro-proto/v3/openapiv3"
) )

View File

@@ -1,24 +1,20 @@
syntax = "proto3"; syntax = "proto3";
package micro.server.health; package micro.server.health;
option go_package = "github.com/unistack-org/micro/v3/server/health;health"; option go_package = "go.unistack.org/micro/v3/server/health;health";
import "api/annotations.proto"; import "api/annotations.proto";
import "openapiv2/annotations.proto"; import "openapiv3/annotations.proto";
import "codec/frame.proto"; import "codec/frame.proto";
service Health { service Health {
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) { rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = { option (micro.openapiv3.openapiv3_operation) = {
operation_id: "Live"; operation_id: "Live";
responses: { responses: {
response_code: { default: {
name: "default"; reference: {
value: { _ref: "micro.codec.Frame";
json_reference: {
description: "Error response";
_ref: "micro.codec.Frame";
};
}; };
}; };
}; };
@@ -26,16 +22,12 @@ service Health {
option (micro.api.http) = { get: "/live"; }; option (micro.api.http) = { get: "/live"; };
}; };
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) { rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = { option (micro.openapiv3.openapiv3_operation) = {
operation_id: "Ready"; operation_id: "Ready";
responses: { responses: {
response_code: { default: {
name: "default"; reference: {
value: { _ref: "micro.codec.Frame";
json_reference: {
description: "Error response";
_ref: "micro.codec.Frame";
};
}; };
}; };
}; };
@@ -43,16 +35,12 @@ service Health {
option (micro.api.http) = { get: "/ready"; }; option (micro.api.http) = { get: "/ready"; };
}; };
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) { rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = { option (micro.openapiv3.openapiv3_operation) = {
operation_id: "Version"; operation_id: "Version";
responses: { responses: {
response_code: { default: {
name: "default"; reference: {
value: { _ref: "micro.codec.Frame";
json_reference: {
description: "Error response";
_ref: "micro.codec.Frame";
};
}; };
}; };
}; };

View File

@@ -1,12 +1,11 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.4.2 // protoc-gen-go-micro version: v3.5.3
// source: health.proto // source: health.proto
package health package health
import ( import (
context "context" context "context"
api "go.unistack.org/micro/v3/api" api "go.unistack.org/micro/v3/api"
codec "go.unistack.org/micro/v3/codec" codec "go.unistack.org/micro/v3/codec"
) )

View File

@@ -1,12 +1,11 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.4.2 // protoc-gen-go-micro version: v3.5.3
// source: health.proto // source: health.proto
package health package health
import ( import (
context "context" context "context"
api "go.unistack.org/micro/v3/api" api "go.unistack.org/micro/v3/api"
codec "go.unistack.org/micro/v3/codec" codec "go.unistack.org/micro/v3/codec"
server "go.unistack.org/micro/v3/server" server "go.unistack.org/micro/v3/server"

View File

@@ -16,6 +16,7 @@ import (
"go.unistack.org/micro/v3/network/transport" "go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
"go.unistack.org/micro/v3/util/id"
) )
// Option func // Option func
@@ -106,7 +107,7 @@ func NewOptions(opts ...Option) Options {
Address: DefaultAddress, Address: DefaultAddress,
Name: DefaultName, Name: DefaultName,
Version: DefaultVersion, Version: DefaultVersion,
ID: DefaultID, ID: id.Must(),
Namespace: DefaultNamespace, Namespace: DefaultNamespace,
} }

View File

@@ -22,7 +22,7 @@ func (r *rpcMessage) Topic() string {
return r.topic return r.topic
} }
func (r *rpcMessage) Payload() interface{} { func (r *rpcMessage) Body() interface{} {
return r.payload return r.payload
} }
@@ -30,10 +30,6 @@ func (r *rpcMessage) Header() metadata.Metadata {
return r.header return r.header
} }
func (r *rpcMessage) Body() []byte {
return r.body
}
func (r *rpcMessage) Codec() codec.Codec { func (r *rpcMessage) Codec() codec.Codec {
return r.codec return r.codec
} }

View File

@@ -8,7 +8,6 @@ import (
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/util/id"
) )
// DefaultServer default server // DefaultServer default server
@@ -21,8 +20,6 @@ var (
DefaultName = "server" DefaultName = "server"
// DefaultVersion will be used if no version passed // DefaultVersion will be used if no version passed
DefaultVersion = "latest" DefaultVersion = "latest"
// DefaultID will be used if no id passed
DefaultID = id.Must()
// DefaultRegisterCheck holds func that run before register server // DefaultRegisterCheck holds func that run before register server
DefaultRegisterCheck = func(context.Context) error { return nil } DefaultRegisterCheck = func(context.Context) error { return nil }
// DefaultRegisterInterval holds interval for register // DefaultRegisterInterval holds interval for register
@@ -78,13 +75,11 @@ type Message interface {
// Topic of the message // Topic of the message
Topic() string Topic() string
// The decoded payload value // The decoded payload value
Payload() interface{} Body() interface{}
// The content type of the payload // The content type of the payload
ContentType() string ContentType() string
// The raw headers of the message // The raw headers of the message
Header() metadata.Metadata Header() metadata.Metadata
// The raw body of the message
Body() []byte
// Codec used to decode the message // Codec used to decode the message
Codec() codec.Codec Codec() codec.Codec
} }
@@ -126,11 +121,21 @@ type Response interface {
// The last error will be left in Error(). // The last error will be left in Error().
// EOF indicates end of the stream. // EOF indicates end of the stream.
type Stream interface { type Stream interface {
// Context for the stream
Context() context.Context Context() context.Context
// Request returns request
Request() Request Request() Request
// Send will encode and send a request
Send(msg interface{}) error Send(msg interface{}) error
// Recv will decode and read a response
Recv(msg interface{}) error 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 Error() error
// Close closes the stream
Close() error Close() error
} }

View File

@@ -252,7 +252,7 @@ func (n *noopServer) newBatchSubHandler(sb *subscriber, opts Options) broker.Bat
return err return err
} }
rb := reflect.New(req.Type().Elem()) rb := reflect.New(req.Type().Elem())
if err = cf.ReadBody(bytes.NewReader(msg.Body()), rb.Interface()); err != nil { if err = cf.ReadBody(bytes.NewReader(msg.(*rpcMessage).body), rb.Interface()); err != nil {
return err return err
} }
msg.(*rpcMessage).codec = cf msg.(*rpcMessage).codec = cf
@@ -269,7 +269,7 @@ func (n *noopServer) newBatchSubHandler(sb *subscriber, opts Options) broker.Bat
} }
payloads := reflect.MakeSlice(reqType, 0, len(ms)) payloads := reflect.MakeSlice(reqType, 0, len(ms))
for _, m := range ms { for _, m := range ms {
payloads = reflect.Append(payloads, reflect.ValueOf(m.Payload())) payloads = reflect.Append(payloads, reflect.ValueOf(m.Body()))
} }
vals = append(vals, payloads) vals = append(vals, payloads)
@@ -381,7 +381,7 @@ func (n *noopServer) newSubHandler(sb *subscriber, opts Options) broker.Handler
vals = append(vals, reflect.ValueOf(ctx)) vals = append(vals, reflect.ValueOf(ctx))
} }
vals = append(vals, reflect.ValueOf(msg.Payload())) vals = append(vals, reflect.ValueOf(msg.Body()))
returnValues := handler.method.Call(vals) returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil { if rerr := returnValues[0].Interface(); rerr != nil {
@@ -406,7 +406,6 @@ func (n *noopServer) newSubHandler(sb *subscriber, opts Options) broker.Handler
contentType: ct, contentType: ct,
payload: req.Interface(), payload: req.Interface(),
header: msg.Header, header: msg.Header,
body: msg.Body,
}) })
results <- cerr results <- cerr
}() }()

View File

@@ -54,8 +54,10 @@ type Service interface {
// Runtime(string) (runtime.Runtime, bool) // Runtime(string) (runtime.Runtime, bool)
// Profile // Profile
// Profile(string) (profile.Profile, bool) // Profile(string) (profile.Profile, bool)
// Run the service // Run the service and wait
Run() error Run() error
// Start the service
Start() error
// The service implementation // The service implementation
String() string String() string
} }
@@ -257,7 +259,7 @@ func (s *service) Start() error {
s.RUnlock() s.RUnlock()
if config.Loggers[0].V(logger.InfoLevel) { if config.Loggers[0].V(logger.InfoLevel) {
config.Loggers[0].Infof(s.opts.Context, "starting [service] %s", s.Name()) config.Loggers[0].Infof(s.opts.Context, "starting [service] %s version %s", s.Options().Name, s.Options().Version)
} }
for _, fn := range s.opts.BeforeStart { for _, fn := range s.opts.BeforeStart {
@@ -392,8 +394,12 @@ type nameIface interface {
Name() string Name() string
} }
func getNameIndex(n string, ifaces ...interface{}) int { func getNameIndex(n string, ifaces interface{}) int {
for idx, iface := range ifaces { values, ok := ifaces.([]interface{})
if !ok {
return 0
}
for idx, iface := range values {
if ifc, ok := iface.(nameIface); ok && ifc.Name() == n { if ifc, ok := iface.(nameIface); ok && ifc.Name() == n {
return idx return idx
} }

22
service_test.go Normal file
View File

@@ -0,0 +1,22 @@
package micro
import (
"testing"
)
type testItem struct {
name string
}
func (ti *testItem) Name() string {
return ti.name
}
func TestGetNameIndex(t *testing.T) {
item1 := &testItem{name: "first"}
item2 := &testItem{name: "second"}
items := []interface{}{item1, item2}
if idx := getNameIndex("second", items); idx != 1 {
t.Fatalf("getNameIndex func error, item not found")
}
}

View File

@@ -8,14 +8,14 @@ import (
type tracerKey struct{} type tracerKey struct{}
// FromContext returns a tracer from context // FromContext returns a tracer from context
func FromContext(ctx context.Context) Tracer { func FromContext(ctx context.Context) (Tracer, bool) {
if ctx == nil { if ctx == nil {
return DefaultTracer return nil, false
} }
if tracer, ok := ctx.Value(tracerKey{}).(Tracer); ok { if tracer, ok := ctx.Value(tracerKey{}).(Tracer); ok {
return tracer return tracer, true
} }
return DefaultTracer return nil, false
} }
// NewContext saves the tracer in the context // NewContext saves the tracer in the context
@@ -29,14 +29,14 @@ func NewContext(ctx context.Context, tracer Tracer) context.Context {
type spanKey struct{} type spanKey struct{}
// SpanFromContext returns a span from context // SpanFromContext returns a span from context
func SpanFromContext(ctx context.Context) Span { func SpanFromContext(ctx context.Context) (Span, bool) {
if ctx == nil { if ctx == nil {
return &noopSpan{} return nil, false
} }
if span, ok := ctx.Value(spanKey{}).(Span); ok { if span, ok := ctx.Value(spanKey{}).(Span); ok {
return span return span, true
} }
return &noopSpan{} return nil, false
} }
// NewSpanContext saves the span in the context // NewSpanContext saves the span in the context

View File

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

View File

@@ -2,12 +2,16 @@ package tracer
import "go.unistack.org/micro/v3/logger" import "go.unistack.org/micro/v3/logger"
// SpanOptions contains span option
type SpanOptions struct{} type SpanOptions struct{}
// SpanOption func signature
type SpanOption func(o *SpanOptions) type SpanOption func(o *SpanOptions)
// EventOptions contains event options
type EventOptions struct{} type EventOptions struct{}
// EventOption func signature
type EventOption func(o *EventOptions) type EventOption func(o *EventOptions)
// Options struct // Options struct
@@ -18,7 +22,7 @@ type Options struct {
Name string Name string
} }
// Option func // Option func signature
type Option func(o *Options) type Option func(o *Options)
// Logger sets the logger // Logger sets the logger

View File

@@ -38,26 +38,26 @@ type Label struct {
key string key string
} }
func Any(k string, v interface{}) Label { func LabelAny(k string, v interface{}) Label {
return Label{key: k, val: v} return Label{key: k, val: v}
} }
func String(k string, v string) Label { func LabelString(k string, v string) Label {
return Label{key: k, val: v} return Label{key: k, val: v}
} }
func Int(k string, v int) Label { func LabelInt(k string, v int) Label {
return Label{key: k, val: v} return Label{key: k, val: v}
} }
func Int64(k string, v int64) Label { func LabelInt64(k string, v int64) Label {
return Label{key: k, val: v} return Label{key: k, val: v}
} }
func Float64(k string, v float64) Label { func LabelFloat64(k string, v float64) Label {
return Label{key: k, val: v} return Label{key: k, val: v}
} }
func Bool(k string, v bool) Label { func LabelBool(k string, v bool) Label {
return Label{key: k, val: v} return Label{key: k, val: v}
} }

View File

@@ -18,11 +18,11 @@ var (
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md)) labels = make([]tracer.Label, 0, len(md))
for k, v := range md { for k, v := range md {
labels = append(labels, tracer.String(k, v)) labels = append(labels, tracer.LabelString(k, v))
} }
} }
if err != nil { if err != nil {
labels = append(labels, tracer.Bool("error", true)) labels = append(labels, tracer.LabelBool("error", true))
} }
sp.SetLabels(labels...) sp.SetLabels(labels...)
} }
@@ -33,11 +33,11 @@ var (
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md)) labels = make([]tracer.Label, 0, len(md))
for k, v := range md { for k, v := range md {
labels = append(labels, tracer.String(k, v)) labels = append(labels, tracer.LabelString(k, v))
} }
} }
if err != nil { if err != nil {
labels = append(labels, tracer.Bool("error", true)) labels = append(labels, tracer.LabelBool("error", true))
} }
sp.SetLabels(labels...) sp.SetLabels(labels...)
} }
@@ -48,11 +48,11 @@ var (
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md)) labels = make([]tracer.Label, 0, len(md))
for k, v := range md { for k, v := range md {
labels = append(labels, tracer.String(k, v)) labels = append(labels, tracer.LabelString(k, v))
} }
} }
if err != nil { if err != nil {
labels = append(labels, tracer.Bool("error", true)) labels = append(labels, tracer.LabelBool("error", true))
} }
sp.SetLabels(labels...) sp.SetLabels(labels...)
} }
@@ -63,11 +63,11 @@ var (
if md, ok := metadata.FromIncomingContext(ctx); ok { if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md)) labels = make([]tracer.Label, 0, len(md))
for k, v := range md { for k, v := range md {
labels = append(labels, tracer.String(k, v)) labels = append(labels, tracer.LabelString(k, v))
} }
} }
if err != nil { if err != nil {
labels = append(labels, tracer.Bool("error", true)) labels = append(labels, tracer.LabelBool("error", true))
} }
sp.SetLabels(labels...) sp.SetLabels(labels...)
} }
@@ -78,11 +78,11 @@ var (
if md, ok := metadata.FromIncomingContext(ctx); ok { if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md)) labels = make([]tracer.Label, 0, len(md))
for k, v := range md { for k, v := range md {
labels = append(labels, tracer.String(k, v)) labels = append(labels, tracer.LabelString(k, v))
} }
} }
if err != nil { if err != nil {
labels = append(labels, tracer.Bool("error", true)) labels = append(labels, tracer.LabelBool("error", true))
} }
sp.SetLabels(labels...) sp.SetLabels(labels...)
} }
@@ -93,11 +93,11 @@ var (
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md)) labels = make([]tracer.Label, 0, len(md))
for k, v := range md { for k, v := range md {
labels = append(labels, tracer.String(k, v)) labels = append(labels, tracer.LabelString(k, v))
} }
} }
if err != nil { if err != nil {
labels = append(labels, tracer.Bool("error", true)) labels = append(labels, tracer.LabelBool("error", true))
} }
sp.SetLabels(labels...) 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() defer sp.Finish()
err := ot.Client.Call(ctx, req, rsp, opts...) 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() defer sp.Finish()
stream, err := ot.Client.Stream(ctx, req, opts...) 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 { 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() defer sp.Finish()
err := ot.Client.Publish(ctx, msg, opts...) 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() defer sp.Finish()
err := ot.serverHandler(ctx, req, rsp) 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 { 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() defer sp.Finish()
err := ot.serverSubscriber(ctx, msg) 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() defer sp.Finish()
err := ot.clientCallFunc(ctx, addr, req, rsp, opts) 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 ( import (
"bytes" "bytes"
"io"
) )
var _ io.Closer = &Buffer{}
// Buffer bytes.Buffer wrapper to satisfie io.Closer interface
type Buffer struct { type Buffer struct {
*bytes.Buffer *bytes.Buffer
} }

View File

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

View File

@@ -1,239 +1,708 @@
package http 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 ( import (
"fmt"
"net/http"
"regexp" "regexp"
"sort"
"strconv"
"strings" "strings"
"sync"
) )
// TrieOptions contains search options type methodTyp uint
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
}
const ( const (
pathRoot string = "/" mSTUB methodTyp = 1 << iota
pathDelimiter string = "/" mCONNECT
paramDelimiter string = ":" mDELETE
leftPtnDelimiter string = "{" mGET
rightPtnDelimiter string = "}" mHEAD
ptnWildcard string = "(.+)" 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 { func NewTrie() *Trie {
return &Trie{ return &Trie{}
node: &node{ }
label: pathRoot,
actions: make(map[string]interface{}), // Trie holds nodes for path based tree search
children: make(map[string]*node), type Trie struct {
}, // regexp matcher for regexp nodes
rcache: make(map[string]*regexp.Regexp), 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. // addChild appends the new `child` node to the tree using the `pattern` as the trie key.
func (t *Trie) Insert(methods []string, path string, handler interface{}) { // For a URL router like chi's, we split the static, param, regexp and wildcard segments
curNode := t.node // into different nodes. In addition, addChild will recursively call itself until every
if path == pathRoot { // pattern segment is added to the url pattern tree as individual nodes, depending on type.
curNode.label = path func (n *Trie) addChild(child *Trie, prefix string) (*Trie, error) {
for _, method := range methods { search := prefix
curNode.actions[method] = handler
} // handler leaf node added to the tree is the child.
return // 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) // Add child depending on next up segment
for i, p := range ep { switch segTyp {
nextNode, ok := curNode.children[p]
if ok { case ntStatic:
curNode = nextNode // Search prefix is all static (that is, has no params in path)
} // noop
// Create a new node.
if !ok { default:
curNode.children[p] = &node{ // Search prefix contains a param, regexp or wildcard
label: p,
actions: make(map[string]interface{}), if segTyp == ntRegexp {
children: make(map[string]*node), 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. switch {
if i == len(ep)-1 { case segStartIdx == 0:
curNode.label = p // Route starts with a param
for _, method := range methods { child.typ = segTyp
curNode.actions[method] = handler
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 (n *Trie) replaceChild(label, tail byte, child *Trie) error {
func (t *Trie) Search(method string, path string, opts ...TrieOption) (interface{}, map[string]string, bool) { for i := 0; i < len(n.children[child.typ]); i++ {
params := make(map[string]string) if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail {
n.children[child.typ][i] = child
options := TrieOptions{} n.children[child.typ][i].label = label
for _, o := range opts { n.children[child.typ][i].tail = tail
o(&options) return nil
}
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
} }
} }
if path == pathRoot { return fmt.Errorf("replacing missing child")
if len(curNode.actions) == 0 { }
return nil, nil, false
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] func (n *Trie) setEndpoint(method methodTyp, handler interface{}, pattern string) error {
if !ok || handler == nil { // 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 nil, nil, false
} }
return handler, params, true ep, ok := rn.endpoints[methodMap[method]]
} if !ok {
return nil, nil, false
// 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
} }
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 type routeParams struct {
// {id:[^\d+$]} -> id keys []string
// {id} -> id vals []string
func getParamName(label string) string { }
leftI := strings.Index(label, leftPtnDelimiter)
rightI := func(l string) int {
r := []rune(l)
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 t, nds := range nn.children {
for i := 0; i < len(r); i++ { ntyp := nodeTyp(t)
n = i if len(nds) == 0 {
switch string(r[i]) { continue
case paramDelimiter: }
n = i
break loop var xn *Trie
case rightPtnDelimiter: xsearch := search
n = i
break loop 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 { // serially loop through each node grouped by the tail delimiter
n = i + 1 for idx := 0; idx < len(nds); idx++ {
break loop 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 // recursively find the next node..
}(label) 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 (n *Trie) isLeaf() bool {
func splitPath(path string) []string { return n.endpoints != nil
s := strings.Split(path, pathDelimiter) }
var r []string
for _, str := range s { // patNextSegment returns the next segment details from a pattern:
if str != "" { // node type, param key, regexp string, param tail byte, param starting index, param ending index
r = append(r, str) 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 { func patParamKeys(pattern string) ([]string, error) {
if ignoreCase { pat := pattern
return strings.EqualFold(component, literal) 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" "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) { func TestTrieFixedPattern(t *testing.T) {
type handler struct { type handler struct {
name string name string
} }
tr := NewTrie() tr := NewTrie()
tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", &handler{name: "pattern"}) _ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", &handler{name: "pattern"})
tr.Insert([]string{http.MethodPut}, "/v1/create/12", &handler{name: "fixed"}) _ = tr.Insert([]string{http.MethodPut}, "/v1/create/12", &handler{name: "fixed"})
h, _, ok := tr.Search(http.MethodPut, "/v1/create/12", IgnoreCase(false)) h, _, ok := tr.Search(http.MethodPut, "/v1/create/12")
if !ok { if !ok {
t.Fatalf("unexpected error") 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) { func TestTrieNoMatchMethod(t *testing.T) {
tr := NewTrie() 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") _, _, ok := tr.Search(http.MethodPost, "/v1/create")
if ok { if ok {
t.Fatalf("must be not found error") t.Fatalf("must be not found error")
@@ -67,8 +89,7 @@ func TestTrieNoMatchMethod(t *testing.T) {
func TestTrieMatchRegexp(t *testing.T) { func TestTrieMatchRegexp(t *testing.T) {
type handler struct{} type handler struct{}
tr := NewTrie() 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") _, params, ok := tr.Search(http.MethodPut, "/v1/create/test_cat/12345")
switch { switch {
case !ok: case !ok:
@@ -83,8 +104,7 @@ func TestTrieMatchRegexp(t *testing.T) {
func TestTrieMatchRegexpFail(t *testing.T) { func TestTrieMatchRegexpFail(t *testing.T) {
type handler struct{} type handler struct{}
tr := NewTrie() 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") _, _, ok := tr.Search(http.MethodPut, "/v1/create/12345")
if ok { if ok {
t.Fatalf("route must not be not found") t.Fatalf("route must not be not found")
@@ -96,8 +116,8 @@ func TestTrieMatchLongest(t *testing.T) {
name string name string
} }
tr := NewTrie() tr := NewTrie()
tr.Insert([]string{http.MethodPut}, "/v1/create", &handler{name: "first"}) _ = 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/{id:[0-9]+}", &handler{name: "second"})
if h, _, ok := tr.Search(http.MethodPut, "/v1/create/12345"); !ok { if h, _, ok := tr.Search(http.MethodPut, "/v1/create/12345"); !ok {
t.Fatalf("route must be found") t.Fatalf("route must be found")
} else if h.(*handler).name != "second" { } else if h.(*handler).name != "second" {

View File

@@ -1,6 +1,7 @@
package jitter package jitter // import "go.unistack.org/micro/v3/util/jitter"
import ( import (
"context"
"time" "time"
"go.unistack.org/micro/v3/util/rand" "go.unistack.org/micro/v3/util/rand"
@@ -10,13 +11,31 @@ import (
// the min and max duration values (stored internally as int64 nanosecond // the min and max duration values (stored internally as int64 nanosecond
// counts). // counts).
type Ticker struct { type Ticker struct {
C chan time.Time ctx context.Context
done chan chan struct{} done chan chan struct{}
C chan time.Time
min int64 min int64
max int64 max int64
exp int64
exit bool
rng rand.Rand rng rand.Rand
} }
// NewTickerContext returns a pointer to an initialized instance of the Ticker.
// It works like NewTicker except that it has ability to close via context.
// Also it works fine with context.WithTimeout to handle max time to run ticker.
func NewTickerContext(ctx context.Context, min, max time.Duration) *Ticker {
ticker := &Ticker{
C: make(chan time.Time),
done: make(chan chan struct{}),
min: min.Nanoseconds(),
max: max.Nanoseconds(),
ctx: ctx,
}
go ticker.run()
return ticker
}
// NewTicker returns a pointer to an initialized instance of the Ticker. // NewTicker returns a pointer to an initialized instance of the Ticker.
// Min and max are durations of the shortest and longest allowed // Min and max are durations of the shortest and longest allowed
// ticks. Ticker will run in a goroutine until explicitly stopped. // ticks. Ticker will run in a goroutine until explicitly stopped.
@@ -26,6 +45,7 @@ func NewTicker(min, max time.Duration) *Ticker {
done: make(chan chan struct{}), done: make(chan chan struct{}),
min: min.Nanoseconds(), min: min.Nanoseconds(),
max: max.Nanoseconds(), max: max.Nanoseconds(),
ctx: context.Background(),
} }
go ticker.run() go ticker.run()
return ticker return ticker
@@ -33,9 +53,14 @@ func NewTicker(min, max time.Duration) *Ticker {
// Stop terminates the ticker goroutine and closes the C channel. // Stop terminates the ticker goroutine and closes the C channel.
func (ticker *Ticker) Stop() { func (ticker *Ticker) Stop() {
if ticker.exit {
return
}
c := make(chan struct{}) c := make(chan struct{})
ticker.done <- c ticker.done <- c
<-c <-c
// close(ticker.C)
ticker.exit = true
} }
func (ticker *Ticker) run() { func (ticker *Ticker) run() {
@@ -44,6 +69,8 @@ func (ticker *Ticker) run() {
for { for {
// either a stop signal or a timeout // either a stop signal or a timeout
select { select {
case <-ticker.ctx.Done():
t.Stop()
case c := <-ticker.done: case c := <-ticker.done:
t.Stop() t.Stop()
close(c) close(c)

View File

@@ -0,0 +1,62 @@
package jitter
import (
"context"
"testing"
"time"
)
func TestNewTickerContext(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
ticker := NewTickerContext(ctx, 600*time.Millisecond, 1000*time.Millisecond)
defer ticker.Stop()
loop:
for {
select {
case <-ctx.Done():
ticker.Stop()
break loop
case v, ok := <-ticker.C:
if ok {
t.Fatalf("context must be closed %s", v)
}
break loop
}
}
}
func TestTicker(t *testing.T) {
t.Parallel()
min := time.Duration(10)
max := time.Duration(20)
// tick can take a little longer since we're not adjusting it to account for
// processing.
precision := time.Duration(4)
rt := NewTicker(min*time.Millisecond, max*time.Millisecond)
for i := 0; i < 5; i++ {
t0 := time.Now()
t1 := <-rt.C
td := t1.Sub(t0)
if td < min*time.Millisecond {
t.Fatalf("tick was shorter than expected: %s", td)
} else if td > (max+precision)*time.Millisecond {
t.Fatalf("tick was longer than expected: %s", td)
}
}
rt.Stop()
time.Sleep((max + precision) * time.Millisecond)
select {
case v, ok := <-rt.C:
if ok || !v.IsZero() {
t.Fatal("ticker did not shut down")
}
default:
t.Fatal("expected to receive close channel signal")
}
}

View File

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

View File

@@ -10,16 +10,19 @@ type Rand struct {
buf [8]byte buf [8]byte
} }
// Int31 function implementation
func (r *Rand) Int31() int32 { func (r *Rand) Int31() int32 {
_, _ = crand.Read(r.buf[:4]) _, _ = crand.Read(r.buf[:4])
return int32(binary.BigEndian.Uint32(r.buf[:4]) & ^uint32(1<<31)) return int32(binary.BigEndian.Uint32(r.buf[:4]) & ^uint32(1<<31))
} }
// Int function implementation
func (r *Rand) Int() int { func (r *Rand) Int() int {
u := uint(r.Int63()) u := uint(r.Int63())
return int(u << 1 >> 1) // clear sign bit if int == int32 return int(u << 1 >> 1) // clear sign bit if int == int32
} }
// Float64 function implementation
func (r *Rand) Float64() float64 { func (r *Rand) Float64() float64 {
again: again:
f := float64(r.Int63()) / (1 << 63) f := float64(r.Int63()) / (1 << 63)
@@ -29,6 +32,7 @@ again:
return f return f
} }
// Float32 function implementation
func (r *Rand) Float32() float32 { func (r *Rand) Float32() float32 {
again: again:
f := float32(r.Float64()) f := float32(r.Float64())
@@ -38,14 +42,17 @@ again:
return f return f
} }
// Uint32 function implementation
func (r *Rand) Uint32() uint32 { func (r *Rand) Uint32() uint32 {
return uint32(r.Int63() >> 31) return uint32(r.Int63() >> 31)
} }
// Uint64 function implementation
func (r *Rand) Uint64() uint64 { func (r *Rand) Uint64() uint64 {
return uint64(r.Int63())>>31 | uint64(r.Int63())<<32 return uint64(r.Int63())>>31 | uint64(r.Int63())<<32
} }
// Intn function implementation
func (r *Rand) Intn(n int) int { func (r *Rand) Intn(n int) int {
if n <= 1<<31-1 { if n <= 1<<31-1 {
return int(r.Int31n(int32(n))) return int(r.Int31n(int32(n)))
@@ -53,12 +60,13 @@ func (r *Rand) Intn(n int) int {
return int(r.Int63n(int64(n))) return int(r.Int63n(int64(n)))
} }
// Int63 function implementation
func (r *Rand) Int63() int64 { func (r *Rand) Int63() int64 {
_, _ = crand.Read(r.buf[:]) _, _ = crand.Read(r.buf[:])
return int64(binary.BigEndian.Uint64(r.buf[:]) & ^uint64(1<<63)) 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 { func (r *Rand) Int31n(n int32) int32 {
if n&(n-1) == 0 { // n is power of two, can mask if n&(n-1) == 0 { // n is power of two, can mask
return r.Int31() & (n - 1) return r.Int31() & (n - 1)
@@ -71,7 +79,7 @@ func (r *Rand) Int31n(n int32) int32 {
return v % n 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 { func (r *Rand) Int63n(n int64) int64 {
if n&(n-1) == 0 { // n is power of two, can mask if n&(n-1) == 0 { // n is power of two, can mask
return r.Int63() & (n - 1) return r.Int63() & (n - 1)
@@ -84,7 +92,7 @@ func (r *Rand) Int63n(n int64) int64 {
return v % n 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)) { func (r *Rand) Shuffle(n int, swap func(i, j int)) {
if n < 0 { if n < 0 {
panic("invalid argument to Shuffle") panic("invalid argument to Shuffle")

View File

@@ -9,18 +9,30 @@ import (
) )
const ( const (
SplitToken = "." // SplitToken used to detect path components
SplitToken = "."
// IndexCloseChar used to detect index end
IndexCloseChar = "]" IndexCloseChar = "]"
IndexOpenChar = "[" // IndexOpenChar used to detect index start
IndexOpenChar = "["
) )
var ( var (
ErrMalformedIndex = errors.New("Malformed index key") // ErrMalformedIndex returns when index key have invalid format
ErrInvalidIndexUsage = errors.New("Invalid index key usage") ErrMalformedIndex = errors.New("malformed index key")
ErrKeyNotFound = errors.New("Unable to find the key") // ErrInvalidIndexUsage returns when index key usage error
ErrBadJSONPath = errors.New("Bad path: must start with $ and have more then 2 chars") ErrInvalidIndexUsage = errors.New("invalid index key usage")
// ErrKeyNotFound returns when key not found
ErrKeyNotFound = errors.New("unable to find the key")
// ErrBadJSONPath returns when path have invalid syntax
ErrBadJSONPath = errors.New("bad path: must start with $ and have more then 2 chars")
) )
// Lookup performs a lookup into a value, using a path of keys. The key should
// match with a Field or a MapIndex. For slice you can use the syntax key[index]
// to access a specific index. If one key owns to a slice and an index is not
// specificied the rest of the path will be apllied to evaley value of the
// slice, and the value will be merged into a slice.
func Lookup(i interface{}, path string) (reflect.Value, error) { func Lookup(i interface{}, path string) (reflect.Value, error) {
if path == "" || path[0:1] != "$" { if path == "" || path[0:1] != "$" {
return reflect.Value{}, ErrBadJSONPath return reflect.Value{}, ErrBadJSONPath
@@ -37,11 +49,6 @@ func Lookup(i interface{}, path string) (reflect.Value, error) {
return lookup(i, strings.Split(path[2:], SplitToken)...) return lookup(i, strings.Split(path[2:], SplitToken)...)
} }
// Lookup performs a lookup into a value, using a path of keys. The key should
// match with a Field or a MapIndex. For slice you can use the syntax key[index]
// to access a specific index. If one key owns to a slice and an index is not
// specificied the rest of the path will be apllied to evaley value of the
// slice, and the value will be merged into a slice.
func lookup(i interface{}, path ...string) (reflect.Value, error) { func lookup(i interface{}, path ...string) (reflect.Value, error) {
value := reflect.ValueOf(i) value := reflect.ValueOf(i)
var parent reflect.Value var parent reflect.Value

View File

@@ -10,30 +10,40 @@ import (
) )
var ( var (
// ErrInvalidStruct happens when passed not struct and not struct pointer
ErrInvalidStruct = errors.New("invalid struct specified") ErrInvalidStruct = errors.New("invalid struct specified")
ErrInvalidValue = errors.New("invalid value specified") // ErrInvalidValue happens when passed invalid value for field
ErrNotFound = errors.New("struct field not found") 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) type Option func(*Options)
// Options for merge
type Options struct { type Options struct {
Tags []string // Tags specifies tags to lookup
Tags []string
// SliceAppend controls slice appending
SliceAppend bool SliceAppend bool
} }
// Tags sets the merge tags for lookup
func Tags(t []string) Option { func Tags(t []string) Option {
return func(o *Options) { return func(o *Options) {
o.Tags = t o.Tags = t
} }
} }
// SliceAppend sets the option
func SliceAppend(b bool) Option { func SliceAppend(b bool) Option {
return func(o *Options) { return func(o *Options) {
o.SliceAppend = b o.SliceAppend = b
} }
} }
// Merge merges map[string]interface{} to destination struct
func Merge(dst interface{}, mp map[string]interface{}, opts ...Option) error { func Merge(dst interface{}, mp map[string]interface{}, opts ...Option) error {
var err error var err error
var sval reflect.Value var sval reflect.Value
@@ -91,7 +101,10 @@ func Merge(dst interface{}, mp map[string]interface{}, opts ...Option) error {
val, ok := mp[fname] val, ok := mp[fname]
if !ok { if !ok {
continue val, ok = mp[dfld.Name]
if !ok {
continue
}
} }
sval = reflect.ValueOf(val) sval = reflect.ValueOf(val)
@@ -478,6 +491,7 @@ func IsEmpty(v reflect.Value) bool {
return true return true
} }
// FieldName returns map field name that can be looked up in struct field
func FieldName(name string) string { func FieldName(name string) string {
newstr := make([]rune, 0, len(name)) newstr := make([]rune, 0, len(name))
for idx, chr := range name { for idx, chr := range name {

View File

@@ -7,6 +7,7 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"time"
) )
// ErrInvalidParam specifies invalid url query params // ErrInvalidParam specifies invalid url query params
@@ -19,8 +20,8 @@ var bracketSplitter = regexp.MustCompile(`\[|\]`)
// StructField contains struct field path its value and field // StructField contains struct field path its value and field
type StructField struct { type StructField struct {
Path string
Value reflect.Value Value reflect.Value
Path string
Field reflect.StructField Field reflect.StructField
} }
@@ -69,6 +70,89 @@ func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, e
return nil, ErrNotFound return nil, ErrNotFound
} }
// ZeroFieldByPath clean struct field by its path
func ZeroFieldByPath(src 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 IsEmpty(val) {
return nil
}
if !val.CanSet() {
return ErrInvalidStruct
}
val.Set(reflect.Zero(val.Type()))
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 {
sv = sv.Elem()
}
if sv.Kind() != reflect.Struct {
return reflect.Zero(reflect.TypeOf(sv)), ErrInvalidStruct
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if len(fld.PkgPath) != 0 {
continue
}
if fld.Name == tkey || strings.EqualFold(strings.ToLower(fld.Name), strings.ToLower(tkey)) {
return val, nil
}
switch val.Kind() {
case reflect.Ptr:
if val = val.Elem(); val.Kind() == reflect.Struct {
if iface, err := structValueByName(val, tkey); err == nil {
return iface, nil
}
}
case reflect.Struct:
if iface, err := structValueByName(val, tkey); err == nil {
return iface, nil
}
}
}
return reflect.Zero(reflect.TypeOf(sv)), ErrNotFound
}
// StructFieldByPath get struct field by its path // StructFieldByPath get struct field by its path
func StructFieldByPath(src interface{}, path string) (interface{}, error) { func StructFieldByPath(src interface{}, path string) (interface{}, error) {
var err error var err error
@@ -98,10 +182,7 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
if len(fld.PkgPath) != 0 { if len(fld.PkgPath) != 0 {
continue continue
} }
if fld.Name == tkey { 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 return val.Interface(), nil
} }
@@ -153,10 +234,35 @@ func StructFields(src interface{}) ([]StructField, error) {
continue 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() { switch val.Kind() {
// case timeKind: case reflect.Ptr:
// fmt.Printf("GGG\n") if val.CanSet() && fld.Type.Elem().Kind() == reflect.Struct {
// fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name}) if val.IsNil() {
val.Set(reflect.New(fld.Type.Elem()))
}
}
switch reflect.Indirect(val).Kind() {
case 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)
}
default:
fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
}
case reflect.Struct: case reflect.Struct:
infields, err := StructFields(val.Interface()) infields, err := StructFields(val.Interface())
if err != nil { if err != nil {
@@ -167,6 +273,7 @@ func StructFields(src interface{}) ([]StructField, error) {
fields = append(fields, infield) fields = append(fields, infield)
} }
default: default:
fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name}) fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
} }
} }

View File

@@ -4,10 +4,111 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"testing" "testing"
"time"
rutil "go.unistack.org/micro/v3/util/reflect" rutil "go.unistack.org/micro/v3/util/reflect"
) )
func TestStructfields(t *testing.T) {
type NestedConfig struct {
Value string
}
type Config struct {
Time time.Time
Nested *NestedConfig
Metadata map[string]int
Broker string
Addr []string
Wait time.Duration
Verbose bool
}
cfg := &Config{Nested: &NestedConfig{}}
fields, err := rutil.StructFields(cfg)
if err != nil {
t.Fatal(err)
}
if len(fields) != 7 {
for _, field := range fields {
t.Logf("field %#+v\n", field)
}
t.Fatalf("invalid fields number: %d != %d", 7, len(fields))
}
}
func TestSetFieldByPath(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}}
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, "Nested.BBB")
if err != nil {
t.Fatal(err)
}
err = rutil.ZeroFieldByPath(val1, "Nested")
if err != nil {
t.Fatal(err)
}
if val1.Nested.BBB == "ddd" {
t.Fatalf("zero field not works: %v", val1)
}
val2 := &Str2{Name: []string{"first", "second"}, XXX: "ttt", Nested: &NestedStr{BBB: "ddd", CCC: 9}}
err = rutil.ZeroFieldByPath(val2, "Nested")
if err != nil {
t.Fatal(err)
}
if val2.Nested != nil {
t.Fatalf("zero field not works: %v", val2)
}
}
func TestStructFieldsMap(t *testing.T) { func TestStructFieldsMap(t *testing.T) {
type NestedStr struct { type NestedStr struct {
BBB string BBB string
@@ -108,9 +209,9 @@ func TestStructByName(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if v, ok := iface.(*[]string); !ok { if v, ok := iface.([]string); !ok {
t.Fatalf("not *[]string %v", iface) t.Fatalf("not []string %v", iface)
} else if len(*v) != 2 { } else if len(v) != 2 {
t.Fatalf("invalid number %v", iface) t.Fatalf("invalid number %v", iface)
} }
} }

View File

@@ -1,7 +1,11 @@
package register // import "go.unistack.org/micro/v3/util/register" package register // import "go.unistack.org/micro/v3/util/register"
import ( import (
"context"
"time"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
jitter "go.unistack.org/micro/v3/util/jitter"
) )
func addNodes(old, neu []*register.Node) []*register.Node { func addNodes(old, neu []*register.Node) []*register.Node {
@@ -146,3 +150,30 @@ func Remove(old, del []*register.Service) []*register.Service {
return services return services
} }
// WaitService using register wait for service to appear with min/max interval for check and optional timeout.
// Timeout can be 0 to wait infinitive.
func WaitService(ctx context.Context, reg register.Register, name string, min time.Duration, max time.Duration, timeout time.Duration, opts ...register.LookupOption) error {
if timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}
ticker := jitter.NewTickerContext(ctx, min, max)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case _, ok := <-ticker.C:
if _, err := reg.LookupService(ctx, name, opts...); err == nil {
return nil
}
if ok {
return register.ErrNotFound
}
}
}
}

View File

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