Compare commits

..

132 Commits

Author SHA1 Message Date
f76b3171d9 Merge pull request #170 from unistack-org/tracer
tracer: add span kind
2023-01-18 00:28:02 +03:00
db3fc42204 tracer: add span kind
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-18 00:25:15 +03:00
f59023d741 tracer: add span kind
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-18 00:21:18 +03:00
6f17fd891a Merge pull request #169 from unistack-org/tracer
tracer/wrapper: fix observers
2023-01-17 23:39:49 +03:00
fd93308e8e tracer/wrapper: fix observers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-17 23:37:25 +03:00
2aaa0a74d8 tracer/wrapper: fix observers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-17 23:31:27 +03:00
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
132 changed files with 4366 additions and 3930 deletions

View File

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

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: setup
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: 1.17
- name: checkout
@@ -34,7 +34,7 @@ jobs:
- name: checkout
uses: actions/checkout@v3
- name: lint
uses: golangci/golangci-lint-action@v3.1.0
uses: golangci/golangci-lint-action@v3.3.1
continue-on-error: true
with:
# 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
uses: actions/checkout@v3
- name: setup
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: 1.17
# Initializes the CodeQL tools for scanning.
- name: init
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# 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).
# If this step fails, then you should remove it and run the build manually (see below)
- name: autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -75,4 +75,4 @@ jobs:
# make release
- name: analyze
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

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

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: setup
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: 1.17
- name: checkout
@@ -34,7 +34,7 @@ jobs:
- name: checkout
uses: actions/checkout@v3
- name: lint
uses: golangci/golangci-lint-action@v3.1.0
uses: golangci/golangci-lint-action@v3.3.1
continue-on-error: true
with:
# 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/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3) [![Slack](https://img.shields.io/static/v1?label=micro&message=slack&color=blueviolet)](https://unistack-org.slack.com/messages/default)
# Micro [![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.

View File

@@ -3,10 +3,27 @@ package api
import (
"strings"
"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
func TestEncoding(t *testing.T) {
func TestEncode(t *testing.T) {
testData := []*Endpoint{
nil,
{
@@ -150,3 +167,79 @@ func TestValidate(t *testing.T) {
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
var DefaultBroker Broker = NewBroker()
var DefaultBroker = NewBroker()
var (
// 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 (
"context"
"math"
"time"
"go.unistack.org/micro/v3/util/backoff"
@@ -10,6 +11,20 @@ import (
// BackoffFunc is the backoff call func
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
}
// 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"
)
func TestBackoff(t *testing.T) {
func TestBackoffExp(t *testing.T) {
results := []time.Duration{
0 * time.Second,
100 * time.Millisecond,
@@ -22,7 +22,7 @@ func TestBackoff(t *testing.T) {
}
for i := 0; i < 5; i++ {
d, err := exponentialBackoff(context.TODO(), r, i)
d, err := BackoffExp(context.TODO(), r, i)
if err != nil {
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 (
// DefaultClient is the global default client
DefaultClient Client = NewClient()
DefaultClient = NewClient()
// DefaultContentType is the default content-type if not specified
DefaultContentType = "application/json"
// DefaultBackoff is the default backoff function for retries
DefaultBackoff = exponentialBackoff
DefaultContentType = ""
// DefaultBackoff is the default backoff function for retries (minimum 10 millisecond and maximum 5 second)
DefaultBackoff = BackoffInterval(10*time.Millisecond, 5*time.Second)
// DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryNever
// DefaultRetries is the default number of times a request is tried
@@ -74,7 +74,7 @@ type Request interface {
type Response interface {
// Read the response
Codec() codec.Codec
// read the header
// Header data
Header() metadata.Metadata
// Read the undecoded response
Read() ([]byte, error)

View File

@@ -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 (
"context"
"fmt"
"time"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/selector"
)
// 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 {
// 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
}
@@ -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) {
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
}

View File

@@ -3,6 +3,7 @@ package client
import (
"context"
"crypto/tls"
"net"
"time"
"go.unistack.org/micro/v3/broker"
@@ -56,6 +57,8 @@ type Options struct {
PoolSize int
// PoolTTL connection pool ttl
PoolTTL time.Duration
// ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error)
}
// NewCallOptions creates new call options struct
@@ -95,10 +98,23 @@ type CallOptions struct {
StreamTimeout time.Duration
// RequestTimeout request timeout
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 time.Duration
// Retries specifies retries num
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
@@ -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
func WithContentType(ct string) CallOption {
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
// set in Options.CallOptions
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
}
// 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) {
if err == nil {
return false, nil
}
me := errors.FromError(err)
switch me.Code {
// retry on timeout or internal server error
case 408, 500:
return true, 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 = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec()
DefaultCodec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
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:
*ve = string(d)
return nil
case []byte:
copy(ve, d)
return nil
case *[]byte:
*ve = d
return nil

View File

@@ -5,7 +5,7 @@ import (
"testing"
)
func TestNoopBytes(t *testing.T) {
func TestNoopBytesPtr(t *testing.T) {
req := []byte("test 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) {
req := []byte("test req")
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
}
// 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
func FromError(err error) *Error {
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"
)
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) {
if err != nil {
t.Fatal(err)

View File

@@ -3,7 +3,6 @@ package flow
import (
"context"
"fmt"
"path/filepath"
"sync"
"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 {
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())})
}
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())})
}
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())})
}
@@ -181,8 +180,8 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return "", err
}
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid))
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
stepStore := store.NewNamespaceStore(w.opts.Store, "steps"+w.opts.Store.Options().Separator+eid)
workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+eid)
options := NewExecuteOptions(opts...)
@@ -219,7 +218,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
for idx := range steps {
for nidx := range steps[idx] {
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
}
}
@@ -246,32 +245,32 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
wg.Add(1)
go func(step Step) {
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
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
return
}
rsp, serr := step.Execute(nctx, req, nopts...)
if serr != nil {
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)
}
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)
}
cherr <- serr
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)
cherr <- werr
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)
cherr <- werr
return
@@ -279,32 +278,32 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
}(cstep)
wg.Wait()
} 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
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
return
}
rsp, serr := cstep.Execute(nctx, req, nopts...)
if serr != nil {
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)
}
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)
}
cherr <- serr
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)
cherr <- werr
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
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
require (
github.com/ef-ds/deque v1.0.4
github.com/golang-jwt/jwt/v4 v4.4.0
github.com/imdario/mergo v0.3.12
github.com/google/go-cmp v0.5.7 // indirect
github.com/imdario/mergo v0.3.13
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/silas/dag v0.0.0-20211117232152-9d50aa809f35
go.unistack.org/micro-proto/v3 v3.2.7
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b
go.unistack.org/micro-proto/v3 v3.3.1
)

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-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
github.com/ef-ds/deque v1.0.4/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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
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/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/golang-jwt/jwt/v4 v4.4.0 h1:EmVIxB5jzbllGIjiCV5JG4VylbK3KE400tLGLI1cdfU=
github.com/golang-jwt/jwt/v4 v4.4.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/gnostic v0.6.6 h1:MVSM2r2j9aRUvYNym66JGW96Ddd5MN4sTi59yktb6yk=
github.com/google/gnostic v0.6.6/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
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/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.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/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
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/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.unistack.org/micro-proto/v3 v3.2.7 h1:zG6d69kHc+oij2lwQ3AfrCgdjiEVRG2A7TlsxjusWs4=
go.unistack.org/micro-proto/v3 v3.2.7/go.mod h1:ZltVWNECD5yK+40+OCONzGw4OtmSdTpVi8/KFgo9dqM=
go.unistack.org/micro-proto/v3 v3.3.1 h1:nQ0MtWvP2G3QrpOgawVOPhpZZYkq6umTGDqs8FxJYIo=
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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-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-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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/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 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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-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 {
sync.RWMutex
enc *json.Encoder
logFunc LogFunc
logfFunc LogfFunc
opts Options
sync.RWMutex
}
// Init(opts...) should only overwrite provided options
@@ -49,7 +49,7 @@ func (l *defaultLogger) Clone(opts ...Option) Logger {
oldopts.Wrappers = newopts.Wrappers
l.Lock()
cl := &defaultLogger{opts: oldopts, logFunc: l.logFunc, logfFunc: l.logfFunc}
cl := &defaultLogger{opts: oldopts, logFunc: l.logFunc, logfFunc: l.logfFunc, enc: json.NewEncoder(l.opts.Out)}
l.Unlock()
// wrap the Log func

View File

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

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
func (md Metadata) Set(key, val string) {
md[textproto.CanonicalMIMEHeaderKey(key)] = val
func (md Metadata) Set(kv ...string) {
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
func (md Metadata) Del(key string) {
// fast path
delete(md, key)
// slow path
delete(md, textproto.CanonicalMIMEHeaderKey(key))
func (md Metadata) Del(keys ...string) {
for _, key := range keys {
// fast path
delete(md, key)
// slow path
delete(md, textproto.CanonicalMIMEHeaderKey(key))
}
}
// Copy makes a copy of the metadata
@@ -129,13 +136,6 @@ func Pairs(kv ...string) (Metadata, bool) {
return nil, false
}
md := New(len(kv) / 2)
var k string
for i, v := range kv {
if i%2 == 0 {
k = v
continue
}
md.Set(k, v)
}
md.Set(kv...)
return md, true
}

View File

@@ -5,6 +5,21 @@ import (
"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) {
ctx := context.Background()
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 (
// DefaultMeter is the default meter
DefaultMeter Meter = NewMeter()
DefaultMeter = NewMeter()
// DefaultAddress data will be made available on this host:port
DefaultAddress = ":9090"
// DefaultPath the meter endpoint where the Meter data will be made available

View File

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

View File

@@ -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"
"time"
"go.unistack.org/micro/v3/auth"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/config"
@@ -39,8 +38,6 @@ type Options struct {
Configs []config.Config
// Clients holds clients
Clients []client.Client
// Auths holds auths
Auths []auth.Auth
// Stores holds stores
Stores []store.Store
// Registers holds registers
@@ -70,7 +67,6 @@ func NewOptions(opts ...Option) Options {
Brokers: []broker.Broker{broker.DefaultBroker},
Registers: []register.Register{register.DefaultRegister},
Routers: []router.Router{router.DefaultRouter},
Auths: []auth.Auth{auth.DefaultAuth},
Loggers: []logger.Logger{logger.DefaultLogger},
Tracers: []tracer.Tracer{tracer.DefaultTracer},
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
func Config(c ...config.Config) Option {
return func(o *Options) error {

View File

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

@@ -29,10 +29,10 @@ type record struct {
}
type memory struct {
sync.RWMutex
records map[string]services
watchers map[string]*watcher
opts Options
sync.RWMutex
}
// services is a KV map with service name as the key and a map of records as the value
@@ -164,7 +164,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
continue
}
metadata := make(map[string]string)
metadata := make(map[string]string, len(n.Metadata))
// make copy of metadata
for k, v := range n.Metadata {

View File

@@ -301,11 +301,11 @@ func TestWatcher(t *testing.T) {
wg.Add(1)
go func() {
for {
ch, err := wc.Next()
_, err := wc.Next()
if err != nil {
t.Fatal("unexpected err", err)
}
t.Logf("changes %#+v", ch.Service)
// t.Logf("changes %#+v", ch.Service)
wc.Stop()
wg.Done()
return

View File

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

View File

@@ -12,10 +12,9 @@ import (
// Resolver is a DNS network resolve
type Resolver struct {
goresolver *net.Resolver
// Address of resolver to use
Address string
sync.RWMutex
goresolver *net.Resolver
Address string
}
// Resolve tries to resolve endpoint address
@@ -47,7 +46,7 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
if goresolver == nil {
r.Lock()
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{
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 (
// DefaultRouter is the global default router
DefaultRouter Router = NewRouter()
DefaultRouter = NewRouter()
// DefaultNetwork is default micro network
DefaultNetwork = "micro"
// 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{}
for sb, subs := range n.subscribers {
for _, sub := range subs {
for idx := range subs {
if sb.Options().Context != nil {
cx = sb.Options().Context
}
ncx := cx
wg.Add(1)
go func(s broker.Subscriber) {
defer wg.Done()
if config.Logger.V(logger.InfoLevel) {
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) {
config.Logger.Errorf(n.opts.Context, "unsubscribing from topic: %s err: %v", s.Topic(), err)
}
}
}(sub)
}(subs[idx])
}
n.subscribers[sb] = nil
}

View File

@@ -8,6 +8,7 @@ import (
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/server"
)
@@ -50,6 +51,7 @@ func TestNoopSub(t *testing.T) {
t.Fatal(err)
}
logger.DefaultLogger.Init(logger.WithLevel(logger.ErrorLevel))
s := server.NewServer(
server.Broker(b),
server.Codec("application/octet-stream", codec.NewCodec()),

View File

@@ -7,7 +7,6 @@ import (
"sync"
"time"
"go.unistack.org/micro/v3/auth"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
@@ -32,8 +31,6 @@ type Options struct {
Register register.Register
// Tracer holds the tracer
Tracer tracer.Tracer
// Auth holds the auth
Auth auth.Auth
// Logger holds the logger
Logger logger.Logger
// Meter holds the meter
@@ -91,7 +88,6 @@ type Options struct {
// NewOptions returns new options struct with default or passed values
func NewOptions(opts ...Option) Options {
options := Options{
Auth: auth.DefaultAuth,
Codecs: make(map[string]codec.Codec),
Context: context.Background(),
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
func Transport(t transport.Transport) Option {
return func(o *Options) {

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,20 @@
package micro
import (
"context"
"reflect"
"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 {
@@ -20,3 +33,706 @@ func TestGetNameIndex(t *testing.T) {
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 (
"context"
"path/filepath"
"sort"
"strings"
"time"
@@ -33,12 +32,11 @@ type memoryStore struct {
}
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 {
key = m.key(prefix, key)
_, found := m.store.Get(key)
if !found {
return ErrNotFound

View File

@@ -30,20 +30,25 @@ type Options struct {
Name string
// Namespace of the records
Namespace string
// Separator used as key parts separator
Separator string
// Addrs contains store address
Addrs []string
// Wrappers store wrapper that called before actual functions
// Wrappers []Wrapper
// Timeout specifies timeout duration for all operations
Timeout time.Duration
}
// NewOptions creates options struct
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Context: context.Background(),
Codec: codec.DefaultCodec,
Tracer: tracer.DefaultTracer,
Meter: meter.DefaultMeter,
Logger: logger.DefaultLogger,
Context: context.Background(),
Codec: codec.DefaultCodec,
Tracer: tracer.DefaultTracer,
Meter: meter.DefaultMeter,
Separator: DefaultSeparator,
}
for _, o := range opts {
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
func Namespace(ns string) Option {
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.
// For example, an etcd implementation would contain the nodes of the cluster.
// 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 = errors.New("invalid key")
// 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

View File

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

View File

@@ -46,3 +46,13 @@ func NewSpanContext(ctx context.Context, span Span) context.Context {
}
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

@@ -13,6 +13,7 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption)
name: name,
ctx: ctx,
tracer: t,
opts: NewSpanOptions(opts...),
}
if span.ctx == nil {
span.ctx = context.Background()
@@ -35,7 +36,7 @@ type noopSpan struct {
ctx context.Context
tracer Tracer
name string
labels []Label
opts SpanOptions
}
func (s *noopSpan) Finish(opts ...SpanOption) {
@@ -56,8 +57,16 @@ func (s *noopSpan) SetName(name string) {
s.name = name
}
func (s *noopSpan) SetLabels(labels ...Label) {
s.labels = labels
func (s *noopSpan) SetLabels(labels ...interface{}) {
s.opts.Labels = labels
}
func (s *noopSpan) AddLabels(labels ...interface{}) {
s.opts.Labels = append(s.opts.Labels, labels...)
}
func (s *noopSpan) Kind() SpanKind {
return s.opts.Kind
}
// NewTracer returns new memory tracer

View File

@@ -1,9 +1,64 @@
package tracer
import "go.unistack.org/micro/v3/logger"
import (
"context"
"go.unistack.org/micro/v3/logger"
)
type SpanKind int
const (
// SpanKindUnspecified is an unspecified SpanKind and is not a valid
// SpanKind. SpanKindUnspecified should be replaced with SpanKindInternal
// if it is received.
SpanKindUnspecified SpanKind = 0
// SpanKindInternal is a SpanKind for a Span that represents an internal
// operation within an application.
SpanKindInternal SpanKind = 1
// SpanKindServer is a SpanKind for a Span that represents the operation
// of handling a request from a client.
SpanKindServer SpanKind = 2
// SpanKindClient is a SpanKind for a Span that represents the operation
// of client making a request to a server.
SpanKindClient SpanKind = 3
// SpanKindProducer is a SpanKind for a Span that represents the operation
// of a producer sending a message to a message broker. Unlike
// SpanKindClient and SpanKindServer, there is often no direct
// relationship between this kind of Span and a SpanKindConsumer kind. A
// SpanKindProducer Span will end once the message is accepted by the
// message broker which might not overlap with the processing of that
// message.
SpanKindProducer SpanKind = 4
// SpanKindConsumer is a SpanKind for a Span that represents the operation
// of a consumer receiving a message from a message broker. Like
// SpanKindProducer Spans, there is often no direct relationship between
// this Span and the Span that produced the message.
SpanKindConsumer SpanKind = 5
)
func (sk SpanKind) String() string {
switch sk {
case SpanKindInternal:
return "internal"
case SpanKindServer:
return "server"
case SpanKindClient:
return "client"
case SpanKindProducer:
return "producer"
case SpanKindConsumer:
return "consumer"
default:
return "unspecified"
}
}
// SpanOptions contains span option
type SpanOptions struct{}
type SpanOptions struct {
Labels []interface{}
Kind SpanKind
}
// SpanOption func signature
type SpanOption func(o *SpanOptions)
@@ -14,8 +69,22 @@ type EventOptions struct{}
// EventOption func signature
type EventOption func(o *EventOptions)
func WithSpanLabels(labels ...interface{}) SpanOption {
return func(o *SpanOptions) {
o.Labels = labels
}
}
func WithSpanKind(k SpanKind) SpanOption {
return func(o *SpanOptions) {
o.Kind = k
}
}
// Options struct
type Options struct {
// Context used to store custome tracer options
Context context.Context
// Logger used for logging
Logger logger.Logger
// Name of the tracer
@@ -32,6 +101,17 @@ func Logger(l logger.Logger) Option {
}
}
// NewSpanOptions returns default SpanOptions
func NewSpanOptions(opts ...SpanOption) SpanOptions {
options := SpanOptions{
Kind: SpanKindInternal,
}
for _, o := range opts {
o(&options)
}
return options
}
// NewOptions returns default options
func NewOptions(opts ...Option) Options {
options := Options{

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