Compare commits

..

89 Commits

Author SHA1 Message Date
7462b0b53c Merge pull request #178 from unistack-org/fsm
fsm: improve and convert to interface
2023-01-30 00:31:50 +03:00
cb743cee3f logger: remove wrappers support and OmitLogger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-30 00:29:49 +03:00
d18952951c fsm: improve and convert to interface
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-30 00:17:29 +03:00
dependabot[bot]
f6b7f1b4bc chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 (#172)
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.5 to 1.3.6.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.5...v1.3.6)

---
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>
2023-01-24 10:07:13 +03:00
dependabot[bot]
33fa702104 chore(deps): bump golangci/golangci-lint-action from 3.3.1 to 3.4.0 (#173)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.3.1...v3.4.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>
2023-01-24 09:54:07 +03:00
4debc392d1 Merge pull request #171 from unistack-org/fixspan
add span status method
2023-01-18 09:51:39 +03:00
7137d99102 add span status method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-01-18 09:48:58 +03:00
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
111 changed files with 2353 additions and 4351 deletions

View File

@@ -17,7 +17,7 @@ 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:

View File

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

@@ -36,4 +36,4 @@ jobs:
- name: Run coverage
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: codecov
uses: codecov/codecov-action@v3.1.0
uses: codecov/codecov-action@v3.1.1

View File

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

View File

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

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

@@ -7,16 +7,31 @@ import (
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")

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

@@ -13,7 +13,7 @@ var (
// DefaultClient is the global default client
DefaultClient = NewClient()
// DefaultContentType is the default content-type if not specified
DefaultContentType = "application/json"
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
@@ -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

@@ -14,9 +14,25 @@ func TestFromContext(t *testing.T) {
}
}
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")

View File

@@ -9,6 +9,7 @@ import (
"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
@@ -233,18 +234,7 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
callOpts.Address = []string{n.opts.Proxy}
}
// 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
}
var next selector.Next
// return errors.New("go.micro.client", "request timeout", 408)
call := func(i int) error {
@@ -259,6 +249,22 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
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
@@ -323,6 +329,8 @@ 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 {
@@ -374,18 +382,7 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption
callOpts.Address = []string{n.opts.Proxy}
}
// 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
}
var next selector.Next
call := func(i int) (Stream, error) {
// call backoff first. Someone may want an initial start delay
@@ -399,6 +396,22 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption
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)

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

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

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

View File

@@ -5,6 +5,24 @@ import (
"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())

View File

@@ -5,6 +5,24 @@ import (
"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())

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
}

126
fsm/default.go Normal file
View File

@@ -0,0 +1,126 @@
package fsm
import (
"context"
"fmt"
"sync"
)
type state struct {
body interface{}
name string
}
var _ State = &state{}
func (s *state) Name() string {
return s.name
}
func (s *state) Body() interface{} {
return s.body
}
// fsm is a finite state machine
type fsm struct {
statesMap map[string]StateFunc
current string
statesOrder []string
opts Options
mu sync.Mutex
}
// NewFSM creates a new finite state machine having the specified initial state
// with specified options
func NewFSM(opts ...Option) *fsm {
return &fsm{
statesMap: map[string]StateFunc{},
opts: NewOptions(opts...),
}
}
// Current returns the current state
func (f *fsm) Current() string {
f.mu.Lock()
s := f.current
f.mu.Unlock()
return s
}
// 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()
}
// Start runs state machine with provided data
func (f *fsm) Start(ctx context.Context, args interface{}, opts ...Option) (interface{}, error) {
var err error
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()
var s State
s = &state{name: cstate, body: args}
nstate := s.Name()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
fn, ok := states[nstate]
if !ok {
return nil, fmt.Errorf(`state "%s" %w`, nstate, ErrInvalidState)
}
f.mu.Lock()
f.current = nstate
f.mu.Unlock()
// wrap the handler func
for i := len(options.Wrappers); i > 0; i-- {
fn = options.Wrappers[i-1](fn)
}
s, err = fn(ctx, s, sopts...)
switch {
case err != nil:
return s.Body(), err
case s.Name() == StateEnd:
return s.Body(), nil
case s.Name() == "":
for idx := range f.statesOrder {
if f.statesOrder[idx] == nstate && len(f.statesOrder) > idx+1 {
nstate = f.statesOrder[idx+1]
}
}
default:
nstate = s.Name()
}
}
}
}

View File

@@ -3,8 +3,6 @@ package fsm // import "go.unistack.org/micro/v3/fsm"
import (
"context"
"errors"
"fmt"
"sync"
)
var (
@@ -12,170 +10,20 @@ var (
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
type State interface {
Name() string
Body() interface{}
}
// 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
}
}
// StateWrapper wraps the StateFunc and returns the equivalent
type StateWrapper func(StateFunc) StateFunc
// StateFunc called on state transition and return next step and error
type StateFunc func(ctx context.Context, args interface{}, opts ...StateOption) (string, interface{}, error)
type StateFunc func(ctx context.Context, state State, opts ...StateOption) (State, 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
}
}
type FSM interface {
Start(context.Context, interface{}, ...Option) (interface{}, error)
Current() string
Reset()
State(string, StateFunc)
}

View File

@@ -1,63 +1,72 @@
package fsm
import (
"bytes"
"context"
"fmt"
"testing"
"go.unistack.org/micro/v3/logger"
)
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)
if err := logger.DefaultLogger.Init(); err != nil {
t.Fatal(err)
}
pfa := func(_ context.Context, state string, _ interface{}) {
fmt.Fprintf(buf, "after state %s\n", state)
wrapper := func(next StateFunc) StateFunc {
return func(sctx context.Context, s State, opts ...StateOption) (State, error) {
sctx = logger.NewContext(sctx, logger.Fields("state", s.Name()))
return next(sctx, s, opts...)
}
}
f := New(InitialState("1"), HookBefore(pfb), HookAfter(pfa))
f1 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
args := req.(map[string]interface{})
f := NewFSM(InitialState("1"), WrapState(wrapper))
f1 := func(sctx context.Context, s State, opts ...StateOption) (State, error) {
_, ok := logger.FromContext(sctx)
if !ok {
t.Fatal("f1 context does not have logger")
}
args := s.Body().(map[string]interface{})
if v, ok := args["request"].(string); !ok || v == "" {
return "", nil, fmt.Errorf("empty request")
return nil, fmt.Errorf("empty request")
}
return "2", map[string]interface{}{"response": "test2"}, nil
return &state{name: "", body: map[string]interface{}{"response": "state1"}}, 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")
f2 := func(sctx context.Context, s State, opts ...StateOption) (State, error) {
_, ok := logger.FromContext(sctx)
if !ok {
t.Fatal("f2 context does not have logger")
}
return "", map[string]interface{}{"response": "test"}, nil
args := s.Body().(map[string]interface{})
if v, ok := args["response"].(string); !ok || v == "" {
return nil, fmt.Errorf("empty response")
}
return &state{name: "", body: map[string]interface{}{"response": "state2"}}, 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")
f3 := func(sctx context.Context, s State, opts ...StateOption) (State, error) {
_, ok := logger.FromContext(sctx)
if !ok {
t.Fatal("f3 context does not have logger")
}
return StateEnd, map[string]interface{}{"response": "test_last"}, nil
args := s.Body().(map[string]interface{})
if v, ok := args["response"].(string); !ok || v == "" {
return nil, fmt.Errorf("empty response")
}
return &state{name: StateEnd, body: map[string]interface{}{"response": "state3"}}, nil
}
f.State("1", f1)
f.State("2", f2)
f.State("3", f3)
rsp, err := f.Start(ctx, map[string]interface{}{"request": "test1"})
rsp, err := f.Start(ctx, map[string]interface{}{"request": "state"})
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" {
} else if v != "state3" {
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())
}
}

52
fsm/options.go Normal file
View File

@@ -0,0 +1,52 @@
package fsm
// Options struct holding fsm options
type Options struct {
// Initial state
Initial string
// Wrappers runs before state
Wrappers []StateWrapper
// DryRun mode
DryRun bool
}
// 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
}
}
// WrapState adds a state Wrapper to a list of options passed into the fsm
func WrapState(w StateWrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}
// NewOptions returns new Options struct filled by passed Option
func NewOptions(opts ...Option) Options {
options := Options{}
for _, o := range opts {
o(&options)
}
return options
}

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,17 +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.1
github.com/google/gnostic v0.6.8 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/imdario/mergo v0.3.12
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-20220225172249-27dd8689420f
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
go.unistack.org/micro-proto/v3 v3.3.1
)

33
go.sum
View File

@@ -14,8 +14,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
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=
@@ -24,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.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
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=
@@ -42,9 +38,8 @@ 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/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/gnostic v0.6.8 h1:bT56GPYBWh1tvBuBEd94qcS3+60b+y0HQur0ITkGuCk=
github.com/google/gnostic v0.6.8/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=
@@ -55,8 +50,8 @@ 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/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=
@@ -80,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=
@@ -97,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-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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=
@@ -113,15 +106,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -156,18 +145,16 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=

View File

@@ -5,6 +5,24 @@ import (
"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())

View File

@@ -12,11 +12,9 @@ import (
)
type defaultLogger struct {
enc *json.Encoder
opts Options
sync.RWMutex
enc *json.Encoder
logFunc LogFunc
logfFunc LogfFunc
opts Options
}
// Init(opts...) should only overwrite provided options
@@ -27,10 +25,6 @@ func (l *defaultLogger) Init(opts ...Option) error {
}
l.enc = json.NewEncoder(l.opts.Out)
// wrap the Log func
for i := len(l.opts.Wrappers); i > 0; i-- {
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
}
l.Unlock()
return nil
}
@@ -47,17 +41,10 @@ func (l *defaultLogger) Clone(opts ...Option) Logger {
o(&oldopts)
}
oldopts.Wrappers = newopts.Wrappers
l.Lock()
cl := &defaultLogger{opts: oldopts, logFunc: l.logFunc, logfFunc: l.logfFunc}
cl := &defaultLogger{opts: oldopts, enc: json.NewEncoder(l.opts.Out)}
l.Unlock()
// wrap the Log func
for i := len(newopts.Wrappers); i > 0; i-- {
cl.logFunc = newopts.Wrappers[i-1].Log(cl.logFunc)
cl.logfFunc = newopts.Wrappers[i-1].Logf(cl.logfFunc)
}
return cl
}
@@ -75,15 +62,17 @@ func (l *defaultLogger) Level(level Level) {
}
func (l *defaultLogger) Fields(fields ...interface{}) Logger {
l.RLock()
nl := &defaultLogger{opts: l.opts, enc: l.enc}
if len(fields) == 0 {
l.RUnlock()
return nl
} else if len(fields)%2 != 0 {
fields = fields[:len(fields)-1]
}
nl.logFunc = l.logFunc
nl.logfFunc = l.logfFunc
nl.opts.Fields = copyFields(l.opts.Fields)
nl.opts.Fields = append(nl.opts.Fields, fields...)
l.RUnlock()
return nl
}
@@ -143,27 +132,27 @@ func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
}
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
l.logfFunc(ctx, InfoLevel, msg, args...)
l.Logf(ctx, InfoLevel, msg, args...)
}
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
l.logfFunc(ctx, ErrorLevel, msg, args...)
l.Logf(ctx, ErrorLevel, msg, args...)
}
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
l.logfFunc(ctx, DebugLevel, msg, args...)
l.Logf(ctx, DebugLevel, msg, args...)
}
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
l.logfFunc(ctx, WarnLevel, msg, args...)
l.Logf(ctx, WarnLevel, msg, args...)
}
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
l.logfFunc(ctx, TraceLevel, msg, args...)
l.Logf(ctx, TraceLevel, msg, args...)
}
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
l.logfFunc(ctx, FatalLevel, msg, args...)
l.Logf(ctx, FatalLevel, msg, args...)
os.Exit(1)
}
@@ -236,8 +225,6 @@ func NewLogger(opts ...Option) Logger {
l := &defaultLogger{
opts: NewOptions(opts...),
}
l.logFunc = l.Log
l.logfFunc = l.Logf
l.enc = json.NewEncoder(l.opts.Out)
return l
}

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
)

View File

@@ -32,7 +32,33 @@ func TestFields(t *testing.T) {
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Fields("key", "val").Info(ctx, "message")
nl := l.Fields("key", "val")
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestFromContextWithFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
var ok bool
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Fields("key", "val")
ctx = NewContext(ctx, nl)
l, ok = FromContext(ctx)
if !ok {
t.Fatalf("context does not have logger")
}
l.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
@@ -110,39 +136,3 @@ func TestLogger(t *testing.T) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(WrapLogger(NewOmitWrapper())); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
}
func TestOmitLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewOmitLogger(NewLogger(WithLevel(TraceLevel), WithOutput(buf)))
if err := l.Init(); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
}

View File

@@ -19,8 +19,6 @@ type Options struct {
Fields []interface{}
// Name holds the logger name
Name string
// Wrappers logger wrapper that called before actual Log/Logf function
Wrappers []Wrapper
// The logging level the logger should log
Level Level
// CallerSkipCount number of frmaes to skip
@@ -83,10 +81,3 @@ func WithName(n string) Option {
o.Name = n
}
}
// WrapLogger adds a logger Wrapper to a list of options passed into the logger
func WrapLogger(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}

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

@@ -1,166 +0,0 @@
package logger // import "go.unistack.org/micro/v3/logger/wrapper"
import (
"context"
"reflect"
rutil "go.unistack.org/micro/v3/util/reflect"
)
// LogFunc function used for Log method
type LogFunc func(ctx context.Context, level Level, args ...interface{})
// LogfFunc function used for Logf method
type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
type Wrapper interface {
// Log logs message with needed level
Log(LogFunc) LogFunc
// Logf logs message with needed level
Logf(LogfFunc) LogfFunc
}
var _ Logger = &omitLogger{}
type omitLogger struct {
l Logger
}
func NewOmitLogger(l Logger) Logger {
return &omitLogger{l: l}
}
func (w *omitLogger) Init(opts ...Option) error {
return w.l.Init(append(opts, WrapLogger(NewOmitWrapper()))...)
}
func (w *omitLogger) V(level Level) bool {
return w.l.V(level)
}
func (w *omitLogger) Level(level Level) {
w.l.Level(level)
}
func (w *omitLogger) Clone(opts ...Option) Logger {
return w.l.Clone(opts...)
}
func (w *omitLogger) Options() Options {
return w.l.Options()
}
func (w *omitLogger) Fields(fields ...interface{}) Logger {
return w.l.Fields(fields...)
}
func (w *omitLogger) Info(ctx context.Context, args ...interface{}) {
w.l.Info(ctx, args...)
}
func (w *omitLogger) Trace(ctx context.Context, args ...interface{}) {
w.l.Trace(ctx, args...)
}
func (w *omitLogger) Debug(ctx context.Context, args ...interface{}) {
w.l.Debug(ctx, args...)
}
func (w *omitLogger) Warn(ctx context.Context, args ...interface{}) {
w.l.Warn(ctx, args...)
}
func (w *omitLogger) Error(ctx context.Context, args ...interface{}) {
w.l.Error(ctx, args...)
}
func (w *omitLogger) Fatal(ctx context.Context, args ...interface{}) {
w.l.Fatal(ctx, args...)
}
func (w *omitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
w.l.Infof(ctx, msg, args...)
}
func (w *omitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
w.l.Tracef(ctx, msg, args...)
}
func (w *omitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
w.l.Debugf(ctx, msg, args...)
}
func (w *omitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
w.l.Warnf(ctx, msg, args...)
}
func (w *omitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
w.l.Errorf(ctx, msg, args...)
}
func (w *omitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
w.l.Fatalf(ctx, msg, args...)
}
func (w *omitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
w.l.Log(ctx, level, args...)
}
func (w *omitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
w.l.Logf(ctx, level, msg, args...)
}
func (w *omitLogger) String() string {
return w.l.String()
}
type omitWrapper struct{}
func NewOmitWrapper() Wrapper {
return &omitWrapper{}
}
func getArgs(args []interface{}) []interface{} {
nargs := make([]interface{}, 0, len(args))
var err error
for _, arg := range args {
val := reflect.ValueOf(arg)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
narg := arg
if val.Kind() != reflect.Struct {
nargs = append(nargs, narg)
continue
}
if narg, err = rutil.Zero(arg); err != nil {
nargs = append(nargs, narg)
continue
}
rutil.CopyDefaults(narg, arg)
if flds, ferr := rutil.StructFields(narg); ferr == nil {
for _, fld := range flds {
if tv, ok := fld.Field.Tag.Lookup("logger"); ok && tv == "omit" {
fld.Value.Set(reflect.Zero(fld.Value.Type()))
}
}
}
nargs = append(nargs, narg)
}
return nargs
}
func (w *omitWrapper) Log(fn LogFunc) LogFunc {
return func(ctx context.Context, level Level, args ...interface{}) {
fn(ctx, level, getArgs(args)...)
}
}
func (w *omitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
}

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
}

View File

@@ -5,6 +5,24 @@ import (
"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)})

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

View File

@@ -5,6 +5,24 @@ import (
"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())

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)

View File

@@ -5,6 +5,24 @@ import (
"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())

View File

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

@@ -5,6 +5,24 @@ import (
"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())

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
}

View File

@@ -5,6 +5,24 @@ import (
"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())

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

@@ -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,36 +26,34 @@ 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
@@ -218,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 {

View File

@@ -5,7 +5,6 @@ import (
"reflect"
"testing"
"go.unistack.org/micro/v3/auth"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/config"
@@ -537,43 +536,6 @@ func Test_service_Logger(t *testing.T) {
}
}
func Test_service_Auth(t *testing.T) {
a := auth.NewAuth()
type fields struct {
opts Options
}
type args struct {
names []string
}
tests := []struct {
name string
fields fields
args args
want auth.Auth
}{
{
name: "service.Auth",
fields: fields{
opts: Options{Auths: []auth.Auth{a}},
},
args: args{
names: []string{"noop"},
},
want: a,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &service{
opts: tt.fields.opts,
}
if got := s.Auth(tt.args.names...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("service.Auth() = %v, want %v", got, tt.want)
}
})
}
}
func Test_service_Router(t *testing.T) {
r := router.NewRouter()
type fields struct {

View File

@@ -5,6 +5,24 @@ import (
"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())

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

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

View File

@@ -5,6 +5,24 @@ import (
"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())

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()
@@ -32,10 +33,12 @@ func (t *noopTracer) Name() string {
}
type noopSpan struct {
ctx context.Context
tracer Tracer
name string
labels []Label
ctx context.Context
tracer Tracer
name string
opts SpanOptions
status SpanStatus
statusMsg string
}
func (s *noopSpan) Finish(opts ...SpanOption) {
@@ -56,8 +59,25 @@ 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
}
func (s *noopSpan) Status() (SpanStatus, string) {
return s.status, s.statusMsg
}
func (s *noopSpan) SetStatus(st SpanStatus, msg string) {
s.status = st
s.statusMsg = msg
}
// NewTracer returns new memory tracer

View File

@@ -1,9 +1,91 @@
package tracer
import "go.unistack.org/micro/v3/logger"
import (
"context"
"go.unistack.org/micro/v3/logger"
)
type SpanStatus int
const (
// SpanStatusUnset is the default status code.
SpanStatusUnset SpanStatus = 0
// SpanStatusError indicates the operation contains an error.
SpanStatusError SpanStatus = 1
// SpanStatusOK indicates operation has been validated by an Application developers
// or Operator to have completed successfully, or contain no error.
SpanStatusOK SpanStatus = 2
)
func (s SpanStatus) String() string {
switch s {
case SpanStatusUnset:
return "Unset"
case SpanStatusError:
return "Error"
case SpanStatusOK:
return "OK"
default:
return "Unset"
}
}
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 +96,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 +128,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{

View File

@@ -6,7 +6,7 @@ import (
)
// DefaultTracer is the global default tracer
var DefaultTracer Tracer = NewTracer()
var DefaultTracer = NewTracer()
// Tracer is an interface for distributed tracing
type Tracer interface {
@@ -29,35 +29,14 @@ type Span interface {
Context() context.Context
// SetName set the span name
SetName(name string)
// SetStatus set the span status code and msg
SetStatus(status SpanStatus, msg string)
// Status returns span status and msg
Status() (SpanStatus, string)
// SetLabels set the span labels
SetLabels(labels ...Label)
}
type Label struct {
val interface{}
key string
}
func LabelAny(k string, v interface{}) Label {
return Label{key: k, val: v}
}
func LabelString(k string, v string) Label {
return Label{key: k, val: v}
}
func LabelInt(k string, v int) Label {
return Label{key: k, val: v}
}
func LabelInt64(k string, v int64) Label {
return Label{key: k, val: v}
}
func LabelFloat64(k string, v float64) Label {
return Label{key: k, val: v}
}
func LabelBool(k string, v bool) Label {
return Label{key: k, val: v}
SetLabels(labels ...interface{})
// AddLabels append the span labels
AddLabels(labels ...interface{})
// Kind returns span kind
Kind() SpanKind
}

View File

@@ -13,96 +13,108 @@ import (
var (
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
sp.SetName(fmt.Sprintf("Call %s.%s", req.Service(), req.Method()))
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md)+1)
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
labels = append(labels, "kind", sp.Kind())
sp.SetLabels(labels...)
}
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
sp.SetName(fmt.Sprintf("Stream %s.%s", req.Service(), req.Method()))
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
labels = append(labels, "kind", sp.Kind())
sp.SetLabels(labels...)
}
DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Pub to %s", msg.Topic()))
var labels []tracer.Label
sp.SetName(fmt.Sprintf("Publish %s", msg.Topic()))
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
labels = append(labels, "kind", sp.Kind())
sp.SetLabels(labels...)
}
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
sp.SetName(fmt.Sprintf("Handler %s.%s", req.Service(), req.Method()))
var labels []interface{}
if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
labels = append(labels, "kind", sp.Kind())
sp.SetLabels(labels...)
}
DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("Sub from %s", msg.Topic()))
var labels []tracer.Label
sp.SetName(fmt.Sprintf("Subscriber %s", msg.Topic()))
var labels []interface{}
if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
labels = append(labels, "kind", sp.Kind())
sp.SetLabels(labels...)
}
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
var labels []tracer.Label
sp.SetName(fmt.Sprintf("Call %s.%s", req.Service(), req.Method()))
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = make([]tracer.Label, 0, len(md))
labels = make([]interface{}, 0, len(md))
for k, v := range md {
labels = append(labels, tracer.LabelString(k, v))
labels = append(labels, k, v)
}
}
if err != nil {
labels = append(labels, tracer.LabelBool("error", true))
labels = append(labels, "error", err.Error())
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
labels = append(labels, "kind", sp.Kind())
sp.SetLabels(labels...)
}
DefaultSkipEndpoints = []string{"Meter.Metrics"}
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
type tWrapper struct {
@@ -231,7 +243,7 @@ func (ot *tWrapper) Call(ctx context.Context, req client.Request, rsp interface{
sp, ok := tracer.SpanFromContext(ctx)
if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, endpoint)
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindClient))
}
defer sp.Finish()
@@ -254,7 +266,7 @@ func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...clie
sp, ok := tracer.SpanFromContext(ctx)
if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, endpoint)
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindClient))
}
defer sp.Finish()
@@ -270,7 +282,7 @@ func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...clie
func (ot *tWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
sp, ok := tracer.SpanFromContext(ctx)
if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, msg.Topic())
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindProducer))
}
defer sp.Finish()
@@ -284,7 +296,7 @@ func (ot *tWrapper) Publish(ctx context.Context, msg client.Message, opts ...cli
}
func (ot *tWrapper) ServerHandler(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := req.Endpoint()
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Method())
for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint {
return ot.serverHandler(ctx, req, rsp)
@@ -293,7 +305,7 @@ func (ot *tWrapper) ServerHandler(ctx context.Context, req server.Request, rsp i
sp, ok := tracer.SpanFromContext(ctx)
if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, fmt.Sprintf("%s.%s", req.Service(), req.Endpoint()))
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindServer))
}
defer sp.Finish()
@@ -309,7 +321,7 @@ func (ot *tWrapper) ServerHandler(ctx context.Context, req server.Request, rsp i
func (ot *tWrapper) ServerSubscriber(ctx context.Context, msg server.Message) error {
sp, ok := tracer.SpanFromContext(ctx)
if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, msg.Topic())
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindConsumer))
}
defer sp.Finish()
@@ -347,7 +359,7 @@ func NewClientCallWrapper(opts ...Option) client.CallWrapper {
}
func (ot *tWrapper) ClientCallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Method())
for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint {
return ot.ClientCallFunc(ctx, addr, req, rsp, opts)
@@ -356,7 +368,7 @@ func (ot *tWrapper) ClientCallFunc(ctx context.Context, addr string, req client.
sp, ok := tracer.SpanFromContext(ctx)
if !ok {
ctx, sp = ot.opts.Tracer.Start(ctx, endpoint)
ctx, sp = ot.opts.Tracer.Start(ctx, "", tracer.WithSpanKind(tracer.SpanKindClient))
}
defer sp.Finish()

View File

@@ -1,87 +0,0 @@
package auth // import "go.unistack.org/micro/v3/util/auth"
import (
"context"
"time"
"go.unistack.org/micro/v3/auth"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/util/id"
)
// Verify the auth credentials and refresh the auth token periodically
func Verify(a auth.Auth) error {
// extract the account creds from options, these can be set by flags
accID := a.Options().ID
accSecret := a.Options().Secret
// if no credentials were provided, self generate an account
if len(accID) == 0 && len(accSecret) == 0 {
opts := []auth.GenerateOption{
auth.WithType("service"),
auth.WithScopes("service"),
}
id, err := id.New()
if err != nil {
return err
}
acc, err := a.Generate(id, opts...)
if err != nil {
return err
}
if logger.V(logger.DebugLevel) {
logger.Debug(context.TODO(), "Auth [%v] Generated an auth account: %s", a.String())
}
accID = acc.ID
accSecret = acc.Secret
}
// generate the first token
token, err := a.Token(
auth.WithCredentials(accID, accSecret),
auth.WithExpiry(time.Minute*10),
)
if err != nil {
return err
}
// set the credentials and token in auth options
_ = a.Init(
auth.ClientToken(token),
auth.Credentials(accID, accSecret),
)
// periodically check to see if the token needs refreshing
go func() {
timer := time.NewTicker(time.Second * 15)
for {
<-timer.C
// don't refresh the token if it's not close to expiring
tok := a.Options().Token
if tok.Expiry.Unix() > time.Now().Add(time.Minute).Unix() {
continue
}
// generate the first token
tok, err := a.Token(
auth.WithToken(tok.RefreshToken),
auth.WithExpiry(time.Minute*10),
)
if err != nil {
if logger.V(logger.WarnLevel) {
logger.Warn(context.TODO(), "[Auth] Error refreshing token: %v", err)
}
continue
}
// set the token
_ = a.Init(auth.ClientToken(tok))
}
}()
return nil
}

View File

@@ -1,26 +0,0 @@
package ctx // import "go.unistack.org/micro/v3/util/ctx"
import (
"context"
"net/http"
"strings"
"go.unistack.org/micro/v3/metadata"
)
// FromRequest creates context with metadata from http.Request
func FromRequest(r *http.Request) context.Context {
ctx := r.Context()
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.New(len(r.Header) + 2)
}
for key, val := range r.Header {
md.Set(key, strings.Join(val, ","))
}
// pass http host
md["Host"] = r.Host
// pass http method
md["Method"] = r.Method
return metadata.NewIncomingContext(ctx, md)
}

View File

@@ -1,41 +0,0 @@
package ctx
import (
"net/http"
"testing"
"go.unistack.org/micro/v3/metadata"
)
func TestRequestToContext(t *testing.T) {
testData := []struct {
request *http.Request
expect metadata.Metadata
}{
{
&http.Request{
Header: http.Header{
"Foo1": []string{"bar"},
"Foo2": []string{"bar", "baz"},
},
},
metadata.Metadata{
"Foo1": "bar",
"Foo2": "bar,baz",
},
},
}
for _, d := range testData {
ctx := FromRequest(d.request)
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
t.Fatalf("Expected metadata for request %+v", d.request)
}
for k, v := range d.expect {
if val := md[k]; val != v {
t.Fatalf("Expected %s for key %s for expected md %+v, got md %+v", v, k, d.expect, md)
}
}
}
}

View File

@@ -7,6 +7,7 @@ package http
// Modified by Unistack LLC to support interface{} type handler and parameters in map[string]string
import (
"errors"
"fmt"
"net/http"
"regexp"
@@ -15,6 +16,11 @@ import (
"strings"
)
var (
ErrNotFound = errors.New("route not found")
ErrMethodNotAllowed = errors.New("method not allowed")
)
type methodTyp uint
const (
@@ -399,16 +405,19 @@ func (n *Trie) setEndpoint(method methodTyp, handler interface{}, pattern string
}
// Search try to find element in tree with path and method
func (n *Trie) Search(method string, path string) (interface{}, map[string]string, bool) {
func (n *Trie) Search(method string, path string) (interface{}, map[string]string, error) {
params := &routeParams{}
// Find the routing handlers for the path
rn := n.findRoute(params, methodMap[method], path)
if rn == nil {
return nil, nil, false
if rn == nil && !params.methodNotAllowed {
return nil, nil, ErrNotFound
}
if params.methodNotAllowed {
return nil, nil, ErrMethodNotAllowed
}
ep, ok := rn.endpoints[methodMap[method]]
if !ok {
return nil, nil, false
return nil, nil, ErrMethodNotAllowed
}
eparams := make(map[string]string, len(params.keys))
@@ -416,12 +425,13 @@ func (n *Trie) Search(method string, path string) (interface{}, map[string]strin
eparams[key] = params.vals[idx]
}
return ep.handler, eparams, true
return ep.handler, eparams, nil
}
type routeParams struct {
keys []string
vals []string
keys []string
vals []string
methodNotAllowed bool
}
// Recursive edge traversal by checking all nodeTyp groups along the way.
@@ -495,6 +505,7 @@ func (n *Trie) findRoute(params *routeParams, method methodTyp, path string) *Tr
params.keys = append(params.keys, h.paramKeys...)
return xn
}
params.methodNotAllowed = true
}
}
@@ -530,6 +541,7 @@ func (n *Trie) findRoute(params *routeParams, method methodTyp, path string) *Tr
params.keys = append(params.keys, h.paramKeys...)
return xn
}
params.methodNotAllowed = true
}
}

View File

@@ -21,22 +21,22 @@ func TestTrieWildcardPathPrefix(t *testing.T) {
if err = tr.Insert([]string{http.MethodPost}, "/v1/*", &handler{name: "post_create"}); err != nil {
t.Fatal(err)
}
h, _, ok := tr.Search(http.MethodPost, "/v1/test/one")
if !ok {
h, _, err := tr.Search(http.MethodPost, "/v1/test/one")
if err != nil {
t.Fatalf("unexpected error handler not found")
}
if h.(*handler).name != "post_create" {
t.Fatalf("invalid handler %v", h)
}
h, _, ok = tr.Search(http.MethodPost, "/v1/update")
if !ok {
h, _, err = tr.Search(http.MethodPost, "/v1/update")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "post_update" {
t.Fatalf("invalid handler %v", h)
}
h, _, ok = tr.Search(http.MethodPost, "/v1/update/some/{x}")
if !ok {
h, _, err = tr.Search(http.MethodPost, "/v1/update/some/{x}")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "post_create" {
@@ -52,8 +52,8 @@ func TestTriePathPrefix(t *testing.T) {
_ = tr.Insert([]string{http.MethodPost}, "/v1/create/{id}", &handler{name: "post_create"})
_ = tr.Insert([]string{http.MethodPost}, "/v1/update/{id}", &handler{name: "post_update"})
_ = tr.Insert([]string{http.MethodPost}, "/", &handler{name: "post_wildcard"})
h, _, ok := tr.Search(http.MethodPost, "/")
if !ok {
h, _, err := tr.Search(http.MethodPost, "/")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "post_wildcard" {
@@ -68,8 +68,8 @@ func TestTrieFixedPattern(t *testing.T) {
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", &handler{name: "pattern"})
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/12", &handler{name: "fixed"})
h, _, ok := tr.Search(http.MethodPut, "/v1/create/12")
if !ok {
h, _, err := tr.Search(http.MethodPut, "/v1/create/12")
if err != nil {
t.Fatalf("unexpected error")
}
if h.(*handler).name != "fixed" {
@@ -80,8 +80,8 @@ func TestTrieFixedPattern(t *testing.T) {
func TestTrieNoMatchMethod(t *testing.T) {
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", nil)
_, _, ok := tr.Search(http.MethodPost, "/v1/create")
if ok {
_, _, err := tr.Search(http.MethodPost, "/v1/create")
if err == nil && err != ErrNotFound {
t.Fatalf("must be not found error")
}
}
@@ -90,9 +90,9 @@ func TestTrieMatchRegexp(t *testing.T) {
type handler struct{}
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{category}/{id:[0-9]+}", &handler{})
_, params, ok := tr.Search(http.MethodPut, "/v1/create/test_cat/12345")
_, params, err := tr.Search(http.MethodPut, "/v1/create/test_cat/12345")
switch {
case !ok:
case err != nil:
t.Fatalf("route not found")
case len(params) != 2:
t.Fatalf("param matching error %v", params)
@@ -105,8 +105,8 @@ func TestTrieMatchRegexpFail(t *testing.T) {
type handler struct{}
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id:[a-z]+}", &handler{})
_, _, ok := tr.Search(http.MethodPut, "/v1/create/12345")
if ok {
_, _, err := tr.Search(http.MethodPut, "/v1/create/12345")
if err != ErrNotFound {
t.Fatalf("route must not be not found")
}
}
@@ -118,14 +118,28 @@ func TestTrieMatchLongest(t *testing.T) {
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create", &handler{name: "first"})
_ = tr.Insert([]string{http.MethodPut}, "/v1/create/{id:[0-9]+}", &handler{name: "second"})
if h, _, ok := tr.Search(http.MethodPut, "/v1/create/12345"); !ok {
if h, _, err := tr.Search(http.MethodPut, "/v1/create/12345"); err != nil {
t.Fatalf("route must be found")
} else if h.(*handler).name != "second" {
t.Fatalf("invalid handler found: %s != %s", h.(*handler).name, "second")
}
if h, _, ok := tr.Search(http.MethodPut, "/v1/create"); !ok {
if h, _, err := tr.Search(http.MethodPut, "/v1/create"); err != nil {
t.Fatalf("route must be found")
} else if h.(*handler).name != "first" {
t.Fatalf("invalid handler found: %s != %s", h.(*handler).name, "first")
}
}
func TestMethodNotAllowed(t *testing.T) {
type handler struct{}
tr := NewTrie()
_ = tr.Insert([]string{http.MethodPut}, "/v1/create", &handler{})
_, _, err := tr.Search(http.MethodPost, "/v1/create")
if err != ErrMethodNotAllowed {
t.Fatalf("route must be method not allowed: %v", err)
}
_, _, err = tr.Search(http.MethodPut, "/v1/create")
if err != nil {
t.Fatalf("route must be found: %v", err)
}
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Jon Calhoun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,55 +0,0 @@
# qson
This is copy from https://github.com/joncalhoun/qson
As author says he is not acrivelly maintains the repo and not plan to do that.
## Usage
You can either turn a URL query param into a JSON byte array, or unmarshal that directly into a Go object.
Transforming the URL query param into a JSON byte array:
```go
import "github.com/joncalhoun/qson"
func main() {
b, err := qson.ToJSON("bar%5Bone%5D%5Btwo%5D=2&bar[one][red]=112")
if err != nil {
panic(err)
}
fmt.Println(string(b))
// Should output: {"bar":{"one":{"red":112,"two":2}}}
}
```
Or unmarshalling directly into a Go object using JSON struct tags:
```go
import "github.com/joncalhoun/qson"
type unmarshalT struct {
A string `json:"a"`
B unmarshalB `json:"b"`
}
type unmarshalB struct {
C int `json:"c"`
}
func main() {
var out unmarshalT
query := "a=xyz&b[c]=456"
err := Unmarshal(&out, query)
if err != nil {
t.Error(err)
}
// out should equal
// unmarshalT{
// A: "xyz",
// B: unmarshalB{
// C: 456,
// },
// }
}
```
To get a query string like in the two previous examples you can use the `RawQuery` field on the [net/url.URL](https://golang.org/pkg/net/url/#URL) type.

View File

@@ -1,34 +0,0 @@
package qson
// merge merges a with b if they are either both slices
// or map[string]interface{} types. Otherwise it returns b.
func merge(a interface{}, b interface{}) interface{} {
switch aT := a.(type) {
case map[string]interface{}:
return mergeMap(aT, b.(map[string]interface{}))
case []interface{}:
return mergeSlice(aT, b.([]interface{}))
default:
return b
}
}
// mergeMap merges a with b, attempting to merge any nested
// values in nested maps but eventually overwriting anything
// in a that can't be merged with whatever is in b.
func mergeMap(a map[string]interface{}, b map[string]interface{}) map[string]interface{} {
for bK, bV := range b {
if _, ok := a[bK]; ok {
a[bK] = merge(a[bK], bV)
} else {
a[bK] = bV
}
}
return a
}
// mergeSlice merges a with b and returns the result.
func mergeSlice(a []interface{}, b []interface{}) []interface{} {
a = append(a, b...)
return a
}

View File

@@ -1,37 +0,0 @@
package qson
import "testing"
func TestMergeSlice(t *testing.T) {
a := []interface{}{"a"}
b := []interface{}{"b"}
actual := mergeSlice(a, b)
if len(actual) != 2 {
t.Errorf("Expected size to be 2.")
}
if actual[0] != "a" {
t.Errorf("Expected index 0 to have value a. Actual: %s", actual[0])
}
if actual[1] != "b" {
t.Errorf("Expected index 1 to have value b. Actual: %s", actual[1])
}
}
func TestMergeMap(t *testing.T) {
a := map[string]interface{}{
"a": "b",
}
b := map[string]interface{}{
"b": "c",
}
actual := mergeMap(a, b)
if len(actual) != 2 {
t.Errorf("Expected size to be 2.")
}
if actual["a"] != "b" {
t.Errorf("Expected key \"a\" to have value b. Actual: %s", actual["a"])
}
if actual["b"] != "c" {
t.Errorf("Expected key \"b\" to have value c. Actual: %s", actual["b"])
}
}

View File

@@ -1,166 +0,0 @@
// Package qson implmenets decoding of URL query params
// into JSON and Go values (using JSON struct tags).
//
// See https://golang.org/pkg/encoding/json/ for more
// details on JSON struct tags.
package qson // import "go.unistack.org/micro/v3/util/qson"
import (
"encoding/json"
"errors"
"net/url"
"regexp"
"strings"
)
var (
// ErrInvalidParam is returned when invalid data is provided to the ToJSON or Unmarshal function.
// Specifically, this will be returned when there is no equals sign present in the URL query parameter.
ErrInvalidParam = errors.New("qson: invalid url query param provided")
bracketSplitter *regexp.Regexp
)
func init() {
bracketSplitter = regexp.MustCompile(`\[|\]`)
}
func btSplitter(str string) []string {
r := bracketSplitter.Split(str, -1)
for idx, s := range r {
if len(s) == 0 {
if len(r) > idx+1 {
copy(r[idx:], r[idx+1:])
r = r[:len(r)-1]
}
}
}
return r
}
// Unmarshal will take a dest along with URL
// query params and attempt to first turn the query params
// into JSON and then unmarshal those into the dest variable
//
// BUG(joncalhoun): If a URL query param value is something
// like 123 but is expected to be parsed into a string this
// will currently result in an error because the JSON
// transformation will assume this is intended to be an int.
// This should only affect the Unmarshal function and
// could likely be fixed, but someone will need to submit a
// PR if they want that fixed.
func Unmarshal(dst interface{}, query string) error {
b, err := ToJSON(query)
if err != nil {
return err
}
return json.Unmarshal(b, dst)
}
// ToJSON will turn a query string like:
// cat=1&bar%5Bone%5D%5Btwo%5D=2&bar[one][red]=112
// Into a JSON object with all the data merged as nicely as
// possible. Eg the example above would output:
// {"bar":{"one":{"two":2,"red":112}}}
func ToJSON(query string) ([]byte, error) {
var builder interface{} = make(map[string]interface{})
params := strings.Split(query, "&")
for _, part := range params {
tempMap, err := queryToMap(part)
if err != nil {
return nil, err
}
builder = merge(builder, tempMap)
}
return json.Marshal(builder)
}
// queryToMap turns something like a[b][c]=4 into
// map[string]interface{}{
// "a": map[string]interface{}{
// "b": map[string]interface{}{
// "c": 4,
// },
// },
// }
func queryToMap(param string) (map[string]interface{}, error) {
rawKey, rawValue, err := splitKeyAndValue(param)
if err != nil {
return nil, err
}
rawValue, err = url.QueryUnescape(rawValue)
if err != nil {
return nil, err
}
rawKey, err = url.QueryUnescape(rawKey)
if err != nil {
return nil, err
}
pieces := btSplitter(rawKey)
key := pieces[0]
// If len==1 then rawKey has no [] chars and we can just
// decode this as key=value into {key: value}
if len(pieces) == 1 {
var value interface{}
// First we try parsing it as an int, bool, null, etc
err = json.Unmarshal([]byte(rawValue), &value)
if err != nil {
// If we got an error we try wrapping the value in
// quotes and processing it as a string
err = json.Unmarshal([]byte("\""+rawValue+"\""), &value)
if err != nil {
// If we can't decode as a string we return the err
return nil, err
}
}
return map[string]interface{}{
key: value,
}, nil
}
// If len > 1 then we have something like a[b][c]=2
// so we need to turn this into {"a": {"b": {"c": 2}}}
// To do this we break our key into two pieces:
// a and b[c]
// and then we set {"a": queryToMap("b[c]", value)}
ret := make(map[string]interface{})
ret[key], err = queryToMap(buildNewKey(rawKey) + "=" + rawValue)
if err != nil {
return nil, err
}
// When URL params have a set of empty brackets (eg a[]=1)
// it is assumed to be an array. This will get us the
// correct value for the array item and return it as an
// []interface{} so that it can be merged properly.
if pieces[1] == "" {
temp := ret[key].(map[string]interface{})
ret[key] = []interface{}{temp[""]}
}
return ret, nil
}
// buildNewKey will take something like:
// origKey = "bar[one][two]"
// pieces = [bar one two ]
// and return "one[two]"
func buildNewKey(origKey string) string {
pieces := btSplitter(origKey)
ret := origKey[len(pieces[0])+1:]
ret = ret[:len(pieces[1])] + ret[len(pieces[1])+1:]
return ret
}
// splitKeyAndValue splits a URL param at the last equal
// sign and returns the two strings. If no equal sign is
// found, the ErrInvalidParam error is returned.
func splitKeyAndValue(param string) (string, string, error) {
li := strings.LastIndex(param, "=")
if li == -1 {
return "", "", ErrInvalidParam
}
return param[:li], param[li+1:], nil
}

View File

@@ -1,179 +0,0 @@
package qson
import (
"testing"
)
func TestFuzz1(t *testing.T) {
b, err := ToJSON(`[^@hGl5=`)
if err != nil {
t.Fatal(err)
}
_ = b
}
func ExampleUnmarshal() {
type Ex struct {
A string `json:"a"`
B struct {
C int `json:"c"`
} `json:"b"`
}
var ex Ex
if err := Unmarshal(&ex, "a=xyz&b[c]=456"); err != nil {
panic(err)
}
_ = ex
// fmt.Printf("%+v\n", ex)
// Output: {A:xyz B:{C:456}}
}
type unmarshalT struct {
A string `json:"a"`
B unmarshalB `json:"b"`
}
type unmarshalB struct {
D string `json:"D"`
C int `json:"c"`
}
func TestUnmarshal(t *testing.T) {
query := "a=xyz&b[c]=456"
expected := unmarshalT{
A: "xyz",
B: unmarshalB{
C: 456,
},
}
var actual unmarshalT
err := Unmarshal(&actual, query)
if err != nil {
t.Error(err)
}
if expected != actual {
t.Errorf("Expected: %+v Actual: %+v", expected, actual)
}
}
func ExampleToJSON() {
_, err := ToJSON("a=xyz&b[c]=456")
if err != nil {
panic(err)
}
// fmt.Printf(string(b))
// Output: {"a":"xyz","b":{"c":456}}
}
func TestToJSONNested(t *testing.T) {
query := "bar%5Bone%5D%5Btwo%5D=2&bar[one][red]=112"
expected := `{"bar":{"one":{"red":112,"two":2}}}`
actual, err := ToJSON(query)
if err != nil {
t.Error(err)
}
actualStr := string(actual)
if actualStr != expected {
t.Errorf("Expected: %s Actual: %s", expected, actualStr)
}
}
func TestToJSONPlain(t *testing.T) {
query := "cat=1&dog=2"
expected := `{"cat":1,"dog":2}`
actual, err := ToJSON(query)
if err != nil {
t.Error(err)
}
actualStr := string(actual)
if actualStr != expected {
t.Errorf("Expected: %s Actual: %s", expected, actualStr)
}
}
func TestToJSONSlice(t *testing.T) {
query := "cat[]=1&cat[]=34"
expected := `{"cat":[1,34]}`
actual, err := ToJSON(query)
if err != nil {
t.Error(err)
}
actualStr := string(actual)
if actualStr != expected {
t.Errorf("Expected: %s Actual: %s", expected, actualStr)
}
}
func TestToJSONBig(t *testing.T) {
query := "distinct_id=763_1495187301909_3495&timestamp=1495187523&event=product_add_cart&params%5BproductRefId%5D=8284563078&params%5Bapps%5D%5B%5D=precommend&params%5Bapps%5D%5B%5D=bsales&params%5Bsource%5D=item&params%5Boptions%5D%5Bsegment%5D=cart_recommendation&params%5Boptions%5D%5Btype%5D=up_sell&params%5BtimeExpire%5D=1495187599642&params%5Brecommend_system_product_source%5D=item&params%5Bproduct_id%5D=8284563078&params%5Bvariant_id%5D=27661944134&params%5Bsku%5D=00483332%20(black)&params%5Bsources%5D%5B%5D=product_recommendation&params%5Bcart_token%5D=dc2c336a009edf2762128e65806dfb1d&params%5Bquantity%5D=1&params%5Bnew_popup_upsell_mobile%5D=false&params%5BclientDevice%5D=desktop&params%5BclientIsMobile%5D=false&params%5BclientIsSmallScreen%5D=false&params%5Bnew_popup_crossell_mobile%5D=false&api_key=14c5b7dacea9157029265b174491d340"
expected := `{"api_key":"14c5b7dacea9157029265b174491d340","distinct_id":"763_1495187301909_3495","event":"product_add_cart","params":{"apps":["precommend","bsales"],"cart_token":"dc2c336a009edf2762128e65806dfb1d","clientDevice":"desktop","clientIsMobile":false,"clientIsSmallScreen":false,"new_popup_crossell_mobile":false,"new_popup_upsell_mobile":false,"options":{"segment":"cart_recommendation","type":"up_sell"},"productRefId":8284563078,"product_id":8284563078,"quantity":1,"recommend_system_product_source":"item","sku":"00483332 (black)","source":"item","sources":["product_recommendation"],"timeExpire":1495187599642,"variant_id":27661944134},"timestamp":1495187523}`
actual, err := ToJSON(query)
if err != nil {
t.Error(err)
}
actualStr := string(actual)
if actualStr != expected {
t.Errorf("Expected: %s Actual: %s", expected, actualStr)
}
}
func TestToJSONDuplicateKey(t *testing.T) {
query := "cat=1&cat=2"
expected := `{"cat":2}`
actual, err := ToJSON(query)
if err != nil {
t.Error(err)
}
actualStr := string(actual)
if actualStr != expected {
t.Errorf("Expected: %s Actual: %s", expected, actualStr)
}
}
func TestSplitKeyAndValue(t *testing.T) {
param := "a[dog][=cat]=123"
eKey, eValue := "a[dog][=cat]", "123"
aKey, aValue, err := splitKeyAndValue(param)
if err != nil {
t.Error(err)
}
if eKey != aKey {
t.Errorf("Keys do not match. Expected: %s Actual: %s", eKey, aKey)
}
if eValue != aValue {
t.Errorf("Values do not match. Expected: %s Actual: %s", eValue, aValue)
}
}
func TestEncodedAmpersand(t *testing.T) {
query := "a=xyz&b[d]=ben%26jerry"
expected := unmarshalT{
A: "xyz",
B: unmarshalB{
D: "ben&jerry",
},
}
var actual unmarshalT
err := Unmarshal(&actual, query)
if err != nil {
t.Error(err)
}
if expected != actual {
t.Errorf("Expected: %+v Actual: %+v", expected, actual)
}
}
func TestEncodedAmpersand2(t *testing.T) {
query := "filter=parent%3Dflow12345%26request%3Dreq12345&meta.limit=20&meta.offset=0"
expected := map[string]interface{}{"filter": "parent=flow12345&request=req12345", "meta.limit": float64(20), "meta.offset": float64(0)}
actual := make(map[string]interface{})
err := Unmarshal(&actual, query)
if err != nil {
t.Error(err)
}
for k, v := range actual {
if nv, ok := expected[k]; !ok || nv != v {
t.Errorf("Expected: %+v Actual: %+v", expected, actual)
}
}
}

View File

@@ -1,64 +0,0 @@
package sync
import (
"fmt"
"time"
"go.unistack.org/micro/v3/store"
)
func (c *syncStore) syncManager() {
tickerAggregator := make(chan struct{ index int })
for i, ticker := range c.pendingWriteTickers {
go func(index int, c chan struct{ index int }, t *time.Ticker) {
for range t.C {
c <- struct{ index int }{index: index}
}
}(i, tickerAggregator, ticker)
}
for i := range tickerAggregator {
println(i.index, "ticked")
c.processQueue(i.index)
}
}
func (c *syncStore) processQueue(index int) {
c.Lock()
defer c.Unlock()
q := c.pendingWrites[index]
for i := 0; i < q.Len(); i++ {
r, ok := q.PopFront()
if !ok {
panic(fmt.Errorf("retrieved an invalid value from the L%d sync queue", index+1))
}
ir, ok := r.(*internalRecord)
if !ok {
panic(fmt.Errorf("retrieved a non-internal record from the L%d sync queue", index+1))
}
if !ir.expiresAt.IsZero() && time.Now().After(ir.expiresAt) {
continue
}
var opts []store.WriteOption
if !ir.expiresAt.IsZero() {
opts = append(opts, store.WriteTTL(time.Until(ir.expiresAt)))
}
// Todo = internal queue also has to hold the corresponding store.WriteOptions
if err := c.syncOpts.Stores[index+1].Write(c.storeOpts.Context, ir.key, ir.value, opts...); err != nil {
// some error, so queue for retry and bail
q.PushBack(ir)
return
}
}
}
func intpow(x, y int64) int64 {
result := int64(1)
for 0 != y {
if 0 != (y & 1) {
result *= x
}
y >>= 1
x *= x
}
return result
}

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