├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature-request---enhancement.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── tests.yaml │ └── website.yml ├── .gitignore ├── .golangci.yaml ├── .richstyle.yaml ├── LICENSE ├── README.md ├── auth ├── auth.go ├── jwt │ ├── jwt.go │ └── token │ │ ├── jwt.go │ │ ├── jwt_test.go │ │ ├── options.go │ │ ├── test │ │ ├── sample_key │ │ ├── sample_key 2 │ │ └── sample_key.pub │ │ └── token.go ├── noop.go ├── options.go ├── rules.go └── rules_test.go ├── broker ├── broker.go ├── http.go ├── http_test.go ├── memory.go ├── memory_test.go ├── nats │ ├── context.go │ ├── nats.go │ ├── nats_test.go │ └── options.go ├── options.go └── rabbitmq │ ├── auth.go │ ├── channel.go │ ├── connection.go │ ├── connection_test.go │ ├── context.go │ ├── options.go │ ├── rabbitmq.go │ └── rabbitmq_test.go ├── cache ├── cache.go ├── memory.go ├── memory_test.go ├── options.go ├── options_test.go └── redis │ ├── options.go │ ├── options_test.go │ ├── redis.go │ └── redis_test.go ├── client ├── backoff.go ├── backoff_test.go ├── cache.go ├── cache_test.go ├── client.go ├── common_test.go ├── context.go ├── grpc │ ├── codec.go │ ├── error.go │ ├── grpc.go │ ├── grpc_pool.go │ ├── grpc_pool_test.go │ ├── grpc_test.go │ ├── message.go │ ├── options.go │ ├── request.go │ ├── request_test.go │ ├── response.go │ └── stream.go ├── options.go ├── options_test.go ├── retry.go ├── rpc_client.go ├── rpc_client_test.go ├── rpc_codec.go ├── rpc_message.go ├── rpc_request.go ├── rpc_request_test.go ├── rpc_response.go ├── rpc_stream.go └── wrapper.go ├── cmd ├── cmd.go └── options.go ├── codec ├── bytes │ ├── bytes.go │ └── marshaler.go ├── codec.go ├── grpc │ ├── grpc.go │ └── util.go ├── json │ ├── json.go │ └── marshaler.go ├── jsonrpc │ ├── client.go │ ├── jsonrpc.go │ └── server.go ├── proto │ ├── marshaler.go │ ├── message.go │ └── proto.go ├── protorpc │ ├── envelope.pb.go │ ├── envelope.pb.micro.go │ ├── envelope.proto │ ├── netstring.go │ └── protorpc.go └── text │ └── text.go ├── config ├── README.md ├── config.go ├── default.go ├── default_test.go ├── encoder │ ├── encoder.go │ └── json │ │ └── json.go ├── loader │ ├── loader.go │ └── memory │ │ ├── memory.go │ │ └── options.go ├── options.go ├── reader │ ├── json │ │ ├── json.go │ │ ├── json_test.go │ │ ├── values.go │ │ └── values_test.go │ ├── options.go │ ├── preprocessor.go │ ├── preprocessor_test.go │ └── reader.go ├── secrets │ ├── box │ │ ├── box.go │ │ └── box_test.go │ ├── secretbox │ │ ├── secretbox.go │ │ └── secretbox_test.go │ └── secrets.go ├── source │ ├── changeset.go │ ├── cli │ │ ├── README.md │ │ ├── cli.go │ │ ├── cli_test.go │ │ ├── options.go │ │ └── util.go │ ├── env │ │ ├── README.md │ │ ├── env.go │ │ ├── env_test.go │ │ ├── options.go │ │ └── watcher.go │ ├── file │ │ ├── README.md │ │ ├── file.go │ │ ├── file_test.go │ │ ├── format.go │ │ ├── format_test.go │ │ ├── options.go │ │ ├── watcher.go │ │ ├── watcher_linux.go │ │ └── watcher_test.go │ ├── flag │ │ ├── README.md │ │ ├── flag.go │ │ ├── flag_test.go │ │ └── options.go │ ├── memory │ │ ├── README.md │ │ ├── memory.go │ │ ├── options.go │ │ └── watcher.go │ ├── nats │ │ ├── README.md │ │ ├── nats.go │ │ ├── options.go │ │ └── watcher.go │ ├── noop.go │ ├── options.go │ └── source.go └── value.go ├── debug ├── handler │ └── debug.go ├── log │ ├── log.go │ ├── memory │ │ ├── memory.go │ │ ├── memory_test.go │ │ └── stream.go │ ├── noop │ │ └── noop.go │ ├── options.go │ └── os.go ├── profile │ ├── http │ │ └── http.go │ ├── pprof │ │ └── pprof.go │ └── profile.go ├── proto │ ├── debug.pb.go │ ├── debug.pb.micro.go │ └── debug.proto ├── stats │ ├── default.go │ └── stats.go └── trace │ ├── default.go │ ├── noop.go │ ├── options.go │ └── trace.go ├── errors ├── errors.go ├── errors.pb.go ├── errors.pb.micro.go ├── errors.proto └── errors_test.go ├── event.go ├── go.mod ├── go.sum ├── internal ├── README.md └── website │ ├── .gitignore │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── _config.yml │ ├── _layouts │ └── default.html │ ├── docs │ ├── architecture.md │ ├── broker.md │ ├── client-server.md │ ├── getting-started.md │ ├── index.md │ ├── registry.md │ ├── store.md │ └── transport.md │ ├── images │ └── logo.png │ └── index.html ├── logger ├── context.go ├── default.go ├── helper.go ├── level.go ├── logger.go ├── logger_test.go └── options.go ├── logo.png ├── metadata ├── metadata.go └── metadata_test.go ├── micro.go ├── options.go ├── profile └── profile.go ├── registry ├── cache │ ├── README.md │ ├── cache.go │ └── options.go ├── consul │ ├── consul.go │ ├── encoding.go │ ├── encoding_test.go │ ├── options.go │ ├── registry_test.go │ ├── watcher.go │ └── watcher_test.go ├── etcd │ ├── etcd.go │ ├── options.go │ └── watcher.go ├── mdns_registry.go ├── mdns_test.go ├── memory.go ├── memory_test.go ├── memory_util.go ├── memory_watcher.go ├── nats │ ├── nats.go │ ├── nats_assert_test.go │ ├── nats_environment_test.go │ ├── nats_options.go │ ├── nats_registry.go │ ├── nats_test.go │ ├── nats_util.go │ └── nats_watcher.go ├── options.go ├── options_test.go ├── registry.go └── watcher.go ├── selector ├── common_test.go ├── default.go ├── default_test.go ├── filter.go ├── filter_test.go ├── options.go ├── selector.go ├── strategy.go └── strategy_test.go ├── server ├── context.go ├── extractor.go ├── extractor_test.go ├── grpc │ ├── codec.go │ ├── context.go │ ├── error.go │ ├── extractor.go │ ├── extractor_test.go │ ├── grpc.go │ ├── handler.go │ ├── options.go │ ├── request.go │ ├── response.go │ ├── server.go │ ├── stream.go │ ├── subscriber.go │ └── util.go ├── handler.go ├── mock │ ├── mock.go │ ├── mock_handler.go │ ├── mock_subscriber.go │ └── mock_test.go ├── options.go ├── proto │ ├── server.pb.go │ ├── server.pb.micro.go │ └── server.proto ├── rpc_codec.go ├── rpc_codec_test.go ├── rpc_event.go ├── rpc_events.go ├── rpc_handler.go ├── rpc_helper.go ├── rpc_request.go ├── rpc_response.go ├── rpc_router.go ├── rpc_server.go ├── rpc_stream.go ├── rpc_stream_test.go ├── rpc_util.go ├── server.go ├── subscriber.go └── wrapper.go ├── service ├── options.go └── service.go ├── store ├── file.go ├── file_test.go ├── memory.go ├── mysql │ ├── mysql.go │ └── mysql_test.go ├── nats-js-kv │ ├── README.md │ ├── context.go │ ├── helpers_test.go │ ├── keys.go │ ├── nats.go │ ├── nats_test.go │ ├── options.go │ └── test_data.go ├── noop.go ├── options.go ├── postgres │ ├── README.md │ ├── metadata.go │ ├── pgx │ │ ├── README.md │ │ ├── db.go │ │ ├── metadata.go │ │ ├── pgx.go │ │ ├── pgx_test.go │ │ ├── queries.go │ │ └── templates.go │ ├── postgres.go │ └── postgres_test.go └── store.go ├── test ├── benchmark.go └── service.go ├── transport ├── context.go ├── grpc │ ├── grpc.go │ ├── grpc_test.go │ ├── handler.go │ ├── proto │ │ ├── transport.pb.go │ │ ├── transport.pb.micro.go │ │ ├── transport.proto │ │ └── transport_grpc.pb.go │ └── socket.go ├── headers │ └── headers.go ├── http_client.go ├── http_client_test.go ├── http_listener.go ├── http_proxy.go ├── http_socket.go ├── http_transport.go ├── http_transport_test.go ├── memory.go ├── memory_test.go ├── nats │ ├── nats.go │ ├── nats_test.go │ └── options.go ├── options.go └── transport.go ├── util ├── addr │ ├── addr.go │ └── addr_test.go ├── backoff │ └── backoff.go ├── buf │ └── buf.go ├── grpc │ ├── grpc.go │ └── grpc_test.go ├── http │ ├── http.go │ ├── http_test.go │ ├── options.go │ └── roundtripper.go ├── jitter │ └── jitter.go ├── mdns │ ├── .gitignore │ ├── client.go │ ├── dns_sd.go │ ├── dns_sd_test.go │ ├── server.go │ ├── server_test.go │ ├── zone.go │ └── zone_test.go ├── net │ ├── net.go │ └── net_test.go ├── pool │ ├── default.go │ ├── default_test.go │ ├── options.go │ └── pool.go ├── registry │ ├── util.go │ └── util_test.go ├── ring │ ├── buffer.go │ └── buffer_test.go ├── signal │ └── signal.go ├── socket │ ├── pool.go │ └── socket.go ├── test │ └── test.go ├── tls │ └── tls.go └── wrapper │ ├── wrapper.go │ └── wrapper_test.go ├── web ├── options.go ├── service.go ├── service_test.go ├── web.go └── web_test.go └── wrapper └── trace └── opentelemetry ├── README.md ├── opentelemetry.go ├── options.go └── wrapper.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: For reporting bugs in go-micro 4 | title: "[BUG]" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | 1. What are you trying to do? 12 | 2. What did you expect to happen? 13 | 3. What happens instead? 14 | 15 | ## How to reproduce the bug 16 | 17 | If possible, please include a minimal code snippet here. 18 | 19 | ## Environment 20 | 21 | Go Version: please paste `go version` output here 22 | 23 | ```go 24 | please paste `go env` output here 25 | ``` 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request---enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request / Enhancement 3 | about: If you have a need not served by go-micro 4 | title: "[FEATURE]" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about go-micro 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | Before asking, please check if your question has already been answered: 10 | 11 | 1. Check the documentation - https://micro.mu/docs/ 12 | 2. Check the examples and plugins - https://github.com/micro/examples & https://github.com/micro/go-plugins 13 | 3. Search existing issues 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request template 2 | 3 | Please, go through these steps before clicking submit on this PR. 4 | 5 | 1. Make sure this PR targets the `develop` branch. We follow the git-flow branching model. 6 | 2. Give a descriptive title to your PR. 7 | 3. Provide a description of your changes. 8 | 4. Make sure you have some relevant tests. 9 | 5. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable). 10 | 11 | ## PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING 12 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | on: 3 | push: 4 | branches: 5 | - "**" 6 | pull_request: 7 | types: 8 | - opened 9 | - reopened 10 | branches: 11 | - "**" 12 | jobs: 13 | unittests: 14 | name: Unit Tests 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Go 19 | uses: actions/setup-go@v3 20 | with: 21 | go-version: 1.24 22 | check-latest: true 23 | cache: true 24 | - name: Get dependencies 25 | run: | 26 | go install github.com/kyoh86/richgo@latest 27 | go get -v -t -d ./... 28 | - name: Run tests 29 | id: tests 30 | run: richgo test -v -race -cover ./... 31 | env: 32 | IN_TRAVIS_CI: yes 33 | RICHGO_FORCE_COLOR: 1 34 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v5 33 | - name: Build with Jekyll 34 | uses: actions/jekyll-build-pages@v1 35 | with: 36 | source: ./internal/website 37 | destination: ./_site 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Develop tools 2 | /.vscode/ 3 | /.idea/ 4 | /.trunk 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Folders 14 | _obj 15 | _test 16 | _build 17 | 18 | # Architecture specific extensions/prefixes 19 | *.[568vq] 20 | [568vq].out 21 | 22 | *.cgo1.go 23 | *.cgo2.c 24 | _cgo_defun.c 25 | _cgo_gotypes.go 26 | _cgo_export.* 27 | 28 | # Test binary, build with `go test -c` 29 | *.test 30 | 31 | # Output of the go coverage tool, specifically when used with LiteIDE 32 | *.out 33 | 34 | # vim temp files 35 | *~ 36 | *.swp 37 | *.swo 38 | 39 | # go work files 40 | go.work 41 | go.work.sum 42 | -------------------------------------------------------------------------------- /.richstyle.yaml: -------------------------------------------------------------------------------- 1 | labelType: long 2 | coverThreshold: 70 3 | buildStyle: 4 | bold: true 5 | foreground: yellow 6 | startStyle: 7 | foreground: lightBlack 8 | passStyle: 9 | foreground: green 10 | failStyle: 11 | bold: true 12 | foreground: "#821515" 13 | skipStyle: 14 | foreground: lightBlack 15 | passPackageStyle: 16 | foreground: green 17 | hide: false 18 | failPackageStyle: 19 | bold: true 20 | foreground: "#821515" 21 | coveredStyle: 22 | foreground: green 23 | uncoveredStyle: 24 | bold: true 25 | foreground: yellow 26 | fileStyle: 27 | foreground: cyan 28 | lineStyle: 29 | foreground: magenta 30 | -------------------------------------------------------------------------------- /auth/jwt/token/options.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "time" 5 | 6 | "go-micro.dev/v5/store" 7 | ) 8 | 9 | type Options struct { 10 | // Store to persist the tokens 11 | Store store.Store 12 | // PublicKey base64 encoded, used by JWT 13 | PublicKey string 14 | // PrivateKey base64 encoded, used by JWT 15 | PrivateKey string 16 | } 17 | 18 | type Option func(o *Options) 19 | 20 | // WithStore sets the token providers store. 21 | func WithStore(s store.Store) Option { 22 | return func(o *Options) { 23 | o.Store = s 24 | } 25 | } 26 | 27 | // WithPublicKey sets the JWT public key. 28 | func WithPublicKey(key string) Option { 29 | return func(o *Options) { 30 | o.PublicKey = key 31 | } 32 | } 33 | 34 | // WithPrivateKey sets the JWT private key. 35 | func WithPrivateKey(key string) Option { 36 | return func(o *Options) { 37 | o.PrivateKey = key 38 | } 39 | } 40 | 41 | func NewOptions(opts ...Option) Options { 42 | var options Options 43 | for _, o := range opts { 44 | o(&options) 45 | } 46 | // set default store 47 | if options.Store == nil { 48 | options.Store = store.DefaultStore 49 | } 50 | return options 51 | } 52 | 53 | type GenerateOptions struct { 54 | // Expiry for the token 55 | Expiry time.Duration 56 | } 57 | 58 | type GenerateOption func(o *GenerateOptions) 59 | 60 | // WithExpiry for the generated account's token expires. 61 | func WithExpiry(d time.Duration) GenerateOption { 62 | return func(o *GenerateOptions) { 63 | o.Expiry = d 64 | } 65 | } 66 | 67 | // NewGenerateOptions from a slice of options. 68 | func NewGenerateOptions(opts ...GenerateOption) GenerateOptions { 69 | var options GenerateOptions 70 | for _, o := range opts { 71 | o(&options) 72 | } 73 | // set default Expiry of token 74 | if options.Expiry == 0 { 75 | options.Expiry = time.Minute * 15 76 | } 77 | return options 78 | } 79 | -------------------------------------------------------------------------------- /auth/jwt/token/test/sample_key.pub: -------------------------------------------------------------------------------- 1 | LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE4U2JKUDVYYkVpZFJtNWIyc05wTApHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkL1JsOS8xcE1WN01pTzNMSHd0aEhDMkJSWXFyKzF3RmRvWkNHCkJZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUwQkgvbFhRTXF5RXFGNU1JMnplakM0ek16cjE1T04rZ0U0Sm4KaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtL21VZElUL0xhRjdrUXh5WUs1VktuK05nT1d6TWx6S0FBcENuNwpUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsbzlqRGpsWTVvQk9jemZxZU5XSEs1R1hCN1F3cExOaDk0NlB6ClpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDV3bEVxOGhOaG1obTVOTmUvTytHZ2pCRE5TZlVoMDYrcTRuZ20KYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1b1J0UWhnaVk5MTBwUGY5YmF1WFdxd1VDVWE0cXNIempLUjBMLwpOMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVcmdMclBiRUI5ZWdjR2szOCticEtzM1o2UnI1K3RuRDFCSVBJCkZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVVUR0R4VXg4b2o4VkllUm5XRHE2TWMxaUpwOFV5Y2lCSVRSdHcKNGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMGxhUXpBdVAzYWlXWElNcDJzYzhTYytCbCtMalhtQm94QnJhQgpIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YZ0ZLU3NJbkhEckhWT3lXUEJlM2ZhZEVjNzdiK25iL2V4T3pyCjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo= -------------------------------------------------------------------------------- /auth/jwt/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "go-micro.dev/v5/auth" 8 | ) 9 | 10 | var ( 11 | // ErrNotFound is returned when a token cannot be found. 12 | ErrNotFound = errors.New("token not found") 13 | // ErrEncodingToken is returned when the service encounters an error during encoding. 14 | ErrEncodingToken = errors.New("error encoding the token") 15 | // ErrInvalidToken is returned when the token provided is not valid. 16 | ErrInvalidToken = errors.New("invalid token provided") 17 | ) 18 | 19 | // Provider generates and inspects tokens. 20 | type Provider interface { 21 | Generate(account *auth.Account, opts ...GenerateOption) (*Token, error) 22 | Inspect(token string) (*auth.Account, error) 23 | String() string 24 | } 25 | 26 | type Token struct { 27 | // The actual token 28 | Token string `json:"token"` 29 | // Time of token creation 30 | Created time.Time `json:"created"` 31 | // Time of token expiry 32 | Expiry time.Time `json:"expiry"` 33 | } 34 | -------------------------------------------------------------------------------- /broker/memory_test.go: -------------------------------------------------------------------------------- 1 | package broker_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "go-micro.dev/v5/broker" 8 | ) 9 | 10 | func TestMemoryBroker(t *testing.T) { 11 | b := broker.NewMemoryBroker() 12 | 13 | if err := b.Connect(); err != nil { 14 | t.Fatalf("Unexpected connect error %v", err) 15 | } 16 | 17 | topic := "test" 18 | count := 10 19 | 20 | fn := func(p broker.Event) error { 21 | return nil 22 | } 23 | 24 | sub, err := b.Subscribe(topic, fn) 25 | if err != nil { 26 | t.Fatalf("Unexpected error subscribing %v", err) 27 | } 28 | 29 | for i := 0; i < count; i++ { 30 | message := &broker.Message{ 31 | Header: map[string]string{ 32 | "foo": "bar", 33 | "id": fmt.Sprintf("%d", i), 34 | }, 35 | Body: []byte(`hello world`), 36 | } 37 | 38 | if err := b.Publish(topic, message); err != nil { 39 | t.Fatalf("Unexpected error publishing %d", i) 40 | } 41 | } 42 | 43 | if err := sub.Unsubscribe(); err != nil { 44 | t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err) 45 | } 46 | 47 | if err := b.Disconnect(); err != nil { 48 | t.Fatalf("Unexpected connect error %v", err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /broker/nats/context.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/broker" 7 | ) 8 | 9 | // setBrokerOption returns a function to setup a context with given value. 10 | func setBrokerOption(k, v interface{}) broker.Option { 11 | return func(o *broker.Options) { 12 | if o.Context == nil { 13 | o.Context = context.Background() 14 | } 15 | o.Context = context.WithValue(o.Context, k, v) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /broker/nats/options.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | natsp "github.com/nats-io/nats.go" 5 | "go-micro.dev/v5/broker" 6 | ) 7 | 8 | type optionsKey struct{} 9 | type drainConnectionKey struct{} 10 | 11 | // Options accepts nats.Options. 12 | func Options(opts natsp.Options) broker.Option { 13 | return setBrokerOption(optionsKey{}, opts) 14 | } 15 | 16 | // DrainConnection will drain subscription on close. 17 | func DrainConnection() broker.Option { 18 | return setBrokerOption(drainConnectionKey{}, struct{}{}) 19 | } 20 | -------------------------------------------------------------------------------- /broker/rabbitmq/auth.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | type ExternalAuthentication struct { 4 | } 5 | 6 | func (auth *ExternalAuthentication) Mechanism() string { 7 | return "EXTERNAL" 8 | } 9 | 10 | func (auth *ExternalAuthentication) Response() string { 11 | return "" 12 | } 13 | -------------------------------------------------------------------------------- /broker/rabbitmq/context.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/broker" 7 | "go-micro.dev/v5/server" 8 | ) 9 | 10 | // setSubscribeOption returns a function to setup a context with given value. 11 | func setSubscribeOption(k, v interface{}) broker.SubscribeOption { 12 | return func(o *broker.SubscribeOptions) { 13 | if o.Context == nil { 14 | o.Context = context.Background() 15 | } 16 | o.Context = context.WithValue(o.Context, k, v) 17 | } 18 | } 19 | 20 | // setBrokerOption returns a function to setup a context with given value. 21 | func setBrokerOption(k, v interface{}) broker.Option { 22 | return func(o *broker.Options) { 23 | if o.Context == nil { 24 | o.Context = context.Background() 25 | } 26 | o.Context = context.WithValue(o.Context, k, v) 27 | } 28 | } 29 | 30 | // setBrokerOption returns a function to setup a context with given value. 31 | func setServerSubscriberOption(k, v interface{}) server.SubscriberOption { 32 | return func(o *server.SubscriberOptions) { 33 | if o.Context == nil { 34 | o.Context = context.Background() 35 | } 36 | o.Context = context.WithValue(o.Context, k, v) 37 | } 38 | } 39 | 40 | // setPublishOption returns a function to setup a context with given value. 41 | func setPublishOption(k, v interface{}) broker.PublishOption { 42 | return func(o *broker.PublishOptions) { 43 | if o.Context == nil { 44 | o.Context = context.Background() 45 | } 46 | o.Context = context.WithValue(o.Context, k, v) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | ) 8 | 9 | var ( 10 | // DefaultCache is the default cache. 11 | DefaultCache Cache = NewCache() 12 | // DefaultExpiration is the default duration for items stored in 13 | // the cache to expire. 14 | DefaultExpiration time.Duration = 0 15 | 16 | // ErrItemExpired is returned in Cache.Get when the item found in the cache 17 | // has expired. 18 | ErrItemExpired error = errors.New("item has expired") 19 | // ErrKeyNotFound is returned in Cache.Get and Cache.Delete when the 20 | // provided key could not be found in cache. 21 | ErrKeyNotFound error = errors.New("key not found in cache") 22 | ) 23 | 24 | // Cache is the interface that wraps the cache. 25 | type Cache interface { 26 | // Get gets a cached value by key. 27 | Get(ctx context.Context, key string) (interface{}, time.Time, error) 28 | // Put stores a key-value pair into cache. 29 | Put(ctx context.Context, key string, val interface{}, d time.Duration) error 30 | // Delete removes a key from cache. 31 | Delete(ctx context.Context, key string) error 32 | // String returns the name of the implementation. 33 | String() string 34 | } 35 | 36 | // Item represents an item stored in the cache. 37 | type Item struct { 38 | Value interface{} 39 | Expiration int64 40 | } 41 | 42 | // Expired returns true if the item has expired. 43 | func (i *Item) Expired() bool { 44 | if i.Expiration == 0 { 45 | return false 46 | } 47 | 48 | return time.Now().UnixNano() > i.Expiration 49 | } 50 | 51 | // NewCache returns a new cache. 52 | func NewCache(opts ...Option) Cache { 53 | options := NewOptions(opts...) 54 | items := make(map[string]Item) 55 | 56 | if len(options.Items) > 0 { 57 | items = options.Items 58 | } 59 | 60 | return &memCache{ 61 | opts: options, 62 | items: items, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cache/memory.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type memCache struct { 10 | opts Options 11 | 12 | items map[string]Item 13 | sync.RWMutex 14 | } 15 | 16 | func (c *memCache) Get(ctx context.Context, key string) (interface{}, time.Time, error) { 17 | c.RWMutex.RLock() 18 | defer c.RWMutex.RUnlock() 19 | 20 | item, found := c.items[key] 21 | if !found { 22 | return nil, time.Time{}, ErrKeyNotFound 23 | } 24 | if item.Expired() { 25 | return nil, time.Time{}, ErrItemExpired 26 | } 27 | 28 | return item.Value, time.Unix(0, item.Expiration), nil 29 | } 30 | 31 | func (c *memCache) Put(ctx context.Context, key string, val interface{}, d time.Duration) error { 32 | var e int64 33 | if d == DefaultExpiration { 34 | d = c.opts.Expiration 35 | } 36 | if d > 0 { 37 | e = time.Now().Add(d).UnixNano() 38 | } 39 | 40 | c.RWMutex.Lock() 41 | defer c.RWMutex.Unlock() 42 | 43 | c.items[key] = Item{ 44 | Value: val, 45 | Expiration: e, 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func (c *memCache) Delete(ctx context.Context, key string) error { 52 | c.RWMutex.Lock() 53 | defer c.RWMutex.Unlock() 54 | 55 | _, found := c.items[key] 56 | if !found { 57 | return ErrKeyNotFound 58 | } 59 | 60 | delete(c.items, key) 61 | return nil 62 | } 63 | 64 | func (m *memCache) String() string { 65 | return "memory" 66 | } 67 | -------------------------------------------------------------------------------- /cache/options.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "go-micro.dev/v5/logger" 8 | ) 9 | 10 | // Options represents the options for the cache. 11 | type Options struct { 12 | // Context should contain all implementation specific options, using context.WithValue. 13 | Context context.Context 14 | // Logger is the be used logger 15 | Logger logger.Logger 16 | Items map[string]Item 17 | // Address represents the address or other connection information of the cache service. 18 | Address string 19 | Expiration time.Duration 20 | } 21 | 22 | // Option manipulates the Options passed. 23 | type Option func(o *Options) 24 | 25 | // Expiration sets the duration for items stored in the cache to expire. 26 | func Expiration(d time.Duration) Option { 27 | return func(o *Options) { 28 | o.Expiration = d 29 | } 30 | } 31 | 32 | // Items initializes the cache with preconfigured items. 33 | func Items(i map[string]Item) Option { 34 | return func(o *Options) { 35 | o.Items = i 36 | } 37 | } 38 | 39 | // WithAddress sets the cache service address or connection information. 40 | func WithAddress(addr string) Option { 41 | return func(o *Options) { 42 | o.Address = addr 43 | } 44 | } 45 | 46 | // WithContext sets the cache context, for any extra configuration. 47 | func WithContext(c context.Context) Option { 48 | return func(o *Options) { 49 | o.Context = c 50 | } 51 | } 52 | 53 | // WithLogger sets underline logger. 54 | func WithLogger(l logger.Logger) Option { 55 | return func(o *Options) { 56 | o.Logger = l 57 | } 58 | } 59 | 60 | // NewOptions returns a new options struct. 61 | func NewOptions(opts ...Option) Options { 62 | options := Options{ 63 | Expiration: DefaultExpiration, 64 | Items: make(map[string]Item), 65 | Logger: logger.DefaultLogger, 66 | } 67 | 68 | for _, o := range opts { 69 | o(&options) 70 | } 71 | 72 | return options 73 | } 74 | -------------------------------------------------------------------------------- /cache/options_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestOptions(t *testing.T) { 9 | testData := map[string]struct { 10 | set bool 11 | expiration time.Duration 12 | items map[string]Item 13 | }{ 14 | "DefaultOptions": {false, DefaultExpiration, map[string]Item{}}, 15 | "ModifiedOptions": {true, time.Second, map[string]Item{"test": {"hello go-micro", 0}}}, 16 | } 17 | 18 | for k, d := range testData { 19 | t.Run(k, func(t *testing.T) { 20 | var opts Options 21 | 22 | if d.set { 23 | opts = NewOptions( 24 | Expiration(d.expiration), 25 | Items(d.items), 26 | ) 27 | } else { 28 | opts = NewOptions() 29 | } 30 | 31 | // test options 32 | for _, o := range []Options{opts} { 33 | if o.Expiration != d.expiration { 34 | t.Fatalf("Expected expiration '%v', got '%v'", d.expiration, o.Expiration) 35 | } 36 | 37 | if o.Items["test"] != d.items["test"] { 38 | t.Fatalf("Expected items %#v, got %#v", d.items, o.Items) 39 | } 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cache/redis/options.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | 6 | rclient "github.com/go-redis/redis/v8" 7 | "go-micro.dev/v5/cache" 8 | ) 9 | 10 | type redisOptionsContextKey struct{} 11 | 12 | // WithRedisOptions sets advanced options for redis. 13 | func WithRedisOptions(options rclient.UniversalOptions) cache.Option { 14 | return func(o *cache.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | 19 | o.Context = context.WithValue(o.Context, redisOptionsContextKey{}, options) 20 | } 21 | } 22 | 23 | func newUniversalClient(options cache.Options) rclient.UniversalClient { 24 | if options.Context == nil { 25 | options.Context = context.Background() 26 | } 27 | 28 | opts, ok := options.Context.Value(redisOptionsContextKey{}).(rclient.UniversalOptions) 29 | if !ok { 30 | addr := "redis://127.0.0.1:6379" 31 | if len(options.Address) > 0 { 32 | addr = options.Address 33 | } 34 | 35 | redisOptions, err := rclient.ParseURL(addr) 36 | if err != nil { 37 | redisOptions = &rclient.Options{Addr: addr} 38 | } 39 | 40 | return rclient.NewClient(redisOptions) 41 | } 42 | 43 | if len(opts.Addrs) == 0 && len(options.Address) > 0 { 44 | opts.Addrs = []string{options.Address} 45 | } 46 | 47 | return rclient.NewUniversalClient(&opts) 48 | } 49 | -------------------------------------------------------------------------------- /cache/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | rclient "github.com/go-redis/redis/v8" 8 | "go-micro.dev/v5/cache" 9 | ) 10 | 11 | // NewRedisCache returns a new redis cache. 12 | func NewRedisCache(opts ...cache.Option) cache.Cache { 13 | options := cache.NewOptions(opts...) 14 | return &redisCache{ 15 | opts: options, 16 | client: newUniversalClient(options), 17 | } 18 | } 19 | 20 | type redisCache struct { 21 | opts cache.Options 22 | client rclient.UniversalClient 23 | } 24 | 25 | func (c *redisCache) Get(ctx context.Context, key string) (interface{}, time.Time, error) { 26 | val, err := c.client.Get(ctx, key).Bytes() 27 | if err != nil && err == rclient.Nil { 28 | return nil, time.Time{}, cache.ErrKeyNotFound 29 | } else if err != nil { 30 | return nil, time.Time{}, err 31 | } 32 | 33 | dur, err := c.client.TTL(ctx, key).Result() 34 | if err != nil { 35 | return nil, time.Time{}, err 36 | } 37 | if dur == -1 { 38 | return val, time.Unix(1<<63-1, 0), nil 39 | } 40 | if dur == -2 { 41 | return val, time.Time{}, cache.ErrItemExpired 42 | } 43 | 44 | return val, time.Now().Add(dur), nil 45 | } 46 | 47 | func (c *redisCache) Put(ctx context.Context, key string, val interface{}, dur time.Duration) error { 48 | return c.client.Set(ctx, key, val, dur).Err() 49 | } 50 | 51 | func (c *redisCache) Delete(ctx context.Context, key string) error { 52 | return c.client.Del(ctx, key).Err() 53 | } 54 | 55 | func (m *redisCache) String() string { 56 | return "redis" 57 | } 58 | -------------------------------------------------------------------------------- /client/backoff.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "go-micro.dev/v5/util/backoff" 8 | ) 9 | 10 | type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) 11 | 12 | func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) { 13 | return backoff.Do(attempts), nil 14 | } 15 | -------------------------------------------------------------------------------- /client/backoff_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestBackoff(t *testing.T) { 10 | results := []time.Duration{ 11 | 0 * time.Second, 12 | 100 * time.Millisecond, 13 | 600 * time.Millisecond, 14 | 1900 * time.Millisecond, 15 | 4300 * time.Millisecond, 16 | 7900 * time.Millisecond, 17 | } 18 | 19 | c := NewClient() 20 | 21 | for i := 0; i < 5; i++ { 22 | d, err := exponentialBackoff(context.TODO(), c.NewRequest("test", "test", nil), i) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | if d != results[i] { 28 | t.Fatalf("Expected equal than %v, got %v", results[i], d) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/cache.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "hash/fnv" 8 | "time" 9 | 10 | cache "github.com/patrickmn/go-cache" 11 | 12 | "go-micro.dev/v5/metadata" 13 | "go-micro.dev/v5/transport/headers" 14 | ) 15 | 16 | // NewCache returns an initialized cache. 17 | func NewCache() *Cache { 18 | return &Cache{ 19 | cache: cache.New(cache.NoExpiration, 30*time.Second), 20 | } 21 | } 22 | 23 | // Cache for responses. 24 | type Cache struct { 25 | cache *cache.Cache 26 | } 27 | 28 | // Get a response from the cache. 29 | func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) { 30 | return c.cache.Get(key(ctx, req)) 31 | } 32 | 33 | // Set a response in the cache. 34 | func (c *Cache) Set(ctx context.Context, req *Request, rsp interface{}, expiry time.Duration) { 35 | c.cache.Set(key(ctx, req), rsp, expiry) 36 | } 37 | 38 | // List the key value pairs in the cache. 39 | func (c *Cache) List() map[string]string { 40 | items := c.cache.Items() 41 | 42 | rsp := make(map[string]string, len(items)) 43 | 44 | for k, v := range items { 45 | bytes, _ := json.Marshal(v.Object) 46 | rsp[k] = string(bytes) 47 | } 48 | 49 | return rsp 50 | } 51 | 52 | // key returns a hash for the context and request. 53 | func key(ctx context.Context, req *Request) string { 54 | ns, _ := metadata.Get(ctx, headers.Namespace) 55 | 56 | bytes, _ := json.Marshal(map[string]interface{}{ 57 | "namespace": ns, 58 | "request": map[string]interface{}{ 59 | "service": (*req).Service(), 60 | "endpoint": (*req).Endpoint(), 61 | "method": (*req).Method(), 62 | "body": (*req).Body(), 63 | }, 64 | }) 65 | 66 | h := fnv.New64() 67 | h.Write(bytes) 68 | 69 | return fmt.Sprintf("%x", h.Sum(nil)) 70 | } 71 | -------------------------------------------------------------------------------- /client/common_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "go-micro.dev/v5/registry" 5 | ) 6 | 7 | var ( 8 | // mock data. 9 | testData = map[string][]*registry.Service{ 10 | "foo": { 11 | { 12 | Name: "foo", 13 | Version: "1.0.0", 14 | Nodes: []*registry.Node{ 15 | { 16 | Id: "foo-1.0.0-123", 17 | Address: "localhost:9999", 18 | Metadata: map[string]string{ 19 | "protocol": "mucp", 20 | }, 21 | }, 22 | { 23 | Id: "foo-1.0.0-321", 24 | Address: "localhost:9999", 25 | Metadata: map[string]string{ 26 | "protocol": "mucp", 27 | }, 28 | }, 29 | }, 30 | }, 31 | { 32 | Name: "foo", 33 | Version: "1.0.1", 34 | Nodes: []*registry.Node{ 35 | { 36 | Id: "foo-1.0.1-321", 37 | Address: "localhost:6666", 38 | Metadata: map[string]string{ 39 | "protocol": "mucp", 40 | }, 41 | }, 42 | }, 43 | }, 44 | { 45 | Name: "foo", 46 | Version: "1.0.3", 47 | Nodes: []*registry.Node{ 48 | { 49 | Id: "foo-1.0.3-345", 50 | Address: "localhost:8888", 51 | Metadata: map[string]string{ 52 | "protocol": "mucp", 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | } 59 | ) 60 | -------------------------------------------------------------------------------- /client/context.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type clientKey struct{} 8 | 9 | func FromContext(ctx context.Context) (Client, bool) { 10 | c, ok := ctx.Value(clientKey{}).(Client) 11 | return c, ok 12 | } 13 | 14 | func NewContext(ctx context.Context, c Client) context.Context { 15 | return context.WithValue(ctx, clientKey{}, c) 16 | } 17 | -------------------------------------------------------------------------------- /client/grpc/error.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-micro.dev/v5/errors" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func microError(err error) error { 12 | // no error 13 | switch err { 14 | case nil: 15 | return nil 16 | } 17 | 18 | if verr, ok := err.(*errors.Error); ok { 19 | return verr 20 | } 21 | 22 | // grpc error 23 | s, ok := status.FromError(err) 24 | if !ok { 25 | return err 26 | } 27 | 28 | // return first error from details 29 | if details := s.Details(); len(details) > 0 { 30 | return microError(details[0].(error)) 31 | } 32 | 33 | // try to decode micro *errors.Error 34 | if e := errors.Parse(s.Message()); e.Code > 0 { 35 | return e // actually a micro error 36 | } 37 | 38 | // fallback 39 | return errors.New("go.micro.client", s.Message(), microStatusFromGrpcCode(s.Code())) 40 | } 41 | 42 | func microStatusFromGrpcCode(code codes.Code) int32 { 43 | switch code { 44 | case codes.OK: 45 | return http.StatusOK 46 | case codes.InvalidArgument: 47 | return http.StatusBadRequest 48 | case codes.DeadlineExceeded: 49 | return http.StatusRequestTimeout 50 | case codes.NotFound: 51 | return http.StatusNotFound 52 | case codes.AlreadyExists: 53 | return http.StatusConflict 54 | case codes.PermissionDenied: 55 | return http.StatusForbidden 56 | case codes.Unauthenticated: 57 | return http.StatusUnauthorized 58 | case codes.FailedPrecondition: 59 | return http.StatusPreconditionFailed 60 | case codes.Unimplemented: 61 | return http.StatusNotImplemented 62 | case codes.Internal: 63 | return http.StatusInternalServerError 64 | case codes.Unavailable: 65 | return http.StatusServiceUnavailable 66 | } 67 | 68 | return http.StatusInternalServerError 69 | } 70 | -------------------------------------------------------------------------------- /client/grpc/grpc_pool_test.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "google.golang.org/grpc" 10 | pb "google.golang.org/grpc/examples/helloworld/helloworld" 11 | ) 12 | 13 | func testPool(t *testing.T, size int, ttl time.Duration, idle int, ms int) { 14 | // setup server 15 | l, err := net.Listen("tcp", ":0") 16 | if err != nil { 17 | t.Errorf("failed to listen: %v", err) 18 | } 19 | defer l.Close() 20 | 21 | s := grpc.NewServer() 22 | pb.RegisterGreeterServer(s, &greeterServer{}) 23 | 24 | go s.Serve(l) 25 | defer s.Stop() 26 | 27 | // zero pool 28 | p := newPool(size, ttl, idle, ms) 29 | 30 | for i := 0; i < 10; i++ { 31 | // get a conn 32 | cc, err := p.getConn(context.TODO(), l.Addr().String(), grpc.WithInsecure()) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | rsp := pb.HelloReply{} 38 | 39 | err = cc.Invoke(context.TODO(), "/helloworld.Greeter/SayHello", &pb.HelloRequest{Name: "John"}, &rsp) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | if rsp.Message != "Hello John" { 45 | t.Errorf("Got unexpected response %v", rsp.Message) 46 | } 47 | 48 | // release the conn 49 | p.release(l.Addr().String(), cc, nil) 50 | 51 | p.Lock() 52 | if i := p.conns[l.Addr().String()].count; i > size { 53 | p.Unlock() 54 | t.Errorf("pool size %d is greater than expected %d", i, size) 55 | } 56 | p.Unlock() 57 | } 58 | } 59 | 60 | func TestGRPCPool(t *testing.T) { 61 | testPool(t, 0, time.Minute, 10, 2) 62 | testPool(t, 2, time.Minute, 10, 1) 63 | } 64 | -------------------------------------------------------------------------------- /client/grpc/message.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "go-micro.dev/v5/client" 5 | ) 6 | 7 | type grpcEvent struct { 8 | topic string 9 | contentType string 10 | payload interface{} 11 | } 12 | 13 | func newGRPCEvent(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { 14 | var options client.MessageOptions 15 | for _, o := range opts { 16 | o(&options) 17 | } 18 | 19 | if len(options.ContentType) > 0 { 20 | contentType = options.ContentType 21 | } 22 | 23 | return &grpcEvent{ 24 | payload: payload, 25 | topic: topic, 26 | contentType: contentType, 27 | } 28 | } 29 | 30 | func (g *grpcEvent) ContentType() string { 31 | return g.contentType 32 | } 33 | 34 | func (g *grpcEvent) Topic() string { 35 | return g.topic 36 | } 37 | 38 | func (g *grpcEvent) Payload() interface{} { 39 | return g.payload 40 | } 41 | -------------------------------------------------------------------------------- /client/grpc/request_test.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMethodToGRPC(t *testing.T) { 8 | testData := []struct { 9 | service string 10 | method string 11 | expect string 12 | }{ 13 | { 14 | "helloworld", 15 | "Greeter.SayHello", 16 | "/helloworld.Greeter/SayHello", 17 | }, 18 | { 19 | "helloworld", 20 | "/helloworld.Greeter/SayHello", 21 | "/helloworld.Greeter/SayHello", 22 | }, 23 | { 24 | "", 25 | "/helloworld.Greeter/SayHello", 26 | "/helloworld.Greeter/SayHello", 27 | }, 28 | { 29 | "", 30 | "Greeter.SayHello", 31 | "/Greeter/SayHello", 32 | }, 33 | } 34 | 35 | for _, d := range testData { 36 | method := methodToGRPC(d.service, d.method) 37 | if method != d.expect { 38 | t.Fatalf("expected %s got %s", d.expect, method) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/grpc/response.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "strings" 5 | 6 | "go-micro.dev/v5/codec" 7 | "go-micro.dev/v5/codec/bytes" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/encoding" 10 | ) 11 | 12 | type response struct { 13 | conn *grpc.ClientConn 14 | stream grpc.ClientStream 15 | codec encoding.Codec 16 | gcodec codec.Codec 17 | } 18 | 19 | // Read the response. 20 | func (r *response) Codec() codec.Reader { 21 | return r.gcodec 22 | } 23 | 24 | // read the header. 25 | func (r *response) Header() map[string]string { 26 | md, err := r.stream.Header() 27 | if err != nil { 28 | return map[string]string{} 29 | } 30 | hdr := make(map[string]string, len(md)) 31 | for k, v := range md { 32 | hdr[k] = strings.Join(v, ",") 33 | } 34 | return hdr 35 | } 36 | 37 | // Read the undecoded response. 38 | func (r *response) Read() ([]byte, error) { 39 | f := &bytes.Frame{} 40 | if err := r.gcodec.ReadBody(f); err != nil { 41 | return nil, err 42 | } 43 | return f.Data, nil 44 | } 45 | -------------------------------------------------------------------------------- /client/grpc/stream.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "sync" 7 | 8 | "go-micro.dev/v5/client" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | // Implements the streamer interface. 13 | type grpcStream struct { 14 | sync.RWMutex 15 | closed bool 16 | err error 17 | stream grpc.ClientStream 18 | request client.Request 19 | response client.Response 20 | context context.Context 21 | cancel func() 22 | release func(error) 23 | } 24 | 25 | func (g *grpcStream) Context() context.Context { 26 | return g.context 27 | } 28 | 29 | func (g *grpcStream) Request() client.Request { 30 | return g.request 31 | } 32 | 33 | func (g *grpcStream) Response() client.Response { 34 | return g.response 35 | } 36 | 37 | func (g *grpcStream) Send(msg interface{}) error { 38 | if err := g.stream.SendMsg(msg); err != nil { 39 | g.setError(err) 40 | return err 41 | } 42 | return nil 43 | } 44 | 45 | func (g *grpcStream) Recv(msg interface{}) (err error) { 46 | if err = g.stream.RecvMsg(msg); err != nil { 47 | if err != io.EOF { 48 | g.setError(err) 49 | } 50 | return err 51 | } 52 | return 53 | } 54 | 55 | func (g *grpcStream) Error() error { 56 | g.RLock() 57 | defer g.RUnlock() 58 | return g.err 59 | } 60 | 61 | func (g *grpcStream) setError(e error) { 62 | g.Lock() 63 | g.err = e 64 | g.Unlock() 65 | } 66 | 67 | func (g *grpcStream) CloseSend() error { 68 | return g.stream.CloseSend() 69 | } 70 | 71 | func (g *grpcStream) Close() error { 72 | g.Lock() 73 | defer g.Unlock() 74 | 75 | if g.closed { 76 | return nil 77 | } 78 | // cancel the context 79 | g.cancel() 80 | g.closed = true 81 | // release back to pool 82 | g.release(g.err) 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /client/retry.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/errors" 7 | ) 8 | 9 | // note that returning either false or a non-nil error will result in the call not being retried. 10 | type RetryFunc func(ctx context.Context, req Request, retryCount int, err error) (bool, error) 11 | 12 | // RetryAlways always retry on error. 13 | func RetryAlways(ctx context.Context, req Request, retryCount int, err error) (bool, error) { 14 | return true, nil 15 | } 16 | 17 | // RetryOnError retries a request on a 500 or timeout error. 18 | func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (bool, error) { 19 | if err == nil { 20 | return false, nil 21 | } 22 | 23 | e := errors.Parse(err.Error()) 24 | if e == nil { 25 | return false, nil 26 | } 27 | 28 | switch e.Code { 29 | // Retry on timeout, not on 500 internal server error, as that is a business 30 | // logic error that should be handled by the user. 31 | case 408: 32 | return true, nil 33 | default: 34 | return false, nil 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/rpc_message.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | type message struct { 4 | payload interface{} 5 | topic string 6 | contentType string 7 | } 8 | 9 | func newMessage(topic string, payload interface{}, contentType string, opts ...MessageOption) Message { 10 | var options MessageOptions 11 | for _, o := range opts { 12 | o(&options) 13 | } 14 | 15 | if len(options.ContentType) > 0 { 16 | contentType = options.ContentType 17 | } 18 | 19 | return &message{ 20 | payload: payload, 21 | topic: topic, 22 | contentType: contentType, 23 | } 24 | } 25 | 26 | func (m *message) ContentType() string { 27 | return m.contentType 28 | } 29 | 30 | func (m *message) Topic() string { 31 | return m.topic 32 | } 33 | 34 | func (m *message) Payload() interface{} { 35 | return m.payload 36 | } 37 | -------------------------------------------------------------------------------- /client/rpc_request.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | ) 6 | 7 | type rpcRequest struct { 8 | opts RequestOptions 9 | codec codec.Codec 10 | body interface{} 11 | service string 12 | method string 13 | endpoint string 14 | contentType string 15 | } 16 | 17 | func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request { 18 | var opts RequestOptions 19 | 20 | for _, o := range reqOpts { 21 | o(&opts) 22 | } 23 | 24 | // set the content-type specified 25 | if len(opts.ContentType) > 0 { 26 | contentType = opts.ContentType 27 | } 28 | 29 | return &rpcRequest{ 30 | service: service, 31 | method: endpoint, 32 | endpoint: endpoint, 33 | body: request, 34 | contentType: contentType, 35 | opts: opts, 36 | } 37 | } 38 | 39 | func (r *rpcRequest) ContentType() string { 40 | return r.contentType 41 | } 42 | 43 | func (r *rpcRequest) Service() string { 44 | return r.service 45 | } 46 | 47 | func (r *rpcRequest) Method() string { 48 | return r.method 49 | } 50 | 51 | func (r *rpcRequest) Endpoint() string { 52 | return r.endpoint 53 | } 54 | 55 | func (r *rpcRequest) Body() interface{} { 56 | return r.body 57 | } 58 | 59 | func (r *rpcRequest) Codec() codec.Writer { 60 | return r.codec 61 | } 62 | 63 | func (r *rpcRequest) Stream() bool { 64 | return r.opts.Stream 65 | } 66 | -------------------------------------------------------------------------------- /client/rpc_request_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRequestOptions(t *testing.T) { 8 | r := newRequest("service", "endpoint", nil, "application/json") 9 | if r.Service() != "service" { 10 | t.Fatalf("expected 'service' got %s", r.Service()) 11 | } 12 | if r.Endpoint() != "endpoint" { 13 | t.Fatalf("expected 'endpoint' got %s", r.Endpoint()) 14 | } 15 | if r.ContentType() != "application/json" { 16 | t.Fatalf("expected 'endpoint' got %s", r.ContentType()) 17 | } 18 | 19 | r2 := newRequest("service", "endpoint", nil, "application/json", WithContentType("application/protobuf")) 20 | if r2.ContentType() != "application/protobuf" { 21 | t.Fatalf("expected 'endpoint' got %s", r2.ContentType()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/rpc_response.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | "go-micro.dev/v5/transport" 6 | ) 7 | 8 | type rpcResponse struct { 9 | socket transport.Socket 10 | codec codec.Codec 11 | header map[string]string 12 | body []byte 13 | } 14 | 15 | func (r *rpcResponse) Codec() codec.Reader { 16 | return r.codec 17 | } 18 | 19 | func (r *rpcResponse) Header() map[string]string { 20 | return r.header 21 | } 22 | 23 | func (r *rpcResponse) Read() ([]byte, error) { 24 | var msg transport.Message 25 | 26 | if err := r.socket.Recv(&msg); err != nil { 27 | return nil, err 28 | } 29 | 30 | // set internals 31 | r.header = msg.Header 32 | r.body = msg.Body 33 | 34 | return msg.Body, nil 35 | } 36 | -------------------------------------------------------------------------------- /client/wrapper.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/registry" 7 | ) 8 | 9 | // CallFunc represents the individual call func. 10 | type CallFunc func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error 11 | 12 | // CallWrapper is a low level wrapper for the CallFunc. 13 | type CallWrapper func(CallFunc) CallFunc 14 | 15 | // Wrapper wraps a client and returns a client. 16 | type Wrapper func(Client) Client 17 | 18 | // StreamWrapper wraps a Stream and returns the equivalent. 19 | type StreamWrapper func(Stream) Stream 20 | -------------------------------------------------------------------------------- /codec/bytes/bytes.go: -------------------------------------------------------------------------------- 1 | // Package bytes provides a bytes codec which does not encode or decode anything 2 | package bytes 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | type Codec struct { 12 | Conn io.ReadWriteCloser 13 | } 14 | 15 | // Frame gives us the ability to define raw data to send over the pipes. 16 | type Frame struct { 17 | Data []byte 18 | } 19 | 20 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 21 | return nil 22 | } 23 | 24 | func (c *Codec) ReadBody(b interface{}) error { 25 | // read bytes 26 | buf, err := io.ReadAll(c.Conn) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | switch v := b.(type) { 32 | case *[]byte: 33 | *v = buf 34 | case *Frame: 35 | v.Data = buf 36 | default: 37 | return fmt.Errorf("failed to read body: %v is not type of *[]byte", b) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 44 | var v []byte 45 | switch vb := b.(type) { 46 | case *Frame: 47 | v = vb.Data 48 | case *[]byte: 49 | v = *vb 50 | case []byte: 51 | v = vb 52 | default: 53 | return fmt.Errorf("failed to write: %v is not type of *[]byte or []byte", b) 54 | } 55 | _, err := c.Conn.Write(v) 56 | return err 57 | } 58 | 59 | func (c *Codec) Close() error { 60 | return c.Conn.Close() 61 | } 62 | 63 | func (c *Codec) String() string { 64 | return "bytes" 65 | } 66 | 67 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 68 | return &Codec{ 69 | Conn: c, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /codec/bytes/marshaler.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "go-micro.dev/v5/codec" 5 | ) 6 | 7 | type Marshaler struct{} 8 | 9 | type Message struct { 10 | Header map[string]string 11 | Body []byte 12 | } 13 | 14 | func (n Marshaler) Marshal(v interface{}) ([]byte, error) { 15 | switch ve := v.(type) { 16 | case *[]byte: 17 | return *ve, nil 18 | case []byte: 19 | return ve, nil 20 | case *Message: 21 | return ve.Body, nil 22 | } 23 | return nil, codec.ErrInvalidMessage 24 | } 25 | 26 | func (n Marshaler) Unmarshal(d []byte, v interface{}) error { 27 | switch ve := v.(type) { 28 | case *[]byte: 29 | *ve = d 30 | return nil 31 | case *Message: 32 | ve.Body = d 33 | return nil 34 | } 35 | return codec.ErrInvalidMessage 36 | } 37 | 38 | func (n Marshaler) String() string { 39 | return "bytes" 40 | } 41 | -------------------------------------------------------------------------------- /codec/codec.go: -------------------------------------------------------------------------------- 1 | // Package codec is an interface for encoding messages 2 | package codec 3 | 4 | import ( 5 | "errors" 6 | "io" 7 | ) 8 | 9 | const ( 10 | Error MessageType = iota 11 | Request 12 | Response 13 | Event 14 | ) 15 | 16 | var ( 17 | ErrInvalidMessage = errors.New("invalid message") 18 | ) 19 | 20 | type MessageType int 21 | 22 | // Takes in a connection/buffer and returns a new Codec. 23 | type NewCodec func(io.ReadWriteCloser) Codec 24 | 25 | // Codec encodes/decodes various types of messages used within go-micro. 26 | // ReadHeader and ReadBody are called in pairs to read requests/responses 27 | // from the connection. Close is called when finished with the 28 | // connection. ReadBody may be called with a nil argument to force the 29 | // body to be read and discarded. 30 | type Codec interface { 31 | Reader 32 | Writer 33 | Close() error 34 | String() string 35 | } 36 | 37 | type Reader interface { 38 | ReadHeader(*Message, MessageType) error 39 | ReadBody(interface{}) error 40 | } 41 | 42 | type Writer interface { 43 | Write(*Message, interface{}) error 44 | } 45 | 46 | // Marshaler is a simple encoding interface used for the broker/transport 47 | // where headers are not supported by the underlying implementation. 48 | type Marshaler interface { 49 | Marshal(interface{}) ([]byte, error) 50 | Unmarshal([]byte, interface{}) error 51 | String() string 52 | } 53 | 54 | // Message represents detailed information about 55 | // the communication, likely followed by the body. 56 | // In the case of an error, body may be nil. 57 | type Message struct { 58 | 59 | // The values read from the socket 60 | Header map[string]string 61 | Id string 62 | Target string 63 | Method string 64 | Endpoint string 65 | Error string 66 | 67 | Body []byte 68 | Type MessageType 69 | } 70 | -------------------------------------------------------------------------------- /codec/grpc/util.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | var ( 10 | MaxMessageSize = 1024 * 1024 * 4 // 4Mb 11 | maxInt = int(^uint(0) >> 1) 12 | ) 13 | 14 | func decode(r io.Reader) (uint8, []byte, error) { 15 | header := make([]byte, 5) 16 | 17 | // read the header 18 | if _, err := r.Read(header); err != nil { 19 | return uint8(0), nil, err 20 | } 21 | 22 | // get encoding format e.g compressed 23 | cf := uint8(header[0]) 24 | 25 | // get message length 26 | length := binary.BigEndian.Uint32(header[1:]) 27 | 28 | // no encoding format 29 | if length == 0 { 30 | return cf, nil, nil 31 | } 32 | 33 | // 34 | if int64(length) > int64(maxInt) { 35 | return cf, nil, fmt.Errorf("grpc: received message larger than max length allowed on current machine (%d vs. %d)", length, maxInt) 36 | } 37 | if int(length) > MaxMessageSize { 38 | return cf, nil, fmt.Errorf("grpc: received message larger than max (%d vs. %d)", length, MaxMessageSize) 39 | } 40 | 41 | msg := make([]byte, int(length)) 42 | 43 | if _, err := r.Read(msg); err != nil { 44 | if err == io.EOF { 45 | err = io.ErrUnexpectedEOF 46 | } 47 | return cf, nil, err 48 | } 49 | 50 | return cf, msg, nil 51 | } 52 | 53 | func encode(cf uint8, buf []byte, w io.Writer) error { 54 | header := make([]byte, 5) 55 | 56 | // set compression 57 | header[0] = byte(cf) 58 | 59 | // write length as header 60 | binary.BigEndian.PutUint32(header[1:], uint32(len(buf))) 61 | 62 | // read the header 63 | if _, err := w.Write(header); err != nil { 64 | return err 65 | } 66 | 67 | // write the buffer 68 | _, err := w.Write(buf) 69 | return err 70 | } 71 | -------------------------------------------------------------------------------- /codec/json/json.go: -------------------------------------------------------------------------------- 1 | // Package json provides a json codec 2 | package json 3 | 4 | import ( 5 | "encoding/json" 6 | "io" 7 | 8 | "github.com/golang/protobuf/jsonpb" 9 | "github.com/golang/protobuf/proto" 10 | "go-micro.dev/v5/codec" 11 | ) 12 | 13 | type Codec struct { 14 | Conn io.ReadWriteCloser 15 | Encoder *json.Encoder 16 | Decoder *json.Decoder 17 | } 18 | 19 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 20 | return nil 21 | } 22 | 23 | func (c *Codec) ReadBody(b interface{}) error { 24 | if b == nil { 25 | return nil 26 | } 27 | if pb, ok := b.(proto.Message); ok { 28 | return jsonpb.UnmarshalNext(c.Decoder, pb) 29 | } 30 | return c.Decoder.Decode(b) 31 | } 32 | 33 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 34 | if b == nil { 35 | return nil 36 | } 37 | return c.Encoder.Encode(b) 38 | } 39 | 40 | func (c *Codec) Close() error { 41 | return c.Conn.Close() 42 | } 43 | 44 | func (c *Codec) String() string { 45 | return "json" 46 | } 47 | 48 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 49 | return &Codec{ 50 | Conn: c, 51 | Decoder: json.NewDecoder(c), 52 | Encoder: json.NewEncoder(c), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /codec/json/marshaler.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/golang/protobuf/jsonpb" 8 | "github.com/golang/protobuf/proto" 9 | "github.com/oxtoacart/bpool" 10 | ) 11 | 12 | var jsonpbMarshaler = &jsonpb.Marshaler{} 13 | 14 | // create buffer pool with 16 instances each preallocated with 256 bytes. 15 | var bufferPool = bpool.NewSizedBufferPool(16, 256) 16 | 17 | type Marshaler struct{} 18 | 19 | func (j Marshaler) Marshal(v interface{}) ([]byte, error) { 20 | if pb, ok := v.(proto.Message); ok { 21 | buf := bufferPool.Get() 22 | defer bufferPool.Put(buf) 23 | if err := jsonpbMarshaler.Marshal(buf, pb); err != nil { 24 | return nil, err 25 | } 26 | return buf.Bytes(), nil 27 | } 28 | return json.Marshal(v) 29 | } 30 | 31 | func (j Marshaler) Unmarshal(d []byte, v interface{}) error { 32 | if pb, ok := v.(proto.Message); ok { 33 | return jsonpb.Unmarshal(bytes.NewReader(d), pb) 34 | } 35 | return json.Unmarshal(d, v) 36 | } 37 | 38 | func (j Marshaler) String() string { 39 | return "json" 40 | } 41 | -------------------------------------------------------------------------------- /codec/proto/marshaler.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/golang/protobuf/proto" 7 | "github.com/oxtoacart/bpool" 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | // create buffer pool with 16 instances each preallocated with 256 bytes. 12 | var bufferPool = bpool.NewSizedBufferPool(16, 256) 13 | 14 | type Marshaler struct{} 15 | 16 | func (Marshaler) Marshal(v interface{}) ([]byte, error) { 17 | pb, ok := v.(proto.Message) 18 | if !ok { 19 | return nil, codec.ErrInvalidMessage 20 | } 21 | 22 | // looks not good, but allows to reuse underlining bytes 23 | buf := bufferPool.Get() 24 | pbuf := proto.NewBuffer(buf.Bytes()) 25 | defer func() { 26 | bufferPool.Put(bytes.NewBuffer(pbuf.Bytes())) 27 | }() 28 | 29 | if err := pbuf.Marshal(pb); err != nil { 30 | return nil, err 31 | } 32 | 33 | return pbuf.Bytes(), nil 34 | } 35 | 36 | func (Marshaler) Unmarshal(data []byte, v interface{}) error { 37 | pb, ok := v.(proto.Message) 38 | if !ok { 39 | return codec.ErrInvalidMessage 40 | } 41 | 42 | return proto.Unmarshal(data, pb) 43 | } 44 | 45 | func (Marshaler) String() string { 46 | return "proto" 47 | } 48 | -------------------------------------------------------------------------------- /codec/proto/message.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | type Message struct { 4 | Data []byte 5 | } 6 | 7 | func (m *Message) MarshalJSON() ([]byte, error) { 8 | return m.Data, nil 9 | } 10 | 11 | func (m *Message) UnmarshalJSON(data []byte) error { 12 | m.Data = data 13 | return nil 14 | } 15 | 16 | func (m *Message) ProtoMessage() {} 17 | 18 | func (m *Message) Reset() { 19 | *m = Message{} 20 | } 21 | 22 | func (m *Message) String() string { 23 | return string(m.Data) 24 | } 25 | 26 | func (m *Message) Marshal() ([]byte, error) { 27 | return m.Data, nil 28 | } 29 | 30 | func (m *Message) Unmarshal(data []byte) error { 31 | m.Data = data 32 | return nil 33 | } 34 | 35 | func NewMessage(data []byte) *Message { 36 | return &Message{data} 37 | } 38 | -------------------------------------------------------------------------------- /codec/proto/proto.go: -------------------------------------------------------------------------------- 1 | // Package proto provides a proto codec 2 | package proto 3 | 4 | import ( 5 | "io" 6 | 7 | "github.com/golang/protobuf/proto" 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | type Codec struct { 12 | Conn io.ReadWriteCloser 13 | } 14 | 15 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 16 | return nil 17 | } 18 | 19 | func (c *Codec) ReadBody(b interface{}) error { 20 | if b == nil { 21 | return nil 22 | } 23 | buf, err := io.ReadAll(c.Conn) 24 | if err != nil { 25 | return err 26 | } 27 | m, ok := b.(proto.Message) 28 | if !ok { 29 | return codec.ErrInvalidMessage 30 | } 31 | return proto.Unmarshal(buf, m) 32 | } 33 | 34 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 35 | if b == nil { 36 | // Nothing to write 37 | return nil 38 | } 39 | p, ok := b.(proto.Message) 40 | if !ok { 41 | return codec.ErrInvalidMessage 42 | } 43 | buf, err := proto.Marshal(p) 44 | if err != nil { 45 | return err 46 | } 47 | _, err = c.Conn.Write(buf) 48 | return err 49 | } 50 | 51 | func (c *Codec) Close() error { 52 | return c.Conn.Close() 53 | } 54 | 55 | func (c *Codec) String() string { 56 | return "proto" 57 | } 58 | 59 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 60 | return &Codec{ 61 | Conn: c, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /codec/protorpc/envelope.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: codec/protorpc/envelope.proto 3 | 4 | package protorpc 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | -------------------------------------------------------------------------------- /codec/protorpc/envelope.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package protorpc; 4 | 5 | message Request { 6 | string service_method = 1; 7 | fixed64 seq = 2; 8 | } 9 | 10 | message Response { 11 | string service_method = 1; 12 | fixed64 seq = 2; 13 | string error = 3; 14 | } 15 | -------------------------------------------------------------------------------- /codec/protorpc/netstring.go: -------------------------------------------------------------------------------- 1 | package protorpc 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // WriteNetString writes data to a big-endian netstring on a Writer. 9 | // Size is always a 32-bit unsigned int. 10 | func WriteNetString(w io.Writer, data []byte) (written int, err error) { 11 | size := make([]byte, 4) 12 | binary.BigEndian.PutUint32(size, uint32(len(data))) 13 | if written, err = w.Write(size); err != nil { 14 | return 15 | } 16 | return w.Write(data) 17 | } 18 | 19 | // ReadNetString reads data from a big-endian netstring. 20 | func ReadNetString(r io.Reader) (data []byte, err error) { 21 | sizeBuf := make([]byte, 4) 22 | _, err = r.Read(sizeBuf) 23 | if err != nil { 24 | return nil, err 25 | } 26 | size := binary.BigEndian.Uint32(sizeBuf) 27 | if size == 0 { 28 | return nil, nil 29 | } 30 | data = make([]byte, size) 31 | _, err = r.Read(data) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /codec/text/text.go: -------------------------------------------------------------------------------- 1 | // Package text reads any text/* content-type 2 | package text 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | 8 | "go-micro.dev/v5/codec" 9 | ) 10 | 11 | type Codec struct { 12 | Conn io.ReadWriteCloser 13 | } 14 | 15 | // Frame gives us the ability to define raw data to send over the pipes. 16 | type Frame struct { 17 | Data []byte 18 | } 19 | 20 | func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error { 21 | return nil 22 | } 23 | 24 | func (c *Codec) ReadBody(b interface{}) error { 25 | // read bytes 26 | buf, err := io.ReadAll(c.Conn) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | switch v := b.(type) { 32 | case *string: 33 | *v = string(buf) 34 | case *[]byte: 35 | *v = buf 36 | case *Frame: 37 | v.Data = buf 38 | default: 39 | return fmt.Errorf("failed to read body: %v is not type of *[]byte", b) 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func (c *Codec) Write(m *codec.Message, b interface{}) error { 46 | var v []byte 47 | switch ve := b.(type) { 48 | case *Frame: 49 | v = ve.Data 50 | case *[]byte: 51 | v = *ve 52 | case *string: 53 | v = []byte(*ve) 54 | case string: 55 | v = []byte(ve) 56 | case []byte: 57 | v = ve 58 | default: 59 | return fmt.Errorf("failed to write: %v is not type of *[]byte or []byte", b) 60 | } 61 | _, err := c.Conn.Write(v) 62 | return err 63 | } 64 | 65 | func (c *Codec) Close() error { 66 | return c.Conn.Close() 67 | } 68 | 69 | func (c *Codec) String() string { 70 | return "text" 71 | } 72 | 73 | func NewCodec(c io.ReadWriteCloser) codec.Codec { 74 | return &Codec{ 75 | Conn: c, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | # Config [](https://godoc.org/github.com/micro/go-micro/config) 2 | 3 | Config is a pluggable dynamic config package 4 | 5 | Most config in applications are statically configured or include complex logic to load from multiple sources. 6 | Go Config makes this easy, pluggable and mergeable. You'll never have to deal with config in the same way again. 7 | 8 | ## Features 9 | 10 | - **Dynamic Loading** - Load configuration from multiple source as and when needed. Go Config manages watching config sources 11 | in the background and automatically merges and updates an in memory view. 12 | 13 | - **Pluggable Sources** - Choose from any number of sources to load and merge config. The backend source is abstracted away into 14 | a standard format consumed internally and decoded via encoders. Sources can be env vars, flags, file, etcd, k8s configmap, etc. 15 | 16 | - **Mergeable Config** - If you specify multiple sources of config, regardless of format, they will be merged and presented in 17 | a single view. This massively simplifies priority order loading and changes based on environment. 18 | 19 | - **Observe Changes** - Optionally watch the config for changes to specific values. Hot reload your app using Go Config's watcher. 20 | You don't have to handle ad-hoc hup reloading or whatever else, just keep reading the config and watch for changes if you need 21 | to be notified. 22 | 23 | - **Sane Defaults** - In case config loads badly or is completely wiped away for some unknown reason, you can specify fallback 24 | values when accessing any config values directly. This ensures you'll always be reading some sane default in the event of a problem. 25 | -------------------------------------------------------------------------------- /config/encoder/encoder.go: -------------------------------------------------------------------------------- 1 | // Package encoder handles source encoding formats 2 | package encoder 3 | 4 | type Encoder interface { 5 | Encode(interface{}) ([]byte, error) 6 | Decode([]byte, interface{}) error 7 | String() string 8 | } 9 | -------------------------------------------------------------------------------- /config/encoder/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "go-micro.dev/v5/config/encoder" 7 | ) 8 | 9 | type jsonEncoder struct{} 10 | 11 | func (j jsonEncoder) Encode(v interface{}) ([]byte, error) { 12 | return json.Marshal(v) 13 | } 14 | 15 | func (j jsonEncoder) Decode(d []byte, v interface{}) error { 16 | return json.Unmarshal(d, v) 17 | } 18 | 19 | func (j jsonEncoder) String() string { 20 | return "json" 21 | } 22 | 23 | func NewEncoder() encoder.Encoder { 24 | return jsonEncoder{} 25 | } 26 | -------------------------------------------------------------------------------- /config/loader/loader.go: -------------------------------------------------------------------------------- 1 | // Package loader manages loading from multiple sources 2 | package loader 3 | 4 | import ( 5 | "context" 6 | 7 | "go-micro.dev/v5/config/reader" 8 | "go-micro.dev/v5/config/source" 9 | ) 10 | 11 | // Loader manages loading sources. 12 | type Loader interface { 13 | // Stop the loader 14 | Close() error 15 | // Load the sources 16 | Load(...source.Source) error 17 | // A Snapshot of loaded config 18 | Snapshot() (*Snapshot, error) 19 | // Force sync of sources 20 | Sync() error 21 | // Watch for changes 22 | Watch(...string) (Watcher, error) 23 | // Name of loader 24 | String() string 25 | } 26 | 27 | // Watcher lets you watch sources and returns a merged ChangeSet. 28 | type Watcher interface { 29 | // First call to next may return the current Snapshot 30 | // If you are watching a path then only the data from 31 | // that path is returned. 32 | Next() (*Snapshot, error) 33 | // Stop watching for changes 34 | Stop() error 35 | } 36 | 37 | // Snapshot is a merged ChangeSet. 38 | type Snapshot struct { 39 | // The merged ChangeSet 40 | ChangeSet *source.ChangeSet 41 | // Deterministic and comparable version of the snapshot 42 | Version string 43 | } 44 | 45 | // Options contains all options for a config loader. 46 | type Options struct { 47 | Reader reader.Reader 48 | 49 | // for alternative data 50 | Context context.Context 51 | 52 | Source []source.Source 53 | 54 | WithWatcherDisabled bool 55 | } 56 | 57 | // Option is a helper for a single option. 58 | type Option func(o *Options) 59 | 60 | // Copy snapshot. 61 | func Copy(s *Snapshot) *Snapshot { 62 | cs := *(s.ChangeSet) 63 | 64 | return &Snapshot{ 65 | ChangeSet: &cs, 66 | Version: s.Version, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config/loader/memory/options.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "go-micro.dev/v5/config/loader" 5 | "go-micro.dev/v5/config/reader" 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | // WithSource appends a source to list of sources. 10 | func WithSource(s source.Source) loader.Option { 11 | return func(o *loader.Options) { 12 | o.Source = append(o.Source, s) 13 | } 14 | } 15 | 16 | // WithReader sets the config reader. 17 | func WithReader(r reader.Reader) loader.Option { 18 | return func(o *loader.Options) { 19 | o.Reader = r 20 | } 21 | } 22 | 23 | func WithWatcherDisabled() loader.Option { 24 | return func(o *loader.Options) { 25 | o.WithWatcherDisabled = true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /config/options.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "go-micro.dev/v5/config/loader" 5 | "go-micro.dev/v5/config/reader" 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | // WithLoader sets the loader for manager config. 10 | func WithLoader(l loader.Loader) Option { 11 | return func(o *Options) { 12 | o.Loader = l 13 | } 14 | } 15 | 16 | // WithSource appends a source to list of sources. 17 | func WithSource(s source.Source) Option { 18 | return func(o *Options) { 19 | o.Source = append(o.Source, s) 20 | } 21 | } 22 | 23 | // WithReader sets the config reader. 24 | func WithReader(r reader.Reader) Option { 25 | return func(o *Options) { 26 | o.Reader = r 27 | } 28 | } 29 | 30 | func WithWatcherDisabled() Option { 31 | return func(o *Options) { 32 | o.WithWatcherDisabled = true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /config/reader/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/imdario/mergo" 8 | "go-micro.dev/v5/config/encoder" 9 | "go-micro.dev/v5/config/encoder/json" 10 | "go-micro.dev/v5/config/reader" 11 | "go-micro.dev/v5/config/source" 12 | ) 13 | 14 | type jsonReader struct { 15 | opts reader.Options 16 | json encoder.Encoder 17 | } 18 | 19 | func (j *jsonReader) Merge(changes ...*source.ChangeSet) (*source.ChangeSet, error) { 20 | var merged map[string]interface{} 21 | 22 | for _, m := range changes { 23 | if m == nil { 24 | continue 25 | } 26 | 27 | if len(m.Data) == 0 { 28 | continue 29 | } 30 | 31 | codec, ok := j.opts.Encoding[m.Format] 32 | if !ok { 33 | // fallback 34 | codec = j.json 35 | } 36 | 37 | var data map[string]interface{} 38 | if err := codec.Decode(m.Data, &data); err != nil { 39 | return nil, err 40 | } 41 | if err := mergo.Map(&merged, data, mergo.WithOverride); err != nil { 42 | return nil, err 43 | } 44 | } 45 | 46 | b, err := j.json.Encode(merged) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | cs := &source.ChangeSet{ 52 | Timestamp: time.Now(), 53 | Data: b, 54 | Source: "json", 55 | Format: j.json.String(), 56 | } 57 | cs.Checksum = cs.Sum() 58 | 59 | return cs, nil 60 | } 61 | 62 | func (j *jsonReader) Values(ch *source.ChangeSet) (reader.Values, error) { 63 | if ch == nil { 64 | return nil, errors.New("changeset is nil") 65 | } 66 | if ch.Format != "json" { 67 | return nil, errors.New("unsupported format") 68 | } 69 | return newValues(ch) 70 | } 71 | 72 | func (j *jsonReader) String() string { 73 | return "json" 74 | } 75 | 76 | // NewReader creates a json reader. 77 | func NewReader(opts ...reader.Option) reader.Reader { 78 | options := reader.NewOptions(opts...) 79 | return &jsonReader{ 80 | json: json.NewEncoder(), 81 | opts: options, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /config/reader/json/json_test.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "testing" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | func TestReader(t *testing.T) { 10 | data := []byte(`{"foo": "bar", "baz": {"bar": "cat"}}`) 11 | 12 | testData := []struct { 13 | path []string 14 | value string 15 | }{ 16 | { 17 | []string{"foo"}, 18 | "bar", 19 | }, 20 | { 21 | []string{"baz", "bar"}, 22 | "cat", 23 | }, 24 | } 25 | 26 | r := NewReader() 27 | 28 | c, err := r.Merge(&source.ChangeSet{Data: data}, &source.ChangeSet{}) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | values, err := r.Values(c) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | for _, test := range testData { 39 | if v, err := values.Get(test.path...); err != nil { 40 | t.Fatal(err) 41 | } else if v.String("") != test.value { 42 | t.Fatalf("Expected %s got %s for path %v", test.value, v, test.path) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /config/reader/options.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "go-micro.dev/v5/config/encoder" 5 | "go-micro.dev/v5/config/encoder/json" 6 | ) 7 | 8 | type Options struct { 9 | Encoding map[string]encoder.Encoder 10 | } 11 | 12 | type Option func(o *Options) 13 | 14 | func NewOptions(opts ...Option) Options { 15 | options := Options{ 16 | Encoding: map[string]encoder.Encoder{ 17 | "json": json.NewEncoder(), 18 | }, 19 | } 20 | for _, o := range opts { 21 | o(&options) 22 | } 23 | return options 24 | } 25 | 26 | func WithEncoder(e encoder.Encoder) Option { 27 | return func(o *Options) { 28 | if o.Encoding == nil { 29 | o.Encoding = make(map[string]encoder.Encoder) 30 | } 31 | o.Encoding[e.String()] = e 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /config/reader/preprocessor.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "os" 5 | "regexp" 6 | ) 7 | 8 | func ReplaceEnvVars(raw []byte) ([]byte, error) { 9 | re := regexp.MustCompile(`\$\{([A-Za-z0-9_]+)\}`) 10 | if re.Match(raw) { 11 | dataS := string(raw) 12 | res := re.ReplaceAllStringFunc(dataS, replaceEnvVars) 13 | return []byte(res), nil 14 | } else { 15 | return raw, nil 16 | } 17 | } 18 | 19 | func replaceEnvVars(element string) string { 20 | v := element[2 : len(element)-1] 21 | el := os.Getenv(v) 22 | return el 23 | } 24 | -------------------------------------------------------------------------------- /config/reader/reader.go: -------------------------------------------------------------------------------- 1 | // Package reader parses change sets and provides config values 2 | package reader 3 | 4 | import ( 5 | "time" 6 | 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | // Reader is an interface for merging changesets. 11 | type Reader interface { 12 | Merge(...*source.ChangeSet) (*source.ChangeSet, error) 13 | Values(*source.ChangeSet) (Values, error) 14 | String() string 15 | } 16 | 17 | // Values is returned by the reader. 18 | type Values interface { 19 | Bytes() []byte 20 | Get(path ...string) (Value, error) 21 | Set(val interface{}, path ...string) 22 | Del(path ...string) 23 | Map() map[string]interface{} 24 | Scan(v interface{}) error 25 | } 26 | 27 | // Value represents a value of any type. 28 | type Value interface { 29 | Bool(def bool) bool 30 | Int(def int) int 31 | String(def string) string 32 | Float64(def float64) float64 33 | Duration(def time.Duration) time.Duration 34 | StringSlice(def []string) []string 35 | StringMap(def map[string]string) map[string]string 36 | Scan(val interface{}) error 37 | Bytes() []byte 38 | } 39 | -------------------------------------------------------------------------------- /config/secrets/secretbox/secretbox_test.go: -------------------------------------------------------------------------------- 1 | package secretbox 2 | 3 | import ( 4 | "encoding/base64" 5 | "reflect" 6 | "testing" 7 | 8 | "go-micro.dev/v5/config/secrets" 9 | ) 10 | 11 | func TestSecretBox(t *testing.T) { 12 | secretKey, err := base64.StdEncoding.DecodeString("4jbVgq8FsAV7vy+n8WqEZrl7BUtNqh3fYT5RXzXOPFY=") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | s := NewSecrets() 18 | 19 | if err := s.Init(); err == nil { 20 | t.Error("Secretbox accepted an empty secret key") 21 | } 22 | if err := s.Init(secrets.Key([]byte("invalid"))); err == nil { 23 | t.Error("Secretbox accepted a secret key that is invalid") 24 | } 25 | 26 | if err := s.Init(secrets.Key(secretKey)); err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | o := s.Options() 31 | if !reflect.DeepEqual(o.Key, secretKey) { 32 | t.Error("Init() didn't set secret key correctly") 33 | } 34 | if s.String() != "nacl-secretbox" { 35 | t.Error(s.String() + " should be nacl-secretbox") 36 | } 37 | 38 | // Try 10 times to get different nonces 39 | for i := 0; i < 10; i++ { 40 | message := []byte(`Can you hear me, Major Tom?`) 41 | 42 | encrypted, err := s.Encrypt(message) 43 | if err != nil { 44 | t.Errorf("Failed to encrypt message (%s)", err) 45 | } 46 | 47 | decrypted, err := s.Decrypt(encrypted) 48 | if err != nil { 49 | t.Errorf("Failed to decrypt encrypted message (%s)", err) 50 | } 51 | 52 | if !reflect.DeepEqual(message, decrypted) { 53 | t.Errorf("Decrypted Message dod not match encrypted message") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/source/changeset.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | ) 7 | 8 | // Sum returns the md5 checksum of the ChangeSet data. 9 | func (c *ChangeSet) Sum() string { 10 | h := md5.New() 11 | h.Write(c.Data) 12 | return fmt.Sprintf("%x", h.Sum(nil)) 13 | } 14 | -------------------------------------------------------------------------------- /config/source/cli/README.md: -------------------------------------------------------------------------------- 1 | # cli Source 2 | 3 | The cli source reads config from parsed flags via a cli.Context. 4 | 5 | ## Format 6 | 7 | We expect the use of the `urfave/cli` package. Upper case flags will be lower cased. Dashes will be used as delimiters for nesting. 8 | 9 | ### Example 10 | 11 | ```go 12 | micro.Flags( 13 | cli.StringFlag{ 14 | Name: "database-address", 15 | Value: "127.0.0.1", 16 | Usage: "the db address", 17 | }, 18 | cli.IntFlag{ 19 | Name: "database-port", 20 | Value: 3306, 21 | Usage: "the db port", 22 | }, 23 | ) 24 | ``` 25 | 26 | Becomes 27 | 28 | ```json 29 | { 30 | "database": { 31 | "address": "127.0.0.1", 32 | "port": 3306 33 | } 34 | } 35 | ``` 36 | 37 | ## New and Load Source 38 | 39 | Because a cli.Context is needed to retrieve the flags and their values, it is recommended to build your source from within a cli.Action. 40 | 41 | ```go 42 | 43 | func main() { 44 | // New Service 45 | service := micro.NewService( 46 | micro.Name("example"), 47 | micro.Flags( 48 | cli.StringFlag{ 49 | Name: "database-address", 50 | Value: "127.0.0.1", 51 | Usage: "the db address", 52 | }, 53 | ), 54 | ) 55 | 56 | var clisrc source.Source 57 | 58 | service.Init( 59 | micro.Action(func(c *cli.Context) { 60 | clisrc = cli.NewSource( 61 | cli.Context(c), 62 | ) 63 | // Alternatively, just setup your config right here 64 | }), 65 | ) 66 | 67 | // ... Load and use that source ... 68 | conf := config.NewConfig() 69 | conf.Load(clisrc) 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /config/source/cli/options.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/urfave/cli/v2" 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | type contextKey struct{} 11 | 12 | // Context sets the cli context. 13 | func Context(c *cli.Context) source.Option { 14 | return func(o *source.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | o.Context = context.WithValue(o.Context, contextKey{}, c) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/source/cli/util.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "strings" 7 | 8 | "github.com/urfave/cli/v2" 9 | ) 10 | 11 | func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { 12 | switch ff.Value.(type) { 13 | case *cli.StringSlice: 14 | default: 15 | set.Set(name, ff.Value.String()) 16 | } 17 | } 18 | 19 | func normalizeFlags(flags []cli.Flag, set *flag.FlagSet) error { 20 | visited := make(map[string]bool) 21 | set.Visit(func(f *flag.Flag) { 22 | visited[f.Name] = true 23 | }) 24 | for _, f := range flags { 25 | parts := f.Names() 26 | if len(parts) == 1 { 27 | continue 28 | } 29 | var ff *flag.Flag 30 | for _, name := range parts { 31 | name = strings.Trim(name, " ") 32 | if visited[name] { 33 | if ff != nil { 34 | return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) 35 | } 36 | ff = set.Lookup(name) 37 | } 38 | } 39 | if ff == nil { 40 | continue 41 | } 42 | for _, name := range parts { 43 | name = strings.Trim(name, " ") 44 | if !visited[name] { 45 | copyFlag(name, ff, set) 46 | } 47 | } 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /config/source/env/README.md: -------------------------------------------------------------------------------- 1 | # Env Source 2 | 3 | The env source reads config from environment variables 4 | 5 | ## Format 6 | 7 | We expect environment variables to be in the standard format of FOO=bar 8 | 9 | Keys are converted to lowercase and split on underscore. 10 | 11 | ### Format example 12 | 13 | ```bash 14 | DATABASE_ADDRESS=127.0.0.1 15 | DATABASE_PORT=3306 16 | ``` 17 | 18 | Becomes 19 | 20 | ```json 21 | { 22 | "database": { 23 | "address": "127.0.0.1", 24 | "port": 3306 25 | } 26 | } 27 | ``` 28 | 29 | ## Prefixes 30 | 31 | Environment variables can be namespaced so we only have access to a subset. Two options are available: 32 | 33 | ```go 34 | WithPrefix(p ...string) 35 | WithStrippedPrefix(p ...string) 36 | ``` 37 | 38 | The former will preserve the prefix and make it a top level key in the config. The latter eliminates the prefix, reducing the nesting by one. 39 | 40 | ### Prefixes example 41 | 42 | Given ENVs of: 43 | 44 | ```bash 45 | APP_DATABASE_ADDRESS=127.0.0.1 46 | APP_DATABASE_PORT=3306 47 | VAULT_ADDR=vault:1337 48 | ``` 49 | 50 | and a source initialized as follows: 51 | 52 | ```go 53 | src := env.NewSource( 54 | env.WithPrefix("VAULT"), 55 | env.WithStrippedPrefix("APP"), 56 | ) 57 | ``` 58 | 59 | The resulting config will be: 60 | 61 | ```json 62 | { 63 | "database": { 64 | "address": "127.0.0.1", 65 | "port": 3306 66 | }, 67 | "vault": { 68 | "addr": "vault:1337" 69 | } 70 | } 71 | ``` 72 | 73 | ## New Source 74 | 75 | Specify source with data 76 | 77 | ```go 78 | src := env.NewSource( 79 | // optionally specify prefix 80 | env.WithPrefix("MICRO"), 81 | ) 82 | ``` 83 | 84 | ## Load Source 85 | 86 | Load the source into config 87 | 88 | ```go 89 | // Create new config 90 | conf := config.NewConfig() 91 | 92 | // Load env source 93 | conf.Load(src) 94 | ``` 95 | -------------------------------------------------------------------------------- /config/source/env/options.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | type strippedPrefixKey struct{} 11 | type prefixKey struct{} 12 | 13 | // WithStrippedPrefix sets the environment variable prefixes to scope to. 14 | // These prefixes will be removed from the actual config entries. 15 | func WithStrippedPrefix(p ...string) source.Option { 16 | return func(o *source.Options) { 17 | if o.Context == nil { 18 | o.Context = context.Background() 19 | } 20 | 21 | o.Context = context.WithValue(o.Context, strippedPrefixKey{}, appendUnderscore(p)) 22 | } 23 | } 24 | 25 | // WithPrefix sets the environment variable prefixes to scope to. 26 | // These prefixes will not be removed. Each prefix will be considered a top level config entry. 27 | func WithPrefix(p ...string) source.Option { 28 | return func(o *source.Options) { 29 | if o.Context == nil { 30 | o.Context = context.Background() 31 | } 32 | o.Context = context.WithValue(o.Context, prefixKey{}, appendUnderscore(p)) 33 | } 34 | } 35 | 36 | func appendUnderscore(prefixes []string) []string { 37 | //nolint:prealloc 38 | var result []string 39 | for _, p := range prefixes { 40 | if !strings.HasSuffix(p, "_") { 41 | result = append(result, p+"_") 42 | continue 43 | } 44 | 45 | result = append(result, p) 46 | } 47 | 48 | return result 49 | } 50 | -------------------------------------------------------------------------------- /config/source/env/watcher.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "go-micro.dev/v5/config/source" 5 | ) 6 | 7 | type watcher struct { 8 | exit chan struct{} 9 | } 10 | 11 | func (w *watcher) Next() (*source.ChangeSet, error) { 12 | <-w.exit 13 | 14 | return nil, source.ErrWatcherStopped 15 | } 16 | 17 | func (w *watcher) Stop() error { 18 | close(w.exit) 19 | return nil 20 | } 21 | 22 | func newWatcher() (source.Watcher, error) { 23 | return &watcher{exit: make(chan struct{})}, nil 24 | } 25 | -------------------------------------------------------------------------------- /config/source/file/README.md: -------------------------------------------------------------------------------- 1 | # File Source 2 | 3 | The file source reads config from a file. 4 | 5 | It uses the File extension to determine the Format e.g `config.yaml` has the yaml format. 6 | It does not make use of encoders or interpet the file data. If a file extension is not present 7 | the source Format will default to the Encoder in options. 8 | 9 | ## Example 10 | 11 | A config file format in json 12 | 13 | ```json 14 | { 15 | "hosts": { 16 | "database": { 17 | "address": "10.0.0.1", 18 | "port": 3306 19 | }, 20 | "cache": { 21 | "address": "10.0.0.2", 22 | "port": 6379 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | ## New Source 29 | 30 | Specify file source with path to file. Path is optional and will default to `config.json` 31 | 32 | ```go 33 | fileSource := file.NewSource( 34 | file.WithPath("/tmp/config.json"), 35 | ) 36 | ``` 37 | 38 | ## File Format 39 | 40 | To load different file formats e.g yaml, toml, xml simply specify them with their extension 41 | 42 | ```go 43 | fileSource := file.NewSource( 44 | file.WithPath("/tmp/config.yaml"), 45 | ) 46 | ``` 47 | 48 | If you want to specify a file without extension, ensure you set the encoder to the same format 49 | 50 | ```go 51 | e := toml.NewEncoder() 52 | 53 | fileSource := file.NewSource( 54 | file.WithPath("/tmp/config"), 55 | source.WithEncoder(e), 56 | ) 57 | ``` 58 | 59 | ## Load Source 60 | 61 | Load the source into config 62 | 63 | ```go 64 | // Create new config 65 | conf := config.NewConfig() 66 | 67 | // Load file source 68 | conf.Load(fileSource) 69 | ``` 70 | -------------------------------------------------------------------------------- /config/source/file/file.go: -------------------------------------------------------------------------------- 1 | // Package file is a file source. Expected format is json 2 | package file 3 | 4 | import ( 5 | "io" 6 | "io/fs" 7 | "os" 8 | 9 | "go-micro.dev/v5/config/source" 10 | ) 11 | 12 | type file struct { 13 | opts source.Options 14 | fs fs.FS 15 | path string 16 | } 17 | 18 | var ( 19 | DefaultPath = "config.json" 20 | ) 21 | 22 | func (f *file) Read() (*source.ChangeSet, error) { 23 | var fh fs.File 24 | var err error 25 | 26 | if f.fs != nil { 27 | fh, err = f.fs.Open(f.path) 28 | } else { 29 | fh, err = os.Open(f.path) 30 | } 31 | 32 | if err != nil { 33 | return nil, err 34 | } 35 | defer fh.Close() 36 | b, err := io.ReadAll(fh) 37 | if err != nil { 38 | return nil, err 39 | } 40 | info, err := fh.Stat() 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | cs := &source.ChangeSet{ 46 | Format: format(f.path, f.opts.Encoder), 47 | Source: f.String(), 48 | Timestamp: info.ModTime(), 49 | Data: b, 50 | } 51 | cs.Checksum = cs.Sum() 52 | 53 | return cs, nil 54 | } 55 | 56 | func (f *file) String() string { 57 | return "file" 58 | } 59 | 60 | func (f *file) Watch() (source.Watcher, error) { 61 | // do not watch if fs.FS instance is provided 62 | if f.fs != nil { 63 | return source.NewNoopWatcher() 64 | } 65 | 66 | if _, err := os.Stat(f.path); err != nil { 67 | return nil, err 68 | } 69 | return newWatcher(f) 70 | } 71 | 72 | func (f *file) Write(cs *source.ChangeSet) error { 73 | return nil 74 | } 75 | 76 | func NewSource(opts ...source.Option) source.Source { 77 | options := source.NewOptions(opts...) 78 | 79 | fs, _ := options.Context.Value(fsKey{}).(fs.FS) 80 | 81 | path := DefaultPath 82 | f, ok := options.Context.Value(filePathKey{}).(string) 83 | if ok { 84 | path = f 85 | } 86 | return &file{opts: options, fs: fs, path: path} 87 | } 88 | -------------------------------------------------------------------------------- /config/source/file/format.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "strings" 5 | 6 | "go-micro.dev/v5/config/encoder" 7 | ) 8 | 9 | func format(p string, e encoder.Encoder) string { 10 | parts := strings.Split(p, ".") 11 | if len(parts) > 1 { 12 | return parts[len(parts)-1] 13 | } 14 | return e.String() 15 | } 16 | -------------------------------------------------------------------------------- /config/source/file/format_test.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "testing" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | func TestFormat(t *testing.T) { 10 | opts := source.NewOptions() 11 | e := opts.Encoder 12 | 13 | testCases := []struct { 14 | p string 15 | f string 16 | }{ 17 | {"/foo/bar.json", "json"}, 18 | {"/foo/bar.yaml", "yaml"}, 19 | {"/foo/bar.xml", "xml"}, 20 | {"/foo/bar.conf.ini", "ini"}, 21 | {"conf", e.String()}, 22 | } 23 | 24 | for _, d := range testCases { 25 | f := format(d.p, e) 26 | if f != d.f { 27 | t.Fatalf("%s: expected %s got %s", d.p, d.f, f) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /config/source/file/options.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "context" 5 | "io/fs" 6 | 7 | "go-micro.dev/v5/config/source" 8 | ) 9 | 10 | type filePathKey struct{} 11 | type fsKey struct{} 12 | 13 | // WithPath sets the path to file. 14 | func WithPath(p string) source.Option { 15 | return func(o *source.Options) { 16 | if o.Context == nil { 17 | o.Context = context.Background() 18 | } 19 | o.Context = context.WithValue(o.Context, filePathKey{}, p) 20 | } 21 | } 22 | 23 | // WithFS sets the underlying filesystem to lookup file from (default os.FS). 24 | func WithFS(fs fs.FS) source.Option { 25 | return func(o *source.Options) { 26 | if o.Context == nil { 27 | o.Context = context.Background() 28 | } 29 | o.Context = context.WithValue(o.Context, fsKey{}, fs) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config/source/file/watcher.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package file 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | "go-micro.dev/v5/config/source" 11 | ) 12 | 13 | type watcher struct { 14 | f *file 15 | 16 | fw *fsnotify.Watcher 17 | } 18 | 19 | func newWatcher(f *file) (source.Watcher, error) { 20 | fw, err := fsnotify.NewWatcher() 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | fw.Add(f.path) 26 | 27 | return &watcher{ 28 | f: f, 29 | fw: fw, 30 | }, nil 31 | } 32 | 33 | func (w *watcher) Next() (*source.ChangeSet, error) { 34 | // try get the event 35 | select { 36 | case event, ok := <-w.fw.Events: 37 | // check if channel was closed (i.e. Watcher.Close() was called). 38 | if !ok { 39 | return nil, source.ErrWatcherStopped 40 | } 41 | 42 | if event.Has(fsnotify.Rename) { 43 | // check existence of file, and add watch again 44 | _, err := os.Stat(event.Name) 45 | if err == nil || os.IsExist(err) { 46 | w.fw.Add(event.Name) 47 | } 48 | } 49 | 50 | c, err := w.f.Read() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return c, nil 56 | case err, ok := <-w.fw.Errors: 57 | // check if channel was closed (i.e. Watcher.Close() was called). 58 | if !ok { 59 | return nil, source.ErrWatcherStopped 60 | } 61 | 62 | return nil, err 63 | } 64 | } 65 | 66 | func (w *watcher) Stop() error { 67 | return w.fw.Close() 68 | } 69 | -------------------------------------------------------------------------------- /config/source/file/watcher_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package file 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | "go-micro.dev/v5/config/source" 11 | ) 12 | 13 | type watcher struct { 14 | f *file 15 | 16 | fw *fsnotify.Watcher 17 | } 18 | 19 | func newWatcher(f *file) (source.Watcher, error) { 20 | fw, err := fsnotify.NewWatcher() 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | fw.Add(f.path) 26 | 27 | return &watcher{ 28 | f: f, 29 | fw: fw, 30 | }, nil 31 | } 32 | 33 | func (w *watcher) Next() (*source.ChangeSet, error) { 34 | // try get the event 35 | select { 36 | case event, ok := <-w.fw.Events: 37 | // check if channel was closed (i.e. Watcher.Close() was called). 38 | if !ok { 39 | return nil, source.ErrWatcherStopped 40 | } 41 | 42 | if event.Has(fsnotify.Rename) { 43 | // check existence of file, and add watch again 44 | _, err := os.Stat(event.Name) 45 | if err == nil || os.IsExist(err) { 46 | w.fw.Add(event.Name) 47 | } 48 | } 49 | 50 | c, err := w.f.Read() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | // add path again for the event bug of fsnotify 56 | w.fw.Add(w.f.path) 57 | 58 | return c, nil 59 | case err, ok := <-w.fw.Errors: 60 | // check if channel was closed (i.e. Watcher.Close() was called). 61 | if !ok { 62 | return nil, source.ErrWatcherStopped 63 | } 64 | 65 | return nil, err 66 | } 67 | } 68 | 69 | func (w *watcher) Stop() error { 70 | return w.fw.Close() 71 | } 72 | -------------------------------------------------------------------------------- /config/source/flag/README.md: -------------------------------------------------------------------------------- 1 | # Flag Source 2 | 3 | The flag source reads config from flags 4 | 5 | ## Format 6 | 7 | We expect the use of the `flag` package. Upper case flags will be lower cased. Dashes will be used as delimiters. 8 | 9 | ### Example 10 | 11 | ```go 12 | dbAddress := flag.String("database_address", "127.0.0.1", "the db address") 13 | dbPort := flag.Int("database_port", 3306, "the db port) 14 | ``` 15 | 16 | Becomes 17 | 18 | ```json 19 | { 20 | "database": { 21 | "address": "127.0.0.1", 22 | "port": 3306 23 | } 24 | } 25 | ``` 26 | 27 | ## New Source 28 | 29 | ```go 30 | flagSource := flag.NewSource( 31 | // optionally enable reading of unset flags and their default 32 | // values into config, defaults to false 33 | IncludeUnset(true) 34 | ) 35 | ``` 36 | 37 | ## Load Source 38 | 39 | Load the source into config 40 | 41 | ```go 42 | // Create new config 43 | conf := config.NewConfig() 44 | 45 | // Load flag source 46 | conf.Load(flagSource) 47 | ``` 48 | -------------------------------------------------------------------------------- /config/source/flag/flag_test.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "testing" 7 | ) 8 | 9 | var ( 10 | dbuser = flag.String("database-user", "default", "db user") 11 | dbhost = flag.String("database-host", "", "db host") 12 | dbpw = flag.String("database-password", "", "db pw") 13 | ) 14 | 15 | func initTestFlags() { 16 | flag.Set("database-host", "localhost") 17 | flag.Set("database-password", "some-password") 18 | flag.Parse() 19 | } 20 | 21 | func TestFlagsrc_Read(t *testing.T) { 22 | initTestFlags() 23 | source := NewSource() 24 | c, err := source.Read() 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | 29 | var actual map[string]interface{} 30 | if err := json.Unmarshal(c.Data, &actual); err != nil { 31 | t.Error(err) 32 | } 33 | 34 | actualDB := actual["database"].(map[string]interface{}) 35 | if actualDB["host"] != *dbhost { 36 | t.Errorf("expected %v got %v", *dbhost, actualDB["host"]) 37 | } 38 | 39 | if actualDB["password"] != *dbpw { 40 | t.Errorf("expected %v got %v", *dbpw, actualDB["password"]) 41 | } 42 | 43 | // unset flags should not be loaded 44 | if actualDB["user"] != nil { 45 | t.Errorf("expected %v got %v", nil, actualDB["user"]) 46 | } 47 | } 48 | 49 | func TestFlagsrc_ReadAll(t *testing.T) { 50 | initTestFlags() 51 | source := NewSource(IncludeUnset(true)) 52 | c, err := source.Read() 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | 57 | var actual map[string]interface{} 58 | if err := json.Unmarshal(c.Data, &actual); err != nil { 59 | t.Error(err) 60 | } 61 | 62 | actualDB := actual["database"].(map[string]interface{}) 63 | 64 | // unset flag defaults should be loaded 65 | if actualDB["user"] != *dbuser { 66 | t.Errorf("expected %v got %v", *dbuser, actualDB["user"]) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config/source/flag/options.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | type includeUnsetKey struct{} 10 | 11 | // IncludeUnset toggles the loading of unset flags and their respective default values. 12 | // Default behavior is to ignore any unset flags. 13 | func IncludeUnset(b bool) source.Option { 14 | return func(o *source.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | o.Context = context.WithValue(o.Context, includeUnsetKey{}, true) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/source/memory/README.md: -------------------------------------------------------------------------------- 1 | # Memory Source 2 | 3 | The memory source provides in-memory data as a source 4 | 5 | ## Memory Format 6 | 7 | The expected data format is json 8 | 9 | ```json 10 | data := []byte(`{ 11 | "hosts": { 12 | "database": { 13 | "address": "10.0.0.1", 14 | "port": 3306 15 | }, 16 | "cache": { 17 | "address": "10.0.0.2", 18 | "port": 6379 19 | } 20 | } 21 | }`) 22 | ``` 23 | 24 | ## New Source 25 | 26 | Specify source with data 27 | 28 | ```go 29 | memorySource := memory.NewSource( 30 | memory.WithJSON(data), 31 | ) 32 | ``` 33 | 34 | ## Load Source 35 | 36 | Load the source into config 37 | 38 | ```go 39 | // Create new config 40 | conf := config.NewConfig() 41 | 42 | // Load memory source 43 | conf.Load(memorySource) 44 | ``` 45 | -------------------------------------------------------------------------------- /config/source/memory/options.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/config/source" 7 | ) 8 | 9 | type changeSetKey struct{} 10 | 11 | func withData(d []byte, f string) source.Option { 12 | return func(o *source.Options) { 13 | if o.Context == nil { 14 | o.Context = context.Background() 15 | } 16 | o.Context = context.WithValue(o.Context, changeSetKey{}, &source.ChangeSet{ 17 | Data: d, 18 | Format: f, 19 | }) 20 | } 21 | } 22 | 23 | // WithChangeSet allows a changeset to be set. 24 | func WithChangeSet(cs *source.ChangeSet) source.Option { 25 | return func(o *source.Options) { 26 | if o.Context == nil { 27 | o.Context = context.Background() 28 | } 29 | o.Context = context.WithValue(o.Context, changeSetKey{}, cs) 30 | } 31 | } 32 | 33 | // WithJSON allows the source data to be set to json. 34 | func WithJSON(d []byte) source.Option { 35 | return withData(d, "json") 36 | } 37 | 38 | // WithYAML allows the source data to be set to yaml. 39 | func WithYAML(d []byte) source.Option { 40 | return withData(d, "yaml") 41 | } 42 | -------------------------------------------------------------------------------- /config/source/memory/watcher.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "go-micro.dev/v5/config/source" 5 | ) 6 | 7 | type watcher struct { 8 | Updates chan *source.ChangeSet 9 | Source *memory 10 | Id string 11 | } 12 | 13 | func (w *watcher) Next() (*source.ChangeSet, error) { 14 | cs := <-w.Updates 15 | return cs, nil 16 | } 17 | 18 | func (w *watcher) Stop() error { 19 | w.Source.Lock() 20 | delete(w.Source.Watchers, w.Id) 21 | w.Source.Unlock() 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /config/source/nats/README.md: -------------------------------------------------------------------------------- 1 | # Nats Source 2 | 3 | The nats source reads config from nats key/values 4 | 5 | ## Nats Format 6 | 7 | The nats source expects keys under the default bucket `default` default key `micro_config` 8 | 9 | Values are expected to be json 10 | 11 | ``` 12 | nats kv put default micro_config '{"nats": {"address": "10.0.0.1", "port": 8488}}' 13 | ``` 14 | 15 | ``` 16 | conf.Get("nats") 17 | ``` 18 | 19 | ## New Source 20 | 21 | Specify source with data 22 | 23 | ```go 24 | natsSource := nats.NewSource( 25 | nats.WithUrl("127.0.0.1:4222"), 26 | nats.WithBucket("my_bucket"), 27 | nats.WithKey("my_key"), 28 | ) 29 | ``` 30 | 31 | ## Load Source 32 | 33 | Load the source into config 34 | 35 | ```go 36 | // Create new config 37 | conf := config.NewConfig() 38 | 39 | // Load nats source 40 | conf.Load(natsSource) 41 | ``` 42 | 43 | ## Watch 44 | 45 | ```go 46 | wh, _ := natsSource.Watch() 47 | 48 | for { 49 | v, err := watcher.Next() 50 | if err != nil { 51 | log.Fatalf("err %v", err) 52 | } 53 | 54 | log.Infof("data %v", string(v.Data)) 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /config/source/nats/options.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | natsgo "github.com/nats-io/nats.go" 8 | "go-micro.dev/v5/config/source" 9 | ) 10 | 11 | type ( 12 | urlKey struct{} 13 | bucketKey struct{} 14 | keyKey struct{} 15 | ) 16 | 17 | // WithUrl sets the nats url. 18 | func WithUrl(a ...string) source.Option { 19 | return func(o *source.Options) { 20 | if o.Context == nil { 21 | o.Context = context.Background() 22 | } 23 | o.Context = context.WithValue(o.Context, urlKey{}, a) 24 | } 25 | } 26 | 27 | // WithBucket sets the nats key. 28 | func WithBucket(a string) source.Option { 29 | return func(o *source.Options) { 30 | if o.Context == nil { 31 | o.Context = context.Background() 32 | } 33 | o.Context = context.WithValue(o.Context, bucketKey{}, a) 34 | } 35 | } 36 | 37 | // WithKey sets the nats key. 38 | func WithKey(a string) source.Option { 39 | return func(o *source.Options) { 40 | if o.Context == nil { 41 | o.Context = context.Background() 42 | } 43 | o.Context = context.WithValue(o.Context, keyKey{}, a) 44 | } 45 | } 46 | 47 | func Client(url string) (natsgo.JetStreamContext, error) { 48 | nc, err := natsgo.Connect(url) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return nc.JetStream(natsgo.MaxWait(10 * time.Second)) 54 | } 55 | -------------------------------------------------------------------------------- /config/source/nats/watcher.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | import ( 4 | "time" 5 | 6 | natsgo "github.com/nats-io/nats.go" 7 | "go-micro.dev/v5/config/encoder" 8 | "go-micro.dev/v5/config/source" 9 | ) 10 | 11 | type watcher struct { 12 | e encoder.Encoder 13 | name string 14 | bucket string 15 | key string 16 | 17 | ch chan *source.ChangeSet 18 | exit chan bool 19 | } 20 | 21 | func newWatcher(kv natsgo.KeyValue, bucket, key, name string, e encoder.Encoder) (source.Watcher, error) { 22 | w := &watcher{ 23 | e: e, 24 | name: name, 25 | bucket: bucket, 26 | key: key, 27 | ch: make(chan *source.ChangeSet), 28 | exit: make(chan bool), 29 | } 30 | 31 | wh, _ := kv.Watch(key) 32 | 33 | go func() { 34 | for { 35 | select { 36 | case v := <-wh.Updates(): 37 | if v != nil { 38 | w.handle(v.Value()) 39 | } 40 | case <-w.exit: 41 | _ = wh.Stop() 42 | return 43 | } 44 | } 45 | }() 46 | return w, nil 47 | } 48 | 49 | func (w *watcher) handle(data []byte) { 50 | cs := &source.ChangeSet{ 51 | Timestamp: time.Now(), 52 | Format: w.e.String(), 53 | Source: w.name, 54 | Data: data, 55 | } 56 | cs.Checksum = cs.Sum() 57 | 58 | w.ch <- cs 59 | } 60 | 61 | func (w *watcher) Next() (*source.ChangeSet, error) { 62 | select { 63 | case cs := <-w.ch: 64 | return cs, nil 65 | case <-w.exit: 66 | return nil, source.ErrWatcherStopped 67 | } 68 | } 69 | 70 | func (w *watcher) Stop() error { 71 | select { 72 | case <-w.exit: 73 | return nil 74 | default: 75 | close(w.exit) 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /config/source/noop.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type noopWatcher struct { 8 | exit chan struct{} 9 | } 10 | 11 | func (w *noopWatcher) Next() (*ChangeSet, error) { 12 | <-w.exit 13 | 14 | return nil, errors.New("noopWatcher stopped") 15 | } 16 | 17 | func (w *noopWatcher) Stop() error { 18 | close(w.exit) 19 | return nil 20 | } 21 | 22 | // NewNoopWatcher returns a watcher that blocks on Next() until Stop() is called. 23 | func NewNoopWatcher() (Watcher, error) { 24 | return &noopWatcher{exit: make(chan struct{})}, nil 25 | } 26 | -------------------------------------------------------------------------------- /config/source/options.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/client" 7 | "go-micro.dev/v5/config/encoder" 8 | "go-micro.dev/v5/config/encoder/json" 9 | ) 10 | 11 | type Options struct { 12 | // Encoder 13 | Encoder encoder.Encoder 14 | 15 | // for alternative data 16 | Context context.Context 17 | 18 | // Client to use for RPC 19 | Client client.Client 20 | } 21 | 22 | type Option func(o *Options) 23 | 24 | func NewOptions(opts ...Option) Options { 25 | options := Options{ 26 | Encoder: json.NewEncoder(), 27 | Context: context.Background(), 28 | Client: client.DefaultClient, 29 | } 30 | 31 | for _, o := range opts { 32 | o(&options) 33 | } 34 | 35 | return options 36 | } 37 | 38 | // WithEncoder sets the source encoder. 39 | func WithEncoder(e encoder.Encoder) Option { 40 | return func(o *Options) { 41 | o.Encoder = e 42 | } 43 | } 44 | 45 | // WithClient sets the source client. 46 | func WithClient(c client.Client) Option { 47 | return func(o *Options) { 48 | o.Client = c 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /config/source/source.go: -------------------------------------------------------------------------------- 1 | // Package source is the interface for sources 2 | package source 3 | 4 | import ( 5 | "errors" 6 | "time" 7 | ) 8 | 9 | var ( 10 | // ErrWatcherStopped is returned when source watcher has been stopped. 11 | ErrWatcherStopped = errors.New("watcher stopped") 12 | ) 13 | 14 | // Source is the source from which config is loaded. 15 | type Source interface { 16 | Read() (*ChangeSet, error) 17 | Write(*ChangeSet) error 18 | Watch() (Watcher, error) 19 | String() string 20 | } 21 | 22 | // ChangeSet represents a set of changes from a source. 23 | type ChangeSet struct { 24 | Timestamp time.Time 25 | Checksum string 26 | Format string 27 | Source string 28 | Data []byte 29 | } 30 | 31 | // Watcher watches a source for changes. 32 | type Watcher interface { 33 | Next() (*ChangeSet, error) 34 | Stop() error 35 | } 36 | -------------------------------------------------------------------------------- /config/value.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "go-micro.dev/v5/config/reader" 7 | ) 8 | 9 | type value struct{} 10 | 11 | func newValue() reader.Value { 12 | return new(value) 13 | } 14 | 15 | func (v *value) Bool(def bool) bool { 16 | return false 17 | } 18 | 19 | func (v *value) Int(def int) int { 20 | return 0 21 | } 22 | 23 | func (v *value) String(def string) string { 24 | return "" 25 | } 26 | 27 | func (v *value) Float64(def float64) float64 { 28 | return 0.0 29 | } 30 | 31 | func (v *value) Duration(def time.Duration) time.Duration { 32 | return time.Duration(0) 33 | } 34 | 35 | func (v *value) StringSlice(def []string) []string { 36 | return nil 37 | } 38 | 39 | func (v *value) StringMap(def map[string]string) map[string]string { 40 | return map[string]string{} 41 | } 42 | 43 | func (v *value) Scan(val interface{}) error { 44 | return nil 45 | } 46 | 47 | func (v *value) Bytes() []byte { 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /debug/log/log.go: -------------------------------------------------------------------------------- 1 | // Package log provides debug logging 2 | package log 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | var ( 11 | // Default buffer size if any. 12 | DefaultSize = 1024 13 | // DefaultLog logger. 14 | DefaultLog = NewLog() 15 | // Default formatter. 16 | DefaultFormat = TextFormat 17 | ) 18 | 19 | // Log is debug log interface for reading and writing logs. 20 | type Log interface { 21 | // Read reads log entries from the logger 22 | Read(...ReadOption) ([]Record, error) 23 | // Write writes records to log 24 | Write(Record) error 25 | // Stream log records 26 | Stream() (Stream, error) 27 | } 28 | 29 | // Record is log record entry. 30 | type Record struct { 31 | // Timestamp of logged event 32 | Timestamp time.Time `json:"timestamp"` 33 | // Metadata to enrich log record 34 | Metadata map[string]string `json:"metadata"` 35 | // Value contains log entry 36 | Message interface{} `json:"message"` 37 | } 38 | 39 | // Stream returns a log stream. 40 | type Stream interface { 41 | Chan() <-chan Record 42 | Stop() error 43 | } 44 | 45 | // Format is a function which formats the output. 46 | type FormatFunc func(Record) string 47 | 48 | // TextFormat returns text format. 49 | func TextFormat(r Record) string { 50 | t := r.Timestamp.Format("2006-01-02 15:04:05") 51 | return fmt.Sprintf("%s %v", t, r.Message) 52 | } 53 | 54 | // JSONFormat is a json Format func. 55 | func JSONFormat(r Record) string { 56 | b, _ := json.Marshal(r) 57 | return string(b) 58 | } 59 | -------------------------------------------------------------------------------- /debug/log/memory/memory_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "go-micro.dev/v5/debug/log" 8 | ) 9 | 10 | func TestLogger(t *testing.T) { 11 | // set size to some value 12 | size := 100 13 | // override the global logger 14 | lg := NewLog(log.Size(size)) 15 | // make sure we have the right size of the logger ring buffer 16 | if lg.(*memoryLog).Size() != size { 17 | t.Errorf("expected buffer size: %d, got: %d", size, lg.(*memoryLog).Size()) 18 | } 19 | 20 | // Log some cruft 21 | lg.Write(log.Record{Message: "foobar"}) 22 | lg.Write(log.Record{Message: "foo bar"}) 23 | 24 | // Check if the logs are stored in the logger ring buffer 25 | expected := []string{"foobar", "foo bar"} 26 | entries, _ := lg.Read(log.Count(len(expected))) 27 | for i, entry := range entries { 28 | if !reflect.DeepEqual(entry.Message, expected[i]) { 29 | t.Errorf("expected %s, got %s", expected[i], entry.Message) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /debug/log/memory/stream.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "go-micro.dev/v5/debug/log" 5 | ) 6 | 7 | type logStream struct { 8 | stream <-chan log.Record 9 | stop chan bool 10 | } 11 | 12 | func (l *logStream) Chan() <-chan log.Record { 13 | return l.stream 14 | } 15 | 16 | func (l *logStream) Stop() error { 17 | select { 18 | case <-l.stop: 19 | return nil 20 | default: 21 | close(l.stop) 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /debug/log/noop/noop.go: -------------------------------------------------------------------------------- 1 | package noop 2 | 3 | import ( 4 | "go-micro.dev/v5/debug/log" 5 | ) 6 | 7 | type noop struct{} 8 | 9 | func (n *noop) Read(...log.ReadOption) ([]log.Record, error) { 10 | return nil, nil 11 | } 12 | 13 | func (n *noop) Write(log.Record) error { 14 | return nil 15 | } 16 | 17 | func (n *noop) Stream() (log.Stream, error) { 18 | return nil, nil 19 | } 20 | 21 | func NewLog(opts ...log.Option) log.Log { 22 | return new(noop) 23 | } 24 | -------------------------------------------------------------------------------- /debug/log/options.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "time" 4 | 5 | // Option used by the logger. 6 | type Option func(*Options) 7 | 8 | // Options are logger options. 9 | type Options struct { 10 | // Format specifies the output format 11 | Format FormatFunc 12 | // Name of the log 13 | Name string 14 | // Size is the size of ring buffer 15 | Size int 16 | } 17 | 18 | // Name of the log. 19 | func Name(n string) Option { 20 | return func(o *Options) { 21 | o.Name = n 22 | } 23 | } 24 | 25 | // Size sets the size of the ring buffer. 26 | func Size(s int) Option { 27 | return func(o *Options) { 28 | o.Size = s 29 | } 30 | } 31 | 32 | func Format(f FormatFunc) Option { 33 | return func(o *Options) { 34 | o.Format = f 35 | } 36 | } 37 | 38 | // DefaultOptions returns default options. 39 | func DefaultOptions() Options { 40 | return Options{ 41 | Size: DefaultSize, 42 | } 43 | } 44 | 45 | // ReadOptions for querying the logs. 46 | type ReadOptions struct { 47 | // Since what time in past to return the logs 48 | Since time.Time 49 | // Count specifies number of logs to return 50 | Count int 51 | // Stream requests continuous log stream 52 | Stream bool 53 | } 54 | 55 | // ReadOption used for reading the logs. 56 | type ReadOption func(*ReadOptions) 57 | 58 | // Since sets the time since which to return the log records. 59 | func Since(s time.Time) ReadOption { 60 | return func(o *ReadOptions) { 61 | o.Since = s 62 | } 63 | } 64 | 65 | // Count sets the number of log records to return. 66 | func Count(c int) ReadOption { 67 | return func(o *ReadOptions) { 68 | o.Count = c 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /debug/log/os.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/google/uuid" 7 | "go-micro.dev/v5/util/ring" 8 | ) 9 | 10 | // Should stream from OS. 11 | type osLog struct { 12 | format FormatFunc 13 | buffer *ring.Buffer 14 | subs map[string]*osStream 15 | 16 | sync.RWMutex 17 | once sync.Once 18 | } 19 | 20 | type osStream struct { 21 | stream chan Record 22 | } 23 | 24 | // Read reads log entries from the logger. 25 | func (o *osLog) Read(...ReadOption) ([]Record, error) { 26 | var records []Record 27 | 28 | // read the last 100 records 29 | for _, v := range o.buffer.Get(100) { 30 | records = append(records, v.Value.(Record)) 31 | } 32 | 33 | return records, nil 34 | } 35 | 36 | // Write writes records to log. 37 | func (o *osLog) Write(r Record) error { 38 | o.buffer.Put(r) 39 | return nil 40 | } 41 | 42 | // Stream log records. 43 | func (o *osLog) Stream() (Stream, error) { 44 | o.Lock() 45 | defer o.Unlock() 46 | 47 | // create stream 48 | st := &osStream{ 49 | stream: make(chan Record, 128), 50 | } 51 | 52 | // save stream 53 | o.subs[uuid.New().String()] = st 54 | 55 | return st, nil 56 | } 57 | 58 | func (o *osStream) Chan() <-chan Record { 59 | return o.stream 60 | } 61 | 62 | func (o *osStream) Stop() error { 63 | return nil 64 | } 65 | 66 | func NewLog(opts ...Option) Log { 67 | options := Options{ 68 | Format: DefaultFormat, 69 | } 70 | for _, o := range opts { 71 | o(&options) 72 | } 73 | 74 | l := &osLog{ 75 | format: options.Format, 76 | buffer: ring.New(1024), 77 | subs: make(map[string]*osStream), 78 | } 79 | 80 | return l 81 | } 82 | -------------------------------------------------------------------------------- /debug/profile/http/http.go: -------------------------------------------------------------------------------- 1 | // Package http enables the http profiler 2 | package http 3 | 4 | import ( 5 | "context" 6 | "net/http" 7 | "net/http/pprof" 8 | "sync" 9 | 10 | "go-micro.dev/v5/debug/profile" 11 | ) 12 | 13 | type httpProfile struct { 14 | server *http.Server 15 | sync.Mutex 16 | running bool 17 | } 18 | 19 | var ( 20 | DefaultAddress = ":6060" 21 | ) 22 | 23 | // Start the profiler. 24 | func (h *httpProfile) Start() error { 25 | h.Lock() 26 | defer h.Unlock() 27 | 28 | if h.running { 29 | return nil 30 | } 31 | 32 | go func() { 33 | if err := h.server.ListenAndServe(); err != nil { 34 | h.Lock() 35 | h.running = false 36 | h.Unlock() 37 | } 38 | }() 39 | 40 | h.running = true 41 | 42 | return nil 43 | } 44 | 45 | // Stop the profiler. 46 | func (h *httpProfile) Stop() error { 47 | h.Lock() 48 | defer h.Unlock() 49 | 50 | if !h.running { 51 | return nil 52 | } 53 | 54 | h.running = false 55 | 56 | return h.server.Shutdown(context.TODO()) 57 | } 58 | 59 | func (h *httpProfile) String() string { 60 | return "http" 61 | } 62 | 63 | func NewProfile(opts ...profile.Option) profile.Profile { 64 | mux := http.NewServeMux() 65 | 66 | mux.HandleFunc("/debug/pprof/", pprof.Index) 67 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 68 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 69 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 70 | mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 71 | 72 | return &httpProfile{ 73 | server: &http.Server{ 74 | Addr: DefaultAddress, 75 | Handler: mux, 76 | }, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /debug/profile/profile.go: -------------------------------------------------------------------------------- 1 | // Package profile is for profilers 2 | package profile 3 | 4 | type Profile interface { 5 | // Start the profiler 6 | Start() error 7 | // Stop the profiler 8 | Stop() error 9 | // Name of the profiler 10 | String() string 11 | } 12 | 13 | var ( 14 | DefaultProfile Profile = new(noop) 15 | ) 16 | 17 | type noop struct{} 18 | 19 | func (p *noop) Start() error { 20 | return nil 21 | } 22 | 23 | func (p *noop) Stop() error { 24 | return nil 25 | } 26 | 27 | func (p *noop) String() string { 28 | return "noop" 29 | } 30 | 31 | type Options struct { 32 | // Name to use for the profile 33 | Name string 34 | } 35 | 36 | type Option func(o *Options) 37 | 38 | // Name of the profile. 39 | func Name(n string) Option { 40 | return func(o *Options) { 41 | o.Name = n 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /debug/stats/default.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | 8 | "go-micro.dev/v5/util/ring" 9 | ) 10 | 11 | type stats struct { 12 | // used to store past stats 13 | buffer *ring.Buffer 14 | 15 | sync.RWMutex 16 | started int64 17 | requests uint64 18 | errors uint64 19 | } 20 | 21 | func (s *stats) snapshot() *Stat { 22 | s.RLock() 23 | defer s.RUnlock() 24 | 25 | var mstat runtime.MemStats 26 | runtime.ReadMemStats(&mstat) 27 | 28 | now := time.Now().Unix() 29 | 30 | return &Stat{ 31 | Timestamp: now, 32 | Started: s.started, 33 | Uptime: now - s.started, 34 | Memory: mstat.Alloc, 35 | GC: mstat.PauseTotalNs, 36 | Threads: uint64(runtime.NumGoroutine()), 37 | Requests: s.requests, 38 | Errors: s.errors, 39 | } 40 | } 41 | 42 | func (s *stats) Read() ([]*Stat, error) { 43 | // TODO adjustable size and optional read values 44 | buf := s.buffer.Get(60) 45 | var stats []*Stat 46 | 47 | // get a value from the buffer if it exists 48 | for _, b := range buf { 49 | stat, ok := b.Value.(*Stat) 50 | if !ok { 51 | continue 52 | } 53 | stats = append(stats, stat) 54 | } 55 | 56 | // get a snapshot 57 | stats = append(stats, s.snapshot()) 58 | 59 | return stats, nil 60 | } 61 | 62 | func (s *stats) Write(stat *Stat) error { 63 | s.buffer.Put(stat) 64 | return nil 65 | } 66 | 67 | func (s *stats) Record(err error) error { 68 | s.Lock() 69 | defer s.Unlock() 70 | 71 | // increment the total request count 72 | s.requests++ 73 | 74 | // increment the error count 75 | if err != nil { 76 | s.errors++ 77 | } 78 | 79 | return nil 80 | } 81 | 82 | // NewStats returns a new in memory stats buffer 83 | // TODO add options. 84 | func NewStats() Stats { 85 | return &stats{ 86 | started: time.Now().Unix(), 87 | buffer: ring.New(60), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /debug/stats/stats.go: -------------------------------------------------------------------------------- 1 | // Package stats provides runtime stats 2 | package stats 3 | 4 | // Stats provides stats interface. 5 | type Stats interface { 6 | // Read stat snapshot 7 | Read() ([]*Stat, error) 8 | // Write a stat snapshot 9 | Write(*Stat) error 10 | // Record a request 11 | Record(error) error 12 | } 13 | 14 | // A runtime stat. 15 | type Stat struct { 16 | // Timestamp of recording 17 | Timestamp int64 18 | // Start time as unix timestamp 19 | Started int64 20 | // Uptime in seconds 21 | Uptime int64 22 | // Memory usage in bytes 23 | Memory uint64 24 | // Threads aka go routines 25 | Threads uint64 26 | // Garbage collection in nanoseconds 27 | GC uint64 28 | // Total requests 29 | Requests uint64 30 | // Total errors 31 | Errors uint64 32 | } 33 | 34 | var ( 35 | DefaultStats = NewStats() 36 | ) 37 | -------------------------------------------------------------------------------- /debug/trace/noop.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import "context" 4 | 5 | type noop struct{} 6 | 7 | func (n *noop) Init(...Option) error { 8 | return nil 9 | } 10 | 11 | func (n *noop) Start(ctx context.Context, name string) (context.Context, *Span) { 12 | return nil, nil 13 | } 14 | 15 | func (n *noop) Finish(*Span) error { 16 | return nil 17 | } 18 | 19 | func (n *noop) Read(...ReadOption) ([]*Span, error) { 20 | return nil, nil 21 | } 22 | -------------------------------------------------------------------------------- /debug/trace/options.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | type Options struct { 4 | // Size is the size of ring buffer 5 | Size int 6 | } 7 | 8 | type Option func(o *Options) 9 | 10 | type ReadOptions struct { 11 | // Trace id 12 | Trace string 13 | } 14 | 15 | type ReadOption func(o *ReadOptions) 16 | 17 | // Read the given trace. 18 | func ReadTrace(t string) ReadOption { 19 | return func(o *ReadOptions) { 20 | o.Trace = t 21 | } 22 | } 23 | 24 | const ( 25 | // DefaultSize of the buffer. 26 | DefaultSize = 64 27 | ) 28 | 29 | // DefaultOptions returns default options. 30 | func DefaultOptions() Options { 31 | return Options{ 32 | Size: DefaultSize, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /errors/errors.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: errors.proto 3 | 4 | package errors 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | -------------------------------------------------------------------------------- /errors/errors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package errors; 4 | 5 | message Error { 6 | string id = 1; 7 | int32 code = 2; 8 | string detail = 3; 9 | string status = 4; 10 | }; 11 | 12 | message MultiError { 13 | repeated Error errors = 1; 14 | } -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/client" 7 | ) 8 | 9 | type event struct { 10 | c client.Client 11 | topic string 12 | } 13 | 14 | func (e *event) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error { 15 | return e.c.Publish(ctx, e.c.NewMessage(e.topic, msg), opts...) 16 | } 17 | -------------------------------------------------------------------------------- /internal/README.md: -------------------------------------------------------------------------------- 1 | Internal related things 2 | -------------------------------------------------------------------------------- /internal/website/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | -------------------------------------------------------------------------------- /internal/website/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # gem "rails" 6 | gem 'github-pages', group: :jekyll_plugins 7 | -------------------------------------------------------------------------------- /internal/website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | The Go Micro website including docs 4 | -------------------------------------------------------------------------------- /internal/website/_config.yml: -------------------------------------------------------------------------------- 1 | title: Docs 2 | description: "A Go microservices framework" 3 | baseurl: "" # the subpath of your site, e.g. /blog 4 | url: "" # the base hostname & protocol for your site, e.g. http://example.com 5 | 6 | theme: jekyll-theme-primer 7 | -------------------------------------------------------------------------------- /internal/website/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |