Compare commits

..

486 Commits

Author SHA1 Message Date
d0f9d44fe4 Coverage default.go (#330)
Some checks failed
Go / test (push) Failing after 11s
/ autoupdate (push) Successful in 58s
Coverage default.go

Added tests for default.go. The tests are successful

Co-authored-by: Aleksandr Tolstikhin <atolstikhin@mtsbank.ru>
Co-authored-by: Василий Толстов <v.tolstov@unistack.org>
Reviewed-on: #330
Reviewed-by: Василий Толстов <v.tolstov@unistack.org>
Co-authored-by: Александр Толстихин <tolstihin1996@mail.ru>
Co-committed-by: Александр Толстихин <tolstihin1996@mail.ru>
2024-09-20 17:55:24 +03:00
725ed992cc #335 caller skip count. (#338)
Some checks failed
Go / test (push) Has been cancelled
/ autoupdate (push) Failing after 5s
Co-authored-by: Gorbunov Kirill Andreevich <kgorbunov@mtsbank.ru>
Reviewed-on: #338
Co-authored-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
Co-committed-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
2024-04-15 13:31:14 +03:00
b8928d3da9 semconv: add cache metric names
Some checks failed
Go / test (push) Failing after 6m10s
/ autoupdate (push) Successful in 1m20s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-14 16:46:05 +03:00
76090f7569 util/xpool: package pool
Some checks failed
Go / test (push) Failing after 6m9s
/ autoupdate (push) Successful in 1m18s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-14 00:19:08 +03:00
f8c68a81f7 semconv: add broker group lag
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-13 02:11:05 +03:00
5d997f7654 Merge pull request 'add options in broker' (#333) from devstigneev/micro:master into master
Some checks failed
Go / test (push) Failing after 6m14s
/ autoupdate (push) Successful in 1m20s
Reviewed-on: #333
2024-04-08 23:17:49 +03:00
56d33ae823 rename path to sync
Some checks failed
Go / test (pull_request) Has been cancelled
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-04-07 21:17:42 +03:00
c3cabc1fe5 add options in broker
Some checks failed
Go / test (pull_request) Has been cancelled
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-04-07 20:49:07 +03:00
47497b49b3 util/reflect: detect json.Unmarshaler
Some checks failed
Go / test (push) Failing after 6m45s
/ autoupdate (push) Successful in 1m21s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-04-02 08:54:12 +03:00
e89cfdc80d move sort.Uniq to dedicated package
Some checks failed
/ autoupdate (push) Has been cancelled
Go / test (push) Has been cancelled
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-27 11:18:16 +03:00
f954afb236 logger: fixup Debugf => Debug
Some checks failed
/ autoupdate (push) Has been cancelled
Go / test (push) Has been cancelled
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-25 21:16:24 +03:00
fbfacc82cd update type for WaitGroup in server (#324)
Some checks failed
Go / test (push) Failing after 6m5s
/ autoupdate (push) Failing after 1m2s
## Pull Request template
Please, go through these steps before clicking submit on this PR.

1. Give a descriptive title to your PR.
2. Provide a description of your changes.
3. Make sure you have some relevant tests.
4. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable).

**PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING**

Reviewed-on: #324
Co-authored-by: Evstigneev Denis <danteevstigneev@yandex.ru>
Co-committed-by: Evstigneev Denis <danteevstigneev@yandex.ru>
2024-03-12 23:13:55 +03:00
3d6df76dd5 tracer: add Context init to NewOptions
Some checks failed
Go / test (push) Failing after 6m14s
/ autoupdate (push) Failing after 1m5s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-11 01:14:30 +03:00
2afcb51440 update workflow
Some checks failed
Go / test (push) Failing after 7m59s
/ autoupdate (push) Failing after 59s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-10 22:59:12 +03:00
eb534a46d0 metadata: add exclude to Copy func
Some checks failed
/ autoupdate (push) Failing after 1m2s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-09 23:53:04 +03:00
856aac4aa9 sync/waitgroup: initial sync.WaitGroup wrapper with context support (#319)
Some checks failed
/ autoupdate (push) Failing after 1m12s
Reviewed-on: #319
Co-authored-by: Vasiliy Tolstov <v.tolstov@unistack.org>
Co-committed-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-09 23:35:13 +03:00
e416a5b38c Merge pull request 'rework metadata' (#318) from metav4 into master
Some checks failed
/ autoupdate (push) Failing after 1m40s
Reviewed-on: #318
2024-03-09 17:24:02 +03:00
2f8c0d9f9d rework metadata
All checks were successful
pr / test (pull_request) Successful in 2m10s
lint / lint (pull_request) Successful in 10m35s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-09 17:21:20 +03:00
4b261083d6 fix races
Some checks failed
/ autoupdate (push) Failing after 59s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-07 08:57:45 +03:00
4c3c4859fc logger: add TimeFunc option
Some checks failed
/ autoupdate (push) Failing after 1m3s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-07 00:08:34 +03:00
7ef5c5d804 backport from v3
Some checks failed
/ autoupdate (push) Failing after 1m3s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-06 01:17:19 +03:00
1cbab38d24 tracer and logger improvements
Some checks failed
/ autoupdate (push) Failing after 1m2s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-05 01:27:22 +03:00
c766477aaa backport from v3
Some checks failed
/ autoupdate (push) Failing after 1m2s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-05 00:03:37 +03:00
f28f8e13b3 logger/slog: add stacktrace support
Some checks failed
/ autoupdate (push) Failing after 1m22s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 23:42:15 +03:00
1cbc353479 logger: fixes
Some checks failed
/ autoupdate (push) Failing after 1m16s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 22:22:05 +03:00
7050313bc1 Merge pull request 'issue_294' (#301) from devstigneev/micro:issue_294 into master
Some checks failed
/ autoupdate (push) Failing after 1m3s
Reviewed-on: #301
2024-02-29 23:37:51 +03:00
3654961fde Merge pull request 'Обновить server/options.go' (#305) from vtolstov-patch-1 into master
Some checks failed
/ autoupdate (push) Failing after 2m27s
Reviewed-on: #305
2024-02-29 23:27:56 +03:00
fa2b7c924e Обновить server/options.go
Some checks failed
pr / test (pull_request) Failing after 3m15s
lint / lint (pull_request) Successful in 11m33s
Signed-off-by: Василий Толстов <v.tolstov@unistack.org>
2024-02-29 23:27:18 +03:00
de83f42149 Merge pull request 'add gracefultimeout in server' (#303) from devstigneev/micro:issue_293 into master
Some checks failed
/ autoupdate (push) Failing after 1m5s
Reviewed-on: #303
2024-02-29 22:31:49 +03:00
17ace07b1d skip test service register memory
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-29 18:43:25 +03:00
e212e3dc50 move test
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-29 18:02:12 +03:00
bf0c3016cb add gracefultimeout in server
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-29 13:28:27 +03:00
4124b49481 Merge branch 'master' into issue_294
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-29 13:07:26 +03:00
a0348a2664 implements register noop
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-29 13:06:21 +03:00
f06e0b21e8 add variadic (#302)
Some checks failed
/ autoupdate (push) Failing after 1m0s
Co-authored-by: Vasiliy Tolstov <v.tolstov@unistack.org>
Reviewed-on: #302
Co-authored-by: Evstigneev Denis <danteevstigneev@yandex.ru>
Co-committed-by: Evstigneev Denis <danteevstigneev@yandex.ru>
2024-02-29 01:27:33 +03:00
3be0566550 Merge pull request 'issue_296' (#300) from devstigneev/micro:issue_296 into master
Some checks failed
/ autoupdate (push) Failing after 1m21s
Reviewed-on: #300
2024-02-28 23:52:30 +03:00
249a64db74 add TODO
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-28 23:51:25 +03:00
a428061bf3 fixed changex, have cycle imports 2024-02-28 23:51:05 +03:00
e6feca2fb1 fixup
Some checks failed
pr / test (pull_request) Failing after 2m44s
lint / lint (pull_request) Successful in 11m1s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-28 23:50:38 +03:00
37fa3d6696 Merge remote-tracking branch 'origin/issue_296' into issue_296
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
# Conflicts:
#	options/options.go
2024-02-28 17:02:17 +03:00
47940a5ca2 add option metadata 2024-02-28 17:01:59 +03:00
d667bbee0c add option metadata 2024-02-28 15:06:29 +03:00
96dd5d869a Обновить .gitea/workflows/pr.yml
Some checks failed
/ autoupdate (push) Failing after 1m3s
2024-02-28 00:24:02 +03:00
ea90948315 Обновить .gitea/workflows/autoupdate.yml
Some checks failed
/ autoupdate (push) Failing after 1m9s
2024-02-28 00:17:09 +03:00
a382ea7d45 Merge pull request '#97 add As for Broker.' (#298) from kgorbunov/micro:#97 into master
Some checks failed
/ autoupdate (push) Failing after 1m0s
Reviewed-on: #298
2024-02-27 22:49:03 +03:00
Gorbunov Kirill Andreevich
cdfeaa7e20 #97 add As for all interface.
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-27 22:36:18 +03:00
Gorbunov Kirill Andreevich
c09674ae92 #97 add As for all interface.
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-27 22:11:39 +03:00
Gorbunov Kirill Andreevich
0d497ca0df #97 add As for Broker.
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-27 21:05:42 +03:00
bb0c415a77 Merge pull request 'config: add conditions' (#287) from cond-config into master
Some checks failed
/ autoupdate (push) Failing after 1m4s
Reviewed-on: #287
2024-01-15 00:51:30 +03:00
8182cb008a config: add conditions
Some checks failed
lint / lint (pull_request) Successful in 1m27s
pr / test (pull_request) Failing after 1m13s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-01-15 00:49:27 +03:00
0771fa0647 fixup multiple client handling (#281)
Some checks failed
/ autoupdate (push) Failing after 1m28s
Reviewed-on: #281
2023-11-13 08:23:22 +03:00
b1fd82adf8 Merge pull request 'database: add FormatDSN' (#277) from database-new into master
Some checks failed
/ autoupdate (push) Failing after 1m26s
Reviewed-on: #277
2023-11-02 01:35:12 +03:00
4bc0e25017 database: add FormatDSN
Some checks failed
lint / lint (pull_request) Failing after 1m28s
pr / test (pull_request) Failing after 2m37s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-02 01:31:26 +03:00
aec552aa0b Merge pull request 'database: initial import for dsn parsing' (#275) from database into master
Some checks failed
/ autoupdate (push) Failing after 1m28s
Reviewed-on: #275
2023-11-01 23:44:01 +03:00
cc81bed81d cleanup
Some checks failed
lint / lint (pull_request) Failing after 1m29s
pr / test (pull_request) Failing after 2m37s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-01 23:42:07 +03:00
7fde39fba5 database: initial import for dsn parsing
Some checks failed
lint / lint (pull_request) Failing after 1m29s
pr / test (pull_request) Failing after 2m40s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-01 23:40:39 +03:00
79438f11e0 logger: slog break import cycle
All checks were successful
/ autoupdate (push) Successful in 1m13s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-17 01:48:50 +03:00
8d19abfebd small fixes
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-17 01:11:35 +03:00
77f3731329 change dep
Some checks are pending
/ autoupdate (push) Waiting to run
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-17 01:10:52 +03:00
dbcf6bb74a logger: use key names from opts (#271)
All checks were successful
/ autoupdate (push) Successful in 1m14s
Reviewed-on: #271
2023-10-17 01:05:55 +03:00
17698440ed Merge pull request 'logger: improvements' (#270) from logtrace into master
All checks were successful
/ autoupdate (push) Successful in 1m13s
Reviewed-on: #270
2023-10-17 01:00:20 +03:00
eb8851ab58 logger: improvements
Some checks failed
lint / lint (pull_request) Successful in 1m8s
pr / test (pull_request) Failing after 1m5s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-17 01:00:00 +03:00
69c6d78920 Merge pull request 'logger: add ability to fill fileds from context' (#269) from logger-slog into master
All checks were successful
/ autoupdate (push) Successful in 1m17s
Reviewed-on: #269
2023-10-16 23:44:23 +03:00
7568779db4 logger: add ability to fill fileds from context
Some checks failed
lint / lint (pull_request) Successful in 1m1s
pr / test (pull_request) Failing after 1m7s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-16 23:43:39 +03:00
5c3fa31ad2 logger: slog improve
All checks were successful
/ autoupdate (push) Successful in 1m16s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-16 19:37:38 +03:00
7b9a4924ad Merge pull request 'logger: slog logger' (#266) from slog into master
All checks were successful
/ autoupdate (push) Successful in 1m12s
Reviewed-on: #266
2023-10-14 18:05:46 +03:00
40939e56a3 fixup
All checks were successful
lint / lint (pull_request) Successful in 1m28s
pr / test (pull_request) Successful in 1m11s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-14 17:23:01 +03:00
0df97183f8 tracer: update wrapper
Some checks failed
lint / lint (pull_request) Successful in 1m13s
pr / test (pull_request) Failing after 59s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-14 16:58:40 +03:00
08c40ff7d5 update workflow
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 13:58:39 +03:00
afc6f88852 update workflow
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 13:49:04 +03:00
dbcc2bc262 update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 12:07:51 +03:00
ccffa5cd72 Merge pull request 'Bump github.com/google/uuid from v1.3.0 to v1.3.1' (#265) from pkgdash/go_modules/github.com/google/uuid-v1.3.1 into master
Some checks are pending
/ autoupdate (push) Has started running
Reviewed-on: #265
2023-10-03 00:39:35 +03:00
gitea-actions
3612f639d6 2023-10-02 21:38:45 +00:00
32d0b13f91 update workflow
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 00:32:44 +03:00
4ad61003b9 update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 00:32:33 +03:00
7647a0d3b3 update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 00:32:07 +03:00
c16961e852 update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 00:24:17 +03:00
968feb931c update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-03 00:15:55 +03:00
911a863313 fixup deps
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-02 02:14:34 +03:00
fcaf323b59 fixup deps
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-02 02:13:17 +03:00
52fecd1772 fixup deps
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-02 01:19:37 +03:00
d5f35fe88a fixup deps
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-02 01:16:31 +03:00
1952053c1b fixup deps
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-02 00:46:29 +03:00
75b45d7313 fixup deps
All checks were successful
/ autoupdate (push) Successful in 1m10s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-02 00:35:21 +03:00
pkgdash
eec350c897 2023-10-01 18:38:57 +00:00
pkgdash
360feb0f41 2023-10-01 18:38:48 +00:00
e1975ab66a fixup deps
Some checks failed
/ autoupdate (push) Failing after 1m12s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-01 21:32:00 +03:00
2504345815 fixup deps
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-01 21:28:47 +03:00
9a95fda93f fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-01 16:55:47 +03:00
7ee309a4ae fixup
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-01 16:46:09 +03:00
a47b6b92c4 fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-01 16:28:24 +03:00
8d46a6498d update
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-30 01:32:47 +03:00
65a1612b51 update
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-30 00:55:27 +03:00
26783b341b update
Some checks failed
/ autoupdate (push) Failing after 37s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-30 00:53:53 +03:00
ec21d4f307 update
Some checks failed
/ autoupdate (push) Failing after 53s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-30 00:51:52 +03:00
a3ca5ebb97 update
Some checks failed
/ autoupdate (push) Failing after 54s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-30 00:49:32 +03:00
f67ee26042 update
Some checks failed
/ autoupdate (push) Failing after 38s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-30 00:47:29 +03:00
6b7bba709e fixup autoupdate
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-30 00:27:56 +03:00
8b082b90df disable workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-27 14:42:42 +03:00
b499fe99ca rename cli
All checks were successful
/ autoupdate (push) Successful in 40s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-27 14:27:21 +03:00
178e6e956d updates
Some checks failed
/ autoupdate (push) Failing after 38s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-23 23:43:16 +03:00
a52db1969b add autoupdate
Some checks failed
/ autoupdate (push) Failing after 36s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-23 13:35:04 +03:00
27bf1c3d84 add autoupdate
All checks were successful
/ autoupdate (push) Successful in 7s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-23 12:54:15 +03:00
58b687f134 add autoupdate
All checks were successful
/ test_schedule (push) Successful in 4s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-23 12:52:02 +03:00
8a354bd468 add autoupdate
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-22 23:31:14 +03:00
f600195ee2 go mod tidy
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2023-09-18 10:59:46 +03:00
fd471a89e2 deleted unnecessary operations
Some checks failed
pr / test (pull_request) Has been cancelled
lint / lint (pull_request) Has been cancelled
2023-09-18 10:59:21 +03:00
c346ac43dd add google/slog logger
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2023-09-16 14:20:02 +03:00
666176138b Merge pull request 'util/reflect: add Equal func with ability to skip some fields' (#243) from util-reflect into master
Reviewed-on: #243
2023-09-12 10:30:48 +03:00
884a96ad32 util/reflect: add Equal func with ability to skip some fields
Some checks failed
lint / lint (pull_request) Successful in 1m18s
pr / test (pull_request) Failing after 1m5s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-12 10:30:19 +03:00
65d715c9da Merge pull request 'tracer: improve' (#242) from tracer into master
Reviewed-on: #242
2023-09-08 14:01:01 +03:00
247c7f6fa2 tracer: improve
Some checks failed
pr / test (pull_request) Failing after 2m43s
lint / lint (pull_request) Failing after 1m28s
2023-09-08 13:58:11 +03:00
f890f1d8b4 tracer: add missing UniqLabels function
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-01 15:48:27 +03:00
54b13bbded Merge pull request 'tracer: improve tracing wrapper' (#240) from trracer into master
Reviewed-on: #240
2023-09-01 15:25:45 +03:00
d3f9078bec tracer: improve tracing wrapper
Some checks failed
lint / lint (pull_request) Failing after 1m28s
pr / test (pull_request) Failing after 2m40s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-01 15:25:25 +03:00
fcc29f9eac tracer: update wrapper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-09-01 08:49:45 +03:00
e724425ce2 Merge pull request 'fix some options' (#237) from fixes into master
Reviewed-on: #237
2023-08-15 07:55:44 +03:00
bc55c2fa6f fix some options
Some checks failed
lint / lint (pull_request) Failing after 1m29s
pr / test (pull_request) Failing after 2m39s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-08-15 07:55:26 +03:00
fdd1589122 Merge pull request 'move options to dedicated package' (#234) from options_rewrite into master
Reviewed-on: #234
2023-07-29 00:47:28 +03:00
6f6f850af6 move options to dedicated package
Some checks failed
lint / lint (pull_request) Failing after 1m31s
pr / test (pull_request) Failing after 2m37s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-07-29 00:40:58 +03:00
b1dbd99ce2 Merge pull request 'config/default: add micro:generate uuid/id' (#231) from config-default-gen into master
Reviewed-on: #231
2023-07-13 20:11:12 +03:00
6e24807edf config/default: add micro:generate uuid/id
Some checks failed
lint / lint (pull_request) Failing after 1m28s
pr / test (pull_request) Failing after 2m43s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-07-13 20:10:47 +03:00
654d8fa7e4 Merge pull request 'tracer: add Flush method' (#224) from tracerimp into master
Reviewed-on: #224
2023-07-04 00:24:09 +03:00
dd1a9cd25a tracer: add Flush method
Some checks failed
lint / lint (pull_request) Failing after 1m29s
pr / test (pull_request) Failing after 2m37s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-07-04 00:23:50 +03:00
d463eb20cb Merge pull request 'util/time: ParseDuration fix' (#221) from timefix into master
Reviewed-on: #221
2023-05-29 13:59:59 +03:00
8d5e25f8cf util/time: ParseDuration fix
Some checks failed
lint / lint (pull_request) Successful in 1m8s
pr / test (pull_request) Failing after 1m6s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-05-29 13:59:30 +03:00
27e8043fed Merge pull request 'util/time: fix duration parsing' (#220) from mastertime into master
Reviewed-on: #220
2023-05-27 23:57:27 +03:00
4e86df1721 util/time: fix duration parsing
Some checks failed
pr / test (pull_request) Failing after 1m6s
lint / lint (pull_request) Successful in 1m10s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-05-27 23:56:51 +03:00
03de1ec38f Merge pull request 'cleanup interfaces for v4' (#217) from v4 into master
Reviewed-on: #217
2023-05-09 20:04:40 +03:00
819ad1117a cleanup interfaces for v4
Some checks failed
lint / lint (pull_request) Successful in 1m9s
pr / test (pull_request) Failing after 1m4s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-05-09 20:04:15 +03:00
a2a383606d Merge pull request 'util/test: update test cases code' (#216) from testcase into master
Reviewed-on: #216
2023-04-28 07:10:40 +03:00
55ce58617b util/test: update test cases code
Some checks failed
lint / lint (pull_request) Successful in 51s
pr / test (pull_request) Failing after 52s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-28 07:10:19 +03:00
0e587d923e Merge pull request 'meter: move metrics handling in broker implementations' (#215) from metrics into master
Reviewed-on: #215
2023-04-27 15:32:56 +03:00
fa0248c80c cleanup
All checks were successful
pr / test (pull_request) Successful in 50s
lint / lint (pull_request) Successful in 49s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-27 15:31:59 +03:00
054bd02b59 meter: move metrics handling in broker implementations
All checks were successful
lint / lint (pull_request) Successful in 1m4s
pr / test (pull_request) Successful in 50s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-27 15:30:55 +03:00
0cf246d2d6 Merge pull request 'util/io: add RedirectStderr' (#214) from io-redirect into master
Reviewed-on: #214
2023-04-24 12:59:31 +03:00
af278bd7d3 util/io: add RedirectStderr
All checks were successful
lint / lint (pull_request) Successful in 46s
pr / test (pull_request) Successful in 50s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-24 12:58:05 +03:00
814b90efe5 Merge pull request 'util/test: export GetCases func' (#213) from GetCases into master
Reviewed-on: #213
2023-04-19 01:23:53 +03:00
e403ae3d8e util/test: export GetCases func
All checks were successful
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-19 01:23:34 +03:00
c9816a3957 Merge pull request 'util/test: add helper funcs' (#212) from test into master
Reviewed-on: #212
2023-04-19 00:33:28 +03:00
5691238a6a util/test: add helper funcs
All checks were successful
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-18 23:47:12 +03:00
963a0fa7b7 Merge pull request 'gofmt -s code' (#209) from gofmt into master
Reviewed-on: #209
2023-04-11 23:34:41 +03:00
485257035c gofmt -s code
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-11 23:32:58 +03:00
ebd8ddf05b move to v4
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-11 22:21:25 +03:00
1d5e795443 Merge pull request 'move RawMessage to codec package' (#208) from rawmsg into v3
Reviewed-on: #208
2023-04-02 14:15:02 +03:00
a3a434d923 move RawMessage to codec package
All checks were successful
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-04-02 14:10:57 +03:00
bcc06054f1 Merge pull request 'updates' (#207) from updates into v3
Reviewed-on: #207
2023-03-24 00:29:33 +03:00
270d26f1ae test
All checks were successful
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-24 00:25:47 +03:00
646212cc08 retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 17:09:40 +03:00
00c2c749db retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:46:41 +03:00
2dbada0e94 retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:43:33 +03:00
7b8f4410fb retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:36:38 +03:00
45ebef5544 retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:31:42 +03:00
cf4cac0733 retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:29:58 +03:00
50d60b5825 retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:24:47 +03:00
46ef491764 retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:17:44 +03:00
a51b8b8102 retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:11:52 +03:00
15aac48f1e retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:08:51 +03:00
078069b2d7 retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 16:01:27 +03:00
258812304a retest
Some checks failed
autoapprove
automerge
test
lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 15:12:32 +03:00
da5d50db5b retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 14:48:27 +03:00
384e4d113d retest
All checks were successful
test
lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 14:43:23 +03:00
dfd1da7f0d retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 14:26:03 +03:00
8e5015e580 retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 13:41:59 +03:00
bd0c309b71 retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 13:38:39 +03:00
b4f0c3e29a retest
Some checks failed
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 13:29:21 +03:00
8fddaa0455 retest
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 13:26:42 +03:00
2710c269a8 actions
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 13:21:59 +03:00
70ea93e466 actions
Some checks failed
automerge
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 13:12:10 +03:00
a87d0ab1c1 update deps
Some checks failed
autoapprove
automerge
lint
test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-19 00:43:15 +03:00
2e5e102719 Merge pull request 'config: another fix for Default funcs' (#193) from defaults-fix into v3
Reviewed-on: #193
2023-03-16 07:14:41 +03:00
36e492314d config: another fix for Default funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-16 07:14:21 +03:00
0c78873277 Merge pull request 'config: fix Default funcs' (#192) from config-fix into v3
Reviewed-on: #192
2023-03-15 22:53:21 +03:00
7f57dc09d3 config: fix Default funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-15 22:51:40 +03:00
447206d256 Merge pull request 'config: proper handle AllowFail option' (#191) from config-fix into v3
Reviewed-on: #191
2023-03-14 13:24:37 +03:00
33a7feb970 config: proper handle AllowFail option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-14 13:23:41 +03:00
3950f2ed86 Merge pull request 'config: add Before/After Init funcs' (#190) from config-improve into v3
Reviewed-on: #190
2023-03-14 10:40:12 +03:00
68c1048a7d config: add Before/After Init funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-03-14 10:38:24 +03:00
bff40bd317
Merge pull request #189 from unistack-org/meter-handler
meter/handler: move to go.unistack.org/micro-meter-handler/v3
2023-02-26 13:48:09 +03:00
2878d0a4ea server/health: move to go.unistack.org/micro-server-health/v3
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-26 13:46:10 +03:00
3138a9fded meter/handler: move to go.unistack.org/micro-meter-handler/v3
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-26 13:42:04 +03:00
742b99636a fix and regen
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-22 00:09:52 +03:00
34387f129d
Merge pull request #188 from unistack-org/proto
fix service names in proto
2023-02-21 13:28:13 +03:00
47075acb06 fix service names in proto
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-21 13:26:01 +03:00
09cb998ba4 fix service names in proto
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-21 13:18:03 +03:00
b9dbfb1cfc
Merge pull request #187 from unistack-org/api-REMOVE
api: remove and regen
2023-02-21 02:12:47 +03:00
56efccc4cf api: remove and regen
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-21 02:10:24 +03:00
ce9f896287
Merge pull request #186 from unistack-org/structfs
util/structfs: import https://github.com/unistack-org/go-structfs
2023-02-19 23:38:31 +03:00
83d87a40e4 util/structfs: import https://github.com/unistack-org/go-structfs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-19 23:35:39 +03:00
75fd1e43b9
Merge pull request #185 from unistack-org/server
server: add server.SetHandlerOption helper
2023-02-13 23:33:57 +03:00
395a3eed3d server: add server.SetHandlerOption helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-13 23:31:38 +03:00
3ba8cb7f9e
Merge pull request #184 from unistack-org/duration
util/time: add Marshal/Unmarshal to own Duration
2023-02-13 14:05:16 +03:00
b07806b9a1 tmp
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-13 14:03:02 +03:00
0f583218d4 tmp
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-13 14:02:08 +03:00
f4d0237785
Merge pull request #183 from unistack-org/logger/unwrap
logger/unwrap: fix for nested tagged/untagged
2023-02-08 14:56:51 +03:00
0f343dad0b logger/unwrap: fix for nested tagged/untagged
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-08 14:54:37 +03:00
7c29afba0b
Merge pull request #182 from unistack-org/timeDuration
config/default: handle time.Duration
2023-02-07 06:50:42 +03:00
8159b9d233 config/default: handle time.Duration
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-07 06:48:36 +03:00
45cdac5c29 config/default: handle time.Duration
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-07 06:48:12 +03:00
98db0dc8bc config/default: handle time.Duration
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-07 06:47:46 +03:00
453d2232bd
Merge pull request #181 from unistack-org/unwrap
logger/unwrap: check nested in case of Tagged
2023-02-06 22:38:42 +03:00
9b387312da logger/unwrap: check nested in case of Tagged
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-06 22:36:24 +03:00
84024f7713
Merge pull request #180 from unistack-org/logger-wrapper
logger/unwrap: support sql and proto wrapper types
2023-02-06 18:55:39 +03:00
5a554f9f0c fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-06 18:53:27 +03:00
9c33cbc8e2 logger/unwrap: support sql and proto wrapper types
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-06 18:50:00 +03:00
848fe1c0d4
Merge pull request #179 from unistack-org/logger-unwrap
logger/unwrap: fix unwrap format
2023-02-03 23:36:47 +03:00
6cbf23fec5 logger/unwrap: fix unwrap format
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-03 23:33:24 +03:00
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
289aadb28e
Merge pull request #127 from unistack-org/cover
add more cover stuff
2022-05-03 00:26:13 +03:00
9640cdae1a add more cover stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2022-05-03 00:23:43 +03:00
dependabot[bot]
fb35e73731
chore(deps): bump github/codeql-action from 1 to 2 (#126)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-31 06:11:19 +00:00
6e6c31b5dd
Merge pull request #69 from unistack-org/master
merge master
2021-12-28 09:30:34 +03:00
94929878fe
Merge pull request #68 from unistack-org/improvements
improvements
2021-12-28 09:23:45 +03:00
8ce469a09e tracer: fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-12-28 09:18:52 +03:00
88788776d2 Merge branch 'master' into v3 2021-12-16 15:04:08 +03:00
e143e2b547 client: allow to set metadata for message
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-12-16 15:03:42 +03:00
a36f99e30b
Merge pull request #66 from unistack-org/minor_changes
config: add new error type
2021-11-30 07:35:27 +03:00
326ee53333 config: add new error type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-30 07:34:49 +03:00
1244c5bb4d
Merge pull request #65 from unistack-org/master
merge changes from master
2021-11-24 00:59:00 +03:00
4ccc8a9c85
Merge pull request #64 from unistack-org/minor_changes
minor changes
2021-11-24 00:58:21 +03:00
8a2e84d489 minor changes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-24 00:57:59 +03:00
d29363b78d codec: add NewFrame helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-19 09:22:13 +03:00
734f751055
Merge pull request #63 from unistack-org/master
util/http: add type alias
2021-11-19 03:04:55 +03:00
55d8a9ee20 util/http: add type alias
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-19 03:04:08 +03:00
07c93042ba
Merge pull request #62 from unistack-org/master
merge stable
2021-11-18 16:01:10 +03:00
b9bbfdf159 config: add watch option helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-18 15:57:14 +03:00
fbad257acc
config: add helpers to load/save options (#60) 2021-11-18 15:46:30 +03:00
1829febb6e util/http: fix lint issues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-09 17:07:52 +03:00
7838fa62a8 util/trie: import some code from chi router
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-11-09 16:34:05 +03:00
332803d8de update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-27 22:52:48 +03:00
11c868d476 Merge branch 'v3' 2021-10-27 22:51:58 +03:00
38d6e482d7 util/reflect: fix StructFields
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-27 22:51:35 +03:00
07d4085201 util/reflect: fix reflect methods
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-27 12:55:48 +03:00
45f30c0be3 util/reflect: ZeroFieldByPath and SetFieldByPath
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-26 14:12:37 +03:00
bcaea675a7 util/reflect: add method to zero struct field
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-25 14:41:19 +03:00
3087ba1d73 regen
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-23 23:45:53 +03:00
3f5b19497c meter: add Clone method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-09 23:50:57 +03:00
37d937d7ae meter: add missing options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-09 19:16:45 +03:00
7d68f2396e tracer: dont return noop from context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-07 22:46:47 +03:00
0854a7ea72 micro: add simple test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-07 20:59:27 +03:00
5eb0e56373 move all imports to own domain
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-02 19:55:07 +03:00
6af837fd25 fixup workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-02 18:42:20 +03:00
ada59119cc util/http: add test case
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-02 18:34:22 +03:00
8abc913b28 codec: add MarshalAppend func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-01 01:08:24 +03:00
3247d144a8 lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 21:13:13 +03:00
7b2e3cc8aa lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 21:00:02 +03:00
8688179acd lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 20:32:59 +03:00
3e40bac5f4 config: add helper funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 01:24:16 +03:00
e3fee6f8a6 util/http: add trie case insesitive matching
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:41:47 +03:00
15c020fac5 fix lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:19:07 +03:00
3bc046e5d4 broker/memory: simplify code
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:10:11 +03:00
542f36cfa5 util/reflect: fix tests, lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:09:48 +03:00
8237e6a08e util/router: drop google copy of pattern matcher in favour of util/http trie
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:09:22 +03:00
ecb60e4dc5 fix lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-28 23:43:43 +03:00
a1999ff81c util/http: trie add more tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-28 01:02:28 +03:00
d0f2bc8346 util/http: add trie matching func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-27 23:30:53 +03:00
dependabot[bot]
dd29bf457e
chore(deps): bump actions/github-script from 4 to 5 (#58)
Bumps [actions/github-script](https://github.com/actions/github-script) from 4 to 5.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v4...v5)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:33:25 +03:00
d062c248e3 codec: fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 17:09:26 +03:00
875f66d36e codec: implement proto v1 message for Frame
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 16:59:52 +03:00
818a0e6356 codec: add context helper funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 01:07:27 +03:00
56e02ec463 codec: add ability to pass codec options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 00:57:10 +03:00
6ca851401d update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-21 21:46:23 +03:00
bd8216b397 update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-17 07:47:23 +03:00
2b13b3f128 Revert "update workflows"
This reverts commit 9957380b6d.
2021-09-17 07:42:46 +03:00
9957380b6d update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-17 07:41:17 +03:00
e10f8c0fa0 util/id: move tests to micro-tests repo
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-16 15:31:01 +03:00
45252fe4a6 enable automerge
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-16 10:30:59 +03:00
faad082efe util/rand: add Shuffle func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-15 17:51:25 +03:00
8ab35cbd9b update dependabot
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-12 16:17:28 +03:00
ad58ab6943 fix codeql issue
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-06 10:51:13 +03:00
0e97049e1d
Create SECURITY.md 2021-09-06 10:49:18 +03:00
edb0bbf9cf add codeql
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-06 10:30:21 +03:00
dependabot[bot]
1b01bd22a6
build(deps): bump github.com/unistack-org/micro-proto (#57) 2021-09-06 06:56:44 +00:00
2fbaa26f0f logger: add Clone method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-30 16:21:01 +03:00
35d3e4b332 logger: breaking changes to log level parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-30 16:21:01 +03:00
dependabot[bot]
e98a93d530
build(deps): bump github.com/unistack-org/micro-proto (#56)
Bumps [github.com/unistack-org/micro-proto](https://github.com/unistack-org/micro-proto) from 0.0.5 to 0.0.8.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v0.0.5...v0.0.8)

---
updated-dependencies:
- dependency-name: github.com/unistack-org/micro-proto
  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>
2021-08-30 16:20:10 +03:00
e3545532e8 minor changes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-21 01:00:10 +03:00
09653c2fb2 util/id: specify default size for uuid behaviour
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-20 22:48:03 +03:00
70adfeab0d fix flow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-20 22:44:17 +03:00
a45b672c98 drop uuid and use modified nanoid
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-20 22:40:48 +03:00
4509323cae update and regen all
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-16 23:56:50 +03:00
b3f4c670d5 regen all
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-16 18:57:19 +03:00
778dd449e2 logger: add NewStdLogger and RedirectStdLogger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 13:45:11 +03:00
1d16983b67 logger: add NewStdLogger that can be used as std *log.Logger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 11:52:04 +03:00
f386bffd37 logger: change logger interface
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 02:15:57 +03:00
772bde7938 network/tunnel/broker: fix metadata compile issue
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 02:14:56 +03:00
323 changed files with 12936 additions and 13421 deletions

View File

@ -0,0 +1,18 @@
---
name: Bug report
about: For reporting bugs in micro
title: "[BUG]"
labels: ''
assignees: ''
---
**Describe the bug**
1. What are you trying to do?
2. What did you expect to happen?
3. What happens instead?
**How to reproduce the bug:**
If possible, please include a minimal code snippet here.

View File

@ -0,0 +1,17 @@
---
name: Feature request / Enhancement
about: If you have a need not served by micro
title: "[FEATURE]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,8 @@
---
name: Question
about: Ask a question about micro
title: ''
labels: ''
assignees: ''
---

28
.gitea/autoapprove.yml Normal file
View File

@ -0,0 +1,28 @@
name: "autoapprove"
on:
pull_request_target:
types: [assigned, opened, synchronize, reopened]
workflow_run:
workflows: ["prbuild"]
types:
- completed
permissions:
pull-requests: write
contents: write
jobs:
autoapprove:
runs-on: ubuntu-latest
steps:
- name: approve
run: [ "curl -o tea https://dl.gitea.com/tea/main/tea-main-linux-amd64",
"chmod +x ./tea",
"./tea login add --name unistack --token ${{ secrets.GITHUB_TOKEN }} --url https://git.unistack.org",
"./tea pr --repo ${{ github.event.repository.name }}"
]
if: github.actor == 'vtolstov'
id: approve
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

3
.gitea/pkgdashcli.yaml Normal file
View File

@ -0,0 +1,3 @@
branches:
- master
- v3

View File

@ -0,0 +1,24 @@
on:
push:
branches:
- 'main'
- 'master'
- 'v3'
schedule:
#- cron: '* * * * *'
- cron: '@hourly'
jobs:
autoupdate:
runs-on: ubuntu-latest
steps:
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
with:
go-version: 1.21
- name: checkout
uses: https://gitea.com/actions/checkout@v3
- name: get pkgdashcli
run: GOPROXY=direct GONOSUMDB="git.unistack.org/*" GONOPROXY="git.unistack.org/*" GOBIN=/bin go install git.unistack.org/unistack-org/pkgdash/cmd/pkgdashcli@latest
- name: pkgdashcli check
run: /bin/pkgdashcli check

View File

@ -0,0 +1,30 @@
name: Go
on:
push:
branches: [ master, v3 ]
pull_request:
branches: [ master, v3 ]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup
uses: actions/setup-go@v4
with:
go-version: stable
- name: coverage
run: go test -v -coverprofile coverage.out ./...
- name: badge
uses: ncruces/go-coverage-report@main
with:
coverage-file: coverage.out
reuse-go: true
amend: true

24
.gitea/workflows/lint.yml Normal file
View File

@ -0,0 +1,24 @@
name: lint
on:
pull_request:
branches:
- master
- v3
jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
with:
go-version: 1.21
- name: checkout
uses: https://gitea.com/actions/checkout@v3
- name: deps
run: go get -v -d ./...
- name: lint
uses: https://github.com/golangci/golangci-lint-action@v3.4.0
continue-on-error: true
with:
version: v1.52

23
.gitea/workflows/pr.yml Normal file
View File

@ -0,0 +1,23 @@
name: pr
on:
pull_request:
branches:
- master
- v3
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- name: checkout
uses: https://gitea.com/actions/checkout@v3
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
with:
go-version: 1.21
- name: deps
run: go get -v -t -d ./...
- name: test
env:
INTEGRATION_TESTS: yes
run: go test -v -mod readonly -race -coverprofile=coverage.txt -covermode=atomic ./...

View File

@ -0,0 +1,9 @@
## Pull Request template
Please, go through these steps before clicking submit on this PR.
1. Give a descriptive title to your PR.
2. Provide a description of your changes.
3. Make sure you have some relevant tests.
4. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable).
**PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING**

View File

@ -11,9 +11,16 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
commit-message:
prefix: "chore"
include: "scope"
# Maintain dependencies for Golang # Maintain dependencies for Golang
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
commit-message:
prefix: "chore"
include: "scope"

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,78 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "codeql"
on:
workflow_run:
workflows: ["prbuild"]
types:
- completed
push:
branches: [ master, v3 ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master, v3 ]
schedule:
- cron: '34 1 * * 0'
jobs:
analyze:
name: analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: checkout
uses: actions/checkout@v3
- name: setup
uses: actions/setup-go@v3
with:
go-version: 1.17
# Initializes the CodeQL tools for scanning.
- name: init
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: analyze
uses: github/codeql-action/analyze@v2

View File

@ -0,0 +1,27 @@
name: "dependabot-automerge"
on:
pull_request_target:
types: [assigned, opened, synchronize, reopened]
permissions:
pull-requests: write
contents: write
jobs:
automerge:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: metadata
id: metadata
uses: dependabot/fetch-metadata@v1.3.6
with:
github-token: "${{ secrets.TOKEN }}"
- name: merge
id: merge
if: ${{contains(steps.metadata.outputs.dependency-names, 'go.unistack.org')}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.TOKEN}}

View File

@ -0,0 +1,40 @@
name: prbuild
on:
pull_request:
branches:
- master
- v3
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- name: setup
uses: actions/setup-go@v3
with:
go-version: 1.17
- name: checkout
uses: actions/checkout@v3
- name: cache
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: deps
run: go get -v -t -d ./...
- name: test
env:
INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./...
lint:
name: lint
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: lint
uses: golangci/golangci-lint-action@v3.4.0
continue-on-error: true
with:
version: v1.30

View File

@ -1,62 +0,0 @@
name: prbuild
on:
pull_request:
branches:
- master
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- name: setup
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: cache
uses: actions/cache@v2
with:
path: ~/go/pkg
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: sdk checkout
uses: actions/checkout@v2
- name: sdk deps
run: go get -v -t -d ./...
- name: sdk test
env:
INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./...
- name: tests checkout
uses: actions/checkout@v2
with:
repository: unistack-org/micro-tests
ref: refs/heads/master
path: micro-tests
fetch-depth: 1
- name: tests deps
run: |
cd micro-tests
go mod edit -replace="github.com/unistack-org/micro/v3=../"
go get -v -t -d ./...
- name: tests test
env:
INTEGRATION_TESTS: yes
run: cd micro-tests && go test -mod readonly -v ./...
lint:
name: lint
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: lint
uses: golangci/golangci-lint-action@v2
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.
version: v1.39
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

3
.gitignore vendored
View File

@ -1,6 +1,8 @@
# Develop tools # Develop tools
/.vscode/ /.vscode/
/.idea/ /.idea/
.idea
.vscode
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
@ -13,6 +15,7 @@
_obj _obj
_test _test
_build _build
.DS_Store
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

View File

@ -1,6 +1,5 @@
run: run:
concurrency: 4 concurrency: 4
deadline: 5m
issues-exit-code: 1 issues-exit-code: 1
tests: true tests: true
@ -13,15 +12,13 @@ linters-settings:
linters: linters:
enable: enable:
- govet - govet
- deadcode
- errcheck - errcheck
- govet - govet
- ineffassign - ineffassign
- staticcheck - staticcheck
- structcheck
- typecheck - typecheck
- unused - unused
- varcheck - spancheck
- bodyclose - bodyclose
- gci - gci
- goconst - goconst
@ -30,7 +27,7 @@ linters:
- gofmt - gofmt
- gofumpt - gofumpt
- goimports - goimports
- golint - revive
- gosec - gosec
- makezero - makezero
- misspell - misspell
@ -41,4 +38,5 @@ linters:
- prealloc - prealloc
- unconvert - unconvert
- unparam - unparam
- unused
disable-all: false disable-all: false

View File

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

15
SECURITY.md Normal file
View File

@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 3.7.x | :white_check_mark: |
| < 3.7.0 | :x: |
## Reporting a Vulnerability
If you find any issue, please create github issue in this repo

View File

@ -1,181 +0,0 @@
package api
import (
"errors"
"regexp"
"strings"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/register"
"github.com/unistack-org/micro/v3/server"
)
// Api interface
type Api interface {
// Initialise options
Init(...Option) error
// Get the options
Options() Options
// Register a http handler
Register(*Endpoint) error
// Register a route
Deregister(*Endpoint) error
// Implementation of api
String() string
}
// Options holds the options
type Options struct{}
// Option func signature
type Option func(*Options) error
// Endpoint is a mapping between an RPC method and HTTP endpoint
type Endpoint struct {
// Name Greeter.Hello
Name string
// Desciption for endpoint
Description string
// Handler e.g rpc, proxy
Handler string
// Body destination
// "*" or "" - top level message value
// "string" - inner message value
Body string
// Host e.g example.com
Host []string
// Method e.g GET, POST
Method []string
// Path e.g /greeter. Expect POSIX regex
Path []string
// Stream flag
Stream bool
}
// Service represents an API service
type Service struct {
// Name of service
Name string
// Endpoint for this service
Endpoint *Endpoint
// Services that provides service
Services []*register.Service
}
// Encode encodes an endpoint to endpoint metadata
func Encode(e *Endpoint) map[string]string {
if e == nil {
return nil
}
// endpoint map
ep := make(map[string]string)
// set vals only if they exist
set := func(k, v string) {
if len(v) == 0 {
return
}
ep[k] = v
}
set("endpoint", e.Name)
set("description", e.Description)
set("handler", e.Handler)
set("method", strings.Join(e.Method, ","))
set("path", strings.Join(e.Path, ","))
set("host", strings.Join(e.Host, ","))
set("body", e.Body)
return ep
}
// Decode decodes endpoint metadata into an endpoint
func Decode(e metadata.Metadata) *Endpoint {
if e == nil {
return nil
}
ep := &Endpoint{}
ep.Name, _ = e.Get("endpoint")
ep.Description, _ = e.Get("description")
epmethod, _ := e.Get("method")
ep.Method = []string{epmethod}
eppath, _ := e.Get("path")
ep.Path = []string{eppath}
ephost, _ := e.Get("host")
ep.Host = []string{ephost}
ep.Handler, _ = e.Get("handler")
ep.Body, _ = e.Get("body")
return ep
}
// Validate validates an endpoint to guarantee it won't blow up when being served
func Validate(e *Endpoint) error {
if e == nil {
return errors.New("endpoint is nil")
}
if len(e.Name) == 0 {
return errors.New("name required")
}
for _, p := range e.Path {
ps := p[0]
pe := p[len(p)-1]
if ps == '^' && pe == '$' {
_, err := regexp.CompilePOSIX(p)
if err != nil {
return err
}
} else if ps == '^' && pe != '$' {
return errors.New("invalid path")
} else if ps != '^' && pe == '$' {
return errors.New("invalid path")
}
}
if len(e.Handler) == 0 {
return errors.New("invalid handler")
}
return nil
}
/*
Design ideas
// Gateway is an api gateway interface
type Gateway interface {
// Register a http handler
Handle(pattern string, http.Handler)
// Register a route
RegisterRoute(r Route)
// Init initialises the command line.
// It also parses further options.
Init(...Option) error
// Run the gateway
Run() error
}
// NewGateway returns a new api gateway
func NewGateway() Gateway {
return newGateway()
}
*/
// WithEndpoint returns a server.HandlerOption with endpoint metadata set
//
// Usage:
//
// proto.RegisterHandler(service.Server(), new(Handler), api.WithEndpoint(
// &api.Endpoint{
// Name: "Greeter.Hello",
// Path: []string{"/greeter"},
// },
// ))
func WithEndpoint(e *Endpoint) server.HandlerOption {
return server.EndpointMetadata(e.Name, Encode(e))
}

View File

@ -1,152 +0,0 @@
package api
import (
"strings"
"testing"
)
//nolint:gocyclo
func TestEncoding(t *testing.T) {
testData := []*Endpoint{
nil,
{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test"},
},
}
compare := func(expect, got []string) bool {
// no data to compare, return true
if len(expect) == 0 && len(got) == 0 {
return true
}
// no data expected but got some return false
if len(expect) == 0 && len(got) > 0 {
return false
}
// compare expected with what we got
for _, e := range expect {
var seen bool
for _, g := range got {
if e == g {
seen = true
break
}
}
if !seen {
return false
}
}
// we're done, return true
return true
}
for _, d := range testData {
// encode
e := Encode(d)
// decode
de := Decode(e)
// nil endpoint returns nil
if d == nil {
if e != nil {
t.Fatalf("expected nil got %v", e)
}
if de != nil {
t.Fatalf("expected nil got %v", de)
}
continue
}
// check encoded map
name := e["endpoint"]
desc := e["description"]
method := strings.Split(e["method"], ",")
path := strings.Split(e["path"], ",")
host := strings.Split(e["host"], ",")
handler := e["handler"]
if name != d.Name {
t.Fatalf("expected %v got %v", d.Name, name)
}
if desc != d.Description {
t.Fatalf("expected %v got %v", d.Description, desc)
}
if handler != d.Handler {
t.Fatalf("expected %v got %v", d.Handler, handler)
}
if ok := compare(d.Method, method); !ok {
t.Fatalf("expected %v got %v", d.Method, method)
}
if ok := compare(d.Path, path); !ok {
t.Fatalf("expected %v got %v", d.Path, path)
}
if ok := compare(d.Host, host); !ok {
t.Fatalf("expected %v got %v", d.Host, host)
}
if de.Name != d.Name {
t.Fatalf("expected %v got %v", d.Name, de.Name)
}
if de.Description != d.Description {
t.Fatalf("expected %v got %v", d.Description, de.Description)
}
if de.Handler != d.Handler {
t.Fatalf("expected %v got %v", d.Handler, de.Handler)
}
if ok := compare(d.Method, de.Method); !ok {
t.Fatalf("expected %v got %v", d.Method, de.Method)
}
if ok := compare(d.Path, de.Path); !ok {
t.Fatalf("expected %v got %v", d.Path, de.Path)
}
if ok := compare(d.Host, de.Host); !ok {
t.Fatalf("expected %v got %v", d.Host, de.Host)
}
}
}
func TestValidate(t *testing.T) {
epPcre := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"^/test/?$"},
}
if err := Validate(epPcre); err != nil {
t.Fatal(err)
}
epGpath := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test/{id}"},
}
if err := Validate(epGpath); err != nil {
t.Fatal(err)
}
epPcreInvalid := &Endpoint{
Name: "Foo.Bar",
Description: "A test endpoint",
Handler: "meta",
Host: []string{"foo.com"},
Method: []string{"GET"},
Path: []string{"/test/?$"},
}
if err := Validate(epPcreInvalid); err == nil {
t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0])
}
}

View File

@ -1,14 +0,0 @@
// Package handler provides http handlers
package 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 (
"github.com/unistack-org/micro/v3/api/router"
"github.com/unistack-org/micro/v3/client"
"github.com/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 (
"errors"
"net/http"
"strings"
"github.com/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 (
"net/http"
"github.com/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"
"github.com/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 (
"net/http"
"strings"
"github.com/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 (
"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 (
"net"
"net/http"
"strings"
"github.com/unistack-org/micro/v3/api/resolver"
"github.com/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"
"github.com/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 (
"errors"
"net/http"
"regexp"
"strings"
"github.com/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"
"github.com/unistack-org/micro/v3/api/resolver"
"github.com/unistack-org/micro/v3/api/resolver/vpath"
"github.com/unistack-org/micro/v3/logger"
"github.com/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 (
"net/http"
"github.com/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 (
"context"
"errors"
"time"
"github.com/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 uuid
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 (
"github.com/google/uuid"
)
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) {
uid, err := uuid.NewRandom()
if err != nil {
return nil, err
}
return &Account{ID: uid.String(), 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"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/meter"
"github.com/unistack-org/micro/v3/store"
"github.com/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

@ -1,11 +1,13 @@
// Package broker is an interface used for asynchronous messaging // Package broker is an interface used for asynchronous messaging
package broker package broker // import "go.unistack.org/micro/v4/broker"
import ( import (
"context" "context"
"errors" "errors"
"time"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
) )
// DefaultBroker default memory broker // DefaultBroker default memory broker
@ -16,6 +18,10 @@ var (
ErrNotConnected = errors.New("broker not connected") ErrNotConnected = errors.New("broker not connected")
// ErrDisconnected returns when broker disconnected // ErrDisconnected returns when broker disconnected
ErrDisconnected = errors.New("broker disconnected") ErrDisconnected = errors.New("broker disconnected")
// ErrInvalidMessage returns when message has nvalid format
ErrInvalidMessage = errors.New("broker message has invalid format")
// DefaultGracefulTimeout
DefaultGracefulTimeout = 5 * time.Second
) )
// Broker is an interface used for asynchronous messaging. // Broker is an interface used for asynchronous messaging.
@ -23,7 +29,7 @@ type Broker interface {
// Name returns broker instance name // Name returns broker instance name
Name() string Name() string
// Init initilize broker // Init initilize broker
Init(opts ...Option) error Init(opts ...options.Option) error
// Options returns broker options // Options returns broker options
Options() Options Options() Options
// Address return configured address // Address return configured address
@ -32,91 +38,29 @@ type Broker interface {
Connect(ctx context.Context) error Connect(ctx context.Context) error
// Disconnect disconnect from broker // Disconnect disconnect from broker
Disconnect(ctx context.Context) error Disconnect(ctx context.Context) error
// Publish message to broker topic // Publish message, msg can be single broker.Message or []broker.Message
Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error Publish(ctx context.Context, msg interface{}, opts ...options.Option) error
// Subscribe subscribes to topic message via handler // Subscribe subscribes to topic message via handler
Subscribe(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) Subscribe(ctx context.Context, topic string, handler interface{}, opts ...options.Option) (Subscriber, error)
// BatchPublish messages to broker with multiple topics
BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error
// BatchSubscribe subscribes to topic messages via handler
BatchSubscribe(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
// String type of broker // String type of broker
String() string String() string
} }
// Handler is used to process messages via a subscription of a topic. // Message is given to a subscription handler for processing
type Handler func(Event) error type Message interface {
// Context for the message
// Events contains multiple events Context() context.Context
type Events []Event // Topic
func (evs Events) Ack() error {
var err error
for _, ev := range evs {
if err = ev.Ack(); err != nil {
return err
}
}
return nil
}
func (evs Events) SetError(err error) {
for _, ev := range evs {
ev.SetError(err)
}
}
// BatchHandler is used to process messages in batches via a subscription of a topic.
type BatchHandler func(Events) error
// Event is given to a subscription handler for processing
type Event interface {
// Topic returns event topic
Topic() string Topic() string
// Message returns broker message // Header returns message headers
Message() *Message Header() metadata.Metadata
// Body returns broker message may be []byte slice or some go struct
Body() interface{}
// Ack acknowledge message // Ack acknowledge message
Ack() error Ack() error
// Error returns message error (like decoding errors or some other) // Error returns message error (like decoding errors or some other)
// In this case Body contains raw []byte from broker
Error() error Error() error
// SetError set event processing error
SetError(err error)
}
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
// Message is used to transfer data
type Message struct {
// Header contains message metadata
Header metadata.Metadata
// Body contains message body
Body RawMessage
}
// NewMessage create broker message with topic filled
func NewMessage(topic string) *Message {
m := &Message{Header: metadata.New(2)}
m.Header.Set(metadata.HeaderTopic, topic)
return m
} }
// Subscriber is a convenience return type for the Subscribe method // Subscriber is a convenience return type for the Subscribe method
@ -128,3 +72,9 @@ type Subscriber interface {
// Unsubscribe from topic // Unsubscribe from topic
Unsubscribe(ctx context.Context) error Unsubscribe(ctx context.Context) error
} }
// MessageHandler func signature for single message processing
type MessageHandler func(Message) error
// MessagesHandler func signature for batch message processing
type MessagesHandler func([]Message) error

View File

@ -22,33 +22,3 @@ func NewContext(ctx context.Context, s Broker) context.Context {
} }
return context.WithValue(ctx, brokerKey{}, s) return context.WithValue(ctx, brokerKey{}, s)
} }
// SetSubscribeOption returns a function to setup a context with given value
func SetSubscribeOption(k, v interface{}) SubscribeOption {
return func(o *SubscribeOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// 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)
}
}
// SetPublishOption returns a function to setup a context with given value
func SetPublishOption(k, v interface{}) PublishOption {
return func(o *PublishOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

39
broker/context_test.go Normal file
View File

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

View File

@ -2,17 +2,22 @@ package broker
import ( import (
"context" "context"
"fmt"
"sync" "sync"
"time"
"github.com/google/uuid" "go.unistack.org/micro/v4/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v4/logger"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/metadata"
maddr "github.com/unistack-org/micro/v3/util/addr" "go.unistack.org/micro/v4/options"
mnet "github.com/unistack-org/micro/v3/util/net" "go.unistack.org/micro/v4/semconv"
"github.com/unistack-org/micro/v3/util/rand" maddr "go.unistack.org/micro/v4/util/addr"
"go.unistack.org/micro/v4/util/id"
mnet "go.unistack.org/micro/v4/util/net"
"go.unistack.org/micro/v4/util/rand"
) )
type memoryBroker struct { type MemoryBroker struct {
subscribers map[string][]*memorySubscriber subscribers map[string][]*memorySubscriber
addr string addr string
opts Options opts Options
@ -20,32 +25,15 @@ type memoryBroker struct {
connected bool connected bool
} }
type memoryEvent struct { func (m *MemoryBroker) Options() Options {
err error
message interface{}
topic string
opts Options
}
type memorySubscriber struct {
ctx context.Context
exit chan bool
handler Handler
batchhandler BatchHandler
id string
topic string
opts SubscribeOptions
}
func (m *memoryBroker) Options() Options {
return m.opts return m.opts
} }
func (m *memoryBroker) Address() string { func (m *MemoryBroker) Address() string {
return m.addr return m.addr
} }
func (m *memoryBroker) Connect(ctx context.Context) error { func (m *MemoryBroker) Connect(ctx context.Context) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
@ -69,56 +57,33 @@ func (m *memoryBroker) Connect(ctx context.Context) error {
return nil return nil
} }
func (m *memoryBroker) Disconnect(ctx context.Context) error { func (m *MemoryBroker) Disconnect(ctx context.Context) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
if !m.connected { select {
return nil case <-ctx.Done():
return ctx.Err()
default:
if m.connected {
m.connected = false
}
} }
m.connected = false
return nil return nil
} }
func (m *memoryBroker) Init(opts ...Option) error { func (m *MemoryBroker) Init(opts ...options.Option) error {
var err error
for _, o := range opts { for _, o := range opts {
o(&m.opts) if err = o(&m.opts); err != nil {
}
return nil
}
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
m.RLock()
if !m.connected {
m.RUnlock()
return ErrNotConnected
}
m.RUnlock()
options := NewPublishOptions(opts...)
vs := make([]msgWrapper, 0, 1)
if m.opts.Codec == nil || options.BodyOnly {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
vs = append(vs, msgWrapper{topic: topic, body: msg})
} else {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
buf, err := m.opts.Codec.Marshal(msg)
if err != nil {
return err return err
} }
vs = append(vs, msgWrapper{topic: topic, body: buf})
} }
return nil
return m.publish(ctx, vs, opts...)
} }
type msgWrapper struct { func (m *MemoryBroker) Publish(ctx context.Context, message interface{}, opts ...options.Option) error {
topic string
body interface{}
}
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
@ -126,97 +91,133 @@ func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts .
} }
m.RUnlock() m.RUnlock()
options := NewPublishOptions(opts...)
vs := make([]msgWrapper, 0, len(msgs))
if m.opts.Codec == nil || options.BodyOnly {
for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
vs = append(vs, msgWrapper{topic: topic, body: msg})
}
} else {
for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
buf, err := m.opts.Codec.Marshal(msg)
if err != nil {
return err
}
vs = append(vs, msgWrapper{topic: topic, body: buf})
}
}
return m.publish(ctx, vs, opts...)
}
func (m *memoryBroker) publish(ctx context.Context, vs []msgWrapper, opts ...PublishOption) error {
var err error var err error
msgTopicMap := make(map[string]Events) select {
for _, v := range vs { case <-ctx.Done():
p := &memoryEvent{ return ctx.Err()
topic: v.topic, default:
message: v.body, options := NewPublishOptions(opts...)
opts: m.opts, var msgs []Message
switch v := message.(type) {
case []Message:
msgs = v
case Message:
msgs = append(msgs, v)
default:
return ErrInvalidMessage
} }
msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p) msgTopicMap := make(map[string][]*memoryMessage)
} for _, msg := range msgs {
p := &memoryMessage{opts: options}
beh := m.opts.BatchErrorHandler p.topic, _ = msg.Header().Get(metadata.HeaderTopic)
eh := m.opts.ErrorHandler if v, ok := msg.Body().(*codec.Frame); ok {
p.body = msg.Body()
for t, ms := range msgTopicMap { } else if len(m.opts.Codecs) == 0 {
m.RLock() p.body = msg.Body()
subs, ok := m.subscribers[t] } else {
m.RUnlock() cf, ok := m.opts.Codecs[options.ContentType]
if !ok { if !ok {
continue return fmt.Errorf("%s: %s", codec.ErrUnknownContentType, options.ContentType)
} }
p.body, err = cf.Marshal(v)
for _, sub := range subs { if err != nil {
// batch processing return err
if sub.batchhandler != nil {
if err = sub.batchhandler(ms); err != nil {
ms.SetError(err)
if sub.opts.BatchErrorHandler != nil {
beh = sub.opts.BatchErrorHandler
}
if beh != nil {
beh(ms)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else if sub.opts.AutoAck {
if err = ms.Ack(); err != nil {
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
}
} }
} }
// single processing msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p)
if sub.handler != nil { }
for _, p := range ms {
if err = sub.handler(p); err != nil { eh := m.opts.ErrorHandler
p.SetError(err)
if sub.opts.ErrorHandler != nil { for t, ms := range msgTopicMap {
eh = sub.opts.ErrorHandler ts := time.Now()
}
m.opts.Meter.Counter(semconv.PublishMessageInflight, "endpoint", t).Add(len(ms))
m.opts.Meter.Counter(semconv.SubscribeMessageInflight, "endpoint", t).Add(len(ms))
m.RLock()
subs, ok := m.subscribers[t]
m.RUnlock()
if !ok {
m.opts.Meter.Counter(semconv.PublishMessageTotal, "endpoint", t, "status", "failure").Add(len(ms))
m.opts.Meter.Counter(semconv.PublishMessageInflight, "endpoint", t).Add(-len(ms))
m.opts.Meter.Counter(semconv.SubscribeMessageInflight, "endpoint", t).Add(-len(ms))
continue
}
m.opts.Meter.Counter(semconv.PublishMessageTotal, "endpoint", t, "status", "success").Add(len(ms))
for _, sub := range subs {
if sub.opts.ErrorHandler != nil {
eh = sub.opts.ErrorHandler
}
switch mh := sub.handler.(type) {
case MessagesHandler:
mhs := make([]Message, 0, len(ms))
for _, m := range ms {
mhs = append(mhs, m)
}
if err = mh(mhs); err != nil {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "failure").Add(len(ms))
if eh != nil { if eh != nil {
eh(p) switch meh := eh.(type) {
case MessagesHandler:
_ = meh(mhs)
case MessageHandler:
for _, me := range mhs {
_ = meh(me)
}
}
} else if m.opts.Logger.V(logger.ErrorLevel) { } else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error()) m.opts.Logger.Error(m.opts.Context, err.Error())
} }
} else if sub.opts.AutoAck { }
if err = p.Ack(); err != nil { case MessageHandler:
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err) for _, p := range ms {
if err = mh(p); err != nil {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "failure").Inc()
if eh != nil {
switch meh := eh.(type) {
case MessageHandler:
_ = meh(p)
case MessagesHandler:
_ = meh([]Message{p})
}
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else {
if sub.opts.AutoAck {
if err = p.Ack(); err != nil {
m.opts.Logger.Error(m.opts.Context, "ack failed: "+err.Error())
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "failure").Inc()
} else {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "success").Inc()
}
} else {
m.opts.Meter.Counter(semconv.SubscribeMessageTotal, "endpoint", t, "status", "success").Inc()
}
} }
m.opts.Meter.Counter(semconv.PublishMessageInflight, "endpoint", t).Add(-1)
m.opts.Meter.Counter(semconv.SubscribeMessageInflight, "endpoint", t).Add(-1)
} }
} }
} }
te := time.Since(ts)
m.opts.Meter.Summary(semconv.PublishMessageLatencyMicroseconds, "endpoint", t).Update(te.Seconds())
m.opts.Meter.Histogram(semconv.PublishMessageDurationSeconds, "endpoint", t).Update(te.Seconds())
m.opts.Meter.Summary(semconv.SubscribeMessageLatencyMicroseconds, "endpoint", t).Update(te.Seconds())
m.opts.Meter.Histogram(semconv.SubscribeMessageDurationSeconds, "endpoint", t).Update(te.Seconds())
} }
} }
return nil return nil
} }
func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) { func (m *MemoryBroker) Subscribe(ctx context.Context, topic string, handler interface{}, opts ...options.Option) (Subscriber, error) {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
@ -224,52 +225,7 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler
} }
m.RUnlock() m.RUnlock()
id, err := uuid.NewRandom() sid, err := id.New()
if err != nil {
return nil, err
}
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: id.String(),
topic: topic,
batchhandler: handler,
opts: options,
ctx: ctx,
}
m.Lock()
m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock()
go func() {
<-sub.exit
m.Lock()
newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
for _, sb := range m.subscribers[topic] {
if sb.id == sub.id {
continue
}
newSubscribers = append(newSubscribers, sb)
}
m.subscribers[topic] = newSubscribers
m.Unlock()
}()
return sub, nil
}
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, ErrNotConnected
}
m.RUnlock()
id, err := uuid.NewRandom()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -278,7 +234,7 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
sub := &memorySubscriber{ sub := &memorySubscriber{
exit: make(chan bool, 1), exit: make(chan bool, 1),
id: id.String(), id: sid,
topic: topic, topic: topic,
handler: handler, handler: handler,
opts: options, opts: options,
@ -306,46 +262,54 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
return sub, nil return sub, nil
} }
func (m *memoryBroker) String() string { func (m *MemoryBroker) String() string {
return "memory" return "memory"
} }
func (m *memoryBroker) Name() string { func (m *MemoryBroker) Name() string {
return m.opts.Name return m.opts.Name
} }
func (m *memoryEvent) Topic() string { type memoryMessage struct {
err error
body interface{}
topic string
header metadata.Metadata
opts PublishOptions
ctx context.Context
}
func (m *memoryMessage) Topic() string {
return m.topic return m.topic
} }
func (m *memoryEvent) Message() *Message { func (m *memoryMessage) Header() metadata.Metadata {
switch v := m.message.(type) { return m.header
case *Message: }
return v
case []byte:
msg := &Message{}
if err := m.opts.Codec.Unmarshal(v, msg); err != nil {
if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, "[memory]: failed to unmarshal: %v", err)
}
return nil
}
return msg
}
func (m *memoryMessage) Body() interface{} {
return m.body
}
func (m *memoryMessage) Ack() error {
return nil return nil
} }
func (m *memoryEvent) Ack() error { func (m *memoryMessage) Error() error {
return nil
}
func (m *memoryEvent) Error() error {
return m.err return m.err
} }
func (m *memoryEvent) SetError(err error) { func (m *memoryMessage) Context() context.Context {
m.err = err return m.ctx
}
type memorySubscriber struct {
ctx context.Context
exit chan bool
handler interface{}
id string
topic string
opts SubscribeOptions
} }
func (m *memorySubscriber) Options() SubscribeOptions { func (m *memorySubscriber) Options() SubscribeOptions {
@ -362,8 +326,8 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
} }
// NewBroker return new memory broker // NewBroker return new memory broker
func NewBroker(opts ...Option) Broker { func NewBroker(opts ...options.Option) *MemoryBroker {
return &memoryBroker{ return &MemoryBroker{
opts: NewOptions(opts...), opts: NewOptions(opts...),
subscribers: make(map[string][]*memorySubscriber), subscribers: make(map[string][]*memorySubscriber),
} }

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/metadata"
) )
func TestMemoryBatchBroker(t *testing.T) { func TestMemoryBatchBroker(t *testing.T) {
@ -19,29 +19,35 @@ func TestMemoryBatchBroker(t *testing.T) {
topic := "test" topic := "test"
count := 10 count := 10
fn := func(evts Events) error { fn := func(evts []Message) error {
return evts.Ack() var err error
for _, evt := range evts {
if err = evt.Ack(); err != nil {
return err
}
}
return nil
} }
sub, err := b.BatchSubscribe(ctx, topic, fn) sub, err := b.Subscribe(ctx, topic, fn)
if err != nil { if err != nil {
t.Fatalf("Unexpected error subscribing %v", err) t.Fatalf("Unexpected error subscribing %v", err)
} }
msgs := make([]*Message, 0, 0) msgs := make([]Message, 0, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
message := &Message{ message := &memoryMessage{
Header: map[string]string{ header: metadata.Metadata{
metadata.HeaderTopic: topic, metadata.HeaderTopic: []string{topic},
"foo": "bar", "foo": []string{"bar"},
"id": fmt.Sprintf("%d", i), "id": []string{fmt.Sprintf("%d", i)},
}, },
Body: []byte(`"hello world"`), body: []byte(`"hello world"`),
} }
msgs = append(msgs, message) msgs = append(msgs, message)
} }
if err := b.BatchPublish(ctx, msgs); err != nil { if err := b.Publish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err) t.Fatalf("Unexpected error publishing %v", err)
} }
@ -53,6 +59,7 @@ func TestMemoryBatchBroker(t *testing.T) {
t.Fatalf("Unexpected connect error %v", err) t.Fatalf("Unexpected connect error %v", err)
} }
} }
func TestMemoryBroker(t *testing.T) { func TestMemoryBroker(t *testing.T) {
b := NewBroker() b := NewBroker()
ctx := context.Background() ctx := context.Background()
@ -64,8 +71,8 @@ func TestMemoryBroker(t *testing.T) {
topic := "test" topic := "test"
count := 10 count := 10
fn := func(p Event) error { fn := func(p Message) error {
return nil return p.Ack()
} }
sub, err := b.Subscribe(ctx, topic, fn) sub, err := b.Subscribe(ctx, topic, fn)
@ -73,24 +80,20 @@ func TestMemoryBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err) t.Fatalf("Unexpected error subscribing %v", err)
} }
msgs := make([]*Message, 0, 0) msgs := make([]Message, 0, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
message := &Message{ message := &memoryMessage{
Header: map[string]string{ header: metadata.Metadata{
metadata.HeaderTopic: topic, metadata.HeaderTopic: []string{topic},
"foo": "bar", "foo": []string{"bar"},
"id": fmt.Sprintf("%d", i), "id": []string{fmt.Sprintf("%d", i)},
}, },
Body: []byte(`"hello world"`), body: []byte(`"hello world"`),
} }
msgs = append(msgs, message) msgs = append(msgs, message)
if err := b.Publish(ctx, topic, message); err != nil {
t.Fatalf("Unexpected error publishing %d err: %v", i, err)
}
} }
if err := b.BatchPublish(ctx, msgs); err != nil { if err := b.Publish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err) t.Fatalf("Unexpected error publishing %v", err)
} }

View File

@ -5,11 +5,14 @@ import (
"crypto/tls" "crypto/tls"
"time" "time"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v4/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v4/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v4/metadata"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v4/meter"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/register"
"go.unistack.org/micro/v4/sync"
"go.unistack.org/micro/v4/tracer"
) )
// Options struct // Options struct
@ -18,8 +21,8 @@ type Options struct {
Tracer tracer.Tracer Tracer tracer.Tracer
// Register can be used for clustering // Register can be used for clustering
Register register.Register Register register.Register
// Codec holds the codec for marshal/unmarshal // Codecs holds the codec for marshal/unmarshal
Codec codec.Codec Codecs map[string]codec.Codec
// Logger used for logging // Logger used for logging
Logger logger.Logger Logger logger.Logger
// Meter used for metrics // Meter used for metrics
@ -28,49 +31,51 @@ type Options struct {
Context context.Context Context context.Context
// TLSConfig holds tls.TLSConfig options // TLSConfig holds tls.TLSConfig options
TLSConfig *tls.Config TLSConfig *tls.Config
// ErrorHandler used when broker can't unmarshal incoming message // ErrorHandler used when broker have error while processing message
ErrorHandler Handler ErrorHandler interface{}
// BatchErrorHandler used when broker can't unmashal incoming messages
BatchErrorHandler BatchHandler
// Name holds the broker name // Name holds the broker name
Name string Name string
// Addrs holds the broker address // Address holds the broker address
Addrs []string Address []string
Wait *sync.WaitGroup
GracefulTimeout time.Duration
} }
// NewOptions create new Options // NewOptions create new Options
func NewOptions(opts ...Option) Options { func NewOptions(opts ...options.Option) Options {
options := Options{ newOpts := Options{
Register: register.DefaultRegister, Register: register.DefaultRegister,
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Context: context.Background(), Context: context.Background(),
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
Codec: codec.DefaultCodec, Codecs: make(map[string]codec.Codec),
Tracer: tracer.DefaultTracer, Tracer: tracer.DefaultTracer,
GracefulTimeout: DefaultGracefulTimeout,
} }
for _, o := range opts { for _, o := range opts {
o(&options) o(&newOpts)
}
return options
}
// Context sets the context option
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
} }
return newOpts
} }
// PublishOptions struct // PublishOptions struct
type PublishOptions struct { type PublishOptions struct {
// Context holds external options // Context holds external options
Context context.Context Context context.Context
// Message metadata usually passed as message headers
Metadata metadata.Metadata
// Content-Type of message for marshal
ContentType string
// Topic destination
Topic string
// BodyOnly flag says the message contains raw body bytes // BodyOnly flag says the message contains raw body bytes
BodyOnly bool BodyOnly bool
} }
// NewPublishOptions creates PublishOptions struct // NewPublishOptions creates PublishOptions struct
func NewPublishOptions(opts ...PublishOption) PublishOptions { func NewPublishOptions(opts ...options.Option) PublishOptions {
options := PublishOptions{ options := PublishOptions{
Context: context.Background(), Context: context.Background(),
} }
@ -80,16 +85,21 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
return options return options
} }
// PublishTopic pass topic for messages
func PublishTopic(t string) options.Option {
return func(src interface{}) error {
return options.Set(src, t, ".Topic")
}
}
// SubscribeOptions struct // SubscribeOptions struct
type SubscribeOptions struct { type SubscribeOptions struct {
// Context holds external options // Context holds external options
Context context.Context Context context.Context
// ErrorHandler used when broker can't unmarshal incoming message // ErrorHandler used when broker have error while processing message
ErrorHandler Handler ErrorHandler interface{}
// BatchErrorHandler used when broker can't unmashal incoming messages // QueueGroup holds consumer group
BatchErrorHandler BatchHandler QueueGroup string
// Group holds consumer group
Group string
// AutoAck flag specifies auto ack of incoming message when no error happens // AutoAck flag specifies auto ack of incoming message when no error happens
AutoAck bool AutoAck bool
// BodyOnly flag specifies that message contains only body bytes without header // BodyOnly flag specifies that message contains only body bytes without header
@ -100,179 +110,16 @@ type SubscribeOptions struct {
BatchWait time.Duration BatchWait time.Duration
} }
// Option func
type Option func(*Options)
// PublishOption func
type PublishOption func(*PublishOptions)
// PublishBodyOnly publish only body of the message
func PublishBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishContext sets the context
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
o.Context = ctx
}
}
// Addrs sets the host addresses to be used by the broker
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Addrs = addrs
}
}
// Codec sets the codec used for encoding/decoding used where
// a broker does not support headers
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}
// ErrorHandler will catch all broker errors that cant be handled // ErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors // in normal way, for example Codec errors
func ErrorHandler(h Handler) Option { func ErrorHandler(h interface{}) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.ErrorHandler = h return options.Set(src, h, ".ErrorHandler")
} }
} }
// BatchErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func BatchErrorHandler(h BatchHandler) Option {
return func(o *Options) {
o.BatchErrorHandler = h
}
}
// SubscribeErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func SubscribeErrorHandler(h Handler) SubscribeOption {
return func(o *SubscribeOptions) {
o.ErrorHandler = h
}
}
// SubscribeBatchErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func SubscribeBatchErrorHandler(h BatchHandler) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchErrorHandler = h
}
}
// Queue sets the subscribers queue
// Deprecated
func Queue(name string) SubscribeOption {
return func(o *SubscribeOptions) {
o.Group = name
}
}
// SubscribeGroup sets the name of the queue to share messages on
func SubscribeGroup(name string) SubscribeOption {
return func(o *SubscribeOptions) {
o.Group = name
}
}
// Register sets register option
func Register(r register.Register) Option {
return func(o *Options) {
o.Register = r
}
}
// TLSConfig sets the TLS Config
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
o.TLSConfig = t
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Meter sets the meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// SubscribeContext set context
func SubscribeContext(ctx context.Context) SubscribeOption {
return func(o *SubscribeOptions) {
o.Context = ctx
}
}
// DisableAutoAck disables auto ack
// Deprecated
func DisableAutoAck() SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = false
}
}
// SubscribeAutoAck contol auto acking of messages
// after they have been handled.
func SubscribeAutoAck(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = b
}
}
// SubscribeBodyOnly consumes only body of the message
func SubscribeBodyOnly(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.BodyOnly = b
}
}
// SubscribeBatchSize specifies max batch size
func SubscribeBatchSize(n int) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchSize = n
}
}
// SubscribeBatchWait specifies max batch wait time
func SubscribeBatchWait(td time.Duration) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchWait = td
}
}
// SubscribeOption func
type SubscribeOption func(*SubscribeOptions)
// NewSubscribeOptions creates new SubscribeOptions // NewSubscribeOptions creates new SubscribeOptions
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions { func NewSubscribeOptions(opts ...options.Option) SubscribeOptions {
options := SubscribeOptions{ options := SubscribeOptions{
AutoAck: true, AutoAck: true,
Context: context.Background(), Context: context.Background(),
@ -282,3 +129,39 @@ func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
} }
return options return options
} }
// SubscribeAutoAck contol auto acking of messages
// after they have been handled.
func SubscribeAutoAck(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".AutoAck")
}
}
// BodyOnly transfer only body without
func BodyOnly(b bool) options.Option {
return func(src interface{}) error {
return options.Set(src, b, ".BodyOnly")
}
}
// SubscribeBatchSize specifies max batch size
func SubscribeBatchSize(n int) options.Option {
return func(src interface{}) error {
return options.Set(src, n, ".BatchSize")
}
}
// SubscribeBatchWait specifies max batch wait time
func SubscribeBatchWait(td time.Duration) options.Option {
return func(src interface{}) error {
return options.Set(src, td, ".BatchWait")
}
}
// SubscribeQueueGroup sets the shared queue name distributed messages across subscribers
func SubscribeQueueGroup(n string) options.Option {
return func(src interface{}) error {
return options.Set(src, n, ".QueueGroup")
}
}

98
broker/subscriber.go Normal file
View File

@ -0,0 +1,98 @@
package broker
import (
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
const (
subSig = "func(context.Context, interface{}) error"
batchSubSig = "func([]context.Context, []interface{}) error"
)
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
// Is this an exported - upper case - name?
func isExported(name string) bool {
r, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(r)
}
// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
// ValidateSubscriber func signature
func ValidateSubscriber(sub interface{}) error {
typ := reflect.TypeOf(sub)
var argType reflect.Type
switch typ.Kind() {
case reflect.Func:
name := "Func"
switch typ.NumIn() {
case 1: // func(Message) error
case 2: // func(context.Context, Message) error or func(context.Context, []Message) error
argType = typ.In(2)
// if sub.Options().Batch {
if argType.Kind() != reflect.Slice {
return fmt.Errorf("subscriber %v dont have required signature %s", name, batchSubSig)
}
if strings.Compare(fmt.Sprintf("%v", argType), "[]interface{}") == 0 {
return fmt.Errorf("subscriber %v dont have required signaure %s", name, batchSubSig)
}
// }
default:
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s or %s", name, typ.NumIn(), subSig, batchSubSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
}
if typ.NumOut() != 1 {
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s or %s",
name, typ.NumOut(), subSig, batchSubSig)
}
if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
}
default:
hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
switch method.Type.NumIn() {
case 3:
argType = method.Type.In(2)
default:
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s or %s",
name, method.Name, method.Type.NumIn(), subSig, batchSubSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("%v argument type not exported: %v", name, argType)
}
if method.Type.NumOut() != 1 {
return fmt.Errorf(
"subscriber %v.%v has wrong number of return values: %v require signature %s or %s",
name, method.Name, method.Type.NumOut(), subSig, batchSubSig)
}
if returnType := method.Type.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
}
}
}
return nil
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,29 @@
// Package client is an interface for an RPC client // Package client is an interface for an RPC client
package client package client // import "go.unistack.org/micro/v4/client"
import ( import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v4/codec"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/options"
) )
var ( var (
// DefaultClient is the global default client // DefaultClient is the global default client
DefaultClient Client = NewClient() DefaultClient = NewClient()
// DefaultContentType is the default content-type if not specified // DefaultContentType is the default content-type if not specified
DefaultContentType = "application/json" DefaultContentType = ""
// DefaultBackoff is the default backoff function for retries // DefaultBackoff is the default backoff function for retries (minimum 10 millisecond and maximum 5 second)
DefaultBackoff = exponentialBackoff DefaultBackoff = BackoffInterval(10*time.Millisecond, 5*time.Second)
// DefaultRetry is the default check-for-retry function for retries // DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryNever DefaultRetry = RetryNever
// DefaultRetries is the default number of times a request is tried // DefaultRetries is the default number of times a request is tried
DefaultRetries = 0 DefaultRetries = 0
// DefaultRequestTimeout is the default request timeout // DefaultRequestTimeout is the default request timeout
DefaultRequestTimeout = time.Second * 5 DefaultRequestTimeout = time.Second * 5
// DefaultDialTimeout the default dial timeout
DefaultDialTimeout = time.Second * 5
// DefaultPoolSize sets the connection pool size // DefaultPoolSize sets the connection pool size
DefaultPoolSize = 100 DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl // DefaultPoolTTL sets the connection pool ttl
@ -33,24 +35,14 @@ var (
// It also supports bidirectional streaming of requests. // It also supports bidirectional streaming of requests.
type Client interface { type Client interface {
Name() string Name() string
Init(opts ...Option) error Init(opts ...options.Option) error
Options() Options Options() Options
NewMessage(topic string, msg interface{}, opts ...MessageOption) Message NewRequest(service string, endpoint string, req interface{}, opts ...options.Option) Request
NewRequest(service string, endpoint string, req interface{}, opts ...RequestOption) Request Call(ctx context.Context, req Request, rsp interface{}, opts ...options.Option) error
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error Stream(ctx context.Context, req Request, opts ...options.Option) (Stream, error)
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
Publish(ctx context.Context, msg Message, opts ...PublishOption) error
BatchPublish(ctx context.Context, msg []Message, opts ...PublishOption) error
String() string String() string
} }
// Message is the interface for publishing asynchronously
type Message interface {
Topic() string
Payload() interface{}
ContentType() string
}
// Request is the interface for a synchronous request used by Call or Stream // Request is the interface for a synchronous request used by Call or Stream
type Request interface { type Request interface {
// The service to call // The service to call
@ -67,16 +59,22 @@ type Request interface {
Codec() codec.Codec Codec() codec.Codec
// indicates whether the request will be a streaming one rather than unary // indicates whether the request will be a streaming one rather than unary
Stream() bool Stream() bool
// Header data
// Header() metadata.Metadata
} }
// Response is the response received from a service // Response is the response received from a service
type Response interface { type Response interface {
// Read the response // Read the response
Codec() codec.Codec Codec() codec.Codec
// read the header // The content type
Header() metadata.Metadata // ContentType() string
// Header data
// Header() metadata.Metadata
// Read the undecoded response // Read the undecoded response
Read() ([]byte, error) Read() ([]byte, error)
// The unencoded request body
// Body() interface{}
} }
// Stream is the interface for a bidirectional synchronous stream // Stream is the interface for a bidirectional synchronous stream
@ -91,23 +89,14 @@ type Stream interface {
Send(msg interface{}) error Send(msg interface{}) error
// Recv will decode and read a response // Recv will decode and read a response
Recv(msg interface{}) error Recv(msg interface{}) error
// SendMsg will encode and send a request
SendMsg(msg interface{}) error
// RecvMsg will decode and read a response
RecvMsg(msg interface{}) error
// Error returns the stream error // Error returns the stream error
Error() error Error() error
// Close closes the stream // Close closes the stream
Close() error Close() error
// CloseSend closes the send direction of the stream
CloseSend() error
} }
// Option used by the Client
type Option func(*Options)
// CallOption used by Call or Stream
type CallOption func(*CallOptions)
// PublishOption used by Publish
type PublishOption func(*PublishOptions)
// MessageOption used by NewMessage
type MessageOption func(*MessageOptions)
// RequestOption used by NewRequest
type RequestOption func(*RequestOptions)

View File

@ -2,22 +2,24 @@ package client
import ( import (
"context" "context"
"go.unistack.org/micro/v4/options"
) )
type clientCallOptions struct { type clientCallOptions struct {
Client Client
opts []CallOption opts []options.Option
} }
func (s *clientCallOptions) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error { func (s *clientCallOptions) Call(ctx context.Context, req Request, rsp interface{}, opts ...options.Option) error {
return s.Client.Call(ctx, req, rsp, append(s.opts, opts...)...) return s.Client.Call(ctx, req, rsp, append(s.opts, opts...)...)
} }
func (s *clientCallOptions) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) { func (s *clientCallOptions) Stream(ctx context.Context, req Request, opts ...options.Option) (Stream, error) {
return s.Client.Stream(ctx, req, append(s.opts, opts...)...) return s.Client.Stream(ctx, req, append(s.opts, opts...)...)
} }
// NewClientCallOptions add CallOption to every call // NewClientCallOptions add CallOption to every call
func NewClientCallOptions(c Client, opts ...CallOption) Client { func NewClientCallOptions(c Client, opts ...options.Option) Client {
return &clientCallOptions{c, opts} return &clientCallOptions{c, opts}
} }

View File

@ -0,0 +1,28 @@
package client
import (
"context"
"testing"
"time"
"go.unistack.org/micro/v4/options"
)
func TestNewClientCallOptions(t *testing.T) {
var flag bool
w := func(fn CallFunc) CallFunc {
flag = true
return fn
}
c := NewClientCallOptions(NewClient(),
options.Address("127.0.0.1"),
WithCallWrapper(w),
RequestTimeout(1*time.Millisecond),
Retries(0),
Backoff(BackoffInterval(10*time.Millisecond, 100*time.Millisecond)),
)
_ = c.Call(context.TODO(), c.NewRequest("service", "endpoint", nil), nil)
if !flag {
t.Fatalf("NewClientCallOptions not works")
}
}

View File

@ -22,33 +22,3 @@ func NewContext(ctx context.Context, c Client) context.Context {
} }
return context.WithValue(ctx, clientKey{}, c) return context.WithValue(ctx, clientKey{}, c)
} }
// SetPublishOption returns a function to setup a context with given value
func SetPublishOption(k, v interface{}) PublishOption {
return func(o *PublishOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// SetCallOption returns a function to setup a context with given value
func SetCallOption(k, v interface{}) CallOption {
return func(o *CallOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// 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)
}
}

40
client/context_test.go Normal file
View File

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

View File

@ -4,15 +4,15 @@ import (
"context" "context"
"sort" "sort"
"github.com/unistack-org/micro/v3/errors" "go.unistack.org/micro/v4/errors"
"github.com/unistack-org/micro/v3/router" "go.unistack.org/micro/v4/router"
) )
// LookupFunc is used to lookup routes for a service // LookupFunc is used to lookup routes for a service
type LookupFunc func(context.Context, Request, CallOptions) ([]string, error) type LookupFunc func(context.Context, Request, CallOptions) ([]string, error)
// LookupRoute for a request using the router and then choose one using the selector // LookupRoute for a request using the router and then choose one using the selector
func LookupRoute(ctx context.Context, req Request, opts CallOptions) ([]string, error) { func LookupRoute(_ context.Context, req Request, opts CallOptions) ([]string, error) {
// check to see if an address was provided as a call option // check to see if an address was provided as a call option
if len(opts.Address) > 0 { if len(opts.Address) > 0 {
return opts.Address, nil return opts.Address, nil

View File

@ -2,11 +2,15 @@ package client
import ( import (
"context" "context"
"fmt"
"time"
"github.com/unistack-org/micro/v3/broker" "go.unistack.org/micro/v4/codec"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v4/errors"
"github.com/unistack-org/micro/v3/errors" "go.unistack.org/micro/v4/metadata"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/selector"
"go.unistack.org/micro/v4/semconv"
) )
// DefaultCodecs will be used to encode/decode data // DefaultCodecs will be used to encode/decode data
@ -18,12 +22,6 @@ type noopClient struct {
opts Options opts Options
} }
type noopMessage struct {
topic string
payload interface{}
opts MessageOptions
}
type noopRequest struct { type noopRequest struct {
body interface{} body interface{}
codec codec.Codec codec codec.Codec
@ -35,16 +33,12 @@ type noopRequest struct {
} }
// NewClient returns new noop client // NewClient returns new noop client
func NewClient(opts ...Option) Client { func NewClient(opts ...options.Option) Client {
nc := &noopClient{opts: NewOptions(opts...)} nc := &noopClient{opts: NewOptions(opts...)}
// wrap in reverse // wrap in reverse
c := Client(nc) c := Client(nc)
for i := len(nc.opts.Wrappers); i > 0; i-- {
c = nc.opts.Wrappers[i-1](c)
}
return c return c
} }
@ -119,6 +113,14 @@ func (n *noopStream) Recv(interface{}) error {
return nil return nil
} }
func (n *noopStream) SendMsg(interface{}) error {
return nil
}
func (n *noopStream) RecvMsg(interface{}) error {
return nil
}
func (n *noopStream) Error() error { func (n *noopStream) Error() error {
return nil return nil
} }
@ -127,16 +129,8 @@ func (n *noopStream) Close() error {
return nil return nil
} }
func (n *noopMessage) Topic() string { func (n *noopStream) CloseSend() error {
return n.topic return nil
}
func (n *noopMessage) Payload() interface{} {
return n.payload
}
func (n *noopMessage) ContentType() string {
return n.opts.ContentType
} }
func (n *noopClient) newCodec(contentType string) (codec.Codec, error) { func (n *noopClient) newCodec(contentType string) (codec.Codec, error) {
@ -149,7 +143,7 @@ func (n *noopClient) newCodec(contentType string) (codec.Codec, error) {
return nil, codec.ErrUnknownContentType return nil, codec.ErrUnknownContentType
} }
func (n *noopClient) Init(opts ...Option) error { func (n *noopClient) Init(opts ...options.Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
@ -164,77 +158,307 @@ func (n *noopClient) String() string {
return "noop" return "noop"
} }
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error { func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...options.Option) error {
// make a copy of call opts
callOpts := n.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
// check if we already have a deadline
d, ok := ctx.Deadline()
if !ok {
var cancel context.CancelFunc
// no deadline so we create a new one
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
defer cancel()
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
opt := RequestTimeout(time.Until(d))
opt(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
default:
}
// make copy of call method
hcall := n.call
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
hcall = callOpts.CallWrappers[i-1](hcall)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = n.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = n.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(n.opts.Proxy) > 0 {
callOpts.Address = []string{n.opts.Proxy}
}
var next selector.Next
// return errors.New("go.micro.client", "request timeout", 408)
call := func(i int) error {
// call backoff first. Someone may want an initial start delay
t, err := callOpts.Backoff(ctx, req, i)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
if next == nil {
var routes []string
// lookup the route to send the reques to
// TODO apply any filtering here
routes, err = n.opts.Lookup(ctx, req, callOpts)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err = callOpts.Selector.Select(routes)
if err != nil {
return err
}
}
node := next()
// make the call
err = hcall(ctx, node, req, rsp, callOpts)
// record the result of the call to inform future routing decisions
if verr := n.opts.Selector.Record(node, err); verr != nil {
return verr
}
// try and transform the error to a go-micro error
if verr, ok := err.(*errors.Error); ok {
return verr
}
return err
}
ch := make(chan error, callOpts.Retries)
var gerr error
ts := time.Now()
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", endpoint).Inc()
for i := 0; i <= callOpts.Retries; i++ {
go func() {
ch <- call(i)
}()
select {
case <-ctx.Done():
return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
case err := <-ch:
// if the call succeeded lets bail early
if err == nil {
return nil
}
retry, rerr := callOpts.Retry(ctx, req, i, err)
if rerr != nil {
return rerr
}
if !retry {
return err
}
gerr = err
}
}
if gerr != nil {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "failure").Inc()
} else {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "success").Inc()
}
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", endpoint).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", endpoint).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", endpoint).Update(te.Seconds())
return gerr
}
func (n *noopClient) call(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
return nil return nil
} }
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request { func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...options.Option) Request {
return &noopRequest{service: service, endpoint: endpoint} return &noopRequest{service: service, endpoint: endpoint}
} }
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message { func (n *noopClient) Stream(ctx context.Context, req Request, opts ...options.Option) (Stream, error) {
options := NewMessageOptions(append([]MessageOption{MessageContentType(n.opts.ContentType)}, opts...)...) var err error
return &noopMessage{topic: topic, payload: msg, opts: options}
}
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) { // make a copy of call opts
return &noopStream{}, nil callOpts := n.opts.CallOptions
} for _, o := range opts {
o(&callOpts)
func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
return n.publish(ctx, ps, opts...)
}
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.publish(ctx, []Message{p}, opts...)
}
func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishOption) error {
options := NewPublishOptions(opts...)
msgs := make([]*broker.Message, 0, len(ps))
for _, p := range ps {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(0)
}
md[metadata.HeaderContentType] = p.ContentType()
topic := p.Topic()
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
}
md[metadata.HeaderTopic] = topic
var body []byte
// passed in raw data
if d, ok := p.Payload().(*codec.Frame); ok {
body = d.Data
} else {
// use codec for payload
cf, err := n.newCodec(p.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// set the body
b, err := cf.Marshal(p.Payload())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
body = b
}
msgs = append(msgs, &broker.Message{Header: md, Body: body})
} }
return n.opts.Broker.BatchPublish(ctx, msgs, // check if we already have a deadline
broker.PublishContext(options.Context), d, ok := ctx.Deadline()
broker.PublishBodyOnly(options.BodyOnly), if !ok && callOpts.StreamTimeout > time.Duration(0) {
) var cancel context.CancelFunc
// no deadline so we create a new one
ctx, cancel = context.WithTimeout(ctx, callOpts.StreamTimeout)
defer cancel()
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
o := StreamTimeout(time.Until(d))
o(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
default:
}
/*
// make copy of call method
hstream := h.stream
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
hstream = callOpts.CallWrappers[i-1](hstream)
}
*/
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = n.opts.Router
}
if callOpts.Selector == nil {
callOpts.Selector = n.opts.Selector
}
// inject proxy address
// TODO: don't even bother using Lookup/Select in this case
if len(n.opts.Proxy) > 0 {
callOpts.Address = []string{n.opts.Proxy}
}
var next selector.Next
call := func(i int) (Stream, error) {
// call backoff first. Someone may want an initial start delay
t, cerr := callOpts.Backoff(ctx, req, i)
if cerr != nil {
return nil, errors.InternalServerError("go.micro.client", cerr.Error())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
if next == nil {
var routes []string
// lookup the route to send the reques to
// TODO apply any filtering here
routes, err = n.opts.Lookup(ctx, req, callOpts)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
// balance the list of nodes
next, err = callOpts.Selector.Select(routes)
if err != nil {
return nil, err
}
}
node := next()
// ts := time.Now()
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", endpoint).Inc()
stream, cerr := n.stream(ctx, node, req, callOpts)
if cerr != nil {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "failure").Inc()
} else {
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", endpoint, "status", "success").Inc()
}
// record the result of the call to inform future routing decisions
if verr := n.opts.Selector.Record(node, cerr); verr != nil {
return nil, verr
}
// try and transform the error to a go-micro error
if verr, ok := cerr.(*errors.Error); ok {
return nil, verr
}
return stream, cerr
}
type response struct {
stream Stream
err error
}
ch := make(chan response, callOpts.Retries)
var grr error
for i := 0; i <= callOpts.Retries; i++ {
go func() {
s, cerr := call(i)
ch <- response{s, cerr}
}()
select {
case <-ctx.Done():
return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
case rsp := <-ch:
// if the call succeeded lets bail early
if rsp.err == nil {
return rsp.stream, nil
}
retry, rerr := callOpts.Retry(ctx, req, i, err)
if rerr != nil {
return nil, rerr
}
if !retry {
return nil, rsp.err
}
grr = rsp.err
}
}
return nil, grr
}
func (n *noopClient) stream(ctx context.Context, addr string, req Request, opts CallOptions) (*noopStream, error) {
return &noopStream{}, nil
} }

View File

@ -3,18 +3,18 @@ package client
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"net"
"time" "time"
"github.com/unistack-org/micro/v3/broker" "go.unistack.org/micro/v4/codec"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v4/logger"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v4/metadata"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v4/meter"
"github.com/unistack-org/micro/v3/network/transport" "go.unistack.org/micro/v4/options"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v4/router"
"github.com/unistack-org/micro/v3/router" "go.unistack.org/micro/v4/selector"
"github.com/unistack-org/micro/v3/selector" "go.unistack.org/micro/v4/selector/random"
"github.com/unistack-org/micro/v3/selector/random" "go.unistack.org/micro/v4/tracer"
"github.com/unistack-org/micro/v3/tracer"
) )
// Options holds client options // Options holds client options
@ -25,40 +25,38 @@ type Options struct {
Logger logger.Logger Logger logger.Logger
// Tracer used for tracing // Tracer used for tracing
Tracer tracer.Tracer Tracer tracer.Tracer
// Broker used to publish messages
Broker broker.Broker
// Meter used for metrics // Meter used for metrics
Meter meter.Meter Meter meter.Meter
// Router used to get route
Router router.Router
// Transport used for transfer messages
Transport transport.Transport
// Context is used for external options // Context is used for external options
Context context.Context Context context.Context
// Lookup func used to get destination addr // Router used to get route
Lookup LookupFunc Router router.Router
// Codecs map
Codecs map[string]codec.Codec
// TLSConfig specifies tls.Config for secure connection // TLSConfig specifies tls.Config for secure connection
TLSConfig *tls.Config TLSConfig *tls.Config
// Codecs map
Codecs map[string]codec.Codec
// Lookup func used to get destination addr
Lookup LookupFunc
// Proxy is used for proxy requests // Proxy is used for proxy requests
Proxy string Proxy string
// ContentType is used to select codec // ContentType is used to select codec
ContentType string ContentType string
// Name is the client name // Name is the client name
Name string Name string
// Wrappers contains wrappers
Wrappers []Wrapper
// CallOptions contains default CallOptions // CallOptions contains default CallOptions
CallOptions CallOptions CallOptions CallOptions
// PoolSize connection pool size // PoolSize connection pool size
PoolSize int PoolSize int
// PoolTTL connection pool ttl // PoolTTL connection pool ttl
PoolTTL time.Duration PoolTTL time.Duration
// ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error)
// Hooks may contains Client func wrapper
Hooks options.Hooks
} }
// NewCallOptions creates new call options struct // NewCallOptions creates new call options struct
func NewCallOptions(opts ...CallOption) CallOptions { func NewCallOptions(opts ...options.Option) CallOptions {
options := CallOptions{} options := CallOptions{}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
@ -68,12 +66,12 @@ func NewCallOptions(opts ...CallOption) CallOptions {
// CallOptions holds client call options // CallOptions holds client call options
type CallOptions struct { type CallOptions struct {
// Router used for route
Router router.Router
// Selector selects addr // Selector selects addr
Selector selector.Selector Selector selector.Selector
// Context used for deadline // Context used for deadline
Context context.Context Context context.Context
// Router used for route
Router router.Router
// Retry func used for retries // Retry func used for retries
Retry RetryFunc Retry RetryFunc
// Backoff func used for backoff when retry // Backoff func used for backoff when retry
@ -82,67 +80,39 @@ type CallOptions struct {
Network string Network string
// Content-Type // Content-Type
ContentType string ContentType string
// CallWrappers call wrappers // AuthToken string
CallWrappers []CallWrapper AuthToken string
// SelectOptions selector options
SelectOptions []selector.SelectOption
// Address specifies static addr list // Address specifies static addr list
Address []string Address []string
// Retries specifies retries num // SelectOptions selector options
Retries int SelectOptions []selector.SelectOption
// CallWrappers call wrappers
CallWrappers []CallWrapper
// StreamTimeout stream timeout // StreamTimeout stream timeout
StreamTimeout time.Duration StreamTimeout time.Duration
// RequestTimeout request timeout // RequestTimeout request timeout
RequestTimeout time.Duration RequestTimeout time.Duration
// RequestMetadata holds additional metadata for call
RequestMetadata metadata.Metadata
// ResponseMetadata holds additional metadata from call
ResponseMetadata *metadata.Metadata
// DialTimeout dial timeout // DialTimeout dial timeout
DialTimeout time.Duration DialTimeout time.Duration
// AuthToken string // Retries specifies retries num
AuthToken string Retries int
// ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error)
} }
// Context pass context to client // ContextDialer pass ContextDialer to client
func Context(ctx context.Context) Option { func ContextDialer(fn func(context.Context, string) (net.Conn, error)) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.Context = ctx return options.Set(src, fn, ".ContextDialer")
} }
} }
// NewPublishOptions create new PublishOptions struct from option
func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// PublishOptions holds publish options
type PublishOptions struct {
// BodyOnly will publish only message body
BodyOnly bool
// Context used for external options
Context context.Context
// Exchange topic exchange name
Exchange string
}
// NewMessageOptions creates message options struct
func NewMessageOptions(opts ...MessageOption) MessageOptions {
options := MessageOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// MessageOptions holds client message options
type MessageOptions struct {
// ContentType specify content-type of message
ContentType string
}
// NewRequestOptions creates new RequestOptions struct // NewRequestOptions creates new RequestOptions struct
func NewRequestOptions(opts ...RequestOption) RequestOptions { func NewRequestOptions(opts ...options.Option) RequestOptions {
options := RequestOptions{} options := RequestOptions{}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
@ -161,7 +131,7 @@ type RequestOptions struct {
} }
// NewOptions creates new options struct // NewOptions creates new options struct
func NewOptions(opts ...Option) Options { func NewOptions(opts ...options.Option) Options {
options := Options{ options := Options{
Context: context.Background(), Context: context.Background(),
ContentType: DefaultContentType, ContentType: DefaultContentType,
@ -172,18 +142,16 @@ func NewOptions(opts ...Option) Options {
Retry: DefaultRetry, Retry: DefaultRetry,
Retries: DefaultRetries, Retries: DefaultRetries,
RequestTimeout: DefaultRequestTimeout, RequestTimeout: DefaultRequestTimeout,
DialTimeout: transport.DefaultDialTimeout, DialTimeout: DefaultDialTimeout,
}, },
Lookup: LookupRoute, Lookup: LookupRoute,
PoolSize: DefaultPoolSize, PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL, PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(), Selector: random.NewSelector(),
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker, Meter: meter.DefaultMeter,
Meter: meter.DefaultMeter, Tracer: tracer.DefaultTracer,
Tracer: tracer.DefaultTracer, Router: router.DefaultRouter,
Router: router.DefaultRouter,
Transport: transport.DefaultTransport,
} }
for _, o := range opts { for _, o := range opts {
@ -193,351 +161,131 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// Broker to be used for pub/sub
func Broker(b broker.Broker) Option {
return func(o *Options) {
o.Broker = b
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Logger to be used for log mesages
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter to be used for metrics
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Codec to be used to encode/decode requests for a given content type
func Codec(contentType string, c codec.Codec) Option {
return func(o *Options) {
o.Codecs[contentType] = c
}
}
// ContentType used by default if not specified
func ContentType(ct string) Option {
return func(o *Options) {
o.ContentType = ct
}
}
// Proxy sets the proxy address // Proxy sets the proxy address
func Proxy(addr string) Option { func Proxy(addr string) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.Proxy = addr return options.Set(src, addr, ".Proxy")
} }
} }
// PoolSize sets the connection pool size // PoolSize sets the connection pool size
func PoolSize(d int) Option { func PoolSize(d int) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.PoolSize = d return options.Set(src, d, ".PoolSize")
} }
} }
// PoolTTL sets the connection pool ttl // PoolTTL sets the connection pool ttl
func PoolTTL(d time.Duration) Option { func PoolTTL(td time.Duration) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.PoolTTL = d return options.Set(src, td, ".PoolTTL")
}
}
// Transport to use for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Register sets the routers register
func Register(r register.Register) Option {
return func(o *Options) {
if o.Router != nil {
o.Router.Init(router.Register(r))
}
}
}
// Router is used to lookup routes for a service
func Router(r router.Router) Option {
return func(o *Options) {
o.Router = r
} }
} }
// Selector is used to select a route // Selector is used to select a route
func Selector(s selector.Selector) Option { func Selector(s selector.Selector) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.Selector = s return options.Set(src, s, ".Selector")
}
}
// Wrap adds a wrapper to the list of options passed into the client
func Wrap(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}
// WrapCall adds a wrapper to the list of CallFunc wrappers
func WrapCall(cw ...CallWrapper) Option {
return func(o *Options) {
o.CallOptions.CallWrappers = append(o.CallOptions.CallWrappers, cw...)
} }
} }
// Backoff is used to set the backoff function used when retrying Calls // Backoff is used to set the backoff function used when retrying Calls
func Backoff(fn BackoffFunc) Option { func Backoff(fn BackoffFunc) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.CallOptions.Backoff = fn return options.Set(src, fn, ".Backoff")
}
}
// Name sets the client name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
} }
} }
// Lookup sets the lookup function to use for resolving service names // Lookup sets the lookup function to use for resolving service names
func Lookup(l LookupFunc) Option { func Lookup(fn LookupFunc) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.Lookup = l return options.Set(src, fn, ".Lookup")
} }
} }
// TLSConfig specifies a *tls.Config // WithCallWrapper sets the retry function to be used when re-trying.
func TLSConfig(t *tls.Config) Option { func WithCallWrapper(fn CallWrapper) options.Option {
return func(o *Options) { return func(src interface{}) error {
// set the internal tls return options.Set(src, fn, ".CallWrappers")
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
// set the transport tls
o.Transport.Init(
transport.TLSConfig(t),
)
} }
} }
// Retries sets the retry count when making the request. // Retries sets the retry count when making the request.
func Retries(i int) Option { func Retries(n int) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.CallOptions.Retries = i return options.Set(src, n, ".Retries")
} }
} }
// Retry sets the retry function to be used when re-trying. // Retry sets the retry function to be used when re-trying.
func Retry(fn RetryFunc) Option { func Retry(fn RetryFunc) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.CallOptions.Retry = fn return options.Set(src, fn, ".Retry")
} }
} }
// RequestTimeout is the request timeout. // RequestTimeout is the request timeout.
func RequestTimeout(d time.Duration) Option { func RequestTimeout(td time.Duration) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.CallOptions.RequestTimeout = d return options.Set(src, td, ".RequestTimeout")
} }
} }
// StreamTimeout sets the stream timeout // StreamTimeout sets the stream timeout
func StreamTimeout(d time.Duration) Option { func StreamTimeout(td time.Duration) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.CallOptions.StreamTimeout = d return options.Set(src, td, ".StreamTimeout")
} }
} }
// DialTimeout sets the dial timeout // DialTimeout sets the dial timeout
func DialTimeout(d time.Duration) Option { func DialTimeout(td time.Duration) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.CallOptions.DialTimeout = d return options.Set(src, td, ".DialTimeout")
} }
} }
// WithExchange sets the exchange to route a message through // WithResponseMetadata is a CallOption which adds metadata.Metadata to Options.CallOptions
// Deprecated func ResponseMetadata(md *metadata.Metadata) options.Option {
func WithExchange(e string) PublishOption { return func(src interface{}) error {
return func(o *PublishOptions) { return options.Set(src, md, ".ResponseMetadata")
o.Exchange = e
} }
} }
// PublishExchange sets the exchange to route a message through // WithRequestMetadata is a CallOption which adds metadata.Metadata to Options.CallOptions
func PublishExchange(e string) PublishOption { func RequestMetadata(md metadata.Metadata) options.Option {
return func(o *PublishOptions) { return func(src interface{}) error {
o.Exchange = e return options.Set(src, metadata.Copy(md), ".RequestMetadata")
} }
} }
// WithBodyOnly publish only message body // AuthToken is a CallOption which overrides the
// DERECATED
func WithBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishBodyOnly publish only message body
func PublishBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishContext sets the context in publish options
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
o.Context = ctx
}
}
// WithContentType specifies call content type
func WithContentType(ct string) CallOption {
return func(o *CallOptions) {
o.ContentType = ct
}
}
// WithAddress sets the remote addresses to use rather than using service discovery
func WithAddress(a ...string) CallOption {
return func(o *CallOptions) {
o.Address = a
}
}
// WithCallWrapper is a CallOption which adds to the existing CallFunc wrappers
func WithCallWrapper(cw ...CallWrapper) CallOption {
return func(o *CallOptions) {
o.CallWrappers = append(o.CallWrappers, cw...)
}
}
// WithBackoff is a CallOption which overrides that which
// set in Options.CallOptions
func WithBackoff(fn BackoffFunc) CallOption {
return func(o *CallOptions) {
o.Backoff = fn
}
}
// WithRetry is a CallOption which overrides that which
// set in Options.CallOptions
func WithRetry(fn RetryFunc) CallOption {
return func(o *CallOptions) {
o.Retry = fn
}
}
// WithRetries is a CallOption which overrides that which
// set in Options.CallOptions
func WithRetries(i int) CallOption {
return func(o *CallOptions) {
o.Retries = i
}
}
// WithRequestTimeout is a CallOption which overrides that which
// set in Options.CallOptions
func WithRequestTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.RequestTimeout = d
}
}
// WithStreamTimeout sets the stream timeout
func WithStreamTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.StreamTimeout = d
}
}
// WithDialTimeout is a CallOption which overrides that which
// set in Options.CallOptions
func WithDialTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.DialTimeout = d
}
}
// WithAuthToken is a CallOption which overrides the
// authorization header with the services own auth token // authorization header with the services own auth token
func WithAuthToken(t string) CallOption { func AuthToken(t string) options.Option {
return func(o *CallOptions) { return func(src interface{}) error {
o.AuthToken = t return options.Set(src, t, ".AuthToken")
} }
} }
// WithNetwork is a CallOption which sets the network attribute // Network is a CallOption which sets the network attribute
func WithNetwork(n string) CallOption { func Network(n string) options.Option {
return func(o *CallOptions) { return func(src interface{}) error {
o.Network = n return options.Set(src, n, ".Network")
}
}
// WithRouter sets the router to use for this call
func WithRouter(r router.Router) CallOption {
return func(o *CallOptions) {
o.Router = r
}
}
// WithSelector sets the selector to use for this call
func WithSelector(s selector.Selector) CallOption {
return func(o *CallOptions) {
o.Selector = s
} }
} }
/*
// WithSelectOptions sets the options to pass to the selector for this call // WithSelectOptions sets the options to pass to the selector for this call
func WithSelectOptions(sops ...selector.SelectOption) CallOption { func WithSelectOptions(sops ...selector.SelectOption) options.Option {
return func(o *CallOptions) { return func(o *CallOptions) {
o.SelectOptions = sops o.SelectOptions = sops
} }
} }
*/
// WithMessageContentType sets the message content type
// Deprecated
func WithMessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.ContentType = ct
}
}
// MessageContentType sets the message content type
func MessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.ContentType = ct
}
}
// StreamingRequest specifies that request is streaming // StreamingRequest specifies that request is streaming
func StreamingRequest(b bool) RequestOption { func StreamingRequest(b bool) options.Option {
return func(o *RequestOptions) { return func(src interface{}) error {
o.Stream = b return options.Set(src, b, ".Stream")
}
}
// RequestContentType specifies request content type
func RequestContentType(ct string) RequestOption {
return func(o *RequestOptions) {
o.ContentType = ct
} }
} }

View File

@ -3,7 +3,7 @@ package client
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/errors" "go.unistack.org/micro/v4/errors"
) )
// RetryFunc that returning either false or a non-nil error will result in the call not being retried // RetryFunc that returning either false or a non-nil error will result in the call not being retried
@ -19,18 +19,32 @@ func RetryNever(ctx context.Context, req Request, retryCount int, err error) (bo
return false, nil return false, nil
} }
// RetryOnError retries a request on a 500 or timeout error // RetryOnError retries a request on a 500 or 408 (timeout) error
func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (bool, error) { func RetryOnError(_ context.Context, _ Request, _ int, err error) (bool, error) {
if err == nil { if err == nil {
return false, nil return false, nil
} }
me := errors.FromError(err) me := errors.FromError(err)
switch me.Code { switch me.Code {
// retry on timeout or internal server error // retry on timeout or internal server error
case 408, 500: case 408, 500:
return true, nil return true, nil
} }
return false, nil return false, nil
} }
// RetryOnErrors retries a request on specified error codes
func RetryOnErrors(codes ...int32) RetryFunc {
return func(_ context.Context, _ Request, _ int, err error) (bool, error) {
if err == nil {
return false, nil
}
me := errors.FromError(err)
for _, code := range codes {
if me.Code == code {
return true, nil
}
}
return false, nil
}
}

70
client/retry_test.go Normal file
View File

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

View File

@ -1,7 +1,7 @@
package client package client
import ( import (
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v4/codec"
) )
type testRequest struct { type testRequest struct {

View File

@ -1,11 +1,11 @@
// Package codec is an interface for encoding messages // Package codec is an interface for encoding messages
package codec package codec // import "go.unistack.org/micro/v4/codec"
import ( import (
"errors" "errors"
"io" "io"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/metadata"
) )
// Message types // Message types
@ -25,9 +25,9 @@ var (
var ( var (
// DefaultMaxMsgSize specifies how much data codec can handle // DefaultMaxMsgSize specifies how much data codec can handle
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec // DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec() DefaultCodec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal // DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
DefaultTagName = "codec" DefaultTagName = "codec"
) )
@ -41,11 +41,11 @@ type MessageType int
// connection. ReadBody may be called with a nil argument to force the // connection. ReadBody may be called with a nil argument to force the
// body to be read and discarded. // body to be read and discarded.
type Codec interface { type Codec interface {
ReadHeader(io.Reader, *Message, MessageType) error ReadHeader(r io.Reader, m *Message, mt MessageType) error
ReadBody(io.Reader, interface{}) error ReadBody(r io.Reader, v interface{}) error
Write(io.Writer, *Message, interface{}) error Write(w io.Writer, m *Message, v interface{}) error
Marshal(interface{}) ([]byte, error) Marshal(v interface{}, opts ...Option) ([]byte, error)
Unmarshal([]byte, interface{}) error Unmarshal(b []byte, v interface{}, opts ...Option) error
String() string String() string
} }
@ -58,7 +58,7 @@ type Message struct {
Method string Method string
Endpoint string Endpoint string
Error string Error string
Id string ID string
Body []byte Body []byte
Type MessageType Type MessageType
} }
@ -67,3 +67,41 @@ type Message struct {
func NewMessage(t MessageType) *Message { func NewMessage(t MessageType) *Message {
return &Message{Type: t, Header: metadata.New(0)} return &Message{Type: t, Header: metadata.New(0)}
} }
// MarshalAppend calls codec.Marshal(v) and returns the data appended to buf.
// If codec implements MarshalAppend, that is called instead.
func MarshalAppend(buf []byte, c Codec, v interface{}, opts ...Option) ([]byte, error) {
if nc, ok := c.(interface {
MarshalAppend([]byte, interface{}, ...Option) ([]byte, error)
}); ok {
return nc.MarshalAppend(buf, v, opts...)
}
mbuf, err := c.Marshal(v, opts...)
if err != nil {
return nil, err
}
return append(buf, mbuf...), nil
}
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}

View File

@ -1,26 +1,26 @@
package transport package codec
import ( import (
"context" "context"
) )
type transportKey struct{} type codecKey struct{}
// FromContext get transport from context // FromContext returns codec from context
func FromContext(ctx context.Context) (Transport, bool) { func FromContext(ctx context.Context) (Codec, bool) {
if ctx == nil { if ctx == nil {
return nil, false return nil, false
} }
c, ok := ctx.Value(transportKey{}).(Transport) c, ok := ctx.Value(codecKey{}).(Codec)
return c, ok return c, ok
} }
// NewContext put transport in context // NewContext put codec in context
func NewContext(ctx context.Context, c Transport) context.Context { func NewContext(ctx context.Context, c Codec) context.Context {
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
return context.WithValue(ctx, transportKey{}, c) return context.WithValue(ctx, codecKey{}, c)
} }
// SetOption returns a function to setup a context with given value // SetOption returns a function to setup a context with given value

35
codec/context_test.go Normal file
View File

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

View File

@ -4,3 +4,42 @@ package codec
type Frame struct { type Frame struct {
Data []byte Data []byte
} }
// NewFrame returns new frame with data
func NewFrame(data []byte) *Frame {
return &Frame{Data: data}
}
// MarshalJSON returns frame data
func (m *Frame) MarshalJSON() ([]byte, error) {
return m.Marshal()
}
// UnmarshalJSON set frame data
func (m *Frame) UnmarshalJSON(data []byte) error {
return m.Unmarshal(data)
}
// ProtoMessage noop func
func (m *Frame) ProtoMessage() {}
// Reset resets frame
func (m *Frame) Reset() {
*m = Frame{}
}
// String returns frame as string
func (m *Frame) String() string {
return string(m.Data)
}
// Marshal returns frame data
func (m *Frame) Marshal() ([]byte, error) {
return m.Data, nil
}
// Unmarshal set frame data
func (m *Frame) Unmarshal(data []byte) error {
m.Data = data
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright 2021 Unistack LLC // Copyright 2021-2023 Unistack LLC
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@ syntax = "proto3";
package micro.codec; package micro.codec;
option cc_enable_arenas = true; option cc_enable_arenas = true;
option go_package = "github.com/unistack-org/micro/v3/codec;codec"; option go_package = "go.unistack.org/micro/v4/codec;codec";
option java_multiple_files = true; option java_multiple_files = true;
option java_outer_classname = "MicroCodec"; option java_outer_classname = "MicroCodec";
option java_package = "micro.codec"; option java_package = "micro.codec";

View File

@ -5,7 +5,9 @@ import (
"io" "io"
) )
type noopCodec struct{} type noopCodec struct {
opts Options
}
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error { func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
return nil return nil
@ -69,11 +71,11 @@ func (c *noopCodec) String() string {
} }
// NewCodec returns new noop codec // NewCodec returns new noop codec
func NewCodec() Codec { func NewCodec(opts ...Option) Codec {
return &noopCodec{} return &noopCodec{opts: NewOptions(opts...)}
} }
func (c *noopCodec) Marshal(v interface{}) ([]byte, error) { func (c *noopCodec) Marshal(v interface{}, opts ...Option) ([]byte, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil
} }
@ -96,7 +98,7 @@ func (c *noopCodec) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v) return json.Marshal(v)
} }
func (c *noopCodec) Unmarshal(d []byte, v interface{}) error { func (c *noopCodec) Unmarshal(d []byte, v interface{}, opts ...Option) error {
if v == nil { if v == nil {
return nil return nil
} }
@ -104,6 +106,9 @@ func (c *noopCodec) Unmarshal(d []byte, v interface{}) error {
case *string: case *string:
*ve = string(d) *ve = string(d)
return nil return nil
case []byte:
copy(ve, d)
return nil
case *[]byte: case *[]byte:
*ve = d *ve = d
return nil return nil

View File

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

View File

@ -1,9 +1,11 @@
package codec package codec
import ( import (
"github.com/unistack-org/micro/v3/logger" "context"
"github.com/unistack-org/micro/v3/meter"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/tracer"
) )
// Option func // Option func
@ -17,6 +19,10 @@ type Options struct {
Logger logger.Logger Logger logger.Logger
// Tracer used for tracing // Tracer used for tracing
Tracer tracer.Tracer Tracer tracer.Tracer
// Context stores additional codec options
Context context.Context
// TagName specifies tag name in struct to control codec
TagName string
// MaxMsgSize specifies max messages size that reads by codec // MaxMsgSize specifies max messages size that reads by codec
MaxMsgSize int MaxMsgSize int
} }
@ -28,6 +34,13 @@ func MaxMsgSize(n int) Option {
} }
} }
// TagName sets the codec tag name in struct
func TagName(n string) Option {
return func(o *Options) {
o.TagName = n
}
}
// Logger sets the logger // Logger sets the logger
func Logger(l logger.Logger) Option { func Logger(l logger.Logger) Option {
return func(o *Options) { return func(o *Options) {
@ -52,10 +65,12 @@ func Meter(m meter.Meter) Option {
// NewOptions returns new options // NewOptions returns new options
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Context: context.Background(),
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer, Tracer: tracer.DefaultTracer,
MaxMsgSize: DefaultMaxMsgSize, MaxMsgSize: DefaultMaxMsgSize,
TagName: DefaultTagName,
} }
for _, o := range opts { for _, o := range opts {

View File

@ -1,19 +1,26 @@
// Package config is an interface for dynamic configuration. // Package config is an interface for dynamic configuration.
package config package config // import "go.unistack.org/micro/v4/config"
import ( import (
"context" "context"
"errors" "errors"
"reflect"
"time" "time"
"go.unistack.org/micro/v4/options"
) )
type Validator interface {
Validate() error
}
// DefaultConfig default config // DefaultConfig default config
var DefaultConfig Config = NewConfig() var DefaultConfig = NewConfig()
// DefaultWatcherMinInterval default min interval for poll changes // DefaultWatcherMinInterval default min interval for poll changes
var DefaultWatcherMinInterval = 5 * time.Second var DefaultWatcherMinInterval = 5 * time.Second
// DefaultWatcherMinInterval default max interval for poll changes // DefaultWatcherMaxInterval default max interval for poll changes
var DefaultWatcherMaxInterval = 9 * time.Second var DefaultWatcherMaxInterval = 9 * time.Second
var ( var (
@ -23,6 +30,8 @@ var (
ErrInvalidStruct = errors.New("invalid struct specified") ErrInvalidStruct = errors.New("invalid struct specified")
// ErrWatcherStopped is returned when source watcher has been stopped // ErrWatcherStopped is returned when source watcher has been stopped
ErrWatcherStopped = errors.New("watcher stopped") ErrWatcherStopped = errors.New("watcher stopped")
// ErrWatcherNotImplemented returned when config does not implement watch
ErrWatcherNotImplemented = errors.New("watcher not implemented")
) )
// Config is an interface abstraction for dynamic configuration // Config is an interface abstraction for dynamic configuration
@ -30,15 +39,15 @@ type Config interface {
// Name returns name of config // Name returns name of config
Name() string Name() string
// Init the config // Init the config
Init(opts ...Option) error Init(opts ...options.Option) error
// Options in the config // Options in the config
Options() Options Options() Options
// Load config from sources // Load config from sources
Load(context.Context, ...LoadOption) error Load(context.Context, ...options.Option) error
// Save config to sources // Save config to sources
Save(context.Context, ...SaveOption) error Save(context.Context, ...options.Option) error
// Watch a config for changes // Watch a config for changes
Watch(context.Context, ...WatchOption) (Watcher, error) Watch(context.Context, ...options.Option) (Watcher, error)
// String returns config type name // String returns config type name
String() string String() string
} }
@ -52,7 +61,7 @@ type Watcher interface {
} }
// Load loads config from config sources // Load loads config from config sources
func Load(ctx context.Context, cs []Config, opts ...LoadOption) error { func Load(ctx context.Context, cs []Config, opts ...options.Option) error {
var err error var err error
for _, c := range cs { for _, c := range cs {
if err = c.Init(); err != nil { if err = c.Init(); err != nil {
@ -64,3 +73,147 @@ func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
} }
return nil return nil
} }
// Validate runs Validate() error func for each struct field
func Validate(ctx context.Context, cfg interface{}) error {
if cfg == nil {
return nil
}
if v, ok := cfg.(Validator); ok {
if err := v.Validate(); err != nil {
return err
}
}
sv := reflect.ValueOf(cfg)
if sv.Kind() == reflect.Ptr {
sv = sv.Elem()
}
if sv.Kind() != reflect.Struct {
return nil
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.IsValid() || len(fld.PkgPath) != 0 {
continue
}
if v, ok := val.Interface().(Validator); ok {
if err := v.Validate(); err != nil {
return err
}
}
switch val.Kind() {
case reflect.Ptr:
if reflect.Indirect(val).Kind() == reflect.Struct {
if err := Validate(ctx, val.Interface()); err != nil {
return err
}
}
case reflect.Struct:
if err := Validate(ctx, val.Interface()); err != nil {
return err
}
}
}
return nil
}
var (
// DefaultBeforeLoad default func that runs before config Load
DefaultBeforeLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeLoad {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" BeforeLoad error "+err.Error())
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
// DefaultAfterLoad default func that runs after config Load
DefaultAfterLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterLoad {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" AfterLoad error "+err.Error())
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
// DefaultBeforeSave default func that runs befora config Save
DefaultBeforeSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeSave {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" BeforeSave error "+err.Error())
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
// DefaultAfterSave default func that runs after config Save
DefaultAfterSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterSave {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" AfterSave error "+err.Error())
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
// DefaultBeforeInit default func that runs befora config Init
DefaultBeforeInit = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeInit {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" BeforeInit error "+err.Error())
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
// DefaultAfterInit default func that runs after config Init
DefaultAfterInit = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterSave {
if fn == nil {
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, c.String()+" AfterInit error "+err.Error())
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
)

View File

@ -22,13 +22,3 @@ func NewContext(ctx context.Context, c Config) context.Context {
} }
return context.WithValue(ctx, configKey{}, c) return context.WithValue(ctx, configKey{}, c)
} }
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

42
config/context_test.go Normal file
View File

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

View File

@ -2,13 +2,17 @@ package config
import ( import (
"context" "context"
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/imdario/mergo" "dario.cat/mergo"
rutil "github.com/unistack-org/micro/v3/util/reflect" "github.com/google/uuid"
"go.unistack.org/micro/v4/options"
mid "go.unistack.org/micro/v4/util/id"
rutil "go.unistack.org/micro/v4/util/reflect"
mtime "go.unistack.org/micro/v4/util/time"
) )
type defaultConfig struct { type defaultConfig struct {
@ -19,18 +23,29 @@ func (c *defaultConfig) Options() Options {
return c.opts return c.opts
} }
func (c *defaultConfig) Init(opts ...Option) error { func (c *defaultConfig) Init(opts ...options.Option) error {
for _, o := range opts { for _, o := range opts {
o(&c.opts) o(&c.opts)
} }
if err := DefaultBeforeInit(c.opts.Context, c); err != nil && !c.opts.AllowFail {
return err
}
if err := DefaultAfterInit(c.opts.Context, c); err != nil && !c.opts.AllowFail {
return err
}
return nil return nil
} }
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error { func (c *defaultConfig) Load(ctx context.Context, opts ...options.Option) error {
for _, fn := range c.opts.BeforeLoad { if c.opts.SkipLoad != nil && c.opts.SkipLoad(ctx, c) {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { return nil
return err }
}
if err := DefaultBeforeLoad(ctx, c); err != nil && !c.opts.AllowFail {
return err
} }
options := NewLoadOptions(opts...) options := NewLoadOptions(opts...)
@ -48,20 +63,25 @@ func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
} }
src, err := rutil.Zero(dst) src, err := rutil.Zero(dst)
if err == nil { if err != nil {
if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil { if !c.opts.AllowFail {
err = mergo.Merge(dst, src, mopts...) return err
} }
if err = DefaultAfterLoad(ctx, c); err != nil && !c.opts.AllowFail {
return err
}
}
if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil {
err = mergo.Merge(dst, src, mopts...)
} }
if err != nil && !c.opts.AllowFail { if err != nil && !c.opts.AllowFail {
return err return err
} }
for _, fn := range c.opts.AfterLoad { if err := DefaultAfterLoad(ctx, c); err != nil && !c.opts.AllowFail {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { return err
return err
}
} }
return nil return nil
@ -72,6 +92,7 @@ func fillValue(value reflect.Value, val string) error {
if !rutil.IsEmpty(value) { if !rutil.IsEmpty(value) {
return nil return nil
} }
switch value.Kind() { switch value.Kind() {
case reflect.Map: case reflect.Map:
t := value.Type() t := value.Type()
@ -110,6 +131,20 @@ func fillValue(value reflect.Value, val string) error {
} }
value.Set(reflect.ValueOf(v)) value.Set(reflect.ValueOf(v))
case reflect.String: case reflect.String:
switch val {
case "micro:generate uuid":
uid, err := uuid.NewRandom()
if err != nil {
return err
}
val = uid.String()
case "micro:generate id":
uid, err := mid.New()
if err != nil {
return err
}
val = uid
}
value.Set(reflect.ValueOf(val)) value.Set(reflect.ValueOf(val))
case reflect.Float32: case reflect.Float32:
v, err := strconv.ParseFloat(val, 32) v, err := strconv.ParseFloat(val, 32)
@ -148,11 +183,26 @@ func fillValue(value reflect.Value, val string) error {
} }
value.Set(reflect.ValueOf(int32(v))) value.Set(reflect.ValueOf(int32(v)))
case reflect.Int64: case reflect.Int64:
v, err := strconv.ParseInt(val, 10, 64) switch {
if err != nil { case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "time":
return err v, err := time.ParseDuration(val)
if err != nil {
return err
}
value.Set(reflect.ValueOf(v))
case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "go.unistack.org/micro/v4/util/time":
v, err := mtime.ParseDuration(val)
if err != nil {
return err
}
value.SetInt(int64(v))
default:
v, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return err
}
value.Set(reflect.ValueOf(v))
} }
value.Set(reflect.ValueOf(v))
case reflect.Uint: case reflect.Uint:
v, err := strconv.ParseUint(val, 10, 0) v, err := strconv.ParseUint(val, 10, 0)
if err != nil { if err != nil {
@ -246,17 +296,17 @@ func fillValues(valueOf reflect.Value, tname string) error {
return nil return nil
} }
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error { func (c *defaultConfig) Save(ctx context.Context, _ ...options.Option) error {
for _, fn := range c.opts.BeforeSave { if c.opts.SkipSave != nil && c.opts.SkipSave(ctx, c) {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { return nil
return err
}
} }
for _, fn := range c.opts.AfterSave { if err := DefaultBeforeSave(ctx, c); err != nil {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { return err
return err }
}
if err := DefaultAfterSave(ctx, c); err != nil {
return err
} }
return nil return nil
@ -270,12 +320,12 @@ func (c *defaultConfig) Name() string {
return c.opts.Name return c.opts.Name
} }
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) { func (c *defaultConfig) Watch(ctx context.Context, opts ...options.Option) (Watcher, error) {
return nil, fmt.Errorf("not implemented") return nil, ErrWatcherNotImplemented
} }
// NewConfig returns new default config source // NewConfig returns new default config source
func NewConfig(opts ...Option) Config { func NewConfig(opts ...options.Option) Config {
options := NewOptions(opts...) options := NewOptions(opts...)
if len(options.StructTag) == 0 { if len(options.StructTag) == 0 {
options.StructTag = "default" options.StructTag = "default"

View File

@ -4,34 +4,61 @@ import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/unistack-org/micro/v3/config" "go.unistack.org/micro/v4/config"
mid "go.unistack.org/micro/v4/util/id"
mtime "go.unistack.org/micro/v4/util/time"
) )
type Cfg struct { type cfg struct {
StringValue string `default:"string_value"`
IgnoreValue string `json:"-"`
StructValue *cfgStructValue
IntValue int `default:"99"`
DurationValue time.Duration `default:"10s"`
MDurationValue mtime.Duration `default:"10s"`
MapValue map[string]bool `default:"key1=true,key2=false"`
UUIDValue string `default:"micro:generate uuid"`
IDValue string `default:"micro:generate id"`
}
type cfgStructValue struct {
StringValue string `default:"string_value"` StringValue string `default:"string_value"`
IgnoreValue string `json:"-"` }
StructValue struct {
StringValue string `default:"string_value"` func (c *cfg) Validate() error {
if c.IntValue != 10 {
return fmt.Errorf("invalid IntValue %d != %d", 10, c.IntValue)
} }
IntValue int `default:"99"` if c.MapValue["key1"] != true {
return fmt.Errorf("invalid MapValue %t != %t", true, c.MapValue["key1"])
}
return nil
}
func (c *cfgStructValue) Validate() error {
if c.StringValue != "string_value" {
return fmt.Errorf("invalid StringValue %s != %s", "string_value", c.StringValue)
}
return nil
} }
func TestDefault(t *testing.T) { func TestDefault(t *testing.T) {
ctx := context.Background() ctx := context.Background()
conf := &Cfg{IntValue: 10} conf := &cfg{IntValue: 10}
blfn := func(ctx context.Context, cfg config.Config) error { blfn := func(_ context.Context, c config.Config) error {
nconf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := c.Options().Struct.(*cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", c.Options())
} }
nconf.StringValue = "before_load" nconf.StringValue = "before_load"
return nil return nil
} }
alfn := func(ctx context.Context, cfg config.Config) error { alfn := func(_ context.Context, c config.Config) error {
nconf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := c.Options().Struct.(*cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", c.Options())
} }
nconf.StringValue = "after_load" nconf.StringValue = "after_load"
return nil return nil
@ -47,6 +74,53 @@ func TestDefault(t *testing.T) {
if conf.StringValue != "after_load" { if conf.StringValue != "after_load" {
t.Fatal("AfterLoad option not working") t.Fatal("AfterLoad option not working")
} }
if len(conf.MapValue) != 2 {
t.Fatalf("map value invalid: %#+v\n", conf.MapValue)
}
if conf.UUIDValue == "" {
t.Fatalf("uuid value empty")
} else if len(conf.UUIDValue) != 36 {
t.Fatalf("uuid value invalid: %s", conf.UUIDValue)
}
if conf.IDValue == "" {
t.Fatalf("id value empty")
} else if len(conf.IDValue) != mid.DefaultSize {
t.Fatalf("id value invalid: %s", conf.IDValue)
}
_ = conf _ = conf
//t.Logf("%#+v\n", conf) // t.Logf("%#+v\n", conf)
}
func TestValidate(t *testing.T) {
ctx := context.Background()
conf := &cfg{IntValue: 10}
cfg := config.NewConfig(config.Struct(conf))
if err := cfg.Init(); err != nil {
t.Fatal(err)
}
if err := cfg.Load(ctx); err != nil {
t.Fatal(err)
}
if err := config.Validate(ctx, conf); err != nil {
t.Fatal(err)
}
}
func TestString(t *testing.T) {
cfg := config.NewConfig()
res := cfg.String()
if res != "default" {
t.Fatalf("string value invalid: %s", res)
}
}
func TestName(t *testing.T) {
cfg := config.NewConfig()
res := cfg.Name()
if res != "" {
t.Fatal("name value not empty")
}
} }

View File

@ -4,10 +4,11 @@ import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v4/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v4/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v4/meter"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/tracer"
) )
// Options hold the config options // Options hold the config options
@ -28,23 +29,28 @@ type Options struct {
Name string Name string
// StructTag name // StructTag name
StructTag string StructTag string
// BeforeSave contains slice of funcs that runs before save // BeforeSave contains slice of funcs that runs before Save
BeforeSave []func(context.Context, Config) error BeforeSave []func(context.Context, Config) error
// AfterLoad contains slice of funcs that runs after load // AfterSave contains slice of funcs that runs after Save
AfterLoad []func(context.Context, Config) error
// BeforeLoad contains slice of funcs that runs before load
BeforeLoad []func(context.Context, Config) error
// AfterSave contains slice of funcs that runs after save
AfterSave []func(context.Context, Config) error AfterSave []func(context.Context, Config) error
// BeforeLoad contains slice of funcs that runs before Load
BeforeLoad []func(context.Context, Config) error
// AfterLoad contains slice of funcs that runs after Load
AfterLoad []func(context.Context, Config) error
// BeforeInit contains slice of funcs that runs before Init
BeforeInit []func(context.Context, Config) error
// AfterInit contains slice of funcs that runs after Init
AfterInit []func(context.Context, Config) error
// AllowFail flag to allow fail in config source // AllowFail flag to allow fail in config source
AllowFail bool AllowFail bool
// SkipLoad runs only if condition returns true
SkipLoad func(context.Context, Config) bool
// SkipSave runs only if condition returns true
SkipSave func(context.Context, Config) bool
} }
// Option function signature
type Option func(o *Options)
// NewOptions new options struct with filed values // NewOptions new options struct with filed values
func NewOptions(opts ...Option) Options { func NewOptions(opts ...options.Option) Options {
options := Options{ options := Options{
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
@ -58,17 +64,16 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// LoadOption function signature
type LoadOption func(o *LoadOptions)
// LoadOptions struct // LoadOptions struct
type LoadOptions struct { type LoadOptions struct {
Struct interface{} Struct interface{}
Context context.Context
Override bool Override bool
Append bool Append bool
} }
func NewLoadOptions(opts ...LoadOption) LoadOptions { // NewLoadOptions create LoadOptions struct with provided opts
func NewLoadOptions(opts ...options.Option) LoadOptions {
options := LoadOptions{} options := LoadOptions{}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
@ -77,43 +82,27 @@ func NewLoadOptions(opts ...LoadOption) LoadOptions {
} }
// LoadOverride override values when load // LoadOverride override values when load
func LoadOverride(b bool) LoadOption { func LoadOverride(b bool) options.Option {
return func(o *LoadOptions) { return func(src interface{}) error {
o.Override = b return options.Set(src, b, ".Override")
} }
} }
// LoadAppend override values when load // LoadAppend override values when load
func LoadAppend(b bool) LoadOption { func LoadAppend(b bool) options.Option {
return func(o *LoadOptions) { return func(src interface{}) error {
o.Append = b return options.Set(src, b, ".Append")
} }
} }
// LoadStruct override struct for loading
func LoadStruct(src interface{}) LoadOption {
return func(o *LoadOptions) {
o.Struct = src
}
}
// SaveOption function signature
type SaveOption func(o *SaveOptions)
// SaveOptions struct // SaveOptions struct
type SaveOptions struct { type SaveOptions struct {
Struct interface{} Struct interface{}
} Context context.Context
// SaveStruct override struct for save to config
func SaveStruct(src interface{}) SaveOption {
return func(o *SaveOptions) {
o.Struct = src
}
} }
// NewSaveOptions fill SaveOptions struct // NewSaveOptions fill SaveOptions struct
func NewSaveOptions(opts ...SaveOption) SaveOptions { func NewSaveOptions(opts ...options.Option) SaveOptions {
options := SaveOptions{} options := SaveOptions{}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
@ -122,86 +111,65 @@ func NewSaveOptions(opts ...SaveOption) SaveOptions {
} }
// AllowFail allows config source to fail // AllowFail allows config source to fail
func AllowFail(b bool) Option { func AllowFail(b bool) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.AllowFail = b return options.Set(src, b, ".AllowFail")
}
}
// BeforeInit run funcs before config Init
func BeforeInit(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".BeforeInit")
}
}
// AfterInit run funcs after config Init
func AfterInit(fn ...func(context.Context, Config) error) options.Option {
return func(src interface{}) error {
return options.Set(src, fn, ".AfterInit")
} }
} }
// BeforeLoad run funcs before config load // BeforeLoad run funcs before config load
func BeforeLoad(fn ...func(context.Context, Config) error) Option { func BeforeLoad(fn ...func(context.Context, Config) error) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.BeforeLoad = fn return options.Set(src, fn, ".BeforeLoad")
} }
} }
// AfterLoad run funcs after config load // AfterLoad run funcs after config load
func AfterLoad(fn ...func(context.Context, Config) error) Option { func AfterLoad(fn ...func(context.Context, Config) error) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.AfterLoad = fn return options.Set(src, fn, ".AfterLoad")
} }
} }
// BeforeSave run funcs before save // BeforeSave run funcs before save
func BeforeSave(fn ...func(context.Context, Config) error) Option { func BeforeSave(fn ...func(context.Context, Config) error) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.BeforeSave = fn return options.Set(src, fn, ".BeforeSave")
} }
} }
// AfterSave run fncs after save // AfterSave run fncs after save
func AfterSave(fn ...func(context.Context, Config) error) Option { func AfterSave(fn ...func(context.Context, Config) error) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.AfterSave = fn return options.Set(src, fn, ".AfterSave")
}
}
// Context pass context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Codec sets the source codec
func Codec(c codec.Codec) Option {
return func(o *Options) {
o.Codec = c
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Tracer to be used for tracing
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
} }
} }
// Struct used as config // Struct used as config
func Struct(v interface{}) Option { func Struct(v interface{}) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.Struct = v return options.Set(src, v, ".Struct")
} }
} }
// StructTag sets the struct tag that used for filling // StructTag sets the struct tag that used for filling
func StructTag(name string) Option { func StructTag(name string) options.Option {
return func(o *Options) { return func(src interface{}) error {
o.StructTag = name return options.Set(src, name, ".StructTag")
}
}
// Name sets the name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
} }
} }
@ -209,19 +177,18 @@ func Name(n string) Option {
type WatchOptions struct { type WatchOptions struct {
// Context used by non default options // Context used by non default options
Context context.Context Context context.Context
// Coalesce multiple events to one // Struct for filling
Coalesce bool Struct interface{}
// MinInterval specifies the min time.Duration interval for poll changes // MinInterval specifies the min time.Duration interval for poll changes
MinInterval time.Duration MinInterval time.Duration
// MaxInterval specifies the max time.Duration interval for poll changes // MaxInterval specifies the max time.Duration interval for poll changes
MaxInterval time.Duration MaxInterval time.Duration
// Struct for filling // Coalesce multiple events to one
Struct interface{} Coalesce bool
} }
type WatchOption func(*WatchOptions) // NewWatchOptions create WatchOptions struct with provided opts
func NewWatchOptions(opts ...options.Option) WatchOptions {
func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{ options := WatchOptions{
Context: context.Background(), Context: context.Background(),
MinInterval: DefaultWatcherMinInterval, MinInterval: DefaultWatcherMinInterval,
@ -233,31 +200,20 @@ func NewWatchOptions(opts ...WatchOption) WatchOptions {
return options return options
} }
// WatchContext pass context // Coalesce controls watch event combining
func WatchContext(ctx context.Context) WatchOption { func WatchCoalesce(b bool) options.Option {
return func(o *WatchOptions) { return func(src interface{}) error {
o.Context = ctx return options.Set(src, b, ".Coalesce")
}
}
// WatchCoalesce controls watch event combining
func WatchCoalesce(b bool) WatchOption {
return func(o *WatchOptions) {
o.Coalesce = b
} }
} }
// WatchInterval specifies min and max time.Duration for pulling changes // WatchInterval specifies min and max time.Duration for pulling changes
func WatchInterval(min, max time.Duration) WatchOption { func WatchInterval(min, max time.Duration) options.Option {
return func(o *WatchOptions) { return func(src interface{}) error {
o.MinInterval = min var err error
o.MaxInterval = max if err = options.Set(src, min, ".MinInterval"); err == nil {
} err = options.Set(src, max, ".MaxInterval")
} }
return err
// WatchStruct overrides struct for fill
func WatchStruct(src interface{}) WatchOption {
return func(o *WatchOptions) {
o.Struct = src
} }
} }

24
context_test.go Normal file
View File

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

159
database/dsn.go Normal file
View File

@ -0,0 +1,159 @@
package database
import (
"crypto/tls"
"errors"
"fmt"
"net/url"
"strings"
)
var (
ErrInvalidDSNAddr = errors.New("invalid dsn addr")
ErrInvalidDSNUnescaped = errors.New("dsn must be escaped")
ErrInvalidDSNNoSlash = errors.New("dsn must contains slash")
)
type Config struct {
TLSConfig *tls.Config
Username string
Password string
Scheme string
Host string
Port string
Database string
Params []string
}
func (cfg *Config) FormatDSN() string {
var s strings.Builder
if len(cfg.Scheme) > 0 {
s.WriteString(cfg.Scheme + "://")
}
// [username[:password]@]
if len(cfg.Username) > 0 {
s.WriteString(cfg.Username)
if len(cfg.Password) > 0 {
s.WriteByte(':')
s.WriteString(url.PathEscape(cfg.Password))
}
s.WriteByte('@')
}
// [host:port]
if len(cfg.Host) > 0 {
s.WriteString(cfg.Host)
if len(cfg.Port) > 0 {
s.WriteByte(':')
s.WriteString(cfg.Port)
}
}
// /dbname
s.WriteByte('/')
s.WriteString(url.PathEscape(cfg.Database))
for i := 0; i < len(cfg.Params); i += 2 {
if i == 0 {
s.WriteString("?")
} else {
s.WriteString("&")
}
s.WriteString(cfg.Params[i])
s.WriteString("=")
s.WriteString(cfg.Params[i+1])
}
return s.String()
}
func ParseDSN(dsn string) (*Config, error) {
cfg := &Config{}
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find last '/' that goes before dbname
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] != '/' {
continue
}
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// Find the first ':' in dsn
for j = i; j >= 0; j-- {
if dsn[j] == ':' {
cfg.Scheme = dsn[0:j]
}
}
// [username[:password]@][host]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the second ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
if cfg.Scheme == dsn[:k] {
continue
}
var err error
cfg.Password, err = url.PathUnescape(dsn[k+1 : j])
if err != nil {
return nil, err
}
break
}
}
cfg.Username = dsn[len(cfg.Scheme)+3 : k]
break
}
}
for k = j + 1; k < i; k++ {
if dsn[k] == ':' {
cfg.Host = dsn[j+1 : k]
cfg.Port = dsn[k+1 : i]
break
}
}
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
parts := strings.Split(dsn[j+1:], "&")
cfg.Params = make([]string, 0, len(parts)*2)
for _, p := range parts {
k, v, found := strings.Cut(p, "=")
if !found {
continue
}
cfg.Params = append(cfg.Params, k, v)
}
break
}
}
var err error
dbname := dsn[i+1 : j]
if cfg.Database, err = url.PathUnescape(dbname); err != nil {
return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err)
}
break
}
if !foundSlash && len(dsn) > 0 {
return nil, ErrInvalidDSNNoSlash
}
return cfg, nil
}

31
database/dsn_test.go Normal file
View File

@ -0,0 +1,31 @@
package database
import (
"net/url"
"testing"
)
func TestParseDSN(t *testing.T) {
cfg, err := ParseDSN("postgres://username:p@ssword#@host:12345/dbname?key1=val2&key2=val2")
if err != nil {
t.Fatal(err)
}
if cfg.Password != "p@ssword#" {
t.Fatalf("parsing error")
}
}
func TestFormatDSN(t *testing.T) {
src := "postgres://username:p@ssword#@host:12345/dbname?key1=val2&key2=val2"
cfg, err := ParseDSN(src)
if err != nil {
t.Fatal(err)
}
dst, err := url.PathUnescape(cfg.FormatDSN())
if err != nil {
t.Fatal(err)
}
if src != dst {
t.Fatalf("\n%s\n%s", src, dst)
}
}

View File

@ -1,11 +1,14 @@
// Package errors provides a way to return detailed information // Package errors provides a way to return detailed information
// for an RPC request error. The error is normally JSON encoded. // for an RPC request error. The error is normally JSON encoded.
package errors package errors // import "go.unistack.org/micro/v4/errors"
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings"
) )
var ( var (
@ -37,8 +40,8 @@ var (
// Error type // Error type
type Error struct { type Error struct {
// Id holds error id or service, usually someting like my_service or uuid // ID holds error id or service, usually someting like my_service or id
Id string ID string
// Detail holds some useful details about error // Detail holds some useful details about error
Detail string Detail string
// Status usually holds text of http status // Status usually holds text of http status
@ -53,10 +56,26 @@ func (e *Error) Error() string {
return string(b) return string(b)
} }
/*
// Generator struct holds id of error
type Generator struct {
id string
}
// Generator can emit new error with static id
func NewGenerator(id string) *Generator {
return &Generator{id: id}
}
func (g *Generator) BadRequest(format string, args ...interface{}) error {
return BadRequest(g.id, format, args...)
}
*/
// New generates a custom error // New generates a custom error
func New(id, detail string, code int32) error { func New(id, detail string, code int32) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: code, Code: code,
Detail: detail, Detail: detail,
Status: http.StatusText(int(code)), Status: http.StatusText(int(code)),
@ -66,130 +85,130 @@ func New(id, detail string, code int32) error {
// Parse tries to parse a JSON string into an error. If that // Parse tries to parse a JSON string into an error. If that
// fails, it will set the given string as the error detail. // fails, it will set the given string as the error detail.
func Parse(err string) *Error { func Parse(err string) *Error {
e := new(Error) e := &Error{}
errr := json.Unmarshal([]byte(err), e) nerr := json.Unmarshal([]byte(err), e)
if errr != nil { if nerr != nil {
e.Detail = err e.Detail = err
} }
return e return e
} }
// BadRequest generates a 400 error. // BadRequest generates a 400 error.
func BadRequest(id, format string, a ...interface{}) error { func BadRequest(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 400, Code: 400,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(400), Status: http.StatusText(400),
} }
} }
// Unauthorized generates a 401 error. // Unauthorized generates a 401 error.
func Unauthorized(id, format string, a ...interface{}) error { func Unauthorized(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 401, Code: 401,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(401), Status: http.StatusText(401),
} }
} }
// Forbidden generates a 403 error. // Forbidden generates a 403 error.
func Forbidden(id, format string, a ...interface{}) error { func Forbidden(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 403, Code: 403,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(403), Status: http.StatusText(403),
} }
} }
// NotFound generates a 404 error. // NotFound generates a 404 error.
func NotFound(id, format string, a ...interface{}) error { func NotFound(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 404, Code: 404,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(404), Status: http.StatusText(404),
} }
} }
// MethodNotAllowed generates a 405 error. // MethodNotAllowed generates a 405 error.
func MethodNotAllowed(id, format string, a ...interface{}) error { func MethodNotAllowed(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 405, Code: 405,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(405), Status: http.StatusText(405),
} }
} }
// Timeout generates a 408 error. // Timeout generates a 408 error.
func Timeout(id, format string, a ...interface{}) error { func Timeout(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 408, Code: 408,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(408), Status: http.StatusText(408),
} }
} }
// Conflict generates a 409 error. // Conflict generates a 409 error.
func Conflict(id, format string, a ...interface{}) error { func Conflict(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 409, Code: 409,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(409), Status: http.StatusText(409),
} }
} }
// InternalServerError generates a 500 error. // InternalServerError generates a 500 error.
func InternalServerError(id, format string, a ...interface{}) error { func InternalServerError(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 500, Code: 500,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(500), Status: http.StatusText(500),
} }
} }
// NotImplemented generates a 501 error // NotImplemented generates a 501 error
func NotImplemented(id, format string, a ...interface{}) error { func NotImplemented(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 501, Code: 501,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(501), Status: http.StatusText(501),
} }
} }
// BadGateway generates a 502 error // BadGateway generates a 502 error
func BadGateway(id, format string, a ...interface{}) error { func BadGateway(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 502, Code: 502,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(502), Status: http.StatusText(502),
} }
} }
// ServiceUnavailable generates a 503 error // ServiceUnavailable generates a 503 error
func ServiceUnavailable(id, format string, a ...interface{}) error { func ServiceUnavailable(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 503, Code: 503,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(503), Status: http.StatusText(503),
} }
} }
// GatewayTimeout generates a 504 error // GatewayTimeout generates a 504 error
func GatewayTimeout(id, format string, a ...interface{}) error { func GatewayTimeout(id, format string, args ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 504, Code: 504,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, args...),
Status: http.StatusText(504), Status: http.StatusText(504),
} }
} }
@ -214,6 +233,27 @@ func Equal(err1 error, err2 error) bool {
return true return true
} }
// CodeIn return true if err has specified code
func CodeIn(err interface{}, codes ...int32) bool {
var code int32
switch verr := err.(type) {
case *Error:
code = verr.Code
case int32:
code = verr
default:
return false
}
for _, check := range codes {
if code == check {
return true
}
}
return false
}
// FromError try to convert go error to *Error // FromError try to convert go error to *Error
func FromError(err error) *Error { func FromError(err error) *Error {
if verr, ok := err.(*Error); ok && verr != nil { if verr, ok := err.(*Error); ok && verr != nil {
@ -222,3 +262,81 @@ func FromError(err error) *Error {
return Parse(err.Error()) return Parse(err.Error())
} }
// MarshalJSON returns error data
func (e *Error) MarshalJSON() ([]byte, error) {
return e.Marshal()
}
// UnmarshalJSON set error data
func (e *Error) UnmarshalJSON(data []byte) error {
return e.Unmarshal(data)
}
// ProtoMessage noop func
func (e *Error) ProtoMessage() {}
// Reset resets error
func (e *Error) Reset() {
*e = Error{}
}
// String returns error as string
func (e *Error) String() string {
return fmt.Sprintf(`{"id":"%s","detail":"%s","status":"%s","code":%d}`, addslashes(e.ID), addslashes(e.Detail), addslashes(e.Status), e.Code)
}
// Marshal returns error data
func (e *Error) Marshal() ([]byte, error) {
return []byte(e.String()), nil
}
// Unmarshal set error data
func (e *Error) Unmarshal(data []byte) error {
str := string(data)
if len(data) < 41 {
return fmt.Errorf("invalid data")
}
parts := strings.FieldsFunc(str[1:len(str)-1], func(r rune) bool {
return r == ','
})
for _, part := range parts {
nparts := strings.FieldsFunc(part, func(r rune) bool {
return r == ':'
})
for idx := 0; idx < len(nparts)/2; idx += 2 {
val := strings.Trim(nparts[idx+1], `"`)
if len(val) == 0 {
continue
}
switch {
case nparts[idx] == `"id"`:
e.ID = val
case nparts[idx] == `"detail"`:
e.Detail = val
case nparts[idx] == `"status"`:
e.Status = val
case nparts[idx] == `"code"`:
c, err := strconv.ParseInt(val, 10, 32)
if err != nil {
return err
}
e.Code = int32(c)
}
idx++
}
}
return nil
}
func addslashes(str string) string {
var buf bytes.Buffer
for _, char := range str {
switch char {
case '\'', '"', '\\':
buf.WriteRune('\\')
}
buf.WriteRune(char)
}
return buf.String()
}

31
errors/errors.proto Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2021-2023 Unistack LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package micro.errors;
option cc_enable_arenas = true;
option go_package = "go.unistack.org/micro/v4/errors;errors";
option java_multiple_files = true;
option java_outer_classname = "MicroErrors";
option java_package = "micro.errors";
option objc_class_prefix = "MERRORS";
message Error {
string id = 1;
string detail = 2;
string status = 3;
uint32 code = 4;
}

View File

@ -1,20 +1,43 @@
package errors package errors
import ( import (
"encoding/json"
er "errors" er "errors"
"fmt"
"net/http" "net/http"
"testing" "testing"
) )
func TestMarshalJSON(t *testing.T) {
e := InternalServerError("id", "err: %v", fmt.Errorf("err: %v", `xxx: "UNIX_TIMESTAMP": invalid identifier`))
_, err := json.Marshal(e)
if err != nil {
t.Fatal(err)
}
}
func TestEmpty(t *testing.T) {
msg := "test"
var err *Error
err = FromError(fmt.Errorf(msg))
if err.Detail != msg {
t.Fatalf("invalid error %v", err)
}
err = FromError(fmt.Errorf(`{"id":"","detail":"%s","status":"%s","code":0}`, msg, msg))
if err.Detail != msg || err.Status != msg {
t.Fatalf("invalid error %#+v", err)
}
}
func TestFromError(t *testing.T) { func TestFromError(t *testing.T) {
err := NotFound("go.micro.test", "%s", "example") err := NotFound("go.micro.test", "%s", "example")
merr := FromError(err) merr := FromError(err)
if merr.Id != "go.micro.test" || merr.Code != 404 { if merr.ID != "go.micro.test" || merr.Code != 404 {
t.Fatalf("invalid conversation %v != %v", err, merr) t.Fatalf("invalid conversation %v != %v", err, merr)
} }
err = er.New(err.Error()) err = er.New(err.Error())
merr = FromError(err) merr = FromError(err)
if merr.Id != "go.micro.test" || merr.Code != 404 { if merr.ID != "go.micro.test" || merr.Code != 404 {
t.Fatalf("invalid conversation %v != %v", err, merr) t.Fatalf("invalid conversation %v != %v", err, merr)
} }
} }
@ -36,7 +59,7 @@ func TestEqual(t *testing.T) {
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {
testData := []*Error{ testData := []*Error{
{ {
Id: "test", ID: "test",
Code: 500, Code: 500,
Detail: "Internal server error", Detail: "Internal server error",
Status: http.StatusText(500), Status: http.StatusText(500),
@ -44,7 +67,7 @@ func TestErrors(t *testing.T) {
} }
for _, e := range testData { for _, e := range testData {
ne := New(e.Id, e.Detail, e.Code) ne := New(e.ID, e.Detail, e.Code)
if e.Error() != ne.Error() { if e.Error() != ne.Error() {
t.Fatalf("Expected %s got %s", e.Error(), ne.Error()) t.Fatalf("Expected %s got %s", e.Error(), ne.Error())
@ -56,8 +79,8 @@ func TestErrors(t *testing.T) {
t.Fatalf("Expected error got nil %v", pe) t.Fatalf("Expected error got nil %v", pe)
} }
if pe.Id != e.Id { if pe.ID != e.ID {
t.Fatalf("Expected %s got %s", e.Id, pe.Id) t.Fatalf("Expected %s got %s", e.ID, pe.ID)
} }
if pe.Detail != e.Detail { if pe.Detail != e.Detail {
@ -73,3 +96,19 @@ func TestErrors(t *testing.T) {
} }
} }
} }
func TestCodeIn(t *testing.T) {
err := InternalServerError("id", "%s", "msg")
if ok := CodeIn(err, 400, 500); !ok {
t.Fatalf("CodeIn not works: %v", err)
}
if ok := CodeIn(err.(*Error).Code, 500); !ok {
t.Fatalf("CodeIn not works: %v", err)
}
if ok := CodeIn(err, 100); ok {
t.Fatalf("CodeIn not works: %v", err)
}
}

View File

@ -1,27 +0,0 @@
package micro
import (
"context"
"github.com/unistack-org/micro/v3/client"
)
// Event is used to publish messages to a topic
type Event interface {
// Publish publishes a message to the event topic
Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error
}
type event struct {
c client.Client
topic string
}
// NewEvent creates a new event publisher
func NewEvent(topic string, c client.Client) Event {
return &event{c, topic}
}
func (e *event) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error {
return e.c.Publish(ctx, e.c.NewMessage(e.topic, msg), opts...)
}

View File

@ -22,13 +22,3 @@ func NewContext(ctx context.Context, f Flow) context.Context {
} }
return context.WithValue(ctx, flowKey{}, f) return context.WithValue(ctx, flowKey{}, f)
} }
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

42
flow/context_test.go Normal file
View File

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

View File

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

View File

@ -3,16 +3,17 @@ package flow
import ( import (
"context" "context"
"fmt" "fmt"
"path/filepath"
"sync" "sync"
"github.com/google/uuid"
"github.com/silas/dag" "github.com/silas/dag"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v4/client"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v4/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v4/logger"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/metadata"
"github.com/unistack-org/micro/v3/store" "go.unistack.org/micro/v4/options"
moptions "go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/store"
"go.unistack.org/micro/v4/util/id"
) )
type microFlow struct { type microFlow struct {
@ -20,13 +21,13 @@ type microFlow struct {
} }
type microWorkflow struct { type microWorkflow struct {
id string
g *dag.AcyclicGraph
init bool
sync.RWMutex
opts Options opts Options
g *dag.AcyclicGraph
steps map[string]Step steps map[string]Step
id string
status Status status Status
sync.RWMutex
init bool
} }
func (w *microWorkflow) ID() string { func (w *microWorkflow) ID() string {
@ -149,22 +150,22 @@ func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) {
return steps, nil return steps, nil
} }
func (w *microWorkflow) Abort(ctx context.Context, eid string) error { func (w *microWorkflow) Abort(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+id)
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())}) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
} }
func (w *microWorkflow) Suspend(ctx context.Context, eid string) error { func (w *microWorkflow) Suspend(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+id)
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())}) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
} }
func (w *microWorkflow) Resume(ctx context.Context, eid string) error { func (w *microWorkflow) Resume(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+id)
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())}) return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
} }
func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error) { func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...options.Option) (string, error) {
w.Lock() w.Lock()
if !w.init { if !w.init {
if err := w.g.Validate(); err != nil { if err := w.g.Validate(); err != nil {
@ -176,21 +177,20 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
} }
w.Unlock() w.Unlock()
uid, err := uuid.NewRandom() eid, err := id.New()
if err != nil { if err != nil {
return "", err return "", err
} }
eid := uid.String()
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid)) stepStore := store.NewNamespaceStore(w.opts.Store, "steps"+w.opts.Store.Options().Separator+eid)
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid)) workflowStore := store.NewNamespaceStore(w.opts.Store, "workflows"+w.opts.Store.Options().Separator+eid)
options := NewExecuteOptions(opts...) options := NewExecuteOptions(opts...)
steps, err := w.getSteps(options.Start, options.Reverse) steps, err := w.getSteps(options.Start, options.Reverse)
if err != nil { if err != nil {
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr) w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
} }
return "", err return "", err
} }
@ -202,25 +202,25 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
nctx, cancel := context.WithCancel(ctx) nctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
nopts := make([]ExecuteOption, 0, len(opts)+5) nopts := make([]moptions.Option, 0, len(opts)+5)
nopts = append(nopts, nopts = append(nopts,
ExecuteClient(w.opts.Client), moptions.Client(w.opts.Client),
ExecuteTracer(w.opts.Tracer), moptions.Tracer(w.opts.Tracer),
ExecuteLogger(w.opts.Logger), moptions.Logger(w.opts.Logger),
ExecuteMeter(w.opts.Meter), moptions.Meter(w.opts.Meter),
) )
nopts = append(nopts, opts...) nopts = append(nopts, opts...)
done := make(chan struct{}) done := make(chan struct{})
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr) w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
return eid, werr return eid, werr
} }
for idx := range steps { for idx := range steps {
for nidx := range steps[idx] { for nidx := range steps[idx] {
cstep := steps[idx][nidx] cstep := steps[idx][nidx]
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
return eid, werr return eid, werr
} }
} }
@ -239,76 +239,75 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return return
} }
if w.opts.Logger.V(logger.TraceLevel) { if w.opts.Logger.V(logger.TraceLevel) {
w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx]) w.opts.Logger.Trace(nctx, fmt.Sprintf("step will be executed %v", steps[idx][nidx]))
} }
cstep := steps[idx][nidx] cstep := steps[idx][nidx]
// nolint: nestif
if len(cstep.Requires()) == 0 { if len(cstep.Requires()) == 0 {
wg.Add(1) wg.Add(1)
go func(step Step) { go func(step Step) {
defer wg.Done() defer wg.Done()
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "req"), req); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"req", req); werr != nil {
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr cherr <- werr
return return
} }
rsp, serr := step.Execute(nctx, req, nopts...) rsp, serr := step.Execute(nctx, req, nopts...)
if serr != nil { if serr != nil {
step.SetStatus(StatusFailure) step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
} }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
} }
cherr <- serr cherr <- serr
return return
} else { }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), rsp); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil { if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
cherr <- werr cherr <- werr
return return
}
} }
}(cstep) }(cstep)
wg.Wait() wg.Wait()
} else { } else {
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "req"), req); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"req", req); werr != nil {
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr cherr <- werr
return return
} }
rsp, serr := cstep.Execute(nctx, req, nopts...) rsp, serr := cstep.Execute(nctx, req, nopts...)
if serr != nil { if serr != nil {
cstep.SetStatus(StatusFailure) cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
} }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
} }
cherr <- serr cherr <- serr
return return
} else { }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), rsp); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr) w.opts.Logger.Error(ctx, "store write error", "error", werr.Error())
cherr <- werr cherr <- werr
return return
} }
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil { if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
cherr <- werr cherr <- werr
return return
}
} }
} }
} }
@ -320,7 +319,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return eid, nil return eid, nil
} }
logger.Tracef(ctx, "wait for finish or error") w.opts.Logger.Trace(ctx, "wait for finish or error")
select { select {
case <-nctx.Done(): case <-nctx.Done():
err = nctx.Err() err = nctx.Err()
@ -330,31 +329,29 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
close(cherr) close(cherr)
case <-chstatus: case <-chstatus:
close(chstatus) close(chstatus)
return uid.String(), nil return eid, nil
} }
switch { switch {
case nctx.Err() != nil: case nctx.Err() != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr) w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
} }
break
case err == nil: case err == nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr) w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
} }
break
case err != nil: case err != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil { if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr) w.opts.Logger.Error(w.opts.Context, "store write error", "error", werr.Error())
} }
break
} }
return uid.String(), err return eid, err
} }
func NewFlow(opts ...Option) Flow { // NewFlow create new flow
func NewFlow(opts ...options.Option) Flow {
options := NewOptions(opts...) options := NewOptions(opts...)
return &microFlow{opts: options} return &microFlow{opts: options}
} }
@ -363,7 +360,7 @@ func (f *microFlow) Options() Options {
return f.opts return f.opts
} }
func (f *microFlow) Init(opts ...Option) error { func (f *microFlow) Init(opts ...options.Option) error {
for _, o := range opts { for _, o := range opts {
o(&f.opts) o(&f.opts)
} }
@ -430,11 +427,11 @@ func (f *microFlow) WorkflowLoad(ctx context.Context, id string) (Workflow, erro
} }
type microCallStep struct { type microCallStep struct {
opts StepOptions
service string
method string
rsp *Message rsp *Message
req *Message req *Message
service string
method string
opts StepOptions
status Status status Status
} }
@ -492,18 +489,20 @@ func (s *microCallStep) SetStatus(status Status) {
s.status = status s.status = status
} }
func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) { func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...options.Option) (*Message, error) {
options := NewExecuteOptions(opts...) options := NewExecuteOptions(opts...)
if options.Client == nil { if options.Client == nil {
return nil, ErrMissingClient return nil, ErrMissingClient
} }
rsp := &codec.Frame{} rsp := &codec.Frame{}
copts := []client.CallOption{client.WithRetries(0)} copts := []moptions.Option{client.Retries(0)}
if options.Timeout > 0 { if options.Timeout > 0 {
copts = append(copts, client.WithRequestTimeout(options.Timeout), client.WithDialTimeout(options.Timeout)) copts = append(copts,
client.RequestTimeout(options.Timeout),
client.DialTimeout(options.Timeout))
} }
nctx := metadata.NewOutgoingContext(ctx, req.Header) nctx := metadata.NewOutgoingContext(ctx, req.Header)
err := options.Client.Call(nctx, options.Client.NewRequest(s.service, s.method, &codec.Frame{Data: req.Body}), rsp) err := options.Client.Call(nctx, options.Client.NewRequest(s.service, s.method, &codec.Frame{Data: req.Body}), rsp, copts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -512,10 +511,10 @@ func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...Execu
} }
type microPublishStep struct { type microPublishStep struct {
opts StepOptions
topic string
req *Message req *Message
rsp *Message rsp *Message
topic string
opts StepOptions
status Status status Status
} }
@ -554,7 +553,7 @@ func (s *microPublishStep) String() string {
if s.opts.ID != "" { if s.opts.ID != "" {
return s.opts.ID return s.opts.ID
} }
return fmt.Sprintf("%s", s.topic) return s.topic
} }
func (s *microPublishStep) Name() string { func (s *microPublishStep) Name() string {
@ -573,16 +572,18 @@ func (s *microPublishStep) SetStatus(status Status) {
s.status = status s.status = status
} }
func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) { func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...options.Option) (*Message, error) {
return nil, nil return nil, nil
} }
func NewCallStep(service string, name string, method string, opts ...StepOption) Step { // NewCallStep create new step with client.Call
func NewCallStep(service string, name string, method string, opts ...options.Option) Step {
options := NewStepOptions(opts...) options := NewStepOptions(opts...)
return &microCallStep{service: service, method: name + "." + method, opts: options} return &microCallStep{service: service, method: name + "." + method, opts: options}
} }
func NewPublishStep(topic string, opts ...StepOption) Step { // NewPublishStep create new step with client.Publish
func NewPublishStep(topic string, opts ...options.Option) Step {
options := NewStepOptions(opts...) options := NewStepOptions(opts...)
return &microPublishStep{topic: topic, opts: options} return &microPublishStep{topic: topic, opts: options}
} }

View File

@ -1,5 +1,5 @@
// Package flow is an interface used for saga pattern microservice workflow // Package flow is an interface used for saga pattern microservice workflow
package flow package flow // import "go.unistack.org/micro/v4/flow"
import ( import (
"context" "context"
@ -7,11 +7,14 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
) )
var ( var (
// ErrStepNotExists returns when step not found
ErrStepNotExists = errors.New("step not exists") ErrStepNotExists = errors.New("step not exists")
// ErrMissingClient returns when client.Client is missing
ErrMissingClient = errors.New("client not set") ErrMissingClient = errors.New("client not set")
) )
@ -36,6 +39,7 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// Message used to transfer data between steps
type Message struct { type Message struct {
Header metadata.Metadata Header metadata.Metadata
Body RawMessage Body RawMessage
@ -48,7 +52,7 @@ type Step interface {
// Endpoint returns rpc endpoint service_name.service_method or broker topic // Endpoint returns rpc endpoint service_name.service_method or broker topic
Endpoint() string Endpoint() string
// Execute step run // Execute step run
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) Execute(ctx context.Context, req *Message, opts ...options.Option) (*Message, error)
// Requires returns dependent steps // Requires returns dependent steps
Requires() []string Requires() []string
// Options returns step options // Options returns step options
@ -67,6 +71,7 @@ type Step interface {
Response() *Message Response() *Message
} }
// Status contains step current status
type Status int type Status int
func (status Status) String() string { func (status Status) String() string {
@ -74,15 +79,22 @@ func (status Status) String() string {
} }
const ( const (
// StatusPending step waiting to start
StatusPending Status = iota StatusPending Status = iota
// StatusRunning step is running
StatusRunning StatusRunning
// StatusFailure step competed with error
StatusFailure StatusFailure
// StatusSuccess step completed without error
StatusSuccess StatusSuccess
// StatusAborted step aborted while it running
StatusAborted StatusAborted
// StatusSuspend step suspended
StatusSuspend StatusSuspend
) )
var ( var (
// StatusString contains map status => string
StatusString = map[Status]string{ StatusString = map[Status]string{
StatusPending: "StatusPending", StatusPending: "StatusPending",
StatusRunning: "StatusRunning", StatusRunning: "StatusRunning",
@ -91,6 +103,7 @@ var (
StatusAborted: "StatusAborted", StatusAborted: "StatusAborted",
StatusSuspend: "StatusSuspend", StatusSuspend: "StatusSuspend",
} }
// StringStatus contains map string => status
StringStatus = map[string]Status{ StringStatus = map[string]Status{
"StatusPending": StatusPending, "StatusPending": StatusPending,
"StatusRunning": StatusRunning, "StatusRunning": StatusRunning,
@ -106,7 +119,7 @@ type Workflow interface {
// ID returns id of the workflow // ID returns id of the workflow
ID() string ID() string
// Execute workflow with args, return execution id and error // Execute workflow with args, return execution id and error
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error) Execute(ctx context.Context, req *Message, opts ...options.Option) (string, error)
// RemoveSteps remove steps from workflow // RemoveSteps remove steps from workflow
RemoveSteps(steps ...Step) error RemoveSteps(steps ...Step) error
// AppendSteps append steps to workflow // AppendSteps append steps to workflow
@ -116,11 +129,11 @@ type Workflow interface {
// Steps returns steps slice where parallel steps returned on the same level // Steps returns steps slice where parallel steps returned on the same level
Steps() ([][]Step, error) Steps() ([][]Step, error)
// Suspend suspends execution // Suspend suspends execution
Suspend(ctx context.Context, eid string) error Suspend(ctx context.Context, id string) error
// Resume resumes execution // Resume resumes execution
Resume(ctx context.Context, eid string) error Resume(ctx context.Context, id string) error
// Abort abort execution // Abort abort execution
Abort(ctx context.Context, eid string) error Abort(ctx context.Context, id string) error
} }
// Flow the base interface to interact with workflows // Flow the base interface to interact with workflows
@ -128,7 +141,7 @@ type Flow interface {
// Options returns options // Options returns options
Options() Options Options() Options
// Init initialize // Init initialize
Init(...Option) error Init(...options.Option) error
// WorkflowCreate creates new workflow with specific id and steps // WorkflowCreate creates new workflow with specific id and steps
WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error) WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error)
// WorkflowSave saves workflow // WorkflowSave saves workflow
@ -144,6 +157,7 @@ var (
atomicSteps atomic.Value atomicSteps atomic.Value
) )
// RegisterStep register own step with workflow
func RegisterStep(step Step) { func RegisterStep(step Step) {
flowMu.Lock() flowMu.Lock()
steps, _ := atomicSteps.Load().([]Step) steps, _ := atomicSteps.Load().([]Step)

36
flow/flow_test.go Normal file
View File

@ -0,0 +1,36 @@
package flow
import (
"reflect"
"testing"
)
func FuzzMarshall(f *testing.F) {
f.Fuzz(func(t *testing.T, ref []byte) {
rm := RawMessage(ref)
b, err := rm.MarshalJSON()
if err != nil {
t.Errorf("Error MarshalJSON: %s", err)
}
if !reflect.DeepEqual(ref, b) {
t.Errorf("Error. Expected '%s', was '%s'", ref, b)
}
})
}
func FuzzUnmarshall(f *testing.F) {
f.Fuzz(func(t *testing.T, ref string) {
b := []byte(ref)
rm := RawMessage(b)
if err := rm.UnmarshalJSON(b); err != nil {
t.Errorf("Error UnmarshalJSON: %s", err)
}
if ref != string(rm) {
t.Errorf("Error. Expected '%s', was '%s'", ref, rm)
}
})
}

View File

@ -4,16 +4,14 @@ import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v4/client"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v4/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v4/meter"
"github.com/unistack-org/micro/v3/store" "go.unistack.org/micro/v4/options"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v4/store"
"go.unistack.org/micro/v4/tracer"
) )
// Option func
type Option func(*Options)
// Options server struct // Options server struct
type Options struct { type Options struct {
// Context holds the external options and can be used for flow shutdown // Context holds the external options and can be used for flow shutdown
@ -31,7 +29,7 @@ type Options struct {
} }
// NewOptions returns new options struct with default or passed values // NewOptions returns new options struct with default or passed values
func NewOptions(opts ...Option) Options { func NewOptions(opts ...options.Option) Options {
options := Options{ options := Options{
Context: context.Background(), Context: context.Background(),
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
@ -47,66 +45,13 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// Logger sets the logger option
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter option
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Client to use for sync/async communication
func Client(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// Context specifies a context for the service.
// Can be used to signal shutdown of the flow
// Can be used for extra option values.
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Tracer mechanism for distributed tracking
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Store used for intermediate results
func Store(s store.Store) Option {
return func(o *Options) {
o.Store = s
}
}
// WorflowOption signature
type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options // WorkflowOptions holds workflow options
type WorkflowOptions struct { type WorkflowOptions struct {
ID string
Context context.Context Context context.Context
ID string
} }
// WorkflowID set workflow id // ExecuteOptions holds execute options
func WorkflowID(id string) WorkflowOption {
return func(o *WorkflowOptions) {
o.ID = id
}
}
type ExecuteOptions struct { type ExecuteOptions struct {
// Client holds the client.Client // Client holds the client.Client
Client client.Client Client client.Client
@ -120,65 +65,30 @@ type ExecuteOptions struct {
Context context.Context Context context.Context
// Start step // Start step
Start string Start string
// Reverse execution
Reverse bool
// Timeout for execution // Timeout for execution
Timeout time.Duration Timeout time.Duration
// Reverse execution
Reverse bool
// Async enables async execution // Async enables async execution
Async bool Async bool
} }
type ExecuteOption func(*ExecuteOptions) // Reverse says that dag must be run in reverse order
func Reverse(b bool) options.Option {
func ExecuteClient(c client.Client) ExecuteOption { return func(src interface{}) error {
return func(o *ExecuteOptions) { return options.Set(src, b, ".Reverse")
o.Client = c
} }
} }
func ExecuteTracer(t tracer.Tracer) ExecuteOption { // Async says that caller does not wait for execution complete
return func(o *ExecuteOptions) { func Async(b bool) options.Option {
o.Tracer = t return func(src interface{}) error {
return options.Set(src, b, ".Async")
} }
} }
func ExecuteLogger(l logger.Logger) ExecuteOption { // NewExecuteOptions create new ExecuteOptions struct
return func(o *ExecuteOptions) { func NewExecuteOptions(opts ...options.Option) ExecuteOptions {
o.Logger = l
}
}
func ExecuteMeter(m meter.Meter) ExecuteOption {
return func(o *ExecuteOptions) {
o.Meter = m
}
}
func ExecuteContext(ctx context.Context) ExecuteOption {
return func(o *ExecuteOptions) {
o.Context = ctx
}
}
func ExecuteReverse(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Reverse = b
}
}
func ExecuteTimeout(td time.Duration) ExecuteOption {
return func(o *ExecuteOptions) {
o.Timeout = td
}
}
func ExecuteAsync(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Async = b
}
}
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
options := ExecuteOptions{ options := ExecuteOptions{
Client: client.DefaultClient, Client: client.DefaultClient,
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
@ -192,16 +102,16 @@ func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
return options return options
} }
// StepOptions holds step options
type StepOptions struct { type StepOptions struct {
ID string
Context context.Context Context context.Context
Requires []string
Fallback string Fallback string
ID string
Requires []string
} }
type StepOption func(*StepOptions) // NewStepOptions create new StepOptions struct
func NewStepOptions(opts ...options.Option) StepOptions {
func NewStepOptions(opts ...StepOption) StepOptions {
options := StepOptions{ options := StepOptions{
Context: context.Background(), Context: context.Background(),
} }
@ -211,20 +121,23 @@ func NewStepOptions(opts ...StepOption) StepOptions {
return options return options
} }
func StepID(id string) StepOption { // Requires specifies required steps
return func(o *StepOptions) { func Requires(steps ...string) options.Option {
o.ID = id return func(src interface{}) error {
return options.Set(src, steps, ".Requires")
} }
} }
func StepRequires(steps ...string) StepOption { // Fallback set the step to run on error
return func(o *StepOptions) { func Fallback(step string) options.Option {
o.Requires = steps return func(src interface{}) error {
return options.Set(src, step, ".Fallback")
} }
} }
func StepFallback(step string) StepOption { // ID sets the step ID
return func(o *StepOptions) { func StepID(id string) options.Option {
o.Fallback = step return func(src interface{}) error {
return options.Set(src, id, ".ID")
} }
} }

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

29
fsm/fsm.go Normal file
View File

@ -0,0 +1,29 @@
package fsm // import "go.unistack.org/micro/v4/fsm"
import (
"context"
"errors"
)
var (
ErrInvalidState = errors.New("does not exists")
StateEnd = "end"
)
type State interface {
Name() string
Body() interface{}
}
// 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, state State, opts ...StateOption) (State, error)
type FSM interface {
Start(context.Context, interface{}, ...Option) (interface{}, error)
Current() string
Reset()
State(string, StateFunc)
}

72
fsm/fsm_test.go Normal file
View File

@ -0,0 +1,72 @@
package fsm
import (
"context"
"fmt"
"testing"
"go.unistack.org/micro/v4/logger"
)
func TestFSMStart(t *testing.T) {
ctx := context.TODO()
if err := logger.DefaultLogger.Init(); err != nil {
t.Fatal(err)
}
wrapper := func(next StateFunc) StateFunc {
return func(sctx context.Context, s State, opts ...StateOption) (State, error) {
sctx = logger.NewContext(sctx, logger.Attrs("state", s.Name()))
return next(sctx, s, opts...)
}
}
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 &state{name: "", body: map[string]interface{}{"response": "state1"}}, nil
}
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")
}
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(sctx context.Context, s State, opts ...StateOption) (State, error) {
_, ok := logger.FromContext(sctx)
if !ok {
t.Fatal("f3 context does not have logger")
}
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": "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 != "state3" {
t.Fatalf("invalid rsp %#+v", args)
}
}

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
}

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