Compare commits

...

131 Commits

Author SHA1 Message Date
ccbf23688b Merge pull request #168 from unistack-org/tracer
tracer: add context to Options
2023-01-17 08:33:35 +03:00
3bd6db79cb tracer: add context to Options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-17 08:30:57 +03:00
9347bb0651 use no default content-type (#165)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-19 21:45:49 +03:00
e6c3d734a3 Merge pull request #156 from unistack-org/take_unwrap
logger/unwrap: add tagged option
2022-11-19 15:23:22 +03:00
1374e27531 logger/unwrap: add tagged option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-19 15:20:42 +03:00
1060f6a4c3 Merge pull request #154 from unistack-org/client_metadata
client: fix WithResponseMetadata CallOption
2022-11-14 16:13:22 +03:00
7d72ab05c6 client: fix WithResponseMetadata CallOption
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-14 16:10:54 +03:00
42864ff1c6 Merge pull request #153 from unistack-org/client_metadata
client: add req/rsp metadata to CallOptions
2022-11-14 15:31:58 +03:00
49978b75c0 client: add req/rsp metadata to CallOptions
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-14 15:29:45 +03:00
dependabot[bot]
20770b6e30 chore(deps): bump golangci/golangci-lint-action from 3.3.0 to 3.3.1 (#151)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.3.0 to 3.3.1.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.3.0...v3.3.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 13:43:44 +03:00
b38c6106b2 Merge pull request #152 from unistack-org/dependabot/go_modules/go.unistack.org/micro-proto/v3-3.3.1
chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.7 to 3.3.1
2022-11-14 09:05:23 +03:00
dependabot[bot]
138c4a0888 chore(deps): bump go.unistack.org/micro-proto/v3 from 3.2.7 to 3.3.1
Bumps [go.unistack.org/micro-proto/v3](https://github.com/unistack-org/micro-proto) from 3.2.7 to 3.3.1.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v3.2.7...v3.3.1)

---
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-11-14 06:03:28 +00:00
dependabot[bot]
22f66fc258 chore(deps): bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 (#148)
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.4 to 1.3.5.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.4...v1.3.5)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-12 18:22:11 +03:00
18fafbbbab Merge pull request #150 from unistack-org/unwrap
logger/unwrap: intergate omit functionality
2022-11-07 16:00:45 +03:00
59c08c1d9a logger/unwrap: intergate omit functionality
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-07 15:56:09 +03:00
5fbb1a923e Merge pull request #149 from unistack-org/improvements
minor improvements
2022-11-07 14:56:24 +03:00
396387d1e8 minor improvements
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-11-07 14:53:58 +03:00
dependabot[bot]
4c2f12a419 chore(deps): bump golangci/golangci-lint-action from 3.2.0 to 3.3.0 (#144)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.2.0...v3.3.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-26 19:34:23 +03:00
b2abb86971 Merge pull request #147 from unistack-org/client_context_dialer
client: add ContextDialer/WithContextDialer option
2022-10-26 19:23:01 +03:00
e546eef96c client: add ContextDialer/WithContextDialer option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-10-26 19:20:37 +03:00
91701e7a45 Merge pull request #146 from unistack-org/codec_noop_fix
codec: fix noop
2022-10-26 13:39:52 +03:00
817bf1f4d0 codec: fix noop
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-10-26 13:37:30 +03:00
4120f79b55 Merge pull request #145 from unistack-org/codec_fix
codec: add []byte support to noop codec
2022-10-26 08:28:36 +03:00
d659db69ff codec: add []byte support to noop codec
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-10-26 08:26:10 +03:00
416bb313fc Merge pull request #143 from unistack-org/logger_unwrap
WIP: logger/unwrap: add unwrap method
2022-10-11 00:33:25 +03:00
ec43cfea6b logger/unwrap: add unwrap method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-10-11 00:30:42 +03:00
dependabot[bot]
60194fb42e chore(deps): bump codecov/codecov-action from 3.1.0 to 3.1.1 (#141)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.1.0...v3.1.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-06 17:34:40 +03:00
dependabot[bot]
945d9d16a5 chore(deps): bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 (#142)
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.3 to 1.3.4.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.3...v1.3.4)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-06 17:34:25 +03:00
1c0e5e1a85 Merge pull request #139 from unistack-org/store
store: add Timeout option
2022-07-08 22:41:47 +03:00
33591e0bc9 fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-07-08 22:39:51 +03:00
dependabot[bot]
75cbaf2612 chore(deps): bump dependabot/fetch-metadata from 1.3.1 to 1.3.3 (#137)
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.1 to 1.3.3.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.1...v1.3.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-08 22:33:05 +03:00
f4aee3414b store: add Timeout option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-07-08 22:16:33 +03:00
9f7b61eb17 add test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-06-29 23:04:01 +03:00
5953b5aae6 Merge pull request #133 from unistack-org/mtls
WIP: initial mtls package
2022-06-27 00:22:10 +03:00
4a8f490e0c fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-06-27 00:20:04 +03:00
eb8c1332f0 fix test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-06-27 00:18:14 +03:00
c1c27b6d1d Merge branch 'v3' into mtls 2022-06-27 00:17:56 +03:00
dependabot[bot]
bb22b203cc chore(deps): bump github.com/imdario/mergo from 0.3.12 to 0.3.13 (#134)
Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.12 to 0.3.13.
- [Release notes](https://github.com/imdario/mergo/releases)
- [Commits](https://github.com/imdario/mergo/compare/0.3.12...v0.3.13)

---
updated-dependencies:
- dependency-name: github.com/imdario/mergo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-25 23:28:02 +03:00
dependabot[bot]
4df2f3a5a1 chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.4.1 to 4.4.2 (#135)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.1 to 4.4.2.
- [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.4.1...v4.4.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-25 22:39:53 +03:00
b8ad19a5a2 WIP: initial mtls package
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-12 15:11:31 +03:00
dependabot[bot]
d32a97c846 chore(deps): bump golangci/golangci-lint-action from 3.1.0 to 3.2.0 (#132)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.1.0...v3.2.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-11 11:28:38 +03:00
cfe0473ae0 Merge pull request #130 from unistack-org/improvements
Improvements
2022-05-03 16:08:50 +03:00
c26ad51e25 config: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:56 +03:00
aefc398b71 flow: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:51 +03:00
9af23e3e74 metadata: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:42 +03:00
4ab7f19ef0 logger: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:36 +03:00
d26e9d642b meter: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:28 +03:00
f9ecb9b056 register: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:18 +03:00
dbfcfcd288 router: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:12 +03:00
8b6bdb857b store: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:05:03 +03:00
1181e9dc5e tracer: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:04:57 +03:00
6ac7b53d75 server: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 16:04:48 +03:00
80d342a72a client: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 15:59:38 +03:00
8ff312e71d broker: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 15:51:08 +03:00
20e40ccdfd api: improve coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 15:41:30 +03:00
d4efbb9b22 metadata: allow to Set/Del multiple items
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 15:19:10 +03:00
b433cbcbb6 Merge pull request #129 from unistack-org/api_cleanup
api: cleanup
2022-05-03 14:48:23 +03:00
dae3c1170b api: cleanup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 14:46:03 +03:00
a10dd3d08a Merge pull request #128 from unistack-org/big_remove
global cleanup
2022-05-03 14:40:55 +03:00
b075230ae5 global cleanup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 14:38:44 +03:00
289aadb28e Merge pull request #127 from unistack-org/cover
add more cover stuff
2022-05-03 00:26:13 +03:00
9640cdae1a add more cover stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 00:23:43 +03:00
dependabot[bot]
fb35e73731 chore(deps): bump github/codeql-action from 1 to 2 (#126)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-26 23:42:38 +03:00
f416cb3e0e Merge pull request #125 from unistack-org/cover
add tests
2022-04-24 11:11:32 +03:00
57d06d5d27 add tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-24 11:08:38 +03:00
0628408c27 Merge pull request #124 from unistack-org/cover
util/reflect: improve test coverage
2022-04-22 09:27:03 +03:00
206cd8c3c9 util/reflect: improve test coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-22 09:24:34 +03:00
dependabot[bot]
b38db00ee5 chore(deps): bump codecov/codecov-action from 3.0.0 to 3.1.0 (#123)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-22 06:14:04 +00:00
dependabot[bot]
0ca39a1477 chore(deps): bump dependabot/fetch-metadata from 1.3.0 to 1.3.1 (#122)
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.0...v1.3.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-21 12:47:39 +03:00
d9be99cfde Merge pull request #121 from unistack-org/fsm
fsm: add state execution options
2022-04-19 18:45:14 +03:00
b37c6006c4 fsm: add state execution options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-19 18:42:30 +03:00
12f188e3ad Merge pull request #120 from unistack-org/fsm
fsm: run steps in order
2022-04-19 17:36:05 +03:00
08aaf14a79 fsm: run steps in order
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-19 17:33:33 +03:00
2ce1e94596 Merge pull request #119 from unistack-org/errors
errors: add CodeIn helper func
2022-04-19 17:15:03 +03:00
c5aeaf6db7 errors: add CodeIn helper func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-19 17:12:25 +03:00
1db505decd Merge pull request #118 from unistack-org/cover
improve coverage
2022-04-17 16:28:59 +03:00
8b1a579c9d add context tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-17 16:25:42 +03:00
11b614f2df client: add retry func tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-17 13:11:36 +03:00
fb4d747197 server: fix race
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-17 11:41:49 +03:00
00439e23f3 add client call options tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-16 16:36:43 +03:00
955953b519 client: fix lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-16 16:36:34 +03:00
aa2b5ddaad client: add backoff tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-16 16:36:15 +03:00
46da092899 client: implement Call and Stream methods for noop
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-16 16:35:52 +03:00
b871f64ba6 Merge pull request #117 from unistack-org/race
server: fix race in noop server
2022-04-15 15:53:18 +03:00
74db004f51 server: fix race in noop server
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-15 15:50:45 +03:00
f93ba9d977 Merge pull request #116 from unistack-org/fsm
fsm: initial import
2022-04-15 15:22:55 +03:00
c7da7d5bc8 fsm: initial import
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-15 15:20:17 +03:00
ed27647be5 Merge pull request #115 from unistack-org/dependabot/github_actions/actions/setup-go-3
chore(deps): bump actions/setup-go from 2 to 3
2022-04-11 23:01:22 +03:00
dependabot[bot]
db3b67267e chore(deps): bump actions/setup-go from 2 to 3
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-11 06:17:56 +00:00
9ee9cc2a4a Merge pull request #114 from unistack-org/cover
fix coverage
2022-04-07 19:18:34 +03:00
0b41b4f9c5 fix 2022-04-07 19:15:55 +03:00
8d14753931 Merge branch 'v3' into cover 2022-04-07 19:13:56 +03:00
93fc17bad3 fix coverage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-07 19:12:49 +03:00
5a1cd12d3d Merge pull request #113 from unistack-org/vtolstov-patch-1
Update README.md
2022-04-07 19:11:22 +03:00
5c00e6763f Update README.md
fix link to godoc
2022-04-07 19:08:45 +03:00
497b82ac6c Merge pull request #112 from unistack-org/codecov
add codecov
2022-04-07 19:07:17 +03:00
a8c6690af7 add codecov
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-07 19:04:04 +03:00
98d2264c2a add codecov
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-07 19:00:08 +03:00
63641b9840 add codecov
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-07 18:57:30 +03:00
2b28057918 add codecov
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-04-07 18:56:10 +03:00
25c551411b Merge pull request #111 from unistack-org/config
service: fix ordering
2022-03-30 15:51:27 +03:00
35162a82a4 service: fix ordering
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-30 15:48:43 +03:00
0ce0855b6a Merge pull request #109 from unistack-org/dependabot/go_modules/github.com/golang-jwt/jwt/v4-4.4.1
chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.4.0 to 4.4.1
2022-03-30 15:42:23 +03:00
226ec43ecf Merge pull request #110 from unistack-org/config
service: config load only on start, not init phase
2022-03-30 15:39:51 +03:00
575af66ddc service: config load only on start, not init phase
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-30 15:37:02 +03:00
dependabot[bot]
afb9e8c240 chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.4.0 to 4.4.1
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.0 to 4.4.1.
- [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.4.0...v4.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 06:11:57 +00:00
c10f29ee74 Merge pull request #108 from unistack-org/improve
small improve
2022-03-27 01:39:33 +03:00
03410c4ab1 small improve
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-03-27 01:37:21 +03:00
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
136 changed files with 4441 additions and 3940 deletions

View File

@@ -3,6 +3,10 @@ name: "autoapprove"
on: on:
pull_request_target: pull_request_target:
types: [assigned, opened, synchronize, reopened] types: [assigned, opened, synchronize, reopened]
workflow_run:
workflows: ["prbuild"]
types:
- completed
permissions: permissions:
pull-requests: write pull-requests: write
@@ -13,8 +17,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: approve - name: approve
uses: hmarr/auto-approve-action@v2 uses: hmarr/auto-approve-action@v3
if: github.actor == 'vtolstov' || github.actor == 'dependabot[bot]' if: github.actor == 'vtolstov' || github.actor == 'dependabot[bot]'
id: approve id: approve
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup - name: setup
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: 1.17 go-version: 1.17
- name: checkout - name: checkout
@@ -34,7 +34,7 @@ jobs:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: lint - name: lint
uses: golangci/golangci-lint-action@v3.1.0 uses: golangci/golangci-lint-action@v3.3.1
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.

39
.github/workflows/codecov.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: "codecov"
on:
workflow_run:
workflows: ["build"]
types:
- completed
push:
branches: [ v3 ]
pull_request:
branches: [ v3 ]
schedule:
- cron: '34 1 * * 0'
jobs:
codecov:
name: codecov
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: checkout
uses: actions/checkout@v3
- name: setup
uses: actions/setup-go@v3
with:
go-version: 1.17
- name: Run coverage
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: codecov
uses: codecov/codecov-action@v3.1.1

View File

@@ -45,12 +45,12 @@ jobs:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: setup - name: setup
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: 1.17 go-version: 1.17
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: init - name: init
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -61,7 +61,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@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -75,4 +75,4 @@ jobs:
# make release # make release
- name: analyze - name: analyze
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@@ -15,7 +15,7 @@ jobs:
steps: steps:
- name: metadata - name: metadata
id: metadata id: metadata
uses: dependabot/fetch-metadata@v1.3.0 uses: dependabot/fetch-metadata@v1.3.5
with: with:
github-token: "${{ secrets.TOKEN }}" github-token: "${{ secrets.TOKEN }}"
- name: merge - name: merge

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup - name: setup
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: 1.17 go-version: 1.17
- name: checkout - name: checkout
@@ -34,7 +34,7 @@ jobs:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: lint - name: lint
uses: golangci/golangci-lint-action@v3.1.0 uses: golangci/golangci-lint-action@v3.3.1
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.

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/report/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3) [![Slack](https://img.shields.io/static/v1?label=micro&message=slack&color=blueviolet)](https://unistack-org.slack.com/messages/default) # Micro [![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) [![Coverage](https://codecov.io/gh/unistack-org/micro/branch/v3/graph/badge.svg?token=OZPO2LP7VS)](https://codecov.io/gh/unistack-org/micro)
Micro is a standard library for microservices. Micro is a standard library for microservices.

View File

@@ -3,10 +3,27 @@ package api
import ( import (
"strings" "strings"
"testing" "testing"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/server"
) )
func TestDecode(t *testing.T) {
md := metadata.New(0)
md.Set("host", "localhost", "method", "GET", "path", "/")
ep := Decode(md)
if md == nil {
t.Fatalf("failed to decode md %#+v", md)
} else if len(ep.Host) != 1 || len(ep.Method) != 1 || len(ep.Path) != 1 {
t.Fatalf("ep invalid after decode %#+v", ep)
}
if ep.Host[0] != "localhost" || ep.Method[0] != "GET" || ep.Path[0] != "/" {
t.Fatalf("ep invalid after decode %#+v", ep)
}
}
//nolint:gocyclo //nolint:gocyclo
func TestEncoding(t *testing.T) { func TestEncode(t *testing.T) {
testData := []*Endpoint{ testData := []*Endpoint{
nil, nil,
{ {
@@ -150,3 +167,79 @@ func TestValidate(t *testing.T) {
t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0]) t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0])
} }
} }
func TestWithEndpoint(t *testing.T) {
ep := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test/{id}"},
}
o := WithEndpoint(ep)
opts := server.NewHandlerOptions(o)
if opts.Metadata == nil {
t.Fatalf("WithEndpoint not works %#+v", opts)
}
md, ok := opts.Metadata[ep.Name]
if !ok {
t.Fatalf("WithEndpoint not works %#+v", opts)
}
if v, ok := md.Get("Endpoint"); !ok || v != "Foo.Bar" {
t.Fatalf("WithEndpoint not works %#+v", md)
}
}
func TestValidateNilErr(t *testing.T) {
var ep *Endpoint
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateMissingNameErr(t *testing.T) {
ep := &Endpoint{}
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateMissingHandlerErr(t *testing.T) {
ep := &Endpoint{Name: "test"}
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpStartErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test"}
ep.Path = []string{"^/"}
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpEndErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test", Path: []string{""}}
ep.Path[0] = "/$"
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpNonErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test", Path: []string{""}}
ep.Path[0] = "^/(.*)$"
if err := Validate(ep); err != nil {
t.Fatalf("Validate not works")
}
}
func TestValidateRegexpErr(t *testing.T) {
ep := &Endpoint{Name: "test", Handler: "test", Path: []string{""}}
ep.Path[0] = "^/(.$"
if err := Validate(ep); err == nil {
t.Fatalf("Validate not works")
}
}

View File

@@ -1,14 +0,0 @@
// Package handler provides http handlers
package handler // import "go.unistack.org/micro/v3/api/handler"
import (
"net/http"
)
// Handler represents a HTTP handler that manages a request
type Handler interface {
// standard http handler
http.Handler
// name of handler
String() string
}

View File

@@ -1,70 +0,0 @@
package handler
import (
"go.unistack.org/micro/v3/api/router"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/logger"
)
// DefaultMaxRecvSize specifies max recv size for handler
var DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
// Options struct holds handler options
type Options struct {
Router router.Router
Client client.Client
Logger logger.Logger
Namespace string
MaxRecvSize int64
}
// Option func signature
type Option func(o *Options)
// NewOptions creates new options struct and fills it
func NewOptions(opts ...Option) Options {
options := Options{
Client: client.DefaultClient,
Router: router.DefaultRouter,
Logger: logger.DefaultLogger,
MaxRecvSize: DefaultMaxRecvSize,
}
for _, o := range opts {
o(&options)
}
// set namespace if blank
if len(options.Namespace) == 0 {
WithNamespace("go.micro.api")(&options)
}
return options
}
// WithNamespace specifies the namespace for the handler
func WithNamespace(s string) Option {
return func(o *Options) {
o.Namespace = s
}
}
// WithRouter specifies a router to be used by the handler
func WithRouter(r router.Router) Option {
return func(o *Options) {
o.Router = r
}
}
// WithClient specifies client to be used by the handler
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// WithMaxRecvSize specifies max body size
func WithMaxRecvSize(size int64) Option {
return func(o *Options) {
o.MaxRecvSize = size
}
}

View File

@@ -1,47 +0,0 @@
// Package grpc resolves a grpc service like /greeter.Say/Hello to greeter service
package grpc // import "go.unistack.org/micro/v3/api/resolver/grpc"
import (
"errors"
"net/http"
"strings"
"go.unistack.org/micro/v3/api/resolver"
)
// Resolver struct
type Resolver struct {
opts resolver.Options
}
// Resolve func to resolve enndpoint
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
// /foo.Bar/Service
if req.URL.Path == "/" {
return nil, errors.New("unknown name")
}
// [foo.Bar, Service]
parts := strings.Split(req.URL.Path[1:], "/")
// [foo, Bar]
name := strings.Split(parts[0], ".")
// foo
return &resolver.Endpoint{
Name: strings.Join(name[:len(name)-1], "."),
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
func (r *Resolver) String() string {
return "grpc"
}
// NewResolver is used to create new Resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{opts: resolver.NewOptions(opts...)}
}

View File

@@ -1,35 +0,0 @@
// Package host resolves using http host
package host // import "go.unistack.org/micro/v3/api/resolver/host"
import (
"net/http"
"go.unistack.org/micro/v3/api/resolver"
)
type hostResolver struct {
opts resolver.Options
}
// Resolve endpoint
func (r *hostResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
return &resolver.Endpoint{
Name: req.Host,
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
func (r *hostResolver) String() string {
return "host"
}
// NewResolver creates new host api resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &hostResolver{opts: resolver.NewOptions(opts...)}
}

View File

@@ -1,70 +0,0 @@
package resolver
import (
"context"
"go.unistack.org/micro/v3/register"
)
// Options struct
type Options struct {
// Context is for external defined options
Context context.Context
// Handler name
Handler string
// ServicePrefix is the prefix
ServicePrefix string
}
// Option func
type Option func(o *Options)
// WithHandler sets the handler being used
func WithHandler(h string) Option {
return func(o *Options) {
o.Handler = h
}
}
// WithServicePrefix sets the ServicePrefix option
func WithServicePrefix(p string) Option {
return func(o *Options) {
o.ServicePrefix = p
}
}
// NewOptions returns new initialised options
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// ResolveOptions are used when resolving a request
type ResolveOptions struct {
Domain string
}
// ResolveOption sets an option
type ResolveOption func(*ResolveOptions)
// Domain sets the resolve Domain option
func Domain(n string) ResolveOption {
return func(o *ResolveOptions) {
o.Domain = n
}
}
// NewResolveOptions returns new initialised resolve options
func NewResolveOptions(opts ...ResolveOption) ResolveOptions {
options := ResolveOptions{Domain: register.DefaultDomain}
for _, o := range opts {
o(&options)
}
return options
}

View File

@@ -1,44 +0,0 @@
// Package path resolves using http path
package path // import "go.unistack.org/micro/v3/api/resolver/path"
import (
"net/http"
"strings"
"go.unistack.org/micro/v3/api/resolver"
)
// Resolver the path resolver
type Resolver struct {
opts resolver.Options
}
// Resolve resolves endpoint
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
if req.URL.Path == "/" {
return nil, resolver.ErrNotFound
}
parts := strings.Split(req.URL.Path[1:], "/")
return &resolver.Endpoint{
Name: r.opts.ServicePrefix + "." + parts[0],
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
// String retruns the string representation
func (r *Resolver) String() string {
return "path"
}
// NewResolver returns new path resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{opts: resolver.NewOptions(opts...)}
}

View File

@@ -1,34 +0,0 @@
// Package resolver resolves a http request to an endpoint
package resolver // import "go.unistack.org/micro/v3/api/resolver"
import (
"errors"
"net/http"
)
var (
// ErrNotFound returned when endpoint is not found
ErrNotFound = errors.New("not found")
// ErrInvalidPath returned on invalid path
ErrInvalidPath = errors.New("invalid path")
)
// Resolver resolves requests to endpoints
type Resolver interface {
Resolve(r *http.Request, opts ...ResolveOption) (*Endpoint, error)
String() string
}
// Endpoint is the endpoint for a http request
type Endpoint struct {
// Endpoint name e.g greeter
Name string
// HTTP Host e.g example.com
Host string
// HTTP Methods e.g GET, POST
Method string
// HTTP Path e.g /greeter.
Path string
// Domain endpoint exists within
Domain string
}

View File

@@ -1,90 +0,0 @@
// Package subdomain is a resolver which uses the subdomain to determine the domain to route to. It
// offloads the endpoint resolution to a child resolver which is provided in New.
package subdomain // import "go.unistack.org/micro/v3/api/resolver/subdomain"
import (
"net"
"net/http"
"strings"
"go.unistack.org/micro/v3/api/resolver"
"go.unistack.org/micro/v3/logger"
"golang.org/x/net/publicsuffix"
)
// NewResolver creates new subdomain api resolver
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
options := resolver.NewOptions(opts...)
return &subdomainResolver{opts: options, Resolver: parent}
}
type subdomainResolver struct {
resolver.Resolver
opts resolver.Options
}
// Resolve resolve endpoint based on subdomain
func (r *subdomainResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
if dom := r.Domain(req); len(dom) > 0 {
opts = append(opts, resolver.Domain(dom))
}
return r.Resolver.Resolve(req, opts...)
}
// Domain returns domain
func (r *subdomainResolver) Domain(req *http.Request) string {
// determine the host, e.g. foobar.m3o.app
host := req.URL.Hostname()
if len(host) == 0 {
if h, _, err := net.SplitHostPort(req.Host); err == nil {
host = h // host does contain a port
} else if strings.Contains(err.Error(), "missing port in address") {
host = req.Host // host does not contain a port
}
}
// check for an ip address
if net.ParseIP(host) != nil {
return ""
}
// check for dev environment
if host == "localhost" || host == "127.0.0.1" {
return ""
}
// extract the top level domain plus one (e.g. 'myapp.com')
domain, err := publicsuffix.EffectiveTLDPlusOne(host)
if err != nil {
if logger.V(logger.DebugLevel) {
logger.Debug(r.opts.Context, "Unable to extract domain from %v", host)
}
return ""
}
// there was no subdomain
if host == domain {
return ""
}
// remove the domain from the host, leaving the subdomain, e.g. "staging.foo.myapp.com" => "staging.foo"
subdomain := strings.TrimSuffix(host, "."+domain)
// ignore the API subdomain
if subdomain == "api" {
return ""
}
// return the reversed subdomain as the namespace, e.g. "staging.foo" => "foo-staging"
comps := strings.Split(subdomain, ".")
for i := len(comps)/2 - 1; i >= 0; i-- {
opp := len(comps) - 1 - i
comps[i], comps[opp] = comps[opp], comps[i]
}
return strings.Join(comps, "-")
}
func (r *subdomainResolver) String() string {
return "subdomain"
}

View File

@@ -1,73 +0,0 @@
package subdomain
import (
"net/http"
"net/url"
"testing"
"go.unistack.org/micro/v3/api/resolver/vpath"
)
func TestResolve(t *testing.T) {
tt := []struct {
Name string
Host string
Result string
}{
{
Name: "Top level domain",
Host: "micro.mu",
Result: "micro",
},
{
Name: "Effective top level domain",
Host: "micro.com.au",
Result: "micro",
},
{
Name: "Subdomain dev",
Host: "dev.micro.mu",
Result: "dev",
},
{
Name: "Subdomain foo",
Host: "foo.micro.mu",
Result: "foo",
},
{
Name: "Multi-level subdomain",
Host: "staging.myapp.m3o.app",
Result: "myapp-staging",
},
{
Name: "Dev host",
Host: "127.0.0.1",
Result: "micro",
},
{
Name: "Localhost",
Host: "localhost",
Result: "micro",
},
{
Name: "IP host",
Host: "81.151.101.146",
Result: "micro",
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
r := NewResolver(vpath.NewResolver())
result, err := r.Resolve(&http.Request{URL: &url.URL{Host: tc.Host, Path: "foo/bar"}})
if err != nil {
t.Fatal(err)
}
if result != nil {
if tc.Result != result.Domain {
t.Fatalf("Expected %v but got %v", tc.Result, result.Domain)
}
}
})
}
}

View File

@@ -1,75 +0,0 @@
// Package vpath resolves using http path and recognised versioned urls
package vpath // import "go.unistack.org/micro/v3/api/resolver/vpath"
import (
"errors"
"net/http"
"regexp"
"strings"
"go.unistack.org/micro/v3/api/resolver"
)
// NewResolver creates new vpath api resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &vpathResolver{opts: resolver.NewOptions(opts...)}
}
type vpathResolver struct {
opts resolver.Options
}
var re = regexp.MustCompile("^v[0-9]+$")
// Resolve endpoint
func (r *vpathResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
if req.URL.Path == "/" {
return nil, errors.New("unknown name")
}
options := resolver.NewResolveOptions(opts...)
parts := strings.Split(req.URL.Path[1:], "/")
if len(parts) == 1 {
return &resolver.Endpoint{
Name: r.withPrefix(parts...),
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
// /v1/foo
if re.MatchString(parts[0]) {
return &resolver.Endpoint{
Name: r.withPrefix(parts[0:2]...),
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
return &resolver.Endpoint{
Name: r.withPrefix(parts[0]),
Host: req.Host,
Method: req.Method,
Path: req.URL.Path,
Domain: options.Domain,
}, nil
}
func (r *vpathResolver) String() string {
return "vpath"
}
// withPrefix transforms "foo" into "go.micro.api.foo"
func (r *vpathResolver) withPrefix(parts ...string) string {
p := r.opts.ServicePrefix
if len(p) > 0 {
parts = append([]string{p}, parts...)
}
return strings.Join(parts, ".")
}

View File

@@ -1,75 +0,0 @@
package router
import (
"context"
"go.unistack.org/micro/v3/api/resolver"
"go.unistack.org/micro/v3/api/resolver/vpath"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/register"
)
// Options holds the options for api router
type Options struct {
// Register for service lookup
Register register.Register
// Resolver to use
Resolver resolver.Resolver
// Logger micro logger
Logger logger.Logger
// Context is for external options
Context context.Context
// Handler name
Handler string
}
// Option func signature
type Option func(o *Options)
// NewOptions returns options struct filled by opts
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
Handler: "meta",
}
for _, o := range opts {
o(&options)
}
if options.Resolver == nil {
options.Resolver = vpath.NewResolver(
resolver.WithHandler(options.Handler),
)
}
return options
}
// WithContext sets the context
func WithContext(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// WithHandler sets the handler
func WithHandler(h string) Option {
return func(o *Options) {
o.Handler = h
}
}
// WithRegister sets the register
func WithRegister(r register.Register) Option {
return func(o *Options) {
o.Register = r
}
}
// WithResolver sets the resolver
func WithResolver(r resolver.Resolver) Option {
return func(o *Options) {
o.Resolver = r
}
}

View File

@@ -1,31 +0,0 @@
// Package router provides api service routing
package router // import "go.unistack.org/micro/v3/api/router"
import (
"net/http"
"go.unistack.org/micro/v3/api"
)
// DefaultRouter contains default router implementation
var DefaultRouter Router
// Router is used to determine an endpoint for a request
type Router interface {
// Returns options
Options() Options
// Init initialize router
Init(...Option) error
// Stop the router
Close() error
// Endpoint returns an api.Service endpoint or an error if it does not exist
Endpoint(r *http.Request) (*api.Service, error)
// Register endpoint in router
Register(ep *api.Endpoint) error
// Deregister endpoint from router
Deregister(ep *api.Endpoint) error
// Route returns an api.Service route
Route(r *http.Request) (*api.Service, error)
// String representation of router
String() string
}

View File

@@ -1,141 +0,0 @@
// Package auth provides authentication and authorization capability
package auth // import "go.unistack.org/micro/v3/auth"
import (
"context"
"errors"
"time"
"go.unistack.org/micro/v3/metadata"
)
const (
// BearerScheme used for Authorization header
BearerScheme = "Bearer "
// ScopePublic is the scope applied to a rule to allow access to the public
ScopePublic = ""
// ScopeAccount is the scope applied to a rule to limit to users with any valid account
ScopeAccount = "*"
)
var (
// DefaultAuth holds default auth implementation
DefaultAuth Auth = NewAuth()
// ErrInvalidToken is when the token provided is not valid
ErrInvalidToken = errors.New("invalid token provided")
// ErrForbidden is when a user does not have the necessary scope to access a resource
ErrForbidden = errors.New("resource forbidden")
)
// Auth provides authentication and authorization
type Auth interface {
// Init the auth
Init(opts ...Option) error
// Options set for auth
Options() Options
// Generate a new account
Generate(id string, opts ...GenerateOption) (*Account, error)
// Verify an account has access to a resource using the rules
Verify(acc *Account, res *Resource, opts ...VerifyOption) error
// Inspect a token
Inspect(token string) (*Account, error)
// Token generated using refresh token or credentials
Token(opts ...TokenOption) (*Token, error)
// Grant access to a resource
Grant(rule *Rule) error
// Revoke access to a resource
Revoke(rule *Rule) error
// Rules returns all the rules used to verify requests
Rules(...RulesOption) ([]*Rule, error)
// String returns the name of the implementation
String() string
}
// Account provided by an auth provider
type Account struct {
// Metadata any other associated metadata
Metadata metadata.Metadata `json:"metadata"`
// ID of the account e.g. email or id
ID string `json:"id"`
// Type of the account, e.g. service
Type string `json:"type"`
// Issuer of the account
Issuer string `json:"issuer"`
// Secret for the account, e.g. the password
Secret string `json:"secret"`
// Scopes the account has access to
Scopes []string `json:"scopes"`
}
// Token can be short or long lived
type Token struct {
// Time of token creation
Created time.Time `json:"created"`
// Time of token expiry
Expiry time.Time `json:"expiry"`
// The token to be used for accessing resources
AccessToken string `json:"access_token"`
// RefreshToken to be used to generate a new token
RefreshToken string `json:"refresh_token"`
}
// Expired returns a boolean indicating if the token needs to be refreshed
func (t *Token) Expired() bool {
return t.Expiry.Unix() < time.Now().Unix()
}
// Resource is an entity such as a user or
type Resource struct {
// Name of the resource, e.g. go.micro.service.notes
Name string `json:"name"`
// Type of resource, e.g. service
Type string `json:"type"`
// Endpoint resource e.g NotesService.Create
Endpoint string `json:"endpoint"`
}
// Access defines the type of access a rule grants
type Access int
const (
// AccessGranted to a resource
AccessGranted Access = iota
// AccessDenied to a resource
AccessDenied
)
// Rule is used to verify access to a resource
type Rule struct {
// Resource that rule belongs to
Resource *Resource
// ID of the rule
ID string
// Scope of the rule
Scope string
// Access flag allow/deny
Access Access
// Priority holds the rule priority
Priority int32
}
type accountKey struct{}
// AccountFromContext gets the account from the context, which
// is set by the auth wrapper at the start of a call. If the account
// is not set, a nil account will be returned. The error is only returned
// when there was a problem retrieving an account
func AccountFromContext(ctx context.Context) (*Account, bool) {
if ctx == nil {
return nil, false
}
acc, ok := ctx.Value(accountKey{}).(*Account)
return acc, ok
}
// ContextWithAccount sets the account in the context
func ContextWithAccount(ctx context.Context, account *Account) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, accountKey{}, account)
}

View File

@@ -1,79 +0,0 @@
package auth
import (
"go.unistack.org/micro/v3/util/id"
)
type noopAuth struct {
opts Options
}
// String returns the name of the implementation
func (n *noopAuth) String() string {
return "noop"
}
// Init the auth
func (n *noopAuth) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
return nil
}
// Options set for auth
func (n *noopAuth) Options() Options {
return n.opts
}
// Generate a new account
func (n *noopAuth) Generate(id string, opts ...GenerateOption) (*Account, error) {
options := NewGenerateOptions(opts...)
return &Account{
ID: id,
Secret: options.Secret,
Metadata: options.Metadata,
Scopes: options.Scopes,
Issuer: n.Options().Issuer,
}, nil
}
// Grant access to a resource
func (n *noopAuth) Grant(rule *Rule) error {
return nil
}
// Revoke access to a resource
func (n *noopAuth) Revoke(rule *Rule) error {
return nil
}
// Rules used to verify requests
func (n *noopAuth) Rules(opts ...RulesOption) ([]*Rule, error) {
return []*Rule{}, nil
}
// Verify an account has access to a resource
func (n *noopAuth) Verify(acc *Account, res *Resource, opts ...VerifyOption) error {
return nil
}
// Inspect a token
func (n *noopAuth) Inspect(token string) (*Account, error) {
id, err := id.New()
if err != nil {
return nil, err
}
return &Account{ID: id, Issuer: n.Options().Issuer}, nil
}
// Token generation using an account id and secret
func (n *noopAuth) Token(opts ...TokenOption) (*Token, error) {
return &Token{}, nil
}
// NewAuth returns new noop auth
func NewAuth(opts ...Option) Auth {
return &noopAuth{opts: NewOptions(opts...)}
}

View File

@@ -1,311 +0,0 @@
package auth
import (
"context"
"time"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/tracer"
)
// NewOptions creates Options struct from slice of options
func NewOptions(opts ...Option) Options {
options := Options{
Tracer: tracer.DefaultTracer,
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
}
for _, o := range opts {
o(&options)
}
return options
}
// Options struct holds auth options
type Options struct {
// Context holds the external options
Context context.Context
// Meter used for metrics
Meter meter.Meter
// Logger used for logging
Logger logger.Logger
// Tracer used for tracing
Tracer tracer.Tracer
// Store used for stre data
Store store.Store
// Token is the services token used to authenticate itself
Token *Token
// LoginURL is the relative url path where a user can login
LoginURL string
// PrivateKey for encoding JWTs
PrivateKey string
// PublicKey for decoding JWTs
PublicKey string
// Secret is used to authenticate the service
Secret string
// ID is the services auth ID
ID string
// Issuer of the service's account
Issuer string
// Name holds the auth name
Name string
// Addrs sets the addresses of auth
Addrs []string
}
// Option func
type Option func(o *Options)
// Addrs is the auth addresses to use
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Addrs = addrs
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Issuer of the services account
func Issuer(i string) Option {
return func(o *Options) {
o.Issuer = i
}
}
// Store to back auth
func Store(s store.Store) Option {
return func(o *Options) {
o.Store = s
}
}
// PublicKey is the JWT public key
func PublicKey(key string) Option {
return func(o *Options) {
o.PublicKey = key
}
}
// PrivateKey is the JWT private key
func PrivateKey(key string) Option {
return func(o *Options) {
o.PrivateKey = key
}
}
// Credentials sets the auth credentials
func Credentials(id, secret string) Option {
return func(o *Options) {
o.ID = id
o.Secret = secret
}
}
// ClientToken sets the auth token to use when making requests
func ClientToken(token *Token) Option {
return func(o *Options) {
o.Token = token
}
}
// LoginURL sets the auth LoginURL
func LoginURL(url string) Option {
return func(o *Options) {
o.LoginURL = url
}
}
// GenerateOptions struct
type GenerateOptions struct {
Metadata metadata.Metadata
Provider string
Type string
Secret string
Issuer string
Scopes []string
}
// GenerateOption func
type GenerateOption func(o *GenerateOptions)
// WithSecret for the generated account
func WithSecret(s string) GenerateOption {
return func(o *GenerateOptions) {
o.Secret = s
}
}
// WithType for the generated account
func WithType(t string) GenerateOption {
return func(o *GenerateOptions) {
o.Type = t
}
}
// WithMetadata for the generated account
func WithMetadata(md metadata.Metadata) GenerateOption {
return func(o *GenerateOptions) {
o.Metadata = metadata.Copy(md)
}
}
// WithProvider for the generated account
func WithProvider(p string) GenerateOption {
return func(o *GenerateOptions) {
o.Provider = p
}
}
// WithScopes for the generated account
func WithScopes(s ...string) GenerateOption {
return func(o *GenerateOptions) {
o.Scopes = s
}
}
// WithIssuer for the generated account
func WithIssuer(i string) GenerateOption {
return func(o *GenerateOptions) {
o.Issuer = i
}
}
// NewGenerateOptions from a slice of options
func NewGenerateOptions(opts ...GenerateOption) GenerateOptions {
var options GenerateOptions
for _, o := range opts {
o(&options)
}
return options
}
// TokenOptions struct
type TokenOptions struct {
ID string
Secret string
RefreshToken string
Issuer string
Expiry time.Duration
}
// TokenOption func
type TokenOption func(o *TokenOptions)
// WithExpiry for the token
func WithExpiry(ex time.Duration) TokenOption {
return func(o *TokenOptions) {
o.Expiry = ex
}
}
// WithCredentials sets tye id and secret
func WithCredentials(id, secret string) TokenOption {
return func(o *TokenOptions) {
o.ID = id
o.Secret = secret
}
}
// WithToken sets the refresh token
func WithToken(rt string) TokenOption {
return func(o *TokenOptions) {
o.RefreshToken = rt
}
}
// WithTokenIssuer sets the token issuer option
func WithTokenIssuer(iss string) TokenOption {
return func(o *TokenOptions) {
o.Issuer = iss
}
}
// NewTokenOptions from a slice of options
func NewTokenOptions(opts ...TokenOption) TokenOptions {
var options TokenOptions
for _, o := range opts {
o(&options)
}
// set default expiry of token
if options.Expiry == 0 {
options.Expiry = time.Minute
}
return options
}
// VerifyOptions struct
type VerifyOptions struct {
Context context.Context
Namespace string
}
// VerifyOption func
type VerifyOption func(o *VerifyOptions)
// VerifyContext pass context to verify
func VerifyContext(ctx context.Context) VerifyOption {
return func(o *VerifyOptions) {
o.Context = ctx
}
}
// VerifyNamespace sets thhe namespace for verify
func VerifyNamespace(ns string) VerifyOption {
return func(o *VerifyOptions) {
o.Namespace = ns
}
}
// RulesOptions struct
type RulesOptions struct {
Context context.Context
Namespace string
}
// RulesOption func
type RulesOption func(o *RulesOptions)
// RulesContext pass rules context
func RulesContext(ctx context.Context) RulesOption {
return func(o *RulesOptions) {
o.Context = ctx
}
}
// RulesNamespace sets the rule namespace
func RulesNamespace(ns string) RulesOption {
return func(o *RulesOptions) {
o.Namespace = ns
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Tracer sets the meter
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}

View File

@@ -1,92 +0,0 @@
package auth
import (
"fmt"
"sort"
"strings"
)
// VerifyAccess an account has access to a resource using the rules provided. If the account does not have
// access an error will be returned. If there are no rules provided which match the resource, an error
// will be returned
//nolint:gocyclo
func VerifyAccess(rules []*Rule, acc *Account, res *Resource) error {
// the rule is only to be applied if the type matches the resource or is catch-all (*)
validTypes := []string{"*", res.Type}
// the rule is only to be applied if the name matches the resource or is catch-all (*)
validNames := []string{"*", res.Name}
// rules can have wildcard excludes on endpoints since this can also be a path for web services,
// e.g. /foo/* would include /foo/bar. We also want to check for wildcards and the exact endpoint
validEndpoints := []string{"*", res.Endpoint}
if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 {
for i := 1; i < len(comps)+1; i++ {
wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/"))
validEndpoints = append(validEndpoints, wildcard)
}
}
// filter the rules to the ones which match the criteria above
filteredRules := make([]*Rule, 0)
for _, rule := range rules {
if !include(validTypes, rule.Resource.Type) {
continue
}
if !include(validNames, rule.Resource.Name) {
continue
}
if !include(validEndpoints, rule.Resource.Endpoint) {
continue
}
filteredRules = append(filteredRules, rule)
}
// sort the filtered rules by priority, highest to lowest
sort.SliceStable(filteredRules, func(i, j int) bool {
return filteredRules[i].Priority > filteredRules[j].Priority
})
// loop through the rules and check for a rule which applies to this account
for _, rule := range filteredRules {
// a blank scope indicates the rule applies to everyone, even nil accounts
if rule.Scope == ScopePublic && rule.Access == AccessDenied {
return ErrForbidden
} else if rule.Scope == ScopePublic && rule.Access == AccessGranted {
return nil
}
// all further checks require an account
if acc == nil {
continue
}
// this rule applies to any account
if rule.Scope == ScopeAccount && rule.Access == AccessDenied {
return ErrForbidden
} else if rule.Scope == ScopeAccount && rule.Access == AccessGranted {
return nil
}
// if the account has the necessary scope
if include(acc.Scopes, rule.Scope) && rule.Access == AccessDenied {
return ErrForbidden
} else if include(acc.Scopes, rule.Scope) && rule.Access == AccessGranted {
return nil
}
}
// if no rules matched then return forbidden
return ErrForbidden
}
// include is a helper function which checks to see if the slice contains the value. includes is
// not case sensitive.
func include(slice []string, val string) bool {
for _, s := range slice {
if strings.EqualFold(s, val) {
return true
}
}
return false
}

View File

@@ -1,288 +0,0 @@
package auth
import (
"testing"
)
func TestVerify(t *testing.T) {
srvResource := &Resource{
Type: "service",
Name: "go.micro.service.foo",
Endpoint: "Foo.Bar",
}
webResource := &Resource{
Type: "service",
Name: "go.micro.web.foo",
Endpoint: "/foo/bar",
}
catchallResource := &Resource{
Type: "*",
Name: "*",
Endpoint: "*",
}
tt := []struct {
Error error
Account *Account
Resource *Resource
Name string
Rules []*Rule
}{
{
Name: "NoRules",
Rules: []*Rule{},
Account: nil,
Resource: srvResource,
Error: ErrForbidden,
},
{
Name: "CatchallPublicAccount",
Account: &Account{},
Resource: srvResource,
Rules: []*Rule{
{
Scope: "",
Resource: catchallResource,
},
},
},
{
Name: "CatchallPublicNoAccount",
Resource: srvResource,
Rules: []*Rule{
{
Scope: "",
Resource: catchallResource,
},
},
},
{
Name: "CatchallPrivateAccount",
Account: &Account{},
Resource: srvResource,
Rules: []*Rule{
{
Scope: "*",
Resource: catchallResource,
},
},
},
{
Name: "CatchallPrivateNoAccount",
Resource: srvResource,
Rules: []*Rule{
{
Scope: "*",
Resource: catchallResource,
},
},
Error: ErrForbidden,
},
{
Name: "CatchallServiceRuleMatch",
Resource: srvResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: &Resource{
Type: srvResource.Type,
Name: srvResource.Name,
Endpoint: "*",
},
},
},
},
{
Name: "CatchallServiceRuleNoMatch",
Resource: srvResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: &Resource{
Type: srvResource.Type,
Name: "wrongname",
Endpoint: "*",
},
},
},
Error: ErrForbidden,
},
{
Name: "ExactRuleValidScope",
Resource: srvResource,
Account: &Account{
Scopes: []string{"neededscope"},
},
Rules: []*Rule{
{
Scope: "neededscope",
Resource: srvResource,
},
},
},
{
Name: "ExactRuleInvalidScope",
Resource: srvResource,
Account: &Account{
Scopes: []string{"neededscope"},
},
Rules: []*Rule{
{
Scope: "invalidscope",
Resource: srvResource,
},
},
Error: ErrForbidden,
},
{
Name: "CatchallDenyWithAccount",
Resource: srvResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: catchallResource,
Access: AccessDenied,
},
},
Error: ErrForbidden,
},
{
Name: "CatchallDenyWithNoAccount",
Resource: srvResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: catchallResource,
Access: AccessDenied,
},
},
Error: ErrForbidden,
},
{
Name: "RulePriorityGrantFirst",
Resource: srvResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: catchallResource,
Access: AccessGranted,
Priority: 1,
},
{
Scope: "*",
Resource: catchallResource,
Access: AccessDenied,
Priority: 0,
},
},
},
{
Name: "RulePriorityDenyFirst",
Resource: srvResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: catchallResource,
Access: AccessGranted,
Priority: 0,
},
{
Scope: "*",
Resource: catchallResource,
Access: AccessDenied,
Priority: 1,
},
},
Error: ErrForbidden,
},
{
Name: "WebExactEndpointValid",
Resource: webResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: webResource,
},
},
},
{
Name: "WebExactEndpointInalid",
Resource: webResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: &Resource{
Type: webResource.Type,
Name: webResource.Name,
Endpoint: "invalidendpoint",
},
},
},
Error: ErrForbidden,
},
{
Name: "WebWildcardEndpoint",
Resource: webResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: &Resource{
Type: webResource.Type,
Name: webResource.Name,
Endpoint: "*",
},
},
},
},
{
Name: "WebWildcardPathEndpointValid",
Resource: webResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: &Resource{
Type: webResource.Type,
Name: webResource.Name,
Endpoint: "/foo/*",
},
},
},
},
{
Name: "WebWildcardPathEndpointInvalid",
Resource: webResource,
Account: &Account{},
Rules: []*Rule{
{
Scope: "*",
Resource: &Resource{
Type: webResource.Type,
Name: webResource.Name,
Endpoint: "/bar/*",
},
},
},
Error: ErrForbidden,
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
if err := VerifyAccess(tc.Rules, tc.Account, tc.Resource); err != tc.Error {
t.Errorf("Expected %v but got %v", tc.Error, err)
}
})
}
}

View File

@@ -9,7 +9,7 @@ import (
) )
// DefaultBroker default memory broker // DefaultBroker default memory broker
var DefaultBroker Broker = NewBroker() var DefaultBroker = NewBroker()
var ( var (
// ErrNotConnected returns when broker used but not connected yet // ErrNotConnected returns when broker used but not connected yet

72
broker/context_test.go Normal file
View File

@@ -0,0 +1,72 @@
package broker
import (
"context"
"testing"
)
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), brokerKey{}, NewBroker())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewBroker())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewBroker())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetSubscribeOption(t *testing.T) {
type key struct{}
o := SetSubscribeOption(key{}, "test")
opts := &SubscribeOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetSubscribeOption not works")
}
}
func TestSetPublishOption(t *testing.T) {
type key struct{}
o := SetPublishOption(key{}, "test")
opts := &PublishOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetPublishOption not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -1,32 +0,0 @@
// Package build is for building source into a package
package build // import "go.unistack.org/micro/v3/build"
// Build is an interface for building packages
type Build interface {
// Package builds a package
Package(name string, src *Source) (*Package, error)
// Remove removes the package
Remove(*Package) error
}
// Source is the source of a build
type Source struct {
// Path to the source if local
Path string
// Language is the language of code
Language string
// Location of the source
Repository string
}
// Package is packaged format for source
type Package struct {
// Source of the package
Source *Source
// Name of the package
Name string
// Location of the package
Path string
// Type of package e.g tarball, binary, docker
Type string
}

View File

@@ -1,17 +0,0 @@
package build
// Options struct
type Options struct {
// local path to download source
Path string
}
// Option func
type Option func(o *Options)
// Path is the Local path for repository
func Path(p string) Option {
return func(o *Options) {
o.Path = p
}
}

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

@@ -6,7 +6,7 @@ import (
"time" "time"
) )
func TestBackoff(t *testing.T) { func TestBackoffExp(t *testing.T) {
results := []time.Duration{ results := []time.Duration{
0 * time.Second, 0 * time.Second,
100 * time.Millisecond, 100 * time.Millisecond,
@@ -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)
} }
@@ -32,3 +32,25 @@ func TestBackoff(t *testing.T) {
} }
} }
} }
func TestBackoffInterval(t *testing.T) {
min := 100 * time.Millisecond
max := 300 * time.Millisecond
r := &testRequest{
service: "test",
method: "test",
}
fn := BackoffInterval(min, max)
for i := 0; i < 5; i++ {
d, err := fn(context.TODO(), r, i)
if err != nil {
t.Fatal(err)
}
if d < min || d > max {
t.Fatalf("Expected %v < %v < %v", min, d, max)
}
}
}

View File

@@ -11,11 +11,11 @@ import (
var ( var (
// DefaultClient is the global default client // DefaultClient is the global default client
DefaultClient Client = NewClient() DefaultClient = NewClient()
// DefaultContentType is the default content-type if not specified // DefaultContentType is the default content-type if not specified
DefaultContentType = "application/json" DefaultContentType = ""
// 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
@@ -74,7 +74,7 @@ type Request interface {
type Response interface { type Response interface {
// Read the response // Read the response
Codec() codec.Codec Codec() codec.Codec
// read the header // Header data
Header() metadata.Metadata Header() metadata.Metadata
// Read the undecoded response // Read the undecoded response
Read() ([]byte, error) Read() ([]byte, error)

View File

@@ -0,0 +1,26 @@
package client
import (
"context"
"testing"
"time"
)
func TestNewClientCallOptions(t *testing.T) {
var flag bool
w := func(fn CallFunc) CallFunc {
flag = true
return fn
}
c := NewClientCallOptions(NewClient(),
WithAddress("127.0.0.1"),
WithCallWrapper(w),
WithRequestTimeout(1*time.Millisecond),
WithRetries(0),
WithBackoff(BackoffInterval(10*time.Millisecond, 100*time.Millisecond)),
)
_ = c.Call(context.TODO(), c.NewRequest("service", "endpoint", nil), nil)
if !flag {
t.Fatalf("NewClientCallOptions not works")
}
}

73
client/context_test.go Normal file
View File

@@ -0,0 +1,73 @@
package client
import (
"context"
"testing"
)
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), clientKey{}, NewClient())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewClient())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewClient())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetPublishOption(t *testing.T) {
type key struct{}
o := SetPublishOption(key{}, "test")
opts := &PublishOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetPublishOption not works")
}
}
func TestSetCallOption(t *testing.T) {
type key struct{}
o := SetCallOption(key{}, "test")
opts := &CallOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetCallOption not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -2,11 +2,14 @@ package client
import ( import (
"context" "context"
"fmt"
"time"
"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/errors" "go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/selector"
) )
// DefaultCodecs will be used to encode/decode data // DefaultCodecs will be used to encode/decode data
@@ -181,6 +184,138 @@ func (n *noopClient) String() string {
} }
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error { func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
// make a copy of call opts
callOpts := n.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
// check if we already have a deadline
d, ok := ctx.Deadline()
if !ok {
var cancel context.CancelFunc
// no deadline so we create a new one
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
defer cancel()
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
opt := WithRequestTimeout(time.Until(d))
opt(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
default:
}
// make copy of call method
hcall := n.call
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
hcall = callOpts.CallWrappers[i-1](hcall)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = n.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = n.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(n.opts.Proxy) > 0 {
callOpts.Address = []string{n.opts.Proxy}
}
var next selector.Next
// return errors.New("go.micro.client", "request timeout", 408)
call := func(i int) error {
// call backoff first. Someone may want an initial start delay
t, err := callOpts.Backoff(ctx, req, i)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
if next == nil {
var routes []string
// lookup the route to send the reques to
// TODO apply any filtering here
routes, err = n.opts.Lookup(ctx, req, callOpts)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err = callOpts.Selector.Select(routes)
if err != nil {
return err
}
}
node := next()
// make the call
err = hcall(ctx, node, req, rsp, callOpts)
// record the result of the call to inform future routing decisions
if verr := n.opts.Selector.Record(node, err); verr != nil {
return verr
}
// try and transform the error to a go-micro error
if verr, ok := err.(*errors.Error); ok {
return verr
}
return err
}
ch := make(chan error, callOpts.Retries)
var gerr error
for i := 0; i <= callOpts.Retries; i++ {
go func() {
ch <- call(i)
}()
select {
case <-ctx.Done():
return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
case err := <-ch:
// if the call succeeded lets bail early
if err == nil {
return nil
}
retry, rerr := callOpts.Retry(ctx, req, i, err)
if rerr != nil {
return rerr
}
if !retry {
return err
}
gerr = err
}
}
return gerr
}
func (n *noopClient) call(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
return nil return nil
} }
@@ -194,6 +329,146 @@ func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOp
} }
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) { func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
var err error
// make a copy of call opts
callOpts := n.opts.CallOptions
for _, o := range opts {
o(&callOpts)
}
// check if we already have a deadline
d, ok := ctx.Deadline()
if !ok && callOpts.StreamTimeout > time.Duration(0) {
var cancel context.CancelFunc
// no deadline so we create a new one
ctx, cancel = context.WithTimeout(ctx, callOpts.StreamTimeout)
defer cancel()
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
o := WithStreamTimeout(time.Until(d))
o(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
default:
}
/*
// make copy of call method
hstream := h.stream
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
hstream = callOpts.CallWrappers[i-1](hstream)
}
*/
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = n.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = n.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(n.opts.Proxy) > 0 {
callOpts.Address = []string{n.opts.Proxy}
}
var next selector.Next
call := func(i int) (Stream, error) {
// call backoff first. Someone may want an initial start delay
t, cerr := callOpts.Backoff(ctx, req, i)
if cerr != nil {
return nil, errors.InternalServerError("go.micro.client", cerr.Error())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
if next == nil {
var routes []string
// lookup the route to send the reques to
// TODO apply any filtering here
routes, err = n.opts.Lookup(ctx, req, callOpts)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err = callOpts.Selector.Select(routes)
if err != nil {
return nil, err
}
}
node := next()
stream, cerr := n.stream(ctx, node, req, callOpts)
// record the result of the call to inform future routing decisions
if verr := n.opts.Selector.Record(node, cerr); verr != nil {
return nil, verr
}
// try and transform the error to a go-micro error
if verr, ok := cerr.(*errors.Error); ok {
return nil, verr
}
return stream, cerr
}
type response struct {
stream Stream
err error
}
ch := make(chan response, callOpts.Retries)
var grr error
for i := 0; i <= callOpts.Retries; i++ {
go func() {
s, cerr := call(i)
ch <- response{s, cerr}
}()
select {
case <-ctx.Done():
return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
case rsp := <-ch:
// if the call succeeded lets bail early
if rsp.err == nil {
return rsp.stream, nil
}
retry, rerr := callOpts.Retry(ctx, req, i, err)
if rerr != nil {
return nil, rerr
}
if !retry {
return nil, rsp.err
}
grr = rsp.err
}
}
return nil, grr
}
func (n *noopClient) stream(ctx context.Context, addr string, req Request, opts CallOptions) (Stream, error) {
return &noopStream{}, nil return &noopStream{}, nil
} }

View File

@@ -3,6 +3,7 @@ package client
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"net"
"time" "time"
"go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
@@ -56,6 +57,8 @@ type Options struct {
PoolSize int PoolSize int
// PoolTTL connection pool ttl // PoolTTL connection pool ttl
PoolTTL time.Duration PoolTTL time.Duration
// ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error)
} }
// NewCallOptions creates new call options struct // NewCallOptions creates new call options struct
@@ -95,10 +98,23 @@ type CallOptions struct {
StreamTimeout time.Duration StreamTimeout time.Duration
// RequestTimeout request timeout // RequestTimeout request timeout
RequestTimeout time.Duration RequestTimeout time.Duration
// RequestMetadata holds additional metadata for call
RequestMetadata metadata.Metadata
// ResponseMetadata holds additional metadata from call
ResponseMetadata *metadata.Metadata
// DialTimeout dial timeout // DialTimeout dial timeout
DialTimeout time.Duration DialTimeout time.Duration
// Retries specifies retries num // Retries specifies retries num
Retries int Retries int
// ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error)
}
// ContextDialer pass ContextDialer to client
func ContextDialer(fn func(context.Context, string) (net.Conn, error)) Option {
return func(o *Options) {
o.ContextDialer = fn
}
} }
// Context pass context to client // Context pass context to client
@@ -413,6 +429,13 @@ func PublishContext(ctx context.Context) PublishOption {
} }
} }
// WithContextDialer pass ContextDialer to client call
func WithContextDialer(fn func(context.Context, string) (net.Conn, error)) CallOption {
return func(o *CallOptions) {
o.ContextDialer = fn
}
}
// WithContentType specifies call content type // WithContentType specifies call content type
func WithContentType(ct string) CallOption { func WithContentType(ct string) CallOption {
return func(o *CallOptions) { return func(o *CallOptions) {
@@ -458,6 +481,20 @@ func WithRetries(i int) CallOption {
} }
} }
// WithResponseMetadata is a CallOption which adds metadata.Metadata to Options.CallOptions
func WithResponseMetadata(md *metadata.Metadata) CallOption {
return func(o *CallOptions) {
o.ResponseMetadata = md
}
}
// WithRequestMetadata is a CallOption which adds metadata.Metadata to Options.CallOptions
func WithRequestMetadata(md metadata.Metadata) CallOption {
return func(o *CallOptions) {
o.RequestMetadata = md
}
}
// WithRequestTimeout is a CallOption which overrides that which // WithRequestTimeout is a CallOption which overrides that which
// set in Options.CallOptions // set in Options.CallOptions
func WithRequestTimeout(d time.Duration) CallOption { func WithRequestTimeout(d time.Duration) CallOption {

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(_ context.Context, _ Request, _ 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
}
}

70
client/retry_test.go Normal file
View File

@@ -0,0 +1,70 @@
package client
import (
"context"
"fmt"
"testing"
"go.unistack.org/micro/v3/errors"
)
func TestRetryAlways(t *testing.T) {
tests := []error{
nil,
errors.InternalServerError("test", "%s", "test"),
fmt.Errorf("test"),
}
for _, e := range tests {
ok, er := RetryAlways(context.TODO(), nil, 1, e)
if !ok || er != nil {
t.Fatal("RetryAlways not works properly")
}
}
}
func TestRetryNever(t *testing.T) {
tests := []error{
nil,
errors.InternalServerError("test", "%s", "test"),
fmt.Errorf("test"),
}
for _, e := range tests {
ok, er := RetryNever(context.TODO(), nil, 1, e)
if ok || er != nil {
t.Fatal("RetryNever not works properly")
}
}
}
func TestRetryOnError(t *testing.T) {
tests := []error{
fmt.Errorf("test"),
errors.NotFound("test", "%s", "test"),
errors.Timeout("test", "%s", "test"),
}
for i, e := range tests {
ok, er := RetryOnError(context.TODO(), nil, 1, e)
if i == 2 && (!ok || er != nil) {
t.Fatal("RetryOnError not works properly")
}
}
}
func TestRetryOnErrors(t *testing.T) {
tests := []error{
fmt.Errorf("test"),
errors.NotFound("test", "%s", "test"),
errors.Timeout("test", "%s", "test"),
}
fn := RetryOnErrors(404)
for i, e := range tests {
ok, er := fn(context.TODO(), nil, 1, e)
if i == 1 && (!ok || er != nil) {
t.Fatal("RetryOnErrors not works properly")
}
}
}

View File

@@ -27,7 +27,7 @@ var (
// DefaultMaxMsgSize specifies how much data codec can handle // DefaultMaxMsgSize specifies how much data codec can handle
DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec // DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec() DefaultCodec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal // DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
DefaultTagName = "codec" DefaultTagName = "codec"
) )

35
codec/context_test.go Normal file
View File

@@ -0,0 +1,35 @@
package codec
import (
"context"
"testing"
)
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), codecKey{}, NewCodec())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewCodec())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -106,6 +106,9 @@ func (c *noopCodec) Unmarshal(d []byte, v interface{}, opts ...Option) error {
case *string: case *string:
*ve = string(d) *ve = string(d)
return nil return nil
case []byte:
copy(ve, d)
return nil
case *[]byte: case *[]byte:
*ve = d *ve = d
return nil return nil

View File

@@ -5,7 +5,7 @@ import (
"testing" "testing"
) )
func TestNoopBytes(t *testing.T) { func TestNoopBytesPtr(t *testing.T) {
req := []byte("test req") req := []byte("test req")
rsp := make([]byte, len(req)) rsp := make([]byte, len(req))
@@ -19,6 +19,20 @@ func TestNoopBytes(t *testing.T) {
} }
} }
func TestNoopBytes(t *testing.T) {
req := []byte("test req")
var rsp []byte
nc := NewCodec()
if err := nc.Unmarshal(req, &rsp); err != nil {
t.Fatal(err)
}
if !bytes.Equal(req, rsp) {
t.Fatalf("req not eq rsp: %s != %s", req, rsp)
}
}
func TestNoopString(t *testing.T) { func TestNoopString(t *testing.T) {
req := []byte("test req") req := []byte("test req")
var rsp string var rsp string

86
config/context_test.go Normal file
View File

@@ -0,0 +1,86 @@
package config
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewConfig())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), configKey{}, NewConfig())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewConfig())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}
func TestSetSaveOption(t *testing.T) {
type key struct{}
o := SetSaveOption(key{}, "test")
opts := &SaveOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetSaveOption not works")
}
}
func TestSetLoadOption(t *testing.T) {
type key struct{}
o := SetLoadOption(key{}, "test")
opts := &LoadOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetLoadOption not works")
}
}
func TestSetWatchOption(t *testing.T) {
type key struct{}
o := SetWatchOption(key{}, "test")
opts := &WatchOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetWatchOption not works")
}
}

24
context_test.go Normal file
View File

@@ -0,0 +1,24 @@
package micro
import (
"context"
"testing"
)
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), serviceKey{}, NewService())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewService())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}

View File

@@ -233,6 +233,27 @@ func Equal(err1 error, err2 error) bool {
return true return true
} }
// CodeIn return true if err has specified code
func CodeIn(err interface{}, codes ...int32) bool {
var code int32
switch verr := err.(type) {
case *Error:
code = verr.Code
case int32:
code = verr
default:
return false
}
for _, check := range codes {
if code == check {
return true
}
}
return false
}
// FromError try to convert go error to *Error // FromError try to convert go error to *Error
func FromError(err error) *Error { func FromError(err error) *Error {
if verr, ok := err.(*Error); ok && verr != nil { if verr, ok := err.(*Error); ok && verr != nil {

View File

@@ -96,3 +96,19 @@ func TestErrors(t *testing.T) {
} }
} }
} }
func TestCodeIn(t *testing.T) {
err := InternalServerError("id", "%s", "msg")
if ok := CodeIn(err, 400, 500); !ok {
t.Fatalf("CodeIn not works: %v", err)
}
if ok := CodeIn(err.(*Error).Code, 500); !ok {
t.Fatalf("CodeIn not works: %v", err)
}
if ok := CodeIn(err, 100); ok {
t.Fatalf("CodeIn not works: %v", err)
}
}

53
flow/context_test.go Normal file
View File

@@ -0,0 +1,53 @@
package flow
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewFlow())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), flowKey{}, NewFlow())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewFlow())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -7,6 +7,55 @@ import (
"github.com/silas/dag" "github.com/silas/dag"
) )
func TestDeps(t *testing.T) {
d := &dag.AcyclicGraph{}
v0 := d.Add(&node{"v0"})
v1 := d.Add(&node{"v1"})
v2 := d.Add(&node{"v2"})
v3 := d.Add(&node{"v3"})
v4 := d.Add(&node{"v4"})
d.Connect(dag.BasicEdge(v0, v1))
d.Connect(dag.BasicEdge(v1, v2))
d.Connect(dag.BasicEdge(v2, v4))
d.Connect(dag.BasicEdge(v0, v3))
d.Connect(dag.BasicEdge(v3, v4))
if err := d.Validate(); err != nil {
t.Fatal(err)
}
d.TransitiveReduction()
var steps [][]string
fn := func(n dag.Vertex, idx int) error {
if idx == 0 {
steps = make([][]string, 1)
steps[0] = make([]string, 0, 1)
} else if idx >= len(steps) {
tsteps := make([][]string, idx+1)
copy(tsteps, steps)
steps = tsteps
steps[idx] = make([]string, 0, 1)
}
steps[idx] = append(steps[idx], fmt.Sprintf("%s", n))
return nil
}
start := &node{"v0"}
err := d.SortedDepthFirstWalk([]dag.Vertex{start}, fn)
checkErr(t, err)
for idx, steps := range steps {
fmt.Printf("level %d steps %#+v\n", idx, steps)
}
if len(steps[2]) != 1 {
t.Logf("invalid steps %#+v", steps[2])
}
}
func checkErr(t *testing.T, err error) { func checkErr(t *testing.T, err error) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@@ -3,7 +3,6 @@ package flow
import ( import (
"context" "context"
"fmt" "fmt"
"path/filepath"
"sync" "sync"
"github.com/silas/dag" "github.com/silas/dag"
@@ -150,17 +149,17 @@ func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) {
} }
func (w *microWorkflow) Abort(ctx context.Context, id string) error { func (w *microWorkflow) Abort(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+id)
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())}) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
} }
func (w *microWorkflow) Suspend(ctx context.Context, id string) error { func (w *microWorkflow) Suspend(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+id)
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())}) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
} }
func (w *microWorkflow) Resume(ctx context.Context, id string) error { func (w *microWorkflow) Resume(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+id)
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())}) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
} }
@@ -181,8 +180,8 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return "", err return "", err
} }
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid)) stepStore := store.NewNamespaceStore(w.opts.Store, "steps"+w.opts.Store.Options().Separator+eid)
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+eid)
options := NewExecuteOptions(opts...) options := NewExecuteOptions(opts...)
@@ -219,7 +218,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
for idx := range steps { for idx := range steps {
for nidx := range steps[idx] { for nidx := range steps[idx] {
cstep := steps[idx][nidx] cstep := steps[idx][nidx]
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
return eid, werr return eid, werr
} }
} }
@@ -246,32 +245,32 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
wg.Add(1) wg.Add(1)
go func(step Step) { go func(step Step) {
defer wg.Done() defer wg.Done()
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "req"), req); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"req", req); werr != nil {
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr cherr <- werr
return return
} }
rsp, serr := step.Execute(nctx, req, nopts...) rsp, serr := step.Execute(nctx, req, nopts...)
if serr != nil { if serr != nil {
step.SetStatus(StatusFailure) step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
cherr <- serr cherr <- serr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), rsp); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr cherr <- werr
return return
@@ -279,32 +278,32 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
}(cstep) }(cstep)
wg.Wait() wg.Wait()
} else { } else {
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "req"), req); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"req", req); werr != nil {
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr cherr <- werr
return return
} }
rsp, serr := cstep.Execute(nctx, req, nopts...) rsp, serr := cstep.Execute(nctx, req, nopts...)
if serr != nil { if serr != nil {
cstep.SetStatus(StatusFailure) cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
} }
cherr <- serr cherr <- serr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), rsp); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
cherr <- werr cherr <- werr
return return
} }

181
fsm/fsm.go Normal file
View File

@@ -0,0 +1,181 @@
package fsm // import "go.unistack.org/micro/v3/fsm"
import (
"context"
"errors"
"fmt"
"sync"
)
var (
ErrInvalidState = errors.New("does not exists")
StateEnd = "end"
)
// Options struct holding fsm options
type Options struct {
// DryRun mode
DryRun bool
// Initial state
Initial string
// HooksBefore func slice runs in order before state
HooksBefore []HookBeforeFunc
// HooksAfter func slice runs in order after state
HooksAfter []HookAfterFunc
}
// HookBeforeFunc func signature
type HookBeforeFunc func(ctx context.Context, state string, args interface{})
// HookAfterFunc func signature
type HookAfterFunc func(ctx context.Context, state string, args interface{})
// Option func signature
type Option func(*Options)
// StateOptions holds state options
type StateOptions struct {
DryRun bool
}
// StateDryRun says that state executes in dry run mode
func StateDryRun(b bool) StateOption {
return func(o *StateOptions) {
o.DryRun = b
}
}
// StateOption func signature
type StateOption func(*StateOptions)
// InitialState sets init state for state machine
func InitialState(initial string) Option {
return func(o *Options) {
o.Initial = initial
}
}
// HookBefore provides hook func slice
func HookBefore(fns ...HookBeforeFunc) Option {
return func(o *Options) {
o.HooksBefore = fns
}
}
// HookAfter provides hook func slice
func HookAfter(fns ...HookAfterFunc) Option {
return func(o *Options) {
o.HooksAfter = fns
}
}
// StateFunc called on state transition and return next step and error
type StateFunc func(ctx context.Context, args interface{}, opts ...StateOption) (string, interface{}, error)
// FSM is a finite state machine
type FSM struct {
mu sync.Mutex
statesMap map[string]StateFunc
statesOrder []string
opts *Options
current string
}
// New creates a new finite state machine having the specified initial state
// with specified options
func New(opts ...Option) *FSM {
options := &Options{}
for _, opt := range opts {
opt(options)
}
return &FSM{
statesMap: map[string]StateFunc{},
opts: options,
}
}
// Current returns the current state
func (f *FSM) Current() string {
f.mu.Lock()
defer f.mu.Unlock()
return f.current
}
// Current returns the current state
func (f *FSM) Reset() {
f.mu.Lock()
f.current = f.opts.Initial
f.mu.Unlock()
}
// State adds state to fsm
func (f *FSM) State(state string, fn StateFunc) {
f.mu.Lock()
f.statesMap[state] = fn
f.statesOrder = append(f.statesOrder, state)
f.mu.Unlock()
}
// Init initialize fsm and check states
// Start runs state machine with provided data
func (f *FSM) Start(ctx context.Context, args interface{}, opts ...Option) (interface{}, error) {
var err error
var ok bool
var fn StateFunc
var nstate string
f.mu.Lock()
options := f.opts
for _, opt := range opts {
opt(options)
}
sopts := []StateOption{StateDryRun(options.DryRun)}
cstate := options.Initial
states := make(map[string]StateFunc, len(f.statesMap))
for k, v := range f.statesMap {
states[k] = v
}
f.current = cstate
f.mu.Unlock()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
fn, ok = states[cstate]
if !ok {
return nil, fmt.Errorf(`state "%s" %w`, cstate, ErrInvalidState)
}
f.mu.Lock()
f.current = cstate
f.mu.Unlock()
for _, fn := range options.HooksBefore {
fn(ctx, cstate, args)
}
nstate, args, err = fn(ctx, args, sopts...)
for _, fn := range options.HooksAfter {
fn(ctx, cstate, args)
}
switch {
case err != nil:
return args, err
case nstate == StateEnd:
return args, nil
case nstate == "":
for idx := range f.statesOrder {
if f.statesOrder[idx] == cstate && len(f.statesOrder) > idx+1 {
nstate = f.statesOrder[idx+1]
}
}
}
cstate = nstate
}
}
}

63
fsm/fsm_test.go Normal file
View File

@@ -0,0 +1,63 @@
package fsm
import (
"bytes"
"context"
"fmt"
"testing"
)
func TestFSMStart(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
pfb := func(_ context.Context, state string, _ interface{}) {
fmt.Fprintf(buf, "before state %s\n", state)
}
pfa := func(_ context.Context, state string, _ interface{}) {
fmt.Fprintf(buf, "after state %s\n", state)
}
f := New(InitialState("1"), HookBefore(pfb), HookAfter(pfa))
f1 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
args := req.(map[string]interface{})
if v, ok := args["request"].(string); !ok || v == "" {
return "", nil, fmt.Errorf("empty request")
}
return "2", map[string]interface{}{"response": "test2"}, nil
}
f2 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
args := req.(map[string]interface{})
if v, ok := args["response"].(string); !ok || v == "" {
return "", nil, fmt.Errorf("empty response")
}
return "", map[string]interface{}{"response": "test"}, nil
}
f3 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
args := req.(map[string]interface{})
if v, ok := args["response"].(string); !ok || v == "" {
return "", nil, fmt.Errorf("empty response")
}
return StateEnd, map[string]interface{}{"response": "test_last"}, nil
}
f.State("1", f1)
f.State("2", f2)
f.State("3", f3)
rsp, err := f.Start(ctx, map[string]interface{}{"request": "test1"})
if err != nil {
t.Fatal(err)
}
args := rsp.(map[string]interface{})
if v, ok := args["response"].(string); !ok || v == "" {
t.Fatalf("nil rsp: %#+v", args)
} else if v != "test_last" {
t.Fatalf("invalid rsp %#+v", args)
}
if !bytes.Contains(buf.Bytes(), []byte(`before state 1`)) ||
!bytes.Contains(buf.Bytes(), []byte(`before state 2`)) ||
!bytes.Contains(buf.Bytes(), []byte(`after state 1`)) ||
!bytes.Contains(buf.Bytes(), []byte(`after state 2`)) ||
!bytes.Contains(buf.Bytes(), []byte(`after state 3`)) ||
!bytes.Contains(buf.Bytes(), []byte(`after state 3`)) {
t.Fatalf("fsm not works properly or hooks error, buf: %s", buf.Bytes())
}
}

View File

@@ -1,101 +0,0 @@
//go:build ignore
// +build ignore
package micro
import (
"context"
"time"
"go.unistack.org/micro/v3/server"
)
// Function is a one time executing Service
type Function interface {
// Inherits Service interface
Service
// Done signals to complete execution
Done() error
// Handle registers an RPC handler
Handle(v interface{}) error
// Subscribe registers a subscriber
Subscribe(topic string, v interface{}) error
}
type function struct {
cancel context.CancelFunc
Service
}
// NewFunction returns a new Function for a one time executing Service
func NewFunction(opts ...Option) Function {
return newFunction(opts...)
}
func fnHandlerWrapper(f Function) server.HandlerWrapper {
return func(h server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
defer f.Done()
return h(ctx, req, rsp)
}
}
}
func fnSubWrapper(f Function) server.SubscriberWrapper {
return func(s server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, msg server.Message) error {
defer f.Done()
return s(ctx, msg)
}
}
}
func newFunction(opts ...Option) Function {
ctx, cancel := context.WithCancel(context.Background())
// force ttl/interval
fopts := []Option{
RegisterTTL(time.Minute),
RegisterInterval(time.Second * 30),
}
// prepend to opts
fopts = append(fopts, opts...)
// make context the last thing
fopts = append(fopts, Context(ctx))
service := &service{opts: NewOptions(fopts...)}
fn := &function{
cancel: cancel,
Service: service,
}
service.Server().Init(
// ensure the service waits for requests to finish
server.Wait(nil),
// wrap handlers and subscribers to finish execution
server.WrapHandler(fnHandlerWrapper(fn)),
server.WrapSubscriber(fnSubWrapper(fn)),
)
return fn
}
func (f *function) Done() error {
f.cancel()
return nil
}
func (f *function) Handle(v interface{}) error {
return f.Service.Server().Handle(
f.Service.Server().NewHandler(v),
)
}
func (f *function) Subscribe(topic string, v interface{}) error {
return f.Service.Server().Subscribe(
f.Service.Server().NewSubscriber(topic, v),
)
}

View File

@@ -1,67 +0,0 @@
//go:build ignore
// +build ignore
package micro
/*
import (
"context"
"sync"
"testing"
"go.unistack.org/micro/v3/register"
)
func TestFunction(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
r := register.NewRegister()
ctx := context.TODO()
// create service
fn := NewFunction(
Register(r),
Name("test.function"),
AfterStart(func(ctx context.Context) error {
wg.Done()
return nil
}),
)
// we can't test fn.Init as it parses the command line
// fn.Init()
ch := make(chan error, 2)
go func() {
// run service
ch <- fn.Run()
}()
// wait for start
wg.Wait()
// test call debug
req := fn.Client().NewRequest(
"test.function",
"Debug.Health",
new(proto.HealthRequest),
)
rsp := new(proto.HealthResponse)
err := fn.Client().Call(context.TODO(), req, rsp)
if err != nil {
t.Fatal(err)
}
if rsp.Status != "ok" {
t.Fatalf("function response: %s", rsp.Status)
}
if err := <-ch; err != nil {
t.Fatal(err)
}
}
*/

10
go.mod
View File

@@ -3,11 +3,11 @@ module go.unistack.org/micro/v3
go 1.16 go 1.16
require ( require (
github.com/ef-ds/deque v1.0.4 github.com/google/go-cmp v0.5.7 // indirect
github.com/golang-jwt/jwt/v4 v4.4.0 github.com/imdario/mergo v0.3.13
github.com/imdario/mergo v0.3.12 github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
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-20211117232152-9d50aa809f35 github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35
go.unistack.org/micro-proto/v3 v3.2.7 go.unistack.org/micro-proto/v3 v3.3.1
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b
) )

36
go.sum
View File

@@ -10,11 +10,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 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/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/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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/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/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -23,8 +22,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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/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/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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -41,24 +38,27 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
github.com/google/gnostic v0.6.6/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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.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.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.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.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/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/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/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.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
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/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -75,8 +75,8 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 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= 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.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.3.1 h1:nQ0MtWvP2G3QrpOgawVOPhpZZYkq6umTGDqs8FxJYIo=
go.unistack.org/micro-proto/v3 v3.2.7/go.mod h1:ZltVWNECD5yK+40+OCONzGw4OtmSdTpVi8/KFgo9dqM= go.unistack.org/micro-proto/v3 v3.3.1/go.mod h1:cwRyv8uInM2I7EbU7O8Fx2Ls3N90Uw9UCCcq4olOdfE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -92,8 +92,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-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/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-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -146,17 +144,17 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
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=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.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.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

53
logger/context_test.go Normal file
View File

@@ -0,0 +1,53 @@
package logger
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewLogger())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), loggerKey{}, NewLogger())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewLogger())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

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

View File

@@ -8,9 +8,9 @@ import (
var ( var (
// DefaultLogger variable // DefaultLogger variable
DefaultLogger Logger = NewLogger(WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL")))) DefaultLogger = NewLogger(WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL"))))
// DefaultLevel used by logger // DefaultLevel used by logger
DefaultLevel Level = InfoLevel DefaultLevel = InfoLevel
// DefaultCallerSkipCount used by logger // DefaultCallerSkipCount used by logger
DefaultCallerSkipCount = 2 DefaultCallerSkipCount = 2
) )

577
logger/unwrap/unwrap.go Normal file
View File

@@ -0,0 +1,577 @@
package unwrap
import (
"bytes"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"go.unistack.org/micro/v3/codec"
)
const sf = "0-+# "
var hexDigits = "0123456789abcdef"
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
openBraceBytes = []byte("{")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
ampBytes = []byte("&")
colonBytes = []byte(":")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
commaBytes = []byte(",")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
filteredBytes = []byte("<filtered>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("{")
closeMapBytes = []byte("}")
)
type unwrap struct {
val interface{}
s fmt.State
pointers map[uintptr]int
opts *Options
depth int
ignoreNextType bool
}
// Options struct
type Options struct {
Codec codec.Codec
Indent string
Methods bool
Tagged bool
}
// NewOptions creates new Options struct via provided args
func NewOptions(opts ...Option) Options {
options := Options{
Indent: " ",
Methods: false,
}
for _, o := range opts {
o(&options)
}
return options
}
// Option func signature
type Option func(*Options)
// Indent option specify indent level
func Indent(f string) Option {
return func(o *Options) {
o.Indent = f
}
}
// Methods option toggles fmt.Stringer methods
func Methods(b bool) Option {
return func(o *Options) {
o.Methods = b
}
}
// Codec option automatic marshal arg via specified codec and write it to log
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}
// Tagged option toggles output only logger:"take" fields
func Tagged(b bool) Option {
return func(o *Options) {
o.Tagged = b
}
}
func Unwrap(val interface{}, opts ...Option) *unwrap {
options := NewOptions(opts...)
return &unwrap{val: val, opts: &options, pointers: make(map[uintptr]int)}
}
func (f *unwrap) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *unwrap) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.s.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
_, _ = f.s.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
if f.depth > 0 {
_, _ = f.s.Write(openParenBytes)
}
if f.depth > 0 {
_, _ = f.s.Write(bytes.Repeat(asteriskBytes, indirects))
} else {
_, _ = f.s.Write(bytes.Repeat(ampBytes, indirects))
}
_, _ = f.s.Write([]byte(ve.Type().String()))
if f.depth > 0 {
_, _ = f.s.Write(closeParenBytes)
}
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
_, _ = f.s.Write(openAngleBytes)
_, _ = f.s.Write([]byte(strings.Repeat("*", indirects)))
_, _ = f.s.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.s.Flag('+') && (len(pointerChain) > 0) {
_, _ = f.s.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
_, _ = f.s.Write(pointerChainBytes)
}
getHexPtr(f.s, addr)
}
_, _ = f.s.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound:
_, _ = f.s.Write(nilAngleBytes)
case cycleFound:
_, _ = f.s.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *unwrap) format(v reflect.Value) {
if f.opts.Codec != nil {
buf, err := f.opts.Codec.Marshal(v.Interface())
if err != nil {
_, _ = f.s.Write(invalidAngleBytes)
return
}
_, _ = f.s.Write(buf)
return
}
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
_, _ = f.s.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// get type information unless already handled elsewhere.
if !f.ignoreNextType && f.s.Flag('#') {
if v.Type().Kind() != reflect.Map &&
v.Type().Kind() != reflect.String &&
v.Type().Kind() != reflect.Array &&
v.Type().Kind() != reflect.Slice {
_, _ = f.s.Write(openParenBytes)
}
if v.Kind() != reflect.String {
_, _ = f.s.Write([]byte(v.Type().String()))
}
if v.Type().Kind() != reflect.Map &&
v.Type().Kind() != reflect.String &&
v.Type().Kind() != reflect.Array &&
v.Type().Kind() != reflect.Slice {
_, _ = f.s.Write(closeParenBytes)
}
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if f.opts.Methods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.opts, f.s, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
_, _ = f.s.Write(invalidAngleBytes)
case reflect.Bool:
getBool(f.s, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
getInt(f.s, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
getUint(f.s, v.Uint(), 10)
case reflect.Float32:
getFloat(f.s, v.Float(), 32)
case reflect.Float64:
getFloat(f.s, v.Float(), 64)
case reflect.Complex64:
getComplex(f.s, v.Complex(), 32)
case reflect.Complex128:
getComplex(f.s, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
_, _ = f.s.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
_, _ = f.s.Write(openBraceBytes)
f.depth++
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
_, _ = f.s.Write(commaBytes)
_, _ = f.s.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
f.depth--
_, _ = f.s.Write(closeBraceBytes)
case reflect.String:
_, _ = f.s.Write([]byte(`"` + v.String() + `"`))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
_, _ = f.s.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
_, _ = f.s.Write(nilAngleBytes)
break
}
_, _ = f.s.Write(openMapBytes)
f.depth++
keys := v.MapKeys()
for i, key := range keys {
if i > 0 {
_, _ = f.s.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
_, _ = f.s.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
f.depth--
_, _ = f.s.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
numWritten := 0
_, _ = f.s.Write(openBraceBytes)
f.depth++
vt := v.Type()
prevSkip := false
for i := 0; i < numFields; i++ {
sv, ok := vt.Field(i).Tag.Lookup("logger")
if ok {
switch sv {
case "omit":
prevSkip = true
continue
case "take":
break
}
} else if f.opts.Tagged {
prevSkip = true
continue
}
if i > 0 && !prevSkip {
_, _ = f.s.Write(commaBytes)
_, _ = f.s.Write(spaceBytes)
}
if prevSkip {
prevSkip = false
}
vtf := vt.Field(i)
if f.s.Flag('+') || f.s.Flag('#') {
_, _ = f.s.Write([]byte(vtf.Name))
_, _ = f.s.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
numWritten++
}
f.depth--
if numWritten == 0 && f.depth < 0 {
_, _ = f.s.Write(filteredBytes)
}
_, _ = f.s.Write(closeBraceBytes)
case reflect.Uintptr:
getHexPtr(f.s, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
getHexPtr(f.s, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
_, _ = fmt.Fprintf(f.s, format, v.Interface())
} else {
_, _ = fmt.Fprintf(f.s, format, v.String())
}
}
}
func (f *unwrap) Format(s fmt.State, verb rune) {
f.s = s
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
_, _ = fmt.Fprintf(s, format, f.val)
return
}
if f.val == nil {
if s.Flag('#') {
_, _ = s.Write(interfaceBytes)
}
_, _ = s.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.val))
}
// handle special methods like error.Error() or fmt.Stringer interface
func handleMethods(_ *Options, w io.Writer, v reflect.Value) (handled bool) {
if !v.CanInterface() {
// not our case
return false
}
if !v.CanAddr() {
// not our case
return false
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
_, _ = w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
_, _ = w.Write([]byte(iface.String()))
return true
}
return false
}
// getBool outputs a boolean value as true or false to Writer w.
func getBool(w io.Writer, val bool) {
if val {
_, _ = w.Write(trueBytes)
} else {
_, _ = w.Write(falseBytes)
}
}
// getInt outputs a signed integer value to Writer w.
func getInt(w io.Writer, val int64, base int) {
_, _ = w.Write([]byte(strconv.FormatInt(val, base)))
}
// getUint outputs an unsigned integer value to Writer w.
func getUint(w io.Writer, val uint64, base int) {
_, _ = w.Write([]byte(strconv.FormatUint(val, base)))
}
// getFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func getFloat(w io.Writer, val float64, precision int) {
_, _ = w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// getComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func getComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
_, _ = w.Write(openParenBytes)
_, _ = w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
_, _ = w.Write(plusBytes)
}
_, _ = w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
_, _ = w.Write(iBytes)
_, _ = w.Write(closeParenBytes)
}
// getHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
// prefix to Writer w.
func getHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
_, _ = w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
_, _ = w.Write(buf)
}
func catchPanic(w io.Writer, _ reflect.Value) {
if err := recover(); err != nil {
_, _ = w.Write(panicBytes)
_, _ = fmt.Fprintf(w, "%v", err)
_, _ = w.Write(closeParenBytes)
}
}
func (f *unwrap) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range sf {
if f.s.Flag(int(flag)) {
_, _ = buf.WriteRune(flag)
}
}
_, _ = buf.WriteRune('v')
format = buf.String()
return format
}
func (f *unwrap) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range sf {
if f.s.Flag(int(flag)) {
_, _ = buf.WriteRune(flag)
}
}
if width, ok := f.s.Width(); ok {
_, _ = buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.s.Precision(); ok {
_, _ = buf.Write(precisionBytes)
_, _ = buf.WriteString(strconv.Itoa(precision))
}
_, _ = buf.WriteRune(verb)
format = buf.String()
return format
}

View File

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

View File

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

140
metadata/context_test.go Normal file
View File

@@ -0,0 +1,140 @@
package metadata
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, New(0))
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdKey{}, &rawMetadata{New(0)})
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), New(0))
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromIncomingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdIncomingKey{}, &rawMetadata{New(0)})
c, ok := FromIncomingContext(ctx)
if c == nil || !ok {
t.Fatal("FromIncomingContext not works")
}
}
func TestFromOutgoingContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), mdOutgoingKey{}, &rawMetadata{New(0)})
c, ok := FromOutgoingContext(ctx)
if c == nil || !ok {
t.Fatal("FromOutgoingContext not works")
}
}
func TestSetIncomingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := context.WithValue(context.TODO(), mdIncomingKey{}, &rawMetadata{})
if !SetIncomingContext(ctx, md) {
t.Fatal("SetIncomingContext not works")
}
md, ok := FromIncomingContext(ctx)
if md == nil || !ok {
t.Fatal("SetIncomingContext not works")
} else if v, ok := md.Get("key"); !ok || v != "val" {
t.Fatal("SetIncomingContext not works")
}
}
func TestSetOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := context.WithValue(context.TODO(), mdOutgoingKey{}, &rawMetadata{})
if !SetOutgoingContext(ctx, md) {
t.Fatal("SetOutgoingContext not works")
}
md, ok := FromOutgoingContext(ctx)
if md == nil || !ok {
t.Fatal("SetOutgoingContext not works")
} else if v, ok := md.Get("key"); !ok || v != "val" {
t.Fatal("SetOutgoingContext not works")
}
}
func TestNewIncomingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := NewIncomingContext(context.TODO(), md)
c, ok := FromIncomingContext(ctx)
if c == nil || !ok {
t.Fatal("NewIncomingContext not works")
}
}
func TestNewOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key", "val")
ctx := NewOutgoingContext(context.TODO(), md)
c, ok := FromOutgoingContext(ctx)
if c == nil || !ok {
t.Fatal("NewOutgoingContext not works")
}
}
func TestAppendIncomingContext(t *testing.T) {
md := New(1)
md.Set("key1", "val1")
ctx := AppendIncomingContext(context.TODO(), "key2", "val2")
nmd, ok := FromIncomingContext(ctx)
if nmd == nil || !ok {
t.Fatal("AppendIncomingContext not works")
}
if v, ok := nmd.Get("key2"); !ok || v != "val2" {
t.Fatal("AppendIncomingContext not works")
}
}
func TestAppendOutgoingContext(t *testing.T) {
md := New(1)
md.Set("key1", "val1")
ctx := AppendOutgoingContext(context.TODO(), "key2", "val2")
nmd, ok := FromOutgoingContext(ctx)
if nmd == nil || !ok {
t.Fatal("AppendOutgoingContext not works")
}
if v, ok := nmd.Get("key2"); !ok || v != "val2" {
t.Fatal("AppendOutgoingContext not works")
}
}

View File

@@ -76,16 +76,23 @@ func (md Metadata) Get(key string) (string, bool) {
} }
// Set is used to store value in metadata // Set is used to store value in metadata
func (md Metadata) Set(key, val string) { func (md Metadata) Set(kv ...string) {
md[textproto.CanonicalMIMEHeaderKey(key)] = val if len(kv)%2 == 1 {
kv = kv[:len(kv)-1]
}
for idx := 0; idx < len(kv); idx += 2 {
md[textproto.CanonicalMIMEHeaderKey(kv[idx])] = kv[idx+1]
}
} }
// Del is used to remove value from metadata // Del is used to remove value from metadata
func (md Metadata) Del(key string) { func (md Metadata) Del(keys ...string) {
// fast path for _, key := range keys {
delete(md, key) // fast path
// slow path delete(md, key)
delete(md, textproto.CanonicalMIMEHeaderKey(key)) // slow path
delete(md, textproto.CanonicalMIMEHeaderKey(key))
}
} }
// Copy makes a copy of the metadata // Copy makes a copy of the metadata
@@ -129,13 +136,6 @@ func Pairs(kv ...string) (Metadata, bool) {
return nil, false return nil, false
} }
md := New(len(kv) / 2) md := New(len(kv) / 2)
var k string md.Set(kv...)
for i, v := range kv {
if i%2 == 0 {
k = v
continue
}
md.Set(k, v)
}
return md, true return md, true
} }

View File

@@ -5,6 +5,21 @@ import (
"testing" "testing"
) )
func TestMetadataSetMultiple(t *testing.T) {
md := New(4)
md.Set("key1", "val1", "key2", "val2", "key3")
if v, ok := md.Get("key1"); !ok || v != "val1" {
t.Fatalf("invalid kv %#+v", md)
}
if v, ok := md.Get("key2"); !ok || v != "val2" {
t.Fatalf("invalid kv %#+v", md)
}
if _, ok := md.Get("key3"); ok {
t.Fatalf("invalid kv %#+v", md)
}
}
func TestAppend(t *testing.T) { func TestAppend(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ctx = AppendIncomingContext(ctx, "key1", "val1", "key2", "val2") ctx = AppendIncomingContext(ctx, "key1", "val1", "key2", "val2")

53
meter/context_test.go Normal file
View File

@@ -0,0 +1,53 @@
package meter
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewMeter())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), meterKey{}, NewMeter())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewMeter())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -11,7 +11,7 @@ import (
var ( var (
// DefaultMeter is the default meter // DefaultMeter is the default meter
DefaultMeter Meter = NewMeter() DefaultMeter = NewMeter()
// DefaultAddress data will be made available on this host:port // DefaultAddress data will be made available on this host:port
DefaultAddress = ":9090" DefaultAddress = ":9090"
// DefaultPath the meter endpoint where the Meter data will be made available // DefaultPath the meter endpoint where the Meter data will be made available

View File

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

View File

@@ -1 +0,0 @@
package micro // import "go.unistack.org/micro/v3"

247
mtls/mtls.go Normal file
View File

@@ -0,0 +1,247 @@
package mtls // import "go.unistack.org/micro/v3/mtls"
import (
"bytes"
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"sync"
)
var bp = newBPool()
type bpool struct {
pool sync.Pool
}
func newBPool() *bpool {
var bp bpool
bp.pool.New = alloc
return &bp
}
func alloc() interface{} {
return &bytes.Buffer{}
}
func (bp *bpool) Get() *bytes.Buffer {
return bp.pool.Get().(*bytes.Buffer)
}
func (bp *bpool) Put(buf *bytes.Buffer) {
buf.Reset()
bp.pool.Put(buf)
}
// NewCA creates new CA keypair
func NewCA(opts ...CertificateOption) ([]byte, crypto.PrivateKey, error) {
options := NewCertificateOptions(opts...)
crtreq := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: options.Organization,
OrganizationalUnit: options.OrganizationalUnit,
CommonName: options.CommonName,
},
SignatureAlgorithm: options.SignatureAlgorithm,
}
pemcsr, pemkey, err := newCsr(crtreq)
if err != nil {
return nil, nil, err
}
pemcrt, err := SignCSR(pemcsr, nil, pemkey, opts...)
if err != nil {
return nil, nil, err
}
return pemcrt, pemkey, nil
}
func NewIntermediate(cacrt *x509.Certificate, cakey crypto.PrivateKey, opts ...CertificateOption) ([]byte, crypto.PrivateKey, error) {
options := &CertificateOptions{}
for _, o := range opts {
o(options)
}
crtreq := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: options.Organization,
OrganizationalUnit: options.OrganizationalUnit,
CommonName: options.CommonName,
},
SignatureAlgorithm: options.SignatureAlgorithm,
}
pemcsr, pemkey, err := newCsr(crtreq)
if err != nil {
return nil, nil, err
}
pemcrt, err := SignCSR(pemcsr, cacrt, cakey)
if err != nil {
return nil, nil, err
}
return pemcrt, pemkey, nil
}
// SignCSR sign certificate request and return signed pubkey
func SignCSR(rawcsr []byte, cacrt *x509.Certificate, cakey crypto.PrivateKey, opts ...CertificateOption) ([]byte, error) {
if cacrt == nil {
opts = append(opts, CertificateIsCA(true))
}
options := NewCertificateOptions(opts...)
csr, err := x509.ParseCertificateRequest(rawcsr)
if err == nil {
err = csr.CheckSignature()
}
if err != nil {
return nil, err
}
tpl := &x509.Certificate{
Signature: csr.Signature,
SignatureAlgorithm: csr.SignatureAlgorithm,
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
PublicKey: csr.PublicKey,
SerialNumber: options.SerialNumber,
OCSPServer: options.OCSPServer,
IssuingCertificateURL: options.IssuingCertificateURL,
Subject: csr.Subject,
NotBefore: options.NotBefore,
NotAfter: options.NotAfter,
KeyUsage: options.KeyUsage,
ExtKeyUsage: options.ExtKeyUsage,
BasicConstraintsValid: true,
IsCA: options.IsCA,
}
if options.IsCA {
cacrt = tpl
} else {
tpl.Issuer = cacrt.Subject
}
crt, err := x509.CreateCertificate(rand.Reader, tpl, cacrt, csr.PublicKey, cakey)
if err != nil {
return nil, err
}
return crt, nil
}
// NewCertificateRequest create new certificate signing request and return key, csr in byte slice and err
func NewCertificateRequest(opts ...CertificateOption) ([]byte, crypto.PrivateKey, error) {
options := NewCertificateOptions(opts...)
crtreq := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: options.Organization,
OrganizationalUnit: options.OrganizationalUnit,
CommonName: options.CommonName,
},
SignatureAlgorithm: options.SignatureAlgorithm,
}
return newCsr(crtreq)
}
// newCsr returns CSR and private key
func newCsr(crtreq *x509.CertificateRequest) ([]byte, crypto.PrivateKey, error) {
_, key, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
csr, err := x509.CreateCertificateRequest(rand.Reader, crtreq, key)
if err != nil {
return nil, nil, err
}
return csr, key, nil
}
// ServerOptions holds server specific options
type ServerOptions struct {
ServerName string
RootCAs []string
ClientCAs []string
}
// ServerOption func signature
type ServerOption func(*ServerOptions)
func NewServerConfig(src *tls.Config) *tls.Config {
dst := src.Clone()
// dst.InsecureSkipVerify = true
dst.MinVersion = tls.VersionTLS13
dst.ClientAuth = tls.VerifyClientCertIfGiven
return dst
}
func DecodeCrtKey(rawcrt []byte, rawkey []byte) (*x509.Certificate, crypto.PrivateKey, error) {
var crt *x509.Certificate
var key crypto.PrivateKey
var err error
crt, err = DecodeCrt(rawcrt)
if err == nil {
key, err = DecodeKey(rawkey)
}
if err != nil {
return nil, nil, err
}
return crt, key, nil
}
func DecodeCrt(rawcrt []byte) (*x509.Certificate, error) {
pemcrt, _ := pem.Decode(rawcrt)
return x509.ParseCertificate(pemcrt.Bytes)
}
func EncodeCrt(crts ...*x509.Certificate) ([]byte, error) {
var err error
buf := bp.Get()
defer bp.Put(buf)
for _, crt := range crts {
if err = pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: crt.Raw}); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
func EncodeCsr(csr *x509.Certificate) ([]byte, error) {
buf := bp.Get()
defer bp.Put(buf)
if err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr.Raw}); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func DecodeKey(rawkey []byte) (crypto.PrivateKey, error) {
pemkey, _ := pem.Decode(rawkey)
return x509.ParsePKCS8PrivateKey(pemkey.Bytes)
}
func EncodeKey(privkey crypto.PrivateKey) ([]byte, error) {
buf := bp.Get()
defer bp.Put(buf)
enckey, err := x509.MarshalPKCS8PrivateKey(privkey)
if err == nil {
err = pem.Encode(buf, &pem.Block{Type: "PRIVATE KEY", Bytes: enckey})
}
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

73
mtls/mtls_test.go Normal file
View File

@@ -0,0 +1,73 @@
package mtls
import (
"crypto/ed25519"
"crypto/x509"
"testing"
)
func TestNewCa(t *testing.T) {
bcrt, key, err := NewCA(
CertificateOrganization("test_org"),
CertificateOrganizationalUnit("test_unit"),
CertificateIsCA(true),
)
if err != nil {
t.Fatal(err)
}
if _, ok := key.(ed25519.PrivateKey); !ok {
t.Fatalf("key is not ed25519")
}
crt, err := x509.ParseCertificate(bcrt)
if err != nil {
t.Fatal(err)
}
if !crt.IsCA {
t.Fatalf("crt IsCA invalid %v", crt)
}
if crt.Subject.Organization[0] != "test_org" {
t.Fatalf("crt subject invalid %v", crt.Subject)
}
if crt.Subject.OrganizationalUnit[0] != "test_unit" {
t.Fatalf("crt subject invalid %v", crt.Subject)
}
}
func TestNewIntermediate(t *testing.T) {
bcrt, cakey, err := NewCA(
CertificateOrganization("test_org"),
CertificateOrganizationalUnit("test_unit"),
)
if err != nil {
t.Fatal(err)
}
cacrt, err := x509.ParseCertificate(bcrt)
if err != nil {
t.Fatal(err)
}
bcrt, ikey, err := NewIntermediate(cacrt, cakey,
CertificateOrganization("test_org"),
CertificateOrganizationalUnit("test_unit"),
)
if err != nil {
t.Fatal(err)
}
_ = ikey
icrt, err := x509.ParseCertificate(bcrt)
if err != nil {
t.Fatal(err)
}
if icrt.IsCA {
t.Fatalf("crt IsCA invalid %v", icrt)
}
if icrt.Subject.Organization[0] != "test_org" {
t.Fatalf("crt subject invalid %v", icrt.Subject)
}
if icrt.Subject.OrganizationalUnit[0] != "test_unit" {
t.Fatalf("crt subject invalid %v", icrt.Subject)
}
}

155
mtls/options.go Normal file
View File

@@ -0,0 +1,155 @@
package mtls
import (
"crypto/x509"
"math/big"
"time"
)
// CertificateOptions holds options for x509.CreateCertificate
type CertificateOptions struct {
Organization []string
OrganizationalUnit []string
CommonName string
OCSPServer []string
IssuingCertificateURL []string
SerialNumber *big.Int
NotAfter time.Time
NotBefore time.Time
SignatureAlgorithm x509.SignatureAlgorithm
PublicKeyAlgorithm x509.PublicKeyAlgorithm
ExtKeyUsage []x509.ExtKeyUsage
KeyUsage x509.KeyUsage
IsCA bool
}
// CertificateOrganizationalUnit set OrganizationalUnit in certificate subject
func CertificateOrganizationalUnit(s ...string) CertificateOption {
return func(o *CertificateOptions) {
o.OrganizationalUnit = s
}
}
// CertificateOrganization set Organization in certificate subject
func CertificateOrganization(s ...string) CertificateOption {
return func(o *CertificateOptions) {
o.Organization = s
}
}
// CertificateCommonName set CommonName in certificate subject
func CertificateCommonName(s string) CertificateOption {
return func(o *CertificateOptions) {
o.CommonName = s
}
}
// CertificateOCSPServer set OCSPServer in certificate
func CertificateOCSPServer(s ...string) CertificateOption {
return func(o *CertificateOptions) {
o.OCSPServer = s
}
}
// CertificateIssuingCertificateURL set IssuingCertificateURL in certificate
func CertificateIssuingCertificateURL(s ...string) CertificateOption {
return func(o *CertificateOptions) {
o.IssuingCertificateURL = s
}
}
// CertificateSerialNumber set SerialNumber in certificate
func CertificateSerialNumber(n *big.Int) CertificateOption {
return func(o *CertificateOptions) {
o.SerialNumber = n
}
}
// CertificateNotAfter set NotAfter in certificate
func CertificateNotAfter(t time.Time) CertificateOption {
return func(o *CertificateOptions) {
o.NotAfter = t
}
}
// CertificateNotBefore set SerialNumber in certificate
func CertificateNotBefore(t time.Time) CertificateOption {
return func(o *CertificateOptions) {
o.NotBefore = t
}
}
// CertificateExtKeyUsage set ExtKeyUsage in certificate
func CertificateExtKeyUsage(x ...x509.ExtKeyUsage) CertificateOption {
return func(o *CertificateOptions) {
o.ExtKeyUsage = x
}
}
// CertificateSignatureAlgorithm set SignatureAlgorithm in certificate
func CertificateSignatureAlgorithm(alg x509.SignatureAlgorithm) CertificateOption {
return func(o *CertificateOptions) {
o.SignatureAlgorithm = alg
}
}
// CertificatePublicKeyAlgorithm set PublicKeyAlgorithm in certificate
func CertificatePublicKeyAlgorithm(alg x509.PublicKeyAlgorithm) CertificateOption {
return func(o *CertificateOptions) {
o.PublicKeyAlgorithm = alg
}
}
// CertificateKeyUsage set KeyUsage in certificate
func CertificateKeyUsage(u x509.KeyUsage) CertificateOption {
return func(o *CertificateOptions) {
o.KeyUsage = u
}
}
// CertificateIsCA set IsCA in certificate
func CertificateIsCA(b bool) CertificateOption {
return func(o *CertificateOptions) {
o.IsCA = b
}
}
// CertificateOption func signature
type CertificateOption func(*CertificateOptions)
func NewCertificateOptions(opts ...CertificateOption) CertificateOptions {
options := CertificateOptions{}
for _, o := range opts {
o(&options)
}
if options.SerialNumber == nil {
options.SerialNumber = big.NewInt(time.Now().UnixNano())
}
if options.NotBefore.IsZero() {
options.NotBefore = time.Now()
}
if options.NotAfter.IsZero() {
options.NotAfter = time.Now().Add(10 * time.Minute)
}
if options.SignatureAlgorithm == x509.UnknownSignatureAlgorithm {
options.SignatureAlgorithm = x509.PureEd25519
}
if options.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm {
options.PublicKeyAlgorithm = x509.Ed25519
}
if options.ExtKeyUsage == nil {
options.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
if options.IsCA {
options.ExtKeyUsage = append(options.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning, x509.ExtKeyUsageTimeStamping)
}
}
if options.KeyUsage == 0 {
options.KeyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature
if options.IsCA {
options.KeyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment | x509.KeyUsageCertSign
}
}
return options
}

View File

@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"time" "time"
"go.unistack.org/micro/v3/auth"
"go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/config" "go.unistack.org/micro/v3/config"
@@ -39,8 +38,6 @@ type Options struct {
Configs []config.Config Configs []config.Config
// Clients holds clients // Clients holds clients
Clients []client.Client Clients []client.Client
// Auths holds auths
Auths []auth.Auth
// Stores holds stores // Stores holds stores
Stores []store.Store Stores []store.Store
// Registers holds registers // Registers holds registers
@@ -70,7 +67,6 @@ func NewOptions(opts ...Option) Options {
Brokers: []broker.Broker{broker.DefaultBroker}, Brokers: []broker.Broker{broker.DefaultBroker},
Registers: []register.Register{register.DefaultRegister}, Registers: []register.Register{register.DefaultRegister},
Routers: []router.Router{router.DefaultRouter}, Routers: []router.Router{router.DefaultRouter},
Auths: []auth.Auth{auth.DefaultAuth},
Loggers: []logger.Logger{logger.DefaultLogger}, Loggers: []logger.Logger{logger.DefaultLogger},
Tracers: []tracer.Tracer{tracer.DefaultTracer}, Tracers: []tracer.Tracer{tracer.DefaultTracer},
Meters: []meter.Meter{meter.DefaultMeter}, Meters: []meter.Meter{meter.DefaultMeter},
@@ -497,19 +493,6 @@ func TracerStore(n string) TracerOption {
} }
} }
/*
// Auth sets the auth for the service
func Auth(a auth.Auth) Option {
return func(o *Options) error {
o.Auth = a
if o.Server != nil {
o.Server.Init(server.Auth(a))
}
return nil
}
}
*/
// Config sets the config for the service // Config sets the config for the service
func Config(c ...config.Config) Option { func Config(c ...config.Config) Option {
return func(o *Options) error { return func(o *Options) error {

View File

@@ -49,12 +49,12 @@ func (p *profiler) Start() error {
// create exit channel // create exit channel
p.exit = make(chan bool) p.exit = make(chan bool)
cpuFile := filepath.Join("/tmp", "cpu.pprof") cpuFile := filepath.Join(string(os.PathSeparator)+"tmp", "cpu.pprof")
memFile := filepath.Join("/tmp", "mem.pprof") memFile := filepath.Join(string(os.PathSeparator)+"tmp", "mem.pprof")
if len(p.opts.Name) > 0 { if len(p.opts.Name) > 0 {
cpuFile = filepath.Join("/tmp", p.opts.Name+".cpu.pprof") cpuFile = filepath.Join(string(os.PathSeparator)+"tmp", p.opts.Name+".cpu.pprof")
memFile = filepath.Join("/tmp", p.opts.Name+".mem.pprof") memFile = filepath.Join(string(os.PathSeparator)+"tmp", p.opts.Name+".mem.pprof")
} }
f1, err := os.Create(cpuFile) f1, err := os.Create(cpuFile)

53
register/context_test.go Normal file
View File

@@ -0,0 +1,53 @@
package register
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewRegister())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), registerKey{}, NewRegister())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewRegister())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -2,7 +2,6 @@ package register
import ( import (
"context" "context"
"errors"
"sync" "sync"
"time" "time"
@@ -30,10 +29,10 @@ type record struct {
} }
type memory struct { type memory struct {
sync.RWMutex
records map[string]services records map[string]services
watchers map[string]*watcher watchers map[string]*watcher
opts Options opts Options
sync.RWMutex
} }
// services is a KV map with service name as the key and a map of records as the value // services is a KV map with service name as the key and a map of records as the value
@@ -165,7 +164,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
continue continue
} }
metadata := make(map[string]string) metadata := make(map[string]string, len(n.Metadata))
// make copy of metadata // make copy of metadata
for k, v := range n.Metadata { for k, v := range n.Metadata {
@@ -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"}, _, 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

@@ -18,7 +18,7 @@ var DefaultDomain = "micro"
var ( var (
// DefaultRegister is the global default register // DefaultRegister is the global default register
DefaultRegister Register = NewRegister() DefaultRegister = NewRegister()
// ErrNotFound returned when LookupService is called and no services found // ErrNotFound returned when LookupService is called and no services found
ErrNotFound = errors.New("service not found") ErrNotFound = errors.New("service not found")
// ErrWatcherStopped returned when when watcher is stopped // ErrWatcherStopped returned when when watcher is stopped

View File

@@ -12,10 +12,9 @@ import (
// Resolver is a DNS network resolve // Resolver is a DNS network resolve
type Resolver struct { type Resolver struct {
goresolver *net.Resolver
// Address of resolver to use
Address string
sync.RWMutex sync.RWMutex
goresolver *net.Resolver
Address string
} }
// Resolve tries to resolve endpoint address // Resolve tries to resolve endpoint address
@@ -47,7 +46,7 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
if goresolver == nil { if goresolver == nil {
r.Lock() r.Lock()
r.goresolver = &net.Resolver{ r.goresolver = &net.Resolver{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) { Dial: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
d := net.Dialer{ d := net.Dialer{
Timeout: time.Millisecond * time.Duration(100), Timeout: time.Millisecond * time.Duration(100),
} }

34
router/context.go Normal file
View File

@@ -0,0 +1,34 @@
package router
import (
"context"
)
type routerKey struct{}
// FromContext get router from context
func FromContext(ctx context.Context) (Router, bool) {
if ctx == nil {
return nil, false
}
c, ok := ctx.Value(routerKey{}).(Router)
return c, ok
}
// NewContext put router in context
func NewContext(ctx context.Context, c Router) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, routerKey{}, c)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

53
router/context_test.go Normal file
View File

@@ -0,0 +1,53 @@
package router
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewRouter())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), routerKey{}, NewRouter())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewRouter())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -7,7 +7,7 @@ import (
var ( var (
// DefaultRouter is the global default router // DefaultRouter is the global default router
DefaultRouter Router = NewRouter() DefaultRouter = NewRouter()
// DefaultNetwork is default micro network // DefaultNetwork is default micro network
DefaultNetwork = "micro" DefaultNetwork = "micro"
// ErrRouteNotFound is returned when no route was found in the routing table // ErrRouteNotFound is returned when no route was found in the routing table

View File

@@ -1,309 +0,0 @@
package runtime
import (
"context"
"io"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/logger"
)
// Options configure runtime
type Options struct {
Scheduler Scheduler
Client client.Client
Logger logger.Logger
Type string
Source string
Image string
}
// Option func signature
type Option func(o *Options)
// WithLogger sets the logger
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// WithSource sets the base image / repository
func WithSource(src string) Option {
return func(o *Options) {
o.Source = src
}
}
// WithScheduler specifies a scheduler for updates
func WithScheduler(n Scheduler) Option {
return func(o *Options) {
o.Scheduler = n
}
}
// WithType sets the service type to manage
func WithType(t string) Option {
return func(o *Options) {
o.Type = t
}
}
// WithImage sets the image to use
func WithImage(t string) Option {
return func(o *Options) {
o.Image = t
}
}
// WithClient sets the client to use
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// CreateOption func signature
type CreateOption func(o *CreateOptions)
// ReadOption func signature
type ReadOption func(o *ReadOptions)
// CreateOptions configure runtime services
type CreateOptions struct {
Context context.Context
Output io.Writer
Resources *Resources
Secrets map[string]string
Image string
Namespace string
Type string
Command []string
Args []string
Env []string
Retries int
}
// ReadOptions queries runtime services
type ReadOptions struct {
Context context.Context
Service string
Version string
Type string
Namespace string
}
// CreateType sets the type of service to create
func CreateType(t string) CreateOption {
return func(o *CreateOptions) {
o.Type = t
}
}
// CreateImage sets the image to use
func CreateImage(img string) CreateOption {
return func(o *CreateOptions) {
o.Image = img
}
}
// CreateNamespace sets the namespace
func CreateNamespace(ns string) CreateOption {
return func(o *CreateOptions) {
o.Namespace = ns
}
}
// CreateContext sets the context
func CreateContext(ctx context.Context) CreateOption {
return func(o *CreateOptions) {
o.Context = ctx
}
}
// WithSecret sets a secret to provide the service with
func WithSecret(key, value string) CreateOption {
return func(o *CreateOptions) {
if o.Secrets == nil {
o.Secrets = map[string]string{key: value}
} else {
o.Secrets[key] = value
}
}
}
// WithCommand specifies the command to execute
func WithCommand(cmd ...string) CreateOption {
return func(o *CreateOptions) {
// set command
o.Command = cmd
}
}
// WithArgs specifies the command to execute
func WithArgs(args ...string) CreateOption {
return func(o *CreateOptions) {
// set command
o.Args = args
}
}
// WithRetries sets the max retries attempts
func WithRetries(retries int) CreateOption {
return func(o *CreateOptions) {
o.Retries = retries
}
}
// WithEnv sets the created service environment
func WithEnv(env []string) CreateOption {
return func(o *CreateOptions) {
o.Env = env
}
}
// WithOutput sets the arg output
func WithOutput(out io.Writer) CreateOption {
return func(o *CreateOptions) {
o.Output = out
}
}
// ResourceLimits sets the resources for the service to use
func ResourceLimits(r *Resources) CreateOption {
return func(o *CreateOptions) {
o.Resources = r
}
}
// ReadService returns services with the given name
func ReadService(service string) ReadOption {
return func(o *ReadOptions) {
o.Service = service
}
}
// ReadVersion confifgures service version
func ReadVersion(version string) ReadOption {
return func(o *ReadOptions) {
o.Version = version
}
}
// ReadType returns services of the given type
func ReadType(t string) ReadOption {
return func(o *ReadOptions) {
o.Type = t
}
}
// ReadNamespace sets the namespace
func ReadNamespace(ns string) ReadOption {
return func(o *ReadOptions) {
o.Namespace = ns
}
}
// ReadContext sets the context
func ReadContext(ctx context.Context) ReadOption {
return func(o *ReadOptions) {
o.Context = ctx
}
}
// UpdateOption func signature
type UpdateOption func(o *UpdateOptions)
// UpdateOptions struct
type UpdateOptions struct {
Context context.Context
Secrets map[string]string
Namespace string
}
// UpdateSecret sets a secret to provide the service with
func UpdateSecret(key, value string) UpdateOption {
return func(o *UpdateOptions) {
if o.Secrets == nil {
o.Secrets = map[string]string{key: value}
} else {
o.Secrets[key] = value
}
}
}
// UpdateNamespace sets the namespace
func UpdateNamespace(ns string) UpdateOption {
return func(o *UpdateOptions) {
o.Namespace = ns
}
}
// UpdateContext sets the context
func UpdateContext(ctx context.Context) UpdateOption {
return func(o *UpdateOptions) {
o.Context = ctx
}
}
// DeleteOption func signature
type DeleteOption func(o *DeleteOptions)
// DeleteOptions struct
type DeleteOptions struct {
Context context.Context
Namespace string
}
// DeleteNamespace sets the namespace
func DeleteNamespace(ns string) DeleteOption {
return func(o *DeleteOptions) {
o.Namespace = ns
}
}
// DeleteContext sets the context
func DeleteContext(ctx context.Context) DeleteOption {
return func(o *DeleteOptions) {
o.Context = ctx
}
}
// LogsOption configures runtime logging
type LogsOption func(o *LogsOptions)
// LogsOptions configure runtime logging
type LogsOptions struct {
Context context.Context
Namespace string
Count int64
Stream bool
}
// LogsCount confiures how many existing lines to show
func LogsCount(count int64) LogsOption {
return func(l *LogsOptions) {
l.Count = count
}
}
// LogsStream configures whether to stream new lines
func LogsStream(stream bool) LogsOption {
return func(l *LogsOptions) {
l.Stream = stream
}
}
// LogsNamespace sets the namespace
func LogsNamespace(ns string) LogsOption {
return func(o *LogsOptions) {
o.Namespace = ns
}
}
// LogsContext sets the context
func LogsContext(ctx context.Context) LogsOption {
return func(o *LogsOptions) {
o.Context = ctx
}
}

View File

@@ -1,125 +0,0 @@
// Package runtime is a service runtime manager
package runtime // import "go.unistack.org/micro/v3/runtime"
import (
"errors"
"time"
"go.unistack.org/micro/v3/metadata"
)
// ErrAlreadyExists error
var ErrAlreadyExists = errors.New("already exists")
// Runtime is a service runtime manager
type Runtime interface {
// Init initializes runtime
Init(...Option) error
// Create registers a service
Create(*Service, ...CreateOption) error
// Read returns the service
Read(...ReadOption) ([]*Service, error)
// Update the service in place
Update(*Service, ...UpdateOption) error
// Remove a service
Delete(*Service, ...DeleteOption) error
// Logs returns the logs for a service
Logs(*Service, ...LogsOption) (Logs, error)
// Start starts the runtime
Start() error
// Stop shuts down the runtime
Stop() error
// String describes runtime
String() string
}
// Logs returns a log stream
type Logs interface {
// Error returns error
Error() error
// Chan return chan log
Chan() chan Log
// Stop stops the log stream
Stop() error
}
// Log is a log message
type Log struct {
// Metadata holds metadata
Metadata metadata.Metadata
// Message holds the message
Message string
}
// Scheduler is a runtime service scheduler
type Scheduler interface {
// Notify publishes schedule events
Notify() (<-chan Event, error)
// Close stops the scheduler
Close() error
}
// EventType defines schedule event
type EventType int
const (
// Create is emitted when a new build has been craeted
Create EventType = iota
// Update is emitted when a new update become available
Update
// Delete is emitted when a build has been deleted
Delete
)
// String returns human readable event type
func (t EventType) String() string {
switch t {
case Create:
return "create"
case Delete:
return "delete"
case Update:
return "update"
default:
return "unknown"
}
}
// Event is notification event
type Event struct {
// Timestamp of event
Timestamp time.Time
// Service the event relates to
Service *Service
// Options to use when processing the event
Options *CreateOptions
// ID of the event
ID string
// Type is event type
Type EventType
}
// Service is runtime service
type Service struct {
// Metadata stores metadata
Metadata metadata.Metadata
// Name of the service
Name string
// Version of the service
Version string
// Name of the service
Source string
}
// Resources which are allocated to a serivce
type Resources struct {
// CPU is the maximum amount of CPU the service will be allocated (unit millicpu)
// e.g. 0.25CPU would be passed as 250
CPU int
// Mem is the maximum amount of memory the service will be allocated (unit mebibyte)
// e.g. 128 MiB of memory would be passed as 128
Mem int
// Disk is the maximum amount of disk space the service will be allocated (unit mebibyte)
// e.g. 128 MiB of memory would be passed as 128
Disk int
}

64
server/context_test.go Normal file
View File

@@ -0,0 +1,64 @@
package server
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewServer())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), serverKey{}, NewServer())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewServer())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}
func TestSetSubscriberOption(t *testing.T) {
type key struct{}
o := SetSubscriberOption(key{}, "test")
opts := &SubscriberOptions{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetSubscriberOption not works")
}
}

View File

@@ -277,23 +277,24 @@ func (n *noopServer) Deregister() error {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for sb, subs := range n.subscribers { for sb, subs := range n.subscribers {
for _, sub := range subs { for idx := range subs {
if sb.Options().Context != nil { if sb.Options().Context != nil {
cx = sb.Options().Context cx = sb.Options().Context
} }
ncx := cx
wg.Add(1) wg.Add(1)
go func(s broker.Subscriber) { go func(s broker.Subscriber) {
defer wg.Done() defer wg.Done()
if config.Logger.V(logger.InfoLevel) { if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "unsubscribing from topic: %s", s.Topic()) config.Logger.Infof(n.opts.Context, "unsubscribing from topic: %s", s.Topic())
} }
if err := s.Unsubscribe(cx); err != nil { if err := s.Unsubscribe(ncx); err != nil {
if config.Logger.V(logger.ErrorLevel) { if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "unsubscribing from topic: %s err: %v", s.Topic(), err) config.Logger.Errorf(n.opts.Context, "unsubscribing from topic: %s err: %v", s.Topic(), err)
} }
} }
}(sub) }(subs[idx])
} }
n.subscribers[sb] = nil n.subscribers[sb] = 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/client" "go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
@@ -50,6 +51,7 @@ func TestNoopSub(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
logger.DefaultLogger.Init(logger.WithLevel(logger.ErrorLevel))
s := server.NewServer( s := server.NewServer(
server.Broker(b), server.Broker(b),
server.Codec("application/octet-stream", codec.NewCodec()), server.Codec("application/octet-stream", codec.NewCodec()),

View File

@@ -7,7 +7,6 @@ import (
"sync" "sync"
"time" "time"
"go.unistack.org/micro/v3/auth"
"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"
@@ -32,8 +31,6 @@ type Options struct {
Register register.Register Register register.Register
// Tracer holds the tracer // Tracer holds the tracer
Tracer tracer.Tracer Tracer tracer.Tracer
// Auth holds the auth
Auth auth.Auth
// Logger holds the logger // Logger holds the logger
Logger logger.Logger Logger logger.Logger
// Meter holds the meter // Meter holds the meter
@@ -91,7 +88,6 @@ type Options struct {
// NewOptions returns new options struct with default or passed values // NewOptions returns new options struct with default or passed values
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Auth: auth.DefaultAuth,
Codecs: make(map[string]codec.Codec), Codecs: make(map[string]codec.Codec),
Context: context.Background(), Context: context.Background(),
Metadata: metadata.New(0), Metadata: metadata.New(0),
@@ -211,13 +207,6 @@ func Tracer(t tracer.Tracer) Option {
} }
} }
// Auth mechanism for role based access control
func Auth(a auth.Auth) Option {
return func(o *Options) {
o.Auth = a
}
}
// Transport mechanism for communication e.g http, rabbitmq, etc // Transport mechanism for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option { func Transport(t transport.Transport) Option {
return func(o *Options) { return func(o *Options) {

View File

@@ -11,7 +11,7 @@ import (
) )
// DefaultServer default server // DefaultServer default server
var DefaultServer Server = NewServer() var DefaultServer = NewServer()
var ( var (
// DefaultAddress will be used if no address passed, use secure localhost // DefaultAddress will be used if no address passed, use secure localhost

View File

@@ -36,11 +36,11 @@ type handler struct {
type subscriber struct { type subscriber struct {
typ reflect.Type typ reflect.Type
subscriber interface{} subscriber interface{}
rcvr reflect.Value
topic string topic string
endpoints []*register.Endpoint endpoints []*register.Endpoint
handlers []*handler handlers []*handler
opts SubscriberOptions opts SubscriberOptions
rcvr reflect.Value
} }
// Is this an exported - upper case - name? // Is this an exported - upper case - name?

View File

@@ -1,11 +1,10 @@
// Package micro is a pluggable framework for microservices // Package micro is a pluggable framework for microservices
package micro package micro // import "go.unistack.org/micro/v3"
import ( import (
"fmt" "fmt"
"sync" "sync"
"go.unistack.org/micro/v3/auth"
"go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/config" "go.unistack.org/micro/v3/config"
@@ -27,35 +26,37 @@ type Service interface {
Init(...Option) error Init(...Option) error
// Options returns the current options // Options returns the current options
Options() Options Options() Options
// Auth is for handling auth // Logger is for output log from service components
Auth(...string) auth.Auth
// Logger is for logs
Logger(...string) logger.Logger Logger(...string) logger.Logger
// Config if for config // Config if for config handling via load/save methods and also with ability to watch changes
Config(...string) config.Config Config(...string) config.Config
// Client is for calling services // Client is for sync calling services via RPC
Client(...string) client.Client Client(...string) client.Client
// Broker is for sending and receiving events // Broker is for sending and receiving async events
Broker(...string) broker.Broker Broker(...string) broker.Broker
// Server is for handling requests and events // Server is for handling requests and broker unmarshaled events
Server(...string) server.Server Server(...string) server.Server
// Store is for key/val store // Store is for key/val store
Store(...string) store.Store Store(...string) store.Store
// Register // Register used by client to lookup other services and server registers on it
Register(...string) register.Register Register(...string) register.Register
// Tracer // Tracer
Tracer(...string) tracer.Tracer Tracer(...string) tracer.Tracer
// Router // Router
Router(...string) router.Router Router(...string) router.Router
// Meter // Meter may be used internally by other component to export metrics
Meter(...string) meter.Meter Meter(...string) meter.Meter
// Runtime // Runtime
// 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 for stop
Run() error Run() error
// Start the service and not wait
Start() error
// Stop the service
Stop() error
// The service implementation // The service implementation
String() string String() string
} }
@@ -71,9 +72,8 @@ func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...se
} }
type service struct { type service struct {
opts Options
sync.RWMutex sync.RWMutex
// once sync.Once opts Options
} }
// NewService creates and returns a new Service based on the packages within. // NewService creates and returns a new Service based on the packages within.
@@ -106,11 +106,6 @@ func (s *service) Init(opts ...Option) error {
if err = cfg.Init(config.Context(cfg.Options().Context)); err != nil { if err = cfg.Init(config.Context(cfg.Options().Context)); err != nil {
return err return err
} }
if err = cfg.Load(cfg.Options().Context); err != nil {
return err
}
} }
for _, log := range s.opts.Loggers { for _, log := range s.opts.Loggers {
@@ -220,14 +215,6 @@ func (s *service) Logger(names ...string) logger.Logger {
return s.opts.Loggers[idx] return s.opts.Loggers[idx]
} }
func (s *service) Auth(names ...string) auth.Auth {
idx := 0
if len(names) == 1 {
idx = getNameIndex(names[0], s.opts.Auths)
}
return s.opts.Auths[idx]
}
func (s *service) Router(names ...string) router.Router { func (s *service) Router(names ...string) router.Router {
idx := 0 idx := 0
if len(names) == 1 { if len(names) == 1 {
@@ -245,7 +232,7 @@ func (s *service) Meter(names ...string) meter.Meter {
} }
func (s *service) String() string { func (s *service) String() string {
return "micro" return s.opts.Name
} }
//nolint:gocyclo //nolint:gocyclo
@@ -256,16 +243,6 @@ func (s *service) Start() error {
config := s.opts config := s.opts
s.RUnlock() s.RUnlock()
if config.Loggers[0].V(logger.InfoLevel) {
config.Loggers[0].Infof(s.opts.Context, "starting [service] %s", s.Name())
}
for _, fn := range s.opts.BeforeStart {
if err = fn(s.opts.Context); err != nil {
return err
}
}
for _, cfg := range s.opts.Configs { for _, cfg := range s.opts.Configs {
if cfg.Options().Struct == nil { if cfg.Options().Struct == nil {
// skip config as the struct not passed // skip config as the struct not passed
@@ -277,6 +254,16 @@ func (s *service) Start() error {
} }
} }
for _, fn := range s.opts.BeforeStart {
if err = fn(s.opts.Context); err != nil {
return err
}
}
if config.Loggers[0].V(logger.InfoLevel) {
config.Loggers[0].Infof(s.opts.Context, "starting [service] %s version %s", s.Options().Name, s.Options().Version)
}
if len(s.opts.Servers) == 0 { if len(s.opts.Servers) == 0 {
return fmt.Errorf("cant start nil server") return fmt.Errorf("cant start nil server")
} }

View File

@@ -1,7 +1,20 @@
package micro package micro
import ( import (
"context"
"reflect"
"testing" "testing"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/config"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/tracer"
) )
type testItem struct { type testItem struct {
@@ -20,3 +33,706 @@ func TestGetNameIndex(t *testing.T) {
t.Fatalf("getNameIndex func error, item not found") t.Fatalf("getNameIndex func error, item not found")
} }
} }
func TestRegisterHandler(t *testing.T) {
type args struct {
s server.Server
h interface{}
opts []server.HandlerOption
}
h := struct{}{}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "RegisterHandler",
args: args{
s: server.DefaultServer,
h: h,
opts: nil,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := RegisterHandler(tt.args.s, tt.args.h, tt.args.opts...); (err != nil) != tt.wantErr {
t.Errorf("RegisterHandler() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestRegisterSubscriber(t *testing.T) {
type args struct {
topic string
s server.Server
h interface{}
opts []server.SubscriberOption
}
h := func(_ context.Context, _ interface{}) error {
return nil
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "RegisterSubscriber",
args: args{
topic: "test",
s: server.DefaultServer,
h: h,
opts: nil,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := RegisterSubscriber(tt.args.topic, tt.args.s, tt.args.h, tt.args.opts...); (err != nil) != tt.wantErr {
t.Errorf("RegisterSubscriber() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestNewService(t *testing.T) {
type args struct {
opts []Option
}
tests := []struct {
name string
args args
want Service
}{
{
name: "NewService",
args: args{
opts: []Option{Name("test")},
},
want: NewService(Name("test")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewService(tt.args.opts...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewService() = %v, want %v", got.Options().Name, tt.want.Options().Name)
}
})
}
}
func Test_service_Name(t *testing.T) {
type fields struct {
opts Options
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Test_service_Name",
fields: fields{
opts: Options{Name: "test"},
},
want: "test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Name(); got != tt.want {
t.Errorf("service.Name() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Init(t *testing.T) {
type fields struct {
opts Options
}
type args struct {
opts []Option
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "service.Init()",
fields: fields{
opts: Options{},
},
args: args{
opts: []Option{},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if err := s.Init(tt.args.opts...); (err != nil) != tt.wantErr {
t.Errorf("service.Init() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_service_Options(t *testing.T) {
opts := Options{Name: "test"}
type fields struct {
opts Options
}
tests := []struct {
name string
fields fields
want Options
}{
{
name: "service.Options",
fields: fields{
opts: opts,
},
want: opts,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Options(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Options() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Broker(t *testing.T) {
b := broker.NewBroker()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want broker.Broker
}{
{
name: "service.Broker",
fields: fields{
opts: Options{Brokers: []broker.Broker{b}},
},
args: args{
names: []string{"noop"},
},
want: b,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Broker(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Broker() = %v, want %v", got, tt.want)
}
})
}
}
/*
func TestServiceBroker(t *testing.T) {
b := broker.NewBroker(broker.Name("test"))
srv := server.NewServer()
svc := NewService(Server(srv),Broker(b))
if err := svc.Init(); err != nil {
t.Fatalf("failed to init service")
}
if brk := svc.Server().Options().Broker; brk.Name() != "test" {
t.Fatalf("server broker not set: %v", svc.Server().Options().Broker)
}
}
*/
func Test_service_Tracer(t *testing.T) {
tr := tracer.NewTracer()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want tracer.Tracer
}{
{
name: "service.Tracer",
fields: fields{
opts: Options{Tracers: []tracer.Tracer{tr}},
},
args: args{
names: []string{"noop"},
},
want: tr,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Tracer(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Tracer() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Config(t *testing.T) {
c := config.NewConfig()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want config.Config
}{
{
name: "service.Config",
fields: fields{
opts: Options{Configs: []config.Config{c}},
},
args: args{
names: []string{"noop"},
},
want: c,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Config(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Config() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Client(t *testing.T) {
c := client.NewClient()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want client.Client
}{
{
name: "service.Client",
fields: fields{
opts: Options{Clients: []client.Client{c}},
},
args: args{
names: []string{"noop"},
},
want: c,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Client(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Client() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Server(t *testing.T) {
s := server.NewServer()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want server.Server
}{
{
name: "service.Server",
fields: fields{
opts: Options{Servers: []server.Server{s}},
},
args: args{
names: []string{"noop"},
},
want: s,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Server(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Server() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Store(t *testing.T) {
s := store.NewStore()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want store.Store
}{
{
name: "service.Store",
fields: fields{
opts: Options{Stores: []store.Store{s}},
},
args: args{
names: []string{"noop"},
},
want: s,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Store(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Store() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Register(t *testing.T) {
r := register.NewRegister()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want register.Register
}{
{
name: "service.Register",
fields: fields{
opts: Options{Registers: []register.Register{r}},
},
args: args{
names: []string{"noop"},
},
want: r,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Register(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Register() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Logger(t *testing.T) {
l := logger.NewLogger()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want logger.Logger
}{
{
name: "service.Logger",
fields: fields{
opts: Options{Loggers: []logger.Logger{l}},
},
args: args{
names: []string{"noop"},
},
want: l,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Logger(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Logger() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Router(t *testing.T) {
r := router.NewRouter()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want router.Router
}{
{
name: "service.Router",
fields: fields{
opts: Options{Routers: []router.Router{r}},
},
args: args{
names: []string{"noop"},
},
want: r,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Router(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Router() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Meter(t *testing.T) {
m := meter.NewMeter()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want meter.Meter
}{
{
name: "service.Meter",
fields: fields{
opts: Options{Meters: []meter.Meter{m}},
},
args: args{
names: []string{"noop"},
},
want: m,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Meter(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Meter() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_String(t *testing.T) {
type fields struct {
opts Options
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "service.String",
fields: fields{
opts: Options{Name: "noop"},
},
want: "noop",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.String(); got != tt.want {
t.Errorf("service.String() = %v, want %v", got, tt.want)
}
})
}
}
/*
func Test_service_Start(t *testing.T) {
type fields struct {
RWMutex sync.RWMutex
opts Options
}
tests := []struct {
name string
fields fields
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
RWMutex: tt.fields.RWMutex,
opts: tt.fields.opts,
}
if err := s.Start(); (err != nil) != tt.wantErr {
t.Errorf("service.Start() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_service_Stop(t *testing.T) {
type fields struct {
RWMutex sync.RWMutex
opts Options
}
tests := []struct {
name string
fields fields
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
RWMutex: tt.fields.RWMutex,
opts: tt.fields.opts,
}
if err := s.Stop(); (err != nil) != tt.wantErr {
t.Errorf("service.Stop() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_service_Run(t *testing.T) {
type fields struct {
RWMutex sync.RWMutex
opts Options
}
tests := []struct {
name string
fields fields
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
RWMutex: tt.fields.RWMutex,
opts: tt.fields.opts,
}
if err := s.Run(); (err != nil) != tt.wantErr {
t.Errorf("service.Run() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_getNameIndex(t *testing.T) {
type args struct {
n string
ifaces interface{}
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getNameIndex(tt.args.n, tt.args.ifaces); got != tt.want {
t.Errorf("getNameIndex() = %v, want %v", got, tt.want)
}
})
}
}
*/

53
store/context_test.go Normal file
View File

@@ -0,0 +1,53 @@
package store
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewStore())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), storeKey{}, NewStore())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewStore())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestSetOption(t *testing.T) {
type key struct{}
o := SetOption(key{}, "test")
opts := &Options{}
o(opts)
if v, ok := opts.Context.Value(key{}).(string); !ok || v == "" {
t.Fatal("SetOption not works")
}
}

View File

@@ -2,7 +2,6 @@ package store
import ( import (
"context" "context"
"path/filepath"
"sort" "sort"
"strings" "strings"
"time" "time"
@@ -33,12 +32,11 @@ type memoryStore struct {
} }
func (m *memoryStore) key(prefix, key string) string { func (m *memoryStore) key(prefix, key string) string {
return filepath.Join(prefix, key) return prefix + m.opts.Separator + key
} }
func (m *memoryStore) exists(prefix, key string) error { func (m *memoryStore) exists(prefix, key string) error {
key = m.key(prefix, key) key = m.key(prefix, key)
_, found := m.store.Get(key) _, found := m.store.Get(key)
if !found { if !found {
return ErrNotFound return ErrNotFound

View File

@@ -30,20 +30,25 @@ type Options struct {
Name string Name string
// Namespace of the records // Namespace of the records
Namespace string Namespace string
// Separator used as key parts separator
Separator string
// Addrs contains store address // Addrs contains store address
Addrs []string Addrs []string
// Wrappers store wrapper that called before actual functions // Wrappers store wrapper that called before actual functions
// Wrappers []Wrapper // Wrappers []Wrapper
// Timeout specifies timeout duration for all operations
Timeout time.Duration
} }
// NewOptions creates options struct // NewOptions creates options struct
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Context: context.Background(), Context: context.Background(),
Codec: codec.DefaultCodec, Codec: codec.DefaultCodec,
Tracer: tracer.DefaultTracer, Tracer: tracer.DefaultTracer,
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
Separator: DefaultSeparator,
} }
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
@@ -96,6 +101,13 @@ func Name(n string) Option {
} }
} }
// Separator the value used as key parts separator
func Separator(s string) Option {
return func(o *Options) {
o.Separator = s
}
}
// Namespace sets namespace of the store // Namespace sets namespace of the store
func Namespace(ns string) Option { func Namespace(ns string) Option {
return func(o *Options) { return func(o *Options) {
@@ -110,6 +122,13 @@ func Tracer(t tracer.Tracer) Option {
} }
} }
// Timeout sets the timeout
func Timeout(td time.Duration) Option {
return func(o *Options) {
o.Timeout = td
}
}
// Addrs contains the addresses or other connection information of the backing storage. // Addrs contains the addresses or other connection information of the backing storage.
// For example, an etcd implementation would contain the nodes of the cluster. // For example, an etcd implementation would contain the nodes of the cluster.
// A SQL implementation could contain one or more connection strings. // A SQL implementation could contain one or more connection strings.

View File

@@ -12,7 +12,9 @@ var (
// ErrInvalidKey is returned when a key has empty or have invalid format // ErrInvalidKey is returned when a key has empty or have invalid format
ErrInvalidKey = errors.New("invalid key") ErrInvalidKey = errors.New("invalid key")
// DefaultStore is the global default store // DefaultStore is the global default store
DefaultStore Store = NewStore() DefaultStore = NewStore()
// DefaultSeparator is the gloabal default key parts separator
DefaultSeparator = "/"
) )
// Store is a data storage interface // Store is a data storage interface

View File

@@ -6,9 +6,9 @@ import (
) )
type memorySync struct { type memorySync struct {
mtx gosync.RWMutex
locks map[string]*memoryLock locks map[string]*memoryLock
options Options options Options
mtx gosync.RWMutex
} }
type memoryLock struct { type memoryLock struct {

View File

@@ -46,3 +46,13 @@ func NewSpanContext(ctx context.Context, span Span) context.Context {
} }
return context.WithValue(ctx, spanKey{}, span) return context.WithValue(ctx, spanKey{}, span)
} }
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

42
tracer/context_test.go Normal file
View File

@@ -0,0 +1,42 @@
package tracer
import (
"context"
"testing"
)
func TestFromNilContext(t *testing.T) {
// nolint: staticcheck
c, ok := FromContext(nil)
if ok || c != nil {
t.Fatal("FromContext not works")
}
}
func TestNewNilContext(t *testing.T) {
// nolint: staticcheck
ctx := NewContext(nil, NewTracer())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}
func TestFromContext(t *testing.T) {
ctx := context.WithValue(context.TODO(), tracerKey{}, NewTracer())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("FromContext not works")
}
}
func TestNewContext(t *testing.T) {
ctx := NewContext(context.TODO(), NewTracer())
c, ok := FromContext(ctx)
if c == nil || !ok {
t.Fatal("NewContext not works")
}
}

View File

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

View File

@@ -1,9 +1,15 @@
package tracer package tracer
import "go.unistack.org/micro/v3/logger" import (
"context"
"go.unistack.org/micro/v3/logger"
)
// SpanOptions contains span option // SpanOptions contains span option
type SpanOptions struct{} type SpanOptions struct {
Labels []interface{}
}
// SpanOption func signature // SpanOption func signature
type SpanOption func(o *SpanOptions) type SpanOption func(o *SpanOptions)
@@ -14,12 +20,20 @@ type EventOptions struct{}
// EventOption func signature // EventOption func signature
type EventOption func(o *EventOptions) type EventOption func(o *EventOptions)
func SpanLabels(labels ...interface{}) SpanOption {
return func(o *SpanOptions) {
o.Labels = labels
}
}
// Options struct // Options struct
type Options struct { type Options struct {
// Logger used for logging // Logger used for logging
Logger logger.Logger Logger logger.Logger
// Name of the tracer // Name of the tracer
Name string Name string
// Context used to store custome tracer options
Context context.Context
} }
// Option func signature // Option func signature

Some files were not shown because too many files have changed in this diff Show More