├── .github ├── dependabot.yml └── workflows │ ├── ci.yaml │ └── integration.yaml ├── .gitignore ├── .golangci.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── clue ├── config.go ├── config_test.go ├── exporters.go ├── exporters_test.go ├── options.go ├── options_test.go ├── sampler.go └── sampler_test.go ├── codecov.yaml ├── debug ├── README.md ├── debug.go ├── debug_test.go ├── goa.go ├── goa_test.go ├── grpc.go ├── grpc_test.go ├── http.go ├── http_test.go ├── options.go └── options_test.go ├── example ├── middleware │ └── error_reporter.go └── weather │ ├── Procfile │ ├── README.md │ ├── design │ └── design.go │ ├── diagram │ ├── Weather System Services.svg │ └── diagram.go │ ├── go.mod │ ├── go.sum │ ├── images │ └── signoz.png │ ├── middleware │ └── error_reporter.go │ ├── scripts │ ├── build │ ├── diagram │ ├── gen │ ├── load │ ├── server │ ├── setup │ └── utils │ │ └── common.sh │ └── services │ ├── forecaster │ ├── clients │ │ └── weathergov │ │ │ ├── client.go │ │ │ └── mocks │ │ │ └── client.go │ ├── cmd │ │ └── forecaster │ │ │ └── main.go │ ├── design │ │ └── design.go │ ├── forecast.go │ ├── forecast_test.go │ └── gen │ │ ├── forecaster │ │ ├── client.go │ │ ├── endpoints.go │ │ └── service.go │ │ └── grpc │ │ ├── cli │ │ └── weather_service_api │ │ │ └── cli.go │ │ └── forecaster │ │ ├── client │ │ ├── cli.go │ │ ├── client.go │ │ ├── encode_decode.go │ │ └── types.go │ │ ├── pb │ │ ├── goagen_forecaster_forecaster.pb.go │ │ ├── goagen_forecaster_forecaster.proto │ │ └── goagen_forecaster_forecaster_grpc.pb.go │ │ └── server │ │ ├── encode_decode.go │ │ ├── server.go │ │ └── types.go │ ├── front │ ├── clients │ │ ├── forecaster │ │ │ ├── client.go │ │ │ └── mocks │ │ │ │ └── client.go │ │ ├── locator │ │ │ ├── client.go │ │ │ └── mocks │ │ │ │ └── client.go │ │ └── tester │ │ │ ├── client.go │ │ │ └── mocks │ │ │ └── client.go │ ├── cmd │ │ └── front │ │ │ └── main.go │ ├── design │ │ └── design.go │ ├── front.go │ ├── front_test.go │ └── gen │ │ ├── front │ │ ├── client.go │ │ ├── endpoints.go │ │ └── service.go │ │ └── http │ │ ├── cli │ │ └── weather │ │ │ └── cli.go │ │ ├── front │ │ ├── client │ │ │ ├── cli.go │ │ │ ├── client.go │ │ │ ├── encode_decode.go │ │ │ ├── paths.go │ │ │ └── types.go │ │ └── server │ │ │ ├── encode_decode.go │ │ │ ├── paths.go │ │ │ ├── server.go │ │ │ └── types.go │ │ ├── openapi.json │ │ ├── openapi.yaml │ │ ├── openapi3.json │ │ └── openapi3.yaml │ ├── locator │ ├── clients │ │ └── ipapi │ │ │ ├── client.go │ │ │ └── mocks │ │ │ └── client.go │ ├── cmd │ │ └── locator │ │ │ └── main.go │ ├── design │ │ └── design.go │ ├── gen │ │ ├── grpc │ │ │ ├── cli │ │ │ │ └── ip_location_api │ │ │ │ │ └── cli.go │ │ │ └── locator │ │ │ │ ├── client │ │ │ │ ├── cli.go │ │ │ │ ├── client.go │ │ │ │ ├── encode_decode.go │ │ │ │ └── types.go │ │ │ │ ├── pb │ │ │ │ ├── goagen_locator_locator.pb.go │ │ │ │ ├── goagen_locator_locator.proto │ │ │ │ └── goagen_locator_locator_grpc.pb.go │ │ │ │ └── server │ │ │ │ ├── encode_decode.go │ │ │ │ ├── server.go │ │ │ │ └── types.go │ │ └── locator │ │ │ ├── client.go │ │ │ ├── endpoints.go │ │ │ └── service.go │ ├── locator.go │ └── locator_test.go │ └── tester │ ├── README.md │ ├── clients │ ├── forecaster │ │ ├── client.go │ │ └── mocks │ │ │ └── client.go │ └── locator │ │ ├── client.go │ │ └── mocks │ │ └── client.go │ ├── cmd │ └── tester │ │ └── main.go │ ├── design │ └── design.go │ ├── forecaster_integration.go │ ├── gen │ ├── grpc │ │ ├── cli │ │ │ └── tester_service_api │ │ │ │ └── cli.go │ │ └── tester │ │ │ ├── client │ │ │ ├── cli.go │ │ │ ├── client.go │ │ │ ├── encode_decode.go │ │ │ └── types.go │ │ │ ├── pb │ │ │ ├── goagen_tester_tester.pb.go │ │ │ ├── goagen_tester_tester.proto │ │ │ └── goagen_tester_tester_grpc.pb.go │ │ │ └── server │ │ │ ├── encode_decode.go │ │ │ ├── server.go │ │ │ └── types.go │ └── tester │ │ ├── client.go │ │ ├── endpoints.go │ │ └── service.go │ ├── locator_integration.go │ ├── run_tests.go │ ├── test_methods.go │ └── testing.go ├── go.mod ├── go.sum ├── go.work ├── go.work.sum ├── health ├── README.md ├── checker.go ├── checker_test.go ├── handler.go ├── handler_test.go ├── pinger.go └── pinger_test.go ├── interceptors ├── README.md ├── trace_stream.go ├── trace_stream_client.go ├── trace_stream_client_test.go ├── trace_stream_server.go ├── trace_stream_server_test.go └── trace_stream_test.go ├── internal └── testsvc │ ├── README.md │ ├── design │ └── design.go │ ├── gen │ ├── grpc │ │ ├── cli │ │ │ └── itest │ │ │ │ └── cli.go │ │ └── test │ │ │ ├── client │ │ │ ├── cli.go │ │ │ ├── client.go │ │ │ ├── encode_decode.go │ │ │ └── types.go │ │ │ ├── pb │ │ │ ├── goagen_testsvc_test.pb.go │ │ │ ├── goagen_testsvc_test.proto │ │ │ └── goagen_testsvc_test_grpc.pb.go │ │ │ └── server │ │ │ ├── encode_decode.go │ │ │ ├── server.go │ │ │ └── types.go │ ├── http │ │ ├── cli │ │ │ └── itest │ │ │ │ └── cli.go │ │ ├── openapi.json │ │ ├── openapi.yaml │ │ ├── openapi3.json │ │ ├── openapi3.yaml │ │ └── test │ │ │ ├── client │ │ │ ├── cli.go │ │ │ ├── client.go │ │ │ ├── encode_decode.go │ │ │ ├── paths.go │ │ │ └── types.go │ │ │ └── server │ │ │ ├── encode_decode.go │ │ │ ├── paths.go │ │ │ ├── server.go │ │ │ └── types.go │ └── test │ │ ├── client.go │ │ ├── endpoints.go │ │ └── service.go │ ├── grpc_testing.go │ ├── http_testing.go │ └── service.go ├── log ├── README.md ├── adapt.go ├── adapt_test.go ├── context.go ├── context_test.go ├── fields.go ├── format.go ├── format_test.go ├── grpc.go ├── grpc_test.go ├── http.go ├── http_test.go ├── keys.go ├── log.go ├── log_test.go ├── options.go ├── options_test.go ├── span.go └── span_test.go ├── mock ├── README.md ├── cmd │ └── cmg │ │ ├── main.go │ │ └── pkg │ │ ├── generate.go │ │ ├── generate │ │ ├── _tests │ │ │ ├── conflicts │ │ │ │ ├── conflicts.go │ │ │ │ └── mocks │ │ │ │ │ └── conflicts.go │ │ │ ├── extensive │ │ │ │ ├── aliased │ │ │ │ │ └── imported.go │ │ │ │ ├── extensive.go │ │ │ │ └── mocks │ │ │ │ │ └── extensive.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── testify │ │ │ │ ├── mocks │ │ │ │ └── testify.go │ │ │ │ └── testify.go │ │ ├── import.go │ │ ├── import_test.go │ │ ├── interface.go │ │ ├── method.go │ │ ├── mocks.go │ │ ├── mocks.go.tmpl │ │ ├── mocks_test.go │ │ ├── type.go │ │ └── type_test.go │ │ ├── parse │ │ ├── _tests │ │ │ ├── doer │ │ │ │ └── doer.go │ │ │ ├── external │ │ │ │ └── doer.go │ │ │ └── go.mod │ │ ├── interface.go │ │ ├── interface_test.go │ │ ├── method.go │ │ ├── method_test.go │ │ ├── package.go │ │ ├── package_test.go │ │ ├── type.go │ │ ├── type_test.go │ │ ├── value.go │ │ └── value_test.go │ │ └── version.go ├── mock.go └── mock_test.go ├── scripts ├── cibuild ├── setup ├── test └── utils │ └── common.sh └── staticcheck.conf /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | 10 | jobs: 11 | tests: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version: "1.21" 18 | - name: tests 19 | run: scripts/cibuild 20 | - name: Upload coverage to Codecov 21 | uses: codecov/codecov-action@v5 22 | with: 23 | token: ${{ secrets.CODECOV_TOKEN }} 24 | files: coverage.out 25 | flags: micro 26 | -------------------------------------------------------------------------------- /.github/workflows/integration.yaml: -------------------------------------------------------------------------------- 1 | name: Integration Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | 10 | jobs: 11 | integration-tests: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version: "1.21" 18 | - name: tests 19 | run: | 20 | check_service_health() { 21 | local health_url="$1" 22 | local start_time=$(date +%s) 23 | 24 | while : ; do 25 | if curl -s --fail "$health_url" > /dev/null; then 26 | echo "Service is up!" 27 | return 0 28 | fi 29 | 30 | local current_time=$(date +%s) 31 | if (( current_time - start_time >= 5 )); then 32 | echo "Timed out waiting for service to be up." 33 | return 1 34 | fi 35 | 36 | sleep 0.2 37 | done 38 | } 39 | echo "stop/disable/kill mono" 40 | sudo systemctl stop mono-xsp4.service || true 41 | sudo systemctl disable mono-xsp4.service || true 42 | sudo pkill mono || true 43 | echo "change to weather example directory" 44 | cd example/weather 45 | echo "run setup script" 46 | ./scripts/setup 47 | echo "run server" 48 | ./bin/forecaster & 49 | ./bin/locator & 50 | ./bin/tester & 51 | ./bin/front & 52 | check_service_health "http://localhost:8081/healthz" & 53 | check_service_health "http://localhost:8083/healthz" & 54 | check_service_health "http://localhost:8091/healthz" & 55 | check_service_health "http://localhost:8085/healthz" & 56 | wait -n 57 | echo "-----RUN TESTS-----" 58 | results=$(curl -X POST http://localhost:8084/tester/smoke) 59 | echo "-----RESULTS-----" 60 | echo $results 61 | echo "----------" 62 | if [ $(echo $results | jq '.fail_count') -gt 0 ]; 63 | then 64 | echo "Test errors found." 65 | exit 1 66 | else 67 | echo "Tests passed." 68 | exit 0 69 | fi 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | example/weather/.overmind.sock 3 | example/weather/bin/* 4 | example/weather/services/forecaster/cmd/forecaster/forecaster 5 | example/weather/services/front/cmd/front/front 6 | example/weather/services/locator/cmd/locator/locator 7 | example/weather/signoz 8 | coverage.out 9 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | exclusions: 4 | rules: 5 | - linters: 6 | - staticcheck 7 | text: 'SA1019:' 8 | - linters: 9 | - staticcheck 10 | text: 'ST1001:' 11 | paths: 12 | - '.*design/.*\.go$' -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Clue 2 | 3 | Thank you for your interest in contributing to the Clue project! We appreciate 4 | contributions via submitting Github issues and/or pull requests. 5 | 6 | Below are some guidelines to follow when contributing to this project: 7 | 8 | * Before opening an issue in Github, check [open issues](https://github.com/goadesign/clue/issues) 9 | and [pull requests](https://github.com/goadesign/clue/pulls) for existing 10 | issues and fixes. 11 | * If your issue has not been addressed, [open a Github issue](https://github.com/goadesign/clue/issues/new) 12 | and follow the checklist presented in the issue description section. A simple 13 | code snippet that reproduces your issue helps immensely. 14 | * If you know how to fix your bug, we highly encourage PR contributions. See 15 | [How Can I Get Started section](#how-can-i-get-started) on how to submit a PR. 16 | * For feature requests and submitting major changes, [open an issue](https://github.com/goadesign/clue/issues/new) 17 | or hop on to our slack channel (see to join) to discuss 18 | the feature first. 19 | * Keep conversations friendly! Constructive criticism goes a long way. 20 | * Have fun contributing! 21 | 22 | ## How Can I Get Started? 23 | 24 | 1) Go over the package documentation 25 | 2) To get your hands dirty, fork the Clue repo and issue PRs from the fork. 26 | **PRO Tip:** Add a [git remote](https://git-scm.com/docs/git-remote.html) to 27 | your forked repo in the Clue source code (in $GOPATH/src/goa.design/clue when 28 | installed using `go get`) to avoid messing with import paths while testing 29 | your fix. 30 | 3) [Open issues](https://github.com/goadesign/clue/issues) labeled as `good first 31 | issue` are ideal to understand the source code and make minor contributions. 32 | Issues labeled `help wanted` are bugs/features that are not currently being 33 | worked on and contributing to them are most welcome. 34 | 4) Link the issue that the PR intends to solve in the PR description. If an issue 35 | does not exist, adding a description in the PR that describes the issue and the 36 | fix is recommended. 37 | 5) Ensure the CI build passes when you issue a PR to Clue. 38 | 6) Join our slack channel (see to join)! 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Raphael Simon and Clue Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /clue/options.go: -------------------------------------------------------------------------------- 1 | package clue 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "go.opentelemetry.io/otel" 8 | "go.opentelemetry.io/otel/propagation" 9 | "go.opentelemetry.io/otel/sdk/resource" 10 | ) 11 | 12 | type ( 13 | // Option is a function that initializes the clue configuration. 14 | Option func(*options) 15 | 16 | // options contains the clue configuration options. 17 | options struct { 18 | // readerInterval is the interval at which the metrics reader is 19 | // invoked. 20 | readerInterval time.Duration 21 | // maxSamplingRate is the maximum sampling rate for the trace exporter. 22 | maxSamplingRate int 23 | // sampleSize is the number of requests between two adjustments of the 24 | // sampling rate. 25 | sampleSize int 26 | // propagators is the trace propagators. 27 | propagators propagation.TextMapPropagator 28 | // resource is the resource containing any additional attributes. 29 | resource *resource.Resource 30 | // errorHandler is the error handler used by the otel package. 31 | errorHandler otel.ErrorHandler 32 | } 33 | ) 34 | 35 | // defaultOptions returns a new options struct with default values. 36 | // The logger in ctx is used to log errors. 37 | func defaultOptions(ctx context.Context) *options { 38 | return &options{ 39 | maxSamplingRate: 2, 40 | sampleSize: 10, 41 | propagators: propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}), 42 | errorHandler: NewErrorHandler(ctx), 43 | } 44 | } 45 | 46 | // WithReaderInterval returns an option that sets the interval at which the 47 | // metrics reader is invoked. 48 | func WithReaderInterval(interval time.Duration) Option { 49 | return func(c *options) { 50 | c.readerInterval = interval 51 | } 52 | } 53 | 54 | // WithMaxSamplingRate sets the maximum sampling rate in requests per second. 55 | func WithMaxSamplingRate(rate int) Option { 56 | return func(opts *options) { 57 | opts.maxSamplingRate = rate 58 | } 59 | } 60 | 61 | // WithSampleSize sets the number of requests between two adjustments of the 62 | // sampling rate. 63 | func WithSampleSize(size int) Option { 64 | return func(opts *options) { 65 | opts.sampleSize = size 66 | } 67 | } 68 | 69 | // WithPropagators sets the propagators when extracting and injecting trace 70 | // context. 71 | func WithPropagators(propagator propagation.TextMapPropagator) Option { 72 | return func(opts *options) { 73 | opts.propagators = propagator 74 | } 75 | } 76 | 77 | // WithResource sets the resource containing any additional attributes. 78 | func WithResource(res *resource.Resource) Option { 79 | return func(opts *options) { 80 | opts.resource = res 81 | } 82 | } 83 | 84 | // WithErrorHandler sets the error handler used by the telemetry package. 85 | func WithErrorHandler(errorHandler otel.ErrorHandler) Option { 86 | return func(opts *options) { 87 | opts.errorHandler = errorHandler 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /clue/options_test.go: -------------------------------------------------------------------------------- 1 | package clue 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "go.opentelemetry.io/otel/attribute" 9 | "go.opentelemetry.io/otel/propagation" 10 | "go.opentelemetry.io/otel/sdk/resource" 11 | 12 | "goa.design/clue/log" 13 | ) 14 | 15 | func TestOptions(t *testing.T) { 16 | ctx := log.Context(context.Background()) 17 | cases := []struct { 18 | name string 19 | option Option 20 | want func(*options) // mutate default options 21 | }{ 22 | { 23 | name: "default", 24 | }, 25 | { 26 | name: "with reader interval", 27 | option: WithReaderInterval(1000), 28 | want: func(o *options) { o.readerInterval = 1000 }, 29 | }, 30 | { 31 | name: "with max sampling rate", 32 | option: WithMaxSamplingRate(1000), 33 | want: func(o *options) { o.maxSamplingRate = 1000 }, 34 | }, 35 | { 36 | name: "with sample size", 37 | option: WithSampleSize(1000), 38 | want: func(o *options) { o.sampleSize = 1000 }, 39 | }, 40 | { 41 | name: "with propagator", 42 | option: WithPropagators(propagation.TraceContext{}), 43 | want: func(o *options) { o.propagators = propagation.TraceContext{} }, 44 | }, 45 | { 46 | name: "with resource", 47 | option: WithResource(resource.NewSchemaless(attribute.String("key", "value"))), 48 | want: func(o *options) { o.resource = resource.NewSchemaless(attribute.String("key", "value")) }, 49 | }, 50 | { 51 | name: "with error handler", 52 | option: WithErrorHandler(dummyErrorHandler{}), 53 | want: func(o *options) { o.errorHandler = dummyErrorHandler{} }, 54 | }, 55 | } 56 | for _, c := range cases { 57 | t.Run(c.name, func(t *testing.T) { 58 | got := defaultOptions(ctx) 59 | if c.option != nil { 60 | c.option(got) 61 | } 62 | want := defaultOptions(ctx) 63 | if c.want != nil { 64 | c.want(want) 65 | } 66 | assert.Equal(t, want.maxSamplingRate, got.maxSamplingRate) 67 | assert.Equal(t, want.sampleSize, got.sampleSize) 68 | assert.Equal(t, want.readerInterval, got.readerInterval) 69 | assert.Equal(t, want.propagators, got.propagators) 70 | assert.Equal(t, want.resource, got.resource) 71 | assert.IsType(t, want.errorHandler, got.errorHandler) 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /clue/sampler.go: -------------------------------------------------------------------------------- 1 | package clue 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 7 | "goa.design/goa/v3/middleware" 8 | ) 9 | 10 | // sampler leverages the Goa adaptive sampler implementation. 11 | type sampler struct { 12 | s middleware.Sampler 13 | maxSamplingRate int 14 | sampleSize int 15 | } 16 | 17 | // AdaptiveSampler returns a trace sampler that dynamically computes the 18 | // interval between samples to target a desired maximum sampling rate. 19 | // 20 | // maxSamplingRate is the desired maximum sampling rate in requests per second. 21 | // 22 | // sampleSize sets the number of requests between two adjustments of the 23 | // sampling rate when MaxSamplingRate is set. the sample rate cannot be adjusted 24 | // until the sample size is reached at least once. 25 | func AdaptiveSampler(maxSamplingRate, sampleSize int) sdktrace.Sampler { 26 | return sampler{ 27 | s: middleware.NewAdaptiveSampler(maxSamplingRate, sampleSize), 28 | maxSamplingRate: maxSamplingRate, 29 | sampleSize: sampleSize, 30 | } 31 | } 32 | 33 | // Description returns the description of the sampler. 34 | func (s sampler) Description() string { 35 | return fmt.Sprintf("Adaptive{maxSamplingRate:%d,sampleSize:%d}", s.maxSamplingRate, s.sampleSize) 36 | } 37 | 38 | // ShouldSample returns the sampling decision for the given parameters. 39 | func (s sampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult { 40 | if !s.s.Sample() { 41 | return sdktrace.SamplingResult{Decision: sdktrace.Drop} 42 | } 43 | return sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample} 44 | } 45 | -------------------------------------------------------------------------------- /clue/sampler_test.go: -------------------------------------------------------------------------------- 1 | package clue 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 8 | ) 9 | 10 | func TestAdaptiveSampler(t *testing.T) { 11 | // We don't need to test Goa, keep it simple... 12 | s := AdaptiveSampler(2, 10) 13 | expected := "Adaptive{maxSamplingRate:2,sampleSize:10}" 14 | assert.Equal(t, expected, s.Description()) 15 | res := s.ShouldSample(sdktrace.SamplingParameters{}) 16 | assert.Equal(t, sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample}, res) 17 | s2 := AdaptiveSampler(1, 2) 18 | expected = "Adaptive{maxSamplingRate:1,sampleSize:2}" 19 | assert.Equal(t, expected, s2.Description()) 20 | res2 := s2.ShouldSample(sdktrace.SamplingParameters{}) 21 | res3 := s2.ShouldSample(sdktrace.SamplingParameters{}) 22 | assert.Equal(t, sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample}, res2) 23 | assert.Equal(t, sdktrace.SamplingResult{Decision: sdktrace.Drop}, res3) 24 | } 25 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - '**/testsvc/**' -------------------------------------------------------------------------------- /debug/goa.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "net/http" 5 | 6 | goahttp "goa.design/goa/v3/http" 7 | ) 8 | 9 | // muxAdapter is a debug.Muxer adapter for goahttp.Muxer. 10 | type muxAdapter struct { 11 | muxer goahttp.Muxer 12 | } 13 | 14 | // HTTP methods supported by the adapter. 15 | var httpMethods = []string{ 16 | http.MethodGet, 17 | http.MethodHead, 18 | http.MethodPost, 19 | http.MethodPut, 20 | http.MethodPatch, 21 | http.MethodDelete, 22 | http.MethodConnect, 23 | http.MethodOptions, 24 | http.MethodTrace, 25 | } 26 | 27 | // Adapt returns a debug.Muxer adapter for the given goahttp.Muxer. 28 | func Adapt(m goahttp.Muxer) Muxer { 29 | return muxAdapter{muxer: m} 30 | } 31 | 32 | func (m muxAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) { 33 | m.muxer.ServeHTTP(w, r) 34 | } 35 | 36 | func (m muxAdapter) Handle(path string, handler http.Handler) { 37 | for _, method := range httpMethods { 38 | m.muxer.Handle(method, path, handler.ServeHTTP) 39 | } 40 | } 41 | 42 | func (m muxAdapter) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) { 43 | for _, method := range httpMethods { 44 | m.muxer.Handle(method, path, handler) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /debug/goa_test.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "net/url" 7 | "testing" 8 | 9 | goahttp "goa.design/goa/v3/http" 10 | ) 11 | 12 | func TestAdapt(t *testing.T) { 13 | mux := goahttp.NewMuxer() 14 | adapted := Adapt(mux) 15 | adapted.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { 16 | w.WriteHeader(http.StatusOK) 17 | w.Write([]byte("OK")) // nolint: errcheck 18 | }) 19 | adapted.Handle("/foo", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 20 | w.WriteHeader(http.StatusOK) 21 | w.Write([]byte("FOO")) // nolint: errcheck 22 | })) 23 | req := &http.Request{Method: "GET", URL: &url.URL{Path: "/"}} 24 | w := httptest.NewRecorder() 25 | adapted.ServeHTTP(w, req) 26 | if w.Code != http.StatusOK { 27 | t.Errorf("got status %d, expected %d", w.Code, http.StatusOK) 28 | } 29 | if w.Body.String() != "OK" { 30 | t.Errorf("got body %q, expected %q", w.Body.String(), "OK") 31 | } 32 | req = &http.Request{Method: "GET", URL: &url.URL{Path: "/foo"}} 33 | w = httptest.NewRecorder() 34 | adapted.ServeHTTP(w, req) 35 | if w.Code != http.StatusOK { 36 | t.Errorf("got status %d, expected %d", w.Code, http.StatusOK) 37 | } 38 | if w.Body.String() != "FOO" { 39 | t.Errorf("got body %q, expected %q", w.Body.String(), "FOO") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /debug/grpc.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | 8 | "goa.design/clue/log" 9 | ) 10 | 11 | // UnaryServerInterceptor return an interceptor that manages whether debug log 12 | // entries are written. This interceptor should be used in conjunction with the 13 | // MountDebugLogEnabler function. 14 | func UnaryServerInterceptor() grpc.UnaryServerInterceptor { 15 | return func( 16 | ctx context.Context, 17 | req interface{}, 18 | _ *grpc.UnaryServerInfo, 19 | handler grpc.UnaryHandler, 20 | ) (interface{}, error) { 21 | if debugLogs { 22 | ctx = log.Context(ctx, log.WithDebug()) 23 | } else { 24 | ctx = log.Context(ctx, log.WithNoDebug()) 25 | } 26 | return handler(ctx, req) 27 | } 28 | } 29 | 30 | // StreamServerInterceptor returns a stream interceptor that manages whether 31 | // debug log entries are written. Note: a change in the debug setting is 32 | // effective only for the next stream request. This interceptor should be used 33 | // in conjunction with the MountDebugLogEnabler function. 34 | func StreamServerInterceptor() grpc.StreamServerInterceptor { 35 | return func( 36 | srv interface{}, 37 | stream grpc.ServerStream, 38 | _ *grpc.StreamServerInfo, 39 | handler grpc.StreamHandler, 40 | ) error { 41 | ctx := stream.Context() 42 | if debugLogs { 43 | ctx = log.Context(ctx, log.WithDebug()) 44 | } else { 45 | ctx = log.Context(ctx, log.WithNoDebug()) 46 | } 47 | return handler(srv, &streamWithContext{stream, ctx}) 48 | } 49 | } 50 | 51 | type streamWithContext struct { 52 | grpc.ServerStream 53 | ctx context.Context 54 | } 55 | 56 | func (s *streamWithContext) Context() context.Context { 57 | return s.ctx 58 | } 59 | -------------------------------------------------------------------------------- /debug/http.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "net/http" 5 | 6 | "goa.design/clue/log" 7 | ) 8 | 9 | // HTTP returns a middleware that manages whether debug log entries are written. 10 | // This middleware should be used in conjunction with the MountDebugLogEnabler 11 | // function. 12 | func HTTP() func(http.Handler) http.Handler { 13 | return func(next http.Handler) http.Handler { 14 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | if debugLogs { 16 | ctx := log.Context(r.Context(), log.WithDebug()) 17 | r = r.WithContext(ctx) 18 | } else { 19 | ctx := log.Context(r.Context(), log.WithNoDebug()) 20 | r = r.WithContext(ctx) 21 | } 22 | next.ServeHTTP(w, r) 23 | }) 24 | return handler 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /debug/http_test.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | "goa.design/clue/log" 13 | ) 14 | 15 | func TestHTTP(t *testing.T) { 16 | // Create log context 17 | var buf bytes.Buffer 18 | ctx := log.Context(context.Background(), 19 | log.WithOutput(&buf), 20 | log.WithFormat(logKeyValsOnly)) 21 | log.FlushAndDisableBuffering(ctx) 22 | 23 | // Create HTTP handler 24 | mux := http.NewServeMux() 25 | var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 | if r.URL.Path != "/" { 27 | w.WriteHeader(http.StatusNotFound) 28 | return 29 | } 30 | log.Info(r.Context(), log.KV{K: "test", V: "info"}) 31 | log.Debug(r.Context(), log.KV{K: "test", V: "debug"}) 32 | w.WriteHeader(http.StatusOK) 33 | w.Write([]byte("OK")) // nolint: errcheck 34 | }) 35 | 36 | // Mount debug handler and log middleware 37 | MountDebugLogEnabler(mux) 38 | handler = HTTP()(handler) 39 | handler = log.HTTP(ctx, 40 | log.WithDisableRequestLogging(), 41 | log.WithDisableRequestID())(handler) 42 | 43 | // Start test server 44 | mux.Handle("/", handler) 45 | ts := httptest.NewServer(mux) 46 | defer ts.Close() 47 | 48 | steps := []struct { 49 | name string 50 | on bool 51 | off bool 52 | wantLog string 53 | }{ 54 | {"start", false, false, "test=info "}, 55 | {"turn debug logs on", true, false, "test=info test=debug "}, 56 | {"with debug logs on", false, false, "test=info test=debug "}, 57 | {"turn debug logs off", false, true, "test=info "}, 58 | {"with debug logs off", false, false, "test=info "}, 59 | } 60 | for _, step := range steps { 61 | if step.on { 62 | makeRequest(t, ts.URL+"/debug?debug-logs=on") 63 | } 64 | if step.off { 65 | makeRequest(t, ts.URL+"/debug?debug-logs=off") 66 | } 67 | 68 | status, resp := makeRequest(t, ts.URL) 69 | 70 | assert.Equal(t, http.StatusOK, status) 71 | assert.Equal(t, "OK", resp) 72 | assert.Equal(t, step.wantLog, buf.String()) 73 | buf.Reset() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/middleware/error_reporter.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/otel/codes" 7 | "go.opentelemetry.io/otel/trace" 8 | goa "goa.design/goa/v3/pkg" 9 | ) 10 | 11 | // ErrorReporter is a Goa endpoint middleware that flags a span as failed when an 12 | // error is returned by the service method. 13 | // 14 | // Usage: 15 | // 16 | // endpoints := gen.NewEndpoints(svc) 17 | // endpoints.Use(ErrorReporter()) 18 | func ErrorReporter() func(goa.Endpoint) goa.Endpoint { 19 | return func(e goa.Endpoint) goa.Endpoint { 20 | return func(ctx context.Context, req any) (any, error) { 21 | res, err := e(ctx, req) 22 | if err != nil { 23 | span := trace.SpanFromContext(ctx) 24 | span.SetStatus(codes.Error, err.Error()) 25 | } 26 | return res, err 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/weather/Procfile: -------------------------------------------------------------------------------- 1 | forecaster: bin/forecaster 2 | locator: bin/locator 3 | front: bin/front 4 | tester: bin/tester 5 | -------------------------------------------------------------------------------- /example/weather/design/design.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package design contains shared design resources. 3 | */ 4 | package design 5 | 6 | import ( 7 | . "goa.design/goa/v3/dsl" 8 | ) 9 | 10 | var Forecast = Type("Forecast", func() { 11 | Description("Weather forecast") 12 | Field(1, "location", Location, "Forecast location") 13 | Field(2, "periods", ArrayOf(Period), "Weather forecast periods") 14 | Required("location", "periods") 15 | }) 16 | 17 | var Location = Type("Location", func() { 18 | Description("Geographical location") 19 | Field(1, "lat", Float64, "Latitude", func() { 20 | Example(37.8267) 21 | }) 22 | Field(2, "long", Float64, "Longitude", func() { 23 | Example(-122.4233) 24 | }) 25 | Field(3, "city", String, "City", func() { 26 | Example("San Francisco") 27 | }) 28 | Field(4, "state", String, "State", func() { 29 | Example("CA") 30 | }) 31 | Required("lat", "long", "city", "state") 32 | }) 33 | 34 | var Period = Type("Period", func() { 35 | Description("Weather forecast period") 36 | Field(1, "name", String, "Period name", func() { 37 | Example("Morning") 38 | }) 39 | Field(2, "startTime", String, "Start time", func() { 40 | Format(FormatDateTime) 41 | Example("2020-01-01T00:00:00Z") 42 | }) 43 | Field(3, "endTime", String, "End time", func() { 44 | Format(FormatDateTime) 45 | Example("2020-01-01T00:00:00Z") 46 | }) 47 | Field(4, "temperature", Int, "Temperature", func() { 48 | Example(70) 49 | }) 50 | Field(5, "temperatureUnit", String, "Temperature unit", func() { 51 | Example("F") 52 | }) 53 | Field(6, "summary", String, "Summary", func() { 54 | Example("Clear") 55 | }) 56 | Required("name", "startTime", "endTime", "temperature", "temperatureUnit", "summary") 57 | }) 58 | -------------------------------------------------------------------------------- /example/weather/diagram/diagram.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import . "goa.design/model/dsl" 4 | 5 | var _ = Design("Weather System Architecture", "The Weather example system architecture", func() { 6 | var WeatherGov = SoftwareSystem("weather.gov", "Provides weather forecasts for US locations.", func() { 7 | External() 8 | URL("https://weather.go") 9 | Tag("External") 10 | }) 11 | 12 | var IPAPI = SoftwareSystem("ip-api.com", "Provides IP geolocation information.", func() { 13 | External() 14 | URL("https://ip-api.com") 15 | Tag("External") 16 | }) 17 | 18 | var _ = Person("User", "A client of the weather system.", func() { 19 | Uses("Weather Software System/Front Service", "Makes requests to", "HTTP", Synchronous) 20 | }) 21 | 22 | var System = SoftwareSystem("Weather Software System", "Provides IP based weather forecasts.", func() { 23 | var Location = Container("Location Service", "Leverages ip-api.com to locate IP addresses.", "Go and Goa", func() { 24 | Uses(IPAPI, "Makes requests to", "HTTP", Synchronous) 25 | Tag("Service") 26 | }) 27 | 28 | var Forecast = Container("Forecast Service", "Leverages weather.gov to retrieve weather forecasts for US based locations.", "Go and Goa", func() { 29 | Uses(WeatherGov, "Makes requests to", "HTTP", Synchronous) 30 | Tag("Service") 31 | }) 32 | 33 | Container("Front Service", "Retrieves weather forecasts for a given IP.", "Go and Goa", func() { 34 | Tag("Service") 35 | Uses(Location, "Retrieves IP location from", "gRPC", Synchronous) 36 | Uses(Forecast, "Retrieves weather forecasts from", "gRPC", Asynchronous) 37 | }) 38 | }) 39 | 40 | Views(func() { 41 | ContainerView(System, "Weather System Services", "Weather software system architecture diagram", func() { 42 | AddAll() 43 | AutoLayout(RankLeftRight) 44 | }) 45 | Styles(func() { 46 | ElementStyle("Person", func() { 47 | Background("#e6e6ea") 48 | Stroke("#f75c03") 49 | Shape(ShapePerson) 50 | }) 51 | ElementStyle("Container", func() { 52 | Background("#e6e6ea") 53 | Stroke("#2ab7ca") 54 | }) 55 | ElementStyle("External", func() { 56 | Background("#eae6e6") 57 | Stroke("#cab72a") 58 | }) 59 | ElementStyle("Software System", func() { 60 | Shape(ShapeRoundedBox) 61 | Background("#e6e6ea") 62 | Stroke("#f75c03") 63 | }) 64 | }) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /example/weather/go.mod: -------------------------------------------------------------------------------- 1 | module goa.design/clue/example/weather 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.0 6 | 7 | require ( 8 | github.com/stretchr/testify v1.10.0 9 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 10 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 11 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 12 | go.opentelemetry.io/otel v1.34.0 13 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 14 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 15 | go.opentelemetry.io/otel/trace v1.34.0 16 | goa.design/clue v1.0.6 17 | goa.design/goa/v3 v3.20.0 18 | goa.design/model v1.9.8 19 | goa.design/plugins/v3 v3.19.1 20 | google.golang.org/grpc v1.70.0 21 | google.golang.org/protobuf v1.36.5 22 | ) 23 | 24 | require ( 25 | github.com/aws/smithy-go v1.22.2 // indirect 26 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 27 | github.com/davecgh/go-spew v1.1.1 // indirect 28 | github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect 29 | github.com/felixge/httpsnoop v1.0.4 // indirect 30 | github.com/go-chi/chi/v5 v5.2.1 // indirect 31 | github.com/go-logfmt/logfmt v0.6.0 // indirect 32 | github.com/go-logr/logr v1.4.2 // indirect 33 | github.com/go-logr/stdr v1.2.2 // indirect 34 | github.com/google/uuid v1.6.0 // indirect 35 | github.com/gorilla/websocket v1.5.3 // indirect 36 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect 37 | github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect 38 | github.com/pmezard/go-difflib v1.0.0 // indirect 39 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 40 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect 41 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect 42 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect 43 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect 44 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 // indirect 45 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 46 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 47 | go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect 48 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 49 | golang.org/x/mod v0.23.0 // indirect 50 | golang.org/x/net v0.38.0 // indirect 51 | golang.org/x/sync v0.12.0 // indirect 52 | golang.org/x/sys v0.31.0 // indirect 53 | golang.org/x/term v0.30.0 // indirect 54 | golang.org/x/text v0.23.0 // indirect 55 | golang.org/x/tools v0.30.0 // indirect 56 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect 57 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect 58 | gopkg.in/yaml.v3 v3.0.1 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /example/weather/images/signoz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goadesign/clue/3aaab87668f1f5d6731e52d0e5560d2fb569d528/example/weather/images/signoz.png -------------------------------------------------------------------------------- /example/weather/middleware/error_reporter.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/otel/codes" 7 | "go.opentelemetry.io/otel/trace" 8 | goa "goa.design/goa/v3/pkg" 9 | ) 10 | 11 | // ErrorReporter is a Goa endpoint middleware that flags a span as failed when an 12 | // error is returned by the service method. 13 | // 14 | // Usage: 15 | // 16 | // endpoints := gen.NewEndpoints(svc) 17 | // endpoints.Use(ErrorReporter()) 18 | func ErrorReporter() func(goa.Endpoint) goa.Endpoint { 19 | return func(e goa.Endpoint) goa.Endpoint { 20 | return func(ctx context.Context, req any) (any, error) { 21 | res, err := e(ctx, req) 22 | if err != nil { 23 | span := trace.SpanFromContext(ctx) 24 | span.SetStatus(codes.Error, err.Error()) 25 | } 26 | return res, err 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/weather/scripts/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | GIT_ROOT=$(git rev-parse --show-toplevel) 6 | GIT_COMMIT=$(git rev-list -1 HEAD) 7 | pushd ${GIT_ROOT}/example/weather 8 | 9 | echo "Rebuilding services..." 10 | 11 | mkdir -p bin 12 | 13 | for svc in forecaster locator front tester; do 14 | go build -o bin/${svc} -ldflags "-X goa.design/clue/health.Version=$GIT_COMMIT" goa.design/clue/example/weather/services/${svc}/cmd/${svc} 15 | done 16 | 17 | popd 18 | -------------------------------------------------------------------------------- /example/weather/scripts/diagram: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | GIT_ROOT=$(git rev-parse --show-toplevel) 6 | pushd ${GIT_ROOT}/example/weather 7 | 8 | echo "Starting diagram editor..." 9 | 10 | mdl serve goa.design/clue/example/weather/diagram -dir diagram -port 8095 front 11 | 12 | popd 13 | -------------------------------------------------------------------------------- /example/weather/scripts/gen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | GIT_ROOT=$(git rev-parse --show-toplevel) 6 | pushd ${GIT_ROOT}/example/weather 7 | 8 | echo "Generating Goa code..." 9 | 10 | for svc in front forecaster locator tester; do 11 | goa gen goa.design/clue/example/weather/services/${svc}/design -o services/${svc} 12 | cmg gen ./services/${svc}/clients/*/ 13 | done 14 | 15 | popd 16 | -------------------------------------------------------------------------------- /example/weather/scripts/load: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Total duration for the script to run: 10 minutes (600 seconds) 4 | DURATION=600 5 | 6 | # Start time 7 | START_TIME=$(date +%s) 8 | 9 | # End time = Start time + Duration 10 | END_TIME=$((START_TIME + DURATION)) 11 | 12 | # Loop to run until the current time is less than the end time 13 | while [ $(date +%s) -lt $END_TIME ]; do 14 | # Run the command 15 | http localhost:8084/forecast/142.250.68.14 16 | 17 | # Wait for 1 second before the next iteration 18 | sleep 1 19 | done 20 | 21 | -------------------------------------------------------------------------------- /example/weather/scripts/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | mkdir -p /tmp/cortex/rule 6 | mkdir -p /tmp/tempo 7 | 8 | echo "Starting services..." 9 | 10 | GIT_ROOT=$(git rev-parse --show-toplevel) 11 | pushd ${GIT_ROOT}/example/weather 12 | 13 | source ./scripts/utils/common.sh 14 | 15 | # ugh, host-gateway does not work on WSL because it points to the Windows 16 | # host, not the WSL host. Instead in this case we need to use the WSL 17 | # subsystem IP. 18 | if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null ; then 19 | export HOST_GATEWAY=$(hostname -I) 20 | else 21 | export HOST_GATEWAY='host-gateway' 22 | fi 23 | 24 | docker-compose -f signoz/deploy/docker/docker-compose.yaml up -d 25 | 26 | overmind start 27 | 28 | popd 29 | -------------------------------------------------------------------------------- /example/weather/scripts/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | GIT_ROOT=$(git rev-parse --show-toplevel) 6 | pushd ${GIT_ROOT}/example/weather 7 | 8 | source ./scripts/utils/common.sh 9 | 10 | proto_pkg="protobuf-compiler" 11 | 12 | # install protoc, update version as needed 13 | PROTO_VER=25.1 14 | 15 | if is_mac; then 16 | PROTOC_ZIP=protoc-${PROTO_VER}-osx-universal_binary.zip 17 | proto_pkg="protobuf" 18 | else 19 | PROTOC_ZIP=protoc-${PROTO_VER}-linux-x86_64.zip 20 | fi 21 | 22 | curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v${PROTO_VER}/${PROTOC_ZIP} 23 | sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc 24 | sudo unzip -o $PROTOC_ZIP -d /usr/local 'include/*' 25 | rm -f $PROTOC_ZIP 26 | 27 | check_required_cmd "protoc" $proto_pkg 28 | 29 | if [[ "$CI" == "" ]]; then 30 | check_required_cmd "tmux" 31 | fi 32 | 33 | go mod download 34 | go install goa.design/clue/mock/cmd/cmg@latest 35 | go install goa.design/model/cmd/mdl@latest 36 | go install goa.design/goa/v3/...@v3 37 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1 38 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0 39 | go install github.com/DarthSim/overmind/v2@latest 40 | 41 | # clone SigNoz for docker compose files 42 | if [[ ! -d "signoz" ]]; then 43 | git clone --depth 1 -b main https://github.com/SigNoz/signoz.git 44 | fi 45 | 46 | ./scripts/build 47 | 48 | popd 49 | -------------------------------------------------------------------------------- /example/weather/scripts/utils/common.sh: -------------------------------------------------------------------------------- 1 | # Useful functions for re-use in different scripts 2 | 3 | [[ "$DEBUG" != "" ]] && set -x 4 | 5 | function is_mac { 6 | [[ "$OSTYPE" == "darwin"* ]] 7 | } 8 | 9 | function is_m1_mac { 10 | is_mac && [[ "$(uname -a)" == *"ARM64"* ]] 11 | } 12 | 13 | function check_required_cmd { 14 | cmd="$1" 15 | pkg="$2" 16 | # I feel like you could do this with substitution 17 | # but I didn't feel like fighting bash. 18 | [[ "$pkg" == "" ]] && pkg=$cmd 19 | 20 | if ! command -v $1 &> /dev/null; then 21 | echo "Unable to find '$cmd' in your PATH - cannot continue." 22 | if is_mac; then 23 | echo "Try 'brew install '$pkg'" 24 | else 25 | echo "Try 'apt-get install '$pkg'" 26 | fi 27 | exit 1 28 | fi 29 | } 30 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/design/design.go: -------------------------------------------------------------------------------- 1 | package design 2 | 3 | import ( 4 | . "goa.design/goa/v3/dsl" 5 | 6 | . "goa.design/clue/example/weather/design" 7 | ) 8 | 9 | var _ = API("Weather Service API", func() { 10 | Title("The Weather Service API") 11 | Description("A fully instrumented weather service API") 12 | Version("1.0.0") 13 | }) 14 | 15 | var _ = Service("Forecaster", func() { 16 | Description("Service that provides weather forecasts") 17 | Method("forecast", func() { 18 | Description("Retrieve weather forecast for a given location") 19 | Payload(func() { 20 | Field(1, "lat", Float64, "Latitude", func() { 21 | Example(37.8267) 22 | }) 23 | Field(2, "long", Float64, "Longitude", func() { 24 | Example(-122.4233) 25 | }) 26 | Required("lat", "long") 27 | }) 28 | Result(Forecast) 29 | GRPC(func() {}) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/forecast.go: -------------------------------------------------------------------------------- 1 | package forecaster 2 | 3 | import ( 4 | "context" 5 | 6 | "goa.design/clue/example/weather/services/forecaster/clients/weathergov" 7 | genforecaster "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 8 | ) 9 | 10 | type ( 11 | // Service is the forecast service implementation. 12 | Service struct { 13 | wc weathergov.Client 14 | } 15 | ) 16 | 17 | // New instantiates a new forecast service. 18 | func New(wc weathergov.Client) *Service { 19 | return &Service{wc: wc} 20 | } 21 | 22 | // Forecast returns the forecast for the given location. 23 | func (s *Service) Forecast(ctx context.Context, p *genforecaster.ForecastPayload) (*genforecaster.Forecast2, error) { 24 | f, err := s.wc.GetForecast(ctx, p.Lat, p.Long) 25 | if err != nil { 26 | return nil, err 27 | } 28 | location := &genforecaster.Location{ 29 | Lat: f.Location.Lat, 30 | Long: f.Location.Long, 31 | City: f.Location.City, 32 | State: f.Location.State, 33 | } 34 | periods := make([]*genforecaster.Period, len(f.Periods)) 35 | for i, p := range f.Periods { 36 | periods[i] = &genforecaster.Period{ 37 | Name: p.Name, 38 | StartTime: p.StartTime, 39 | EndTime: p.EndTime, 40 | Temperature: p.Temperature, 41 | TemperatureUnit: p.TemperatureUnit, 42 | Summary: p.Summary, 43 | } 44 | } 45 | return &genforecaster.Forecast2{Location: location, Periods: periods}, nil 46 | } 47 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/forecaster/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster client 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package forecaster 10 | 11 | import ( 12 | "context" 13 | 14 | goa "goa.design/goa/v3/pkg" 15 | ) 16 | 17 | // Client is the "Forecaster" service client. 18 | type Client struct { 19 | ForecastEndpoint goa.Endpoint 20 | } 21 | 22 | // NewClient initializes a "Forecaster" service client given the endpoints. 23 | func NewClient(forecast goa.Endpoint) *Client { 24 | return &Client{ 25 | ForecastEndpoint: forecast, 26 | } 27 | } 28 | 29 | // Forecast calls the "forecast" endpoint of the "Forecaster" service. 30 | func (c *Client) Forecast(ctx context.Context, p *ForecastPayload) (res *Forecast2, err error) { 31 | var ires any 32 | ires, err = c.ForecastEndpoint(ctx, p) 33 | if err != nil { 34 | return 35 | } 36 | return ires.(*Forecast2), nil 37 | } 38 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/forecaster/endpoints.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster endpoints 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package forecaster 10 | 11 | import ( 12 | "context" 13 | 14 | goa "goa.design/goa/v3/pkg" 15 | ) 16 | 17 | // Endpoints wraps the "Forecaster" service endpoints. 18 | type Endpoints struct { 19 | Forecast goa.Endpoint 20 | } 21 | 22 | // NewEndpoints wraps the methods of the "Forecaster" service with endpoints. 23 | func NewEndpoints(s Service) *Endpoints { 24 | return &Endpoints{ 25 | Forecast: NewForecastEndpoint(s), 26 | } 27 | } 28 | 29 | // Use applies the given middleware to all the "Forecaster" service endpoints. 30 | func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { 31 | e.Forecast = m(e.Forecast) 32 | } 33 | 34 | // NewForecastEndpoint returns an endpoint function that calls the method 35 | // "forecast" of service "Forecaster". 36 | func NewForecastEndpoint(s Service) goa.Endpoint { 37 | return func(ctx context.Context, req any) (any, error) { 38 | p := req.(*ForecastPayload) 39 | return s.Forecast(ctx, p) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/forecaster/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster service 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package forecaster 10 | 11 | import ( 12 | "context" 13 | ) 14 | 15 | // Service that provides weather forecasts 16 | type Service interface { 17 | // Retrieve weather forecast for a given location 18 | Forecast(context.Context, *ForecastPayload) (res *Forecast2, err error) 19 | } 20 | 21 | // APIName is the name of the API as defined in the design. 22 | const APIName = "Weather Service API" 23 | 24 | // APIVersion is the version of the API as defined in the design. 25 | const APIVersion = "1.0.0" 26 | 27 | // ServiceName is the name of the service as defined in the design. This is the 28 | // same value that is set in the endpoint request contexts under the ServiceKey 29 | // key. 30 | const ServiceName = "Forecaster" 31 | 32 | // MethodNames lists the service method names as defined in the design. These 33 | // are the same values that are set in the endpoint request contexts under the 34 | // MethodKey key. 35 | var MethodNames = [1]string{"forecast"} 36 | 37 | // Forecast2 is the result type of the Forecaster service forecast method. 38 | type Forecast2 struct { 39 | // Forecast location 40 | Location *Location 41 | // Weather forecast periods 42 | Periods []*Period 43 | } 44 | 45 | // ForecastPayload is the payload type of the Forecaster service forecast 46 | // method. 47 | type ForecastPayload struct { 48 | // Latitude 49 | Lat float64 50 | // Longitude 51 | Long float64 52 | } 53 | 54 | // Geographical location 55 | type Location struct { 56 | // Latitude 57 | Lat float64 58 | // Longitude 59 | Long float64 60 | // City 61 | City string 62 | // State 63 | State string 64 | } 65 | 66 | // Weather forecast period 67 | type Period struct { 68 | // Period name 69 | Name string 70 | // Start time 71 | StartTime string 72 | // End time 73 | EndTime string 74 | // Temperature 75 | Temperature int 76 | // Temperature unit 77 | TemperatureUnit string 78 | // Summary 79 | Summary string 80 | } 81 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/grpc/forecaster/client/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster gRPC client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package client 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | 15 | forecaster "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 16 | forecasterpb "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/pb" 17 | ) 18 | 19 | // BuildForecastPayload builds the payload for the Forecaster forecast endpoint 20 | // from CLI flags. 21 | func BuildForecastPayload(forecasterForecastMessage string) (*forecaster.ForecastPayload, error) { 22 | var err error 23 | var message forecasterpb.ForecastRequest 24 | { 25 | if forecasterForecastMessage != "" { 26 | err = json.Unmarshal([]byte(forecasterForecastMessage), &message) 27 | if err != nil { 28 | return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"lat\": 37.8267,\n \"long\": -122.4233\n }'") 29 | } 30 | } 31 | } 32 | v := &forecaster.ForecastPayload{ 33 | Lat: message.Lat, 34 | Long: message.Long, 35 | } 36 | 37 | return v, nil 38 | } 39 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/grpc/forecaster/client/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster gRPC client 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package client 10 | 11 | import ( 12 | "context" 13 | 14 | forecasterpb "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/pb" 15 | goagrpc "goa.design/goa/v3/grpc" 16 | goa "goa.design/goa/v3/pkg" 17 | "google.golang.org/grpc" 18 | ) 19 | 20 | // Client lists the service endpoint gRPC clients. 21 | type Client struct { 22 | grpccli forecasterpb.ForecasterClient 23 | opts []grpc.CallOption 24 | } // NewClient instantiates gRPC client for all the Forecaster service servers. 25 | func NewClient(cc *grpc.ClientConn, opts ...grpc.CallOption) *Client { 26 | return &Client{ 27 | grpccli: forecasterpb.NewForecasterClient(cc), 28 | opts: opts, 29 | } 30 | } // Forecast calls the "Forecast" function in forecasterpb.ForecasterClient 31 | // interface. 32 | func (c *Client) Forecast() goa.Endpoint { 33 | return func(ctx context.Context, v any) (any, error) { 34 | inv := goagrpc.NewInvoker( 35 | BuildForecastFunc(c.grpccli, c.opts...), 36 | EncodeForecastRequest, 37 | DecodeForecastResponse) 38 | res, err := inv.Invoke(ctx, v) 39 | if err != nil { 40 | return nil, goa.Fault("%s", err.Error()) 41 | } 42 | return res, nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/grpc/forecaster/client/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster gRPC client encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package client 10 | 11 | import ( 12 | "context" 13 | 14 | forecaster "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 15 | forecasterpb "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/pb" 16 | goagrpc "goa.design/goa/v3/grpc" 17 | "google.golang.org/grpc" 18 | "google.golang.org/grpc/metadata" 19 | ) 20 | 21 | // BuildForecastFunc builds the remote method to invoke for "Forecaster" 22 | // service "forecast" endpoint. 23 | func BuildForecastFunc(grpccli forecasterpb.ForecasterClient, cliopts ...grpc.CallOption) goagrpc.RemoteFunc { 24 | return func(ctx context.Context, reqpb any, opts ...grpc.CallOption) (any, error) { 25 | for _, opt := range cliopts { 26 | opts = append(opts, opt) 27 | } 28 | if reqpb != nil { 29 | return grpccli.Forecast(ctx, reqpb.(*forecasterpb.ForecastRequest), opts...) 30 | } 31 | return grpccli.Forecast(ctx, &forecasterpb.ForecastRequest{}, opts...) 32 | } 33 | } 34 | 35 | // EncodeForecastRequest encodes requests sent to Forecaster forecast endpoint. 36 | func EncodeForecastRequest(ctx context.Context, v any, md *metadata.MD) (any, error) { 37 | payload, ok := v.(*forecaster.ForecastPayload) 38 | if !ok { 39 | return nil, goagrpc.ErrInvalidType("Forecaster", "forecast", "*forecaster.ForecastPayload", v) 40 | } 41 | return NewProtoForecastRequest(payload), nil 42 | } 43 | 44 | // DecodeForecastResponse decodes responses from the Forecaster forecast 45 | // endpoint. 46 | func DecodeForecastResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { 47 | message, ok := v.(*forecasterpb.ForecastResponse) 48 | if !ok { 49 | return nil, goagrpc.ErrInvalidType("Forecaster", "forecast", "*forecasterpb.ForecastResponse", v) 50 | } 51 | if err := ValidateForecastResponse(message); err != nil { 52 | return nil, err 53 | } 54 | res := NewForecastResult(message) 55 | return res, nil 56 | } 57 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.proto: -------------------------------------------------------------------------------- 1 | // Code generated with goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster protocol buffer definition 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | syntax = "proto3"; 10 | 11 | package forecaster; 12 | 13 | option go_package = "/forecasterpb"; 14 | 15 | // Service that provides weather forecasts 16 | service Forecaster { 17 | // Retrieve weather forecast for a given location 18 | rpc Forecast (ForecastRequest) returns (ForecastResponse); 19 | } 20 | 21 | message ForecastRequest { 22 | // Latitude 23 | double lat = 1; 24 | // Longitude 25 | double long = 2; 26 | } 27 | 28 | message ForecastResponse { 29 | // Forecast location 30 | Location location = 1; 31 | // Weather forecast periods 32 | repeated Period periods = 2; 33 | } 34 | // Geographical location 35 | message Location { 36 | // Latitude 37 | double lat = 1; 38 | // Longitude 39 | double long = 2; 40 | // City 41 | string city = 3; 42 | // State 43 | string state = 4; 44 | } 45 | // Weather forecast period 46 | message Period { 47 | // Period name 48 | string name = 1; 49 | // Start time 50 | string start_time = 2; 51 | // End time 52 | string end_time = 3; 53 | // Temperature 54 | sint32 temperature = 4; 55 | // Temperature unit 56 | string temperature_unit = 5; 57 | // Summary 58 | string summary = 6; 59 | } 60 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/grpc/forecaster/server/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster gRPC server encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package server 10 | 11 | import ( 12 | "context" 13 | 14 | forecaster "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 15 | forecasterpb "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/pb" 16 | goagrpc "goa.design/goa/v3/grpc" 17 | "google.golang.org/grpc/metadata" 18 | ) 19 | 20 | // EncodeForecastResponse encodes responses from the "Forecaster" service 21 | // "forecast" endpoint. 22 | func EncodeForecastResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 23 | result, ok := v.(*forecaster.Forecast2) 24 | if !ok { 25 | return nil, goagrpc.ErrInvalidType("Forecaster", "forecast", "*forecaster.Forecast2", v) 26 | } 27 | resp := NewProtoForecastResponse(result) 28 | return resp, nil 29 | } 30 | 31 | // DecodeForecastRequest decodes requests sent to "Forecaster" service 32 | // "forecast" endpoint. 33 | func DecodeForecastRequest(ctx context.Context, v any, md metadata.MD) (any, error) { 34 | var ( 35 | message *forecasterpb.ForecastRequest 36 | ok bool 37 | ) 38 | { 39 | if message, ok = v.(*forecasterpb.ForecastRequest); !ok { 40 | return nil, goagrpc.ErrInvalidType("Forecaster", "forecast", "*forecasterpb.ForecastRequest", v) 41 | } 42 | } 43 | var payload *forecaster.ForecastPayload 44 | { 45 | payload = NewForecastPayload(message) 46 | } 47 | return payload, nil 48 | } 49 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/grpc/forecaster/server/server.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster gRPC server 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package server 10 | 11 | import ( 12 | "context" 13 | 14 | forecaster "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 15 | forecasterpb "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/pb" 16 | goagrpc "goa.design/goa/v3/grpc" 17 | goa "goa.design/goa/v3/pkg" 18 | ) 19 | 20 | // Server implements the forecasterpb.ForecasterServer interface. 21 | type Server struct { 22 | ForecastH goagrpc.UnaryHandler 23 | forecasterpb.UnimplementedForecasterServer 24 | } 25 | 26 | // New instantiates the server struct with the Forecaster service endpoints. 27 | func New(e *forecaster.Endpoints, uh goagrpc.UnaryHandler) *Server { 28 | return &Server{ 29 | ForecastH: NewForecastHandler(e.Forecast, uh), 30 | } 31 | } 32 | 33 | // NewForecastHandler creates a gRPC handler which serves the "Forecaster" 34 | // service "forecast" endpoint. 35 | func NewForecastHandler(endpoint goa.Endpoint, h goagrpc.UnaryHandler) goagrpc.UnaryHandler { 36 | if h == nil { 37 | h = goagrpc.NewUnaryHandler(endpoint, DecodeForecastRequest, EncodeForecastResponse) 38 | } 39 | return h 40 | } 41 | 42 | // Forecast implements the "Forecast" method in forecasterpb.ForecasterServer 43 | // interface. 44 | func (s *Server) Forecast(ctx context.Context, message *forecasterpb.ForecastRequest) (*forecasterpb.ForecastResponse, error) { 45 | ctx = context.WithValue(ctx, goa.MethodKey, "forecast") 46 | ctx = context.WithValue(ctx, goa.ServiceKey, "Forecaster") 47 | resp, err := s.ForecastH.Handle(ctx, message) 48 | if err != nil { 49 | return nil, goagrpc.EncodeError(err) 50 | } 51 | return resp.(*forecasterpb.ForecastResponse), nil 52 | } 53 | -------------------------------------------------------------------------------- /example/weather/services/forecaster/gen/grpc/forecaster/server/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // Forecaster gRPC server types 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/forecaster/design -o 7 | // services/forecaster 8 | 9 | package server 10 | 11 | import ( 12 | forecaster "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 13 | forecasterpb "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/pb" 14 | ) 15 | 16 | // NewForecastPayload builds the payload of the "forecast" endpoint of the 17 | // "Forecaster" service from the gRPC request type. 18 | func NewForecastPayload(message *forecasterpb.ForecastRequest) *forecaster.ForecastPayload { 19 | v := &forecaster.ForecastPayload{ 20 | Lat: message.Lat, 21 | Long: message.Long, 22 | } 23 | return v 24 | } 25 | 26 | // NewProtoForecastResponse builds the gRPC response type from the result of 27 | // the "forecast" endpoint of the "Forecaster" service. 28 | func NewProtoForecastResponse(result *forecaster.Forecast2) *forecasterpb.ForecastResponse { 29 | message := &forecasterpb.ForecastResponse{} 30 | if result.Location != nil { 31 | message.Location = svcForecasterLocationToForecasterpbLocation(result.Location) 32 | } 33 | if result.Periods != nil { 34 | message.Periods = make([]*forecasterpb.Period, len(result.Periods)) 35 | for i, val := range result.Periods { 36 | message.Periods[i] = &forecasterpb.Period{ 37 | Name: val.Name, 38 | StartTime: val.StartTime, 39 | EndTime: val.EndTime, 40 | Temperature: int32(val.Temperature), 41 | TemperatureUnit: val.TemperatureUnit, 42 | Summary: val.Summary, 43 | } 44 | } 45 | } 46 | return message 47 | } 48 | 49 | // svcForecasterLocationToForecasterpbLocation builds a value of type 50 | // *forecasterpb.Location from a value of type *forecaster.Location. 51 | func svcForecasterLocationToForecasterpbLocation(v *forecaster.Location) *forecasterpb.Location { 52 | res := &forecasterpb.Location{ 53 | Lat: v.Lat, 54 | Long: v.Long, 55 | City: v.City, 56 | State: v.State, 57 | } 58 | 59 | return res 60 | } 61 | 62 | // protobufForecasterpbLocationToForecasterLocation builds a value of type 63 | // *forecaster.Location from a value of type *forecasterpb.Location. 64 | func protobufForecasterpbLocationToForecasterLocation(v *forecasterpb.Location) *forecaster.Location { 65 | res := &forecaster.Location{ 66 | Lat: v.Lat, 67 | Long: v.Long, 68 | City: v.City, 69 | State: v.State, 70 | } 71 | 72 | return res 73 | } 74 | -------------------------------------------------------------------------------- /example/weather/services/front/clients/forecaster/client.go: -------------------------------------------------------------------------------- 1 | package forecaster 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | 8 | "goa.design/clue/debug" 9 | genforecast "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 10 | gengrpcclient "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/client" 11 | ) 12 | 13 | type ( 14 | // Client is a client for the forecast service. 15 | Client interface { 16 | // GetForecast gets the forecast for the given location. 17 | GetForecast(ctx context.Context, lat, long float64) (*Forecast, error) 18 | } 19 | 20 | // Forecast represents the forecast for a given location. 21 | Forecast struct { 22 | // Location is the location of the forecast. 23 | Location *Location 24 | // Periods is the forecast for the location. 25 | Periods []*Period 26 | } 27 | 28 | // Location represents the geographical location of a forecast. 29 | Location struct { 30 | // Lat is the latitude of the location. 31 | Lat float64 32 | // Long is the longitude of the location. 33 | Long float64 34 | // City is the city of the location. 35 | City string 36 | // State is the state of the location. 37 | State string 38 | } 39 | 40 | // Period represents a forecast period. 41 | Period struct { 42 | // Name is the name of the forecast period. 43 | Name string 44 | // StartTime is the start time of the forecast period in RFC3339 format. 45 | StartTime string 46 | // EndTime is the end time of the forecast period in RFC3339 format. 47 | EndTime string 48 | // Temperature is the temperature of the forecast period. 49 | Temperature int 50 | // TemperatureUnit is the temperature unit of the forecast period. 51 | TemperatureUnit string 52 | // Summary is the summary of the forecast period. 53 | Summary string 54 | } 55 | 56 | // client is the client implementation. 57 | client struct { 58 | genc *genforecast.Client 59 | } 60 | ) 61 | 62 | // New instantiates a new forecast service client. 63 | func New(cc *grpc.ClientConn) Client { 64 | c := gengrpcclient.NewClient(cc, grpc.WaitForReady(true)) 65 | forecast := debug.LogPayloads(debug.WithClient())(c.Forecast()) 66 | return &client{genc: genforecast.NewClient(forecast)} 67 | } 68 | 69 | // Forecast returns the forecast for the given location or current location if 70 | // lat or long are nil. 71 | func (c *client) GetForecast(ctx context.Context, lat, long float64) (*Forecast, error) { 72 | res, err := c.genc.Forecast(ctx, &genforecast.ForecastPayload{Lat: lat, Long: long}) 73 | if err != nil { 74 | return nil, err 75 | } 76 | l := Location(*res.Location) 77 | ps := make([]*Period, len(res.Periods)) 78 | for i, p := range res.Periods { 79 | pval := Period(*p) 80 | ps[i] = &pval 81 | } 82 | return &Forecast{&l, ps}, nil 83 | } 84 | -------------------------------------------------------------------------------- /example/weather/services/front/clients/forecaster/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator v0.18.2, DO NOT EDIT. 2 | // 3 | // Command: 4 | // $ cmg gen goa.design/clue/example/weather/services/front/clients/forecaster 5 | 6 | package mockforecaster 7 | 8 | import ( 9 | "context" 10 | "testing" 11 | 12 | "goa.design/clue/mock" 13 | 14 | "goa.design/clue/example/weather/services/front/clients/forecaster" 15 | ) 16 | 17 | type ( 18 | Client struct { 19 | m *mock.Mock 20 | t *testing.T 21 | } 22 | 23 | ClientGetForecastFunc func(ctx context.Context, lat, long float64) (*forecaster.Forecast, error) 24 | ) 25 | 26 | func NewClient(t *testing.T) *Client { 27 | var ( 28 | m = &Client{mock.New(), t} 29 | _ forecaster.Client = m 30 | ) 31 | return m 32 | } 33 | 34 | func (m *Client) AddGetForecast(f ClientGetForecastFunc) { 35 | m.m.Add("GetForecast", f) 36 | } 37 | 38 | func (m *Client) SetGetForecast(f ClientGetForecastFunc) { 39 | m.m.Set("GetForecast", f) 40 | } 41 | 42 | func (m *Client) GetForecast(ctx context.Context, lat, long float64) (*forecaster.Forecast, error) { 43 | if f := m.m.Next("GetForecast"); f != nil { 44 | return f.(ClientGetForecastFunc)(ctx, lat, long) 45 | } 46 | m.t.Helper() 47 | m.t.Error("unexpected GetForecast call") 48 | return nil, nil 49 | } 50 | 51 | func (m *Client) HasMore() bool { 52 | return m.m.HasMore() 53 | } 54 | -------------------------------------------------------------------------------- /example/weather/services/front/clients/locator/client.go: -------------------------------------------------------------------------------- 1 | package locator 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | 8 | "goa.design/clue/debug" 9 | genclient "goa.design/clue/example/weather/services/locator/gen/grpc/locator/client" 10 | genlocator "goa.design/clue/example/weather/services/locator/gen/locator" 11 | ) 12 | 13 | type ( 14 | // Client is a client for the locator service. 15 | Client interface { 16 | // GetLocation gets the location for the given ip 17 | GetLocation(ctx context.Context, ip string) (*WorldLocation, error) 18 | } 19 | 20 | // WorldLocation represents the location for the given IP address. 21 | WorldLocation struct { 22 | // Lat is the latitude of the location. 23 | Lat float64 24 | // Long is the longitude of the location. 25 | Long float64 26 | // City is the city of the location. 27 | City string 28 | // Region is the region/state of the location. 29 | Region string 30 | // Country is the country of the location. 31 | Country string 32 | } 33 | 34 | // client is the client implementation. 35 | client struct { 36 | genc *genlocator.Client 37 | } 38 | ) 39 | 40 | // New instantiates a new locator service client. 41 | func New(cc *grpc.ClientConn) Client { 42 | c := genclient.NewClient(cc, grpc.WaitForReady(true)) 43 | locator := debug.LogPayloads(debug.WithClient())(c.GetLocation()) 44 | return &client{genc: genlocator.NewClient(locator)} 45 | } 46 | 47 | // GetLocation returns the location for the given IP address. 48 | func (c *client) GetLocation(ctx context.Context, ip string) (*WorldLocation, error) { 49 | res, err := c.genc.GetLocation(ctx, ip) 50 | if err != nil { 51 | return nil, err 52 | } 53 | l := WorldLocation(*res) 54 | return &l, nil 55 | } 56 | -------------------------------------------------------------------------------- /example/weather/services/front/clients/locator/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator v0.18.2, DO NOT EDIT. 2 | // 3 | // Command: 4 | // $ cmg gen goa.design/clue/example/weather/services/front/clients/locator 5 | 6 | package mocklocator 7 | 8 | import ( 9 | "context" 10 | "testing" 11 | 12 | "goa.design/clue/mock" 13 | 14 | "goa.design/clue/example/weather/services/front/clients/locator" 15 | ) 16 | 17 | type ( 18 | Client struct { 19 | m *mock.Mock 20 | t *testing.T 21 | } 22 | 23 | ClientGetLocationFunc func(ctx context.Context, ip string) (*locator.WorldLocation, error) 24 | ) 25 | 26 | func NewClient(t *testing.T) *Client { 27 | var ( 28 | m = &Client{mock.New(), t} 29 | _ locator.Client = m 30 | ) 31 | return m 32 | } 33 | 34 | func (m *Client) AddGetLocation(f ClientGetLocationFunc) { 35 | m.m.Add("GetLocation", f) 36 | } 37 | 38 | func (m *Client) SetGetLocation(f ClientGetLocationFunc) { 39 | m.m.Set("GetLocation", f) 40 | } 41 | 42 | func (m *Client) GetLocation(ctx context.Context, ip string) (*locator.WorldLocation, error) { 43 | if f := m.m.Next("GetLocation"); f != nil { 44 | return f.(ClientGetLocationFunc)(ctx, ip) 45 | } 46 | m.t.Helper() 47 | m.t.Error("unexpected GetLocation call") 48 | return nil, nil 49 | } 50 | 51 | func (m *Client) HasMore() bool { 52 | return m.m.HasMore() 53 | } 54 | -------------------------------------------------------------------------------- /example/weather/services/front/clients/tester/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator v0.18.2, DO NOT EDIT. 2 | // 3 | // Command: 4 | // $ cmg gen goa.design/clue/example/weather/services/front/clients/tester 5 | 6 | package mocktester 7 | 8 | import ( 9 | "context" 10 | "testing" 11 | 12 | "goa.design/clue/mock" 13 | 14 | "goa.design/clue/example/weather/services/front/clients/tester" 15 | "goa.design/clue/example/weather/services/front/gen/front" 16 | ) 17 | 18 | type ( 19 | Client struct { 20 | m *mock.Mock 21 | t *testing.T 22 | } 23 | 24 | ClientTestAllFunc func(ctx context.Context, included, excluded []string) (*front.TestResults, error) 25 | ClientTestSmokeFunc func(ctx context.Context) (*front.TestResults, error) 26 | ) 27 | 28 | func NewClient(t *testing.T) *Client { 29 | var ( 30 | m = &Client{mock.New(), t} 31 | _ tester.Client = m 32 | ) 33 | return m 34 | } 35 | 36 | func (m *Client) AddTestAll(f ClientTestAllFunc) { 37 | m.m.Add("TestAll", f) 38 | } 39 | 40 | func (m *Client) SetTestAll(f ClientTestAllFunc) { 41 | m.m.Set("TestAll", f) 42 | } 43 | 44 | func (m *Client) TestAll(ctx context.Context, included, excluded []string) (*front.TestResults, error) { 45 | if f := m.m.Next("TestAll"); f != nil { 46 | return f.(ClientTestAllFunc)(ctx, included, excluded) 47 | } 48 | m.t.Helper() 49 | m.t.Error("unexpected TestAll call") 50 | return nil, nil 51 | } 52 | 53 | func (m *Client) AddTestSmoke(f ClientTestSmokeFunc) { 54 | m.m.Add("TestSmoke", f) 55 | } 56 | 57 | func (m *Client) SetTestSmoke(f ClientTestSmokeFunc) { 58 | m.m.Set("TestSmoke", f) 59 | } 60 | 61 | func (m *Client) TestSmoke(ctx context.Context) (*front.TestResults, error) { 62 | if f := m.m.Next("TestSmoke"); f != nil { 63 | return f.(ClientTestSmokeFunc)(ctx) 64 | } 65 | m.t.Helper() 66 | m.t.Error("unexpected TestSmoke call") 67 | return nil, nil 68 | } 69 | 70 | func (m *Client) HasMore() bool { 71 | return m.m.HasMore() 72 | } 73 | -------------------------------------------------------------------------------- /example/weather/services/front/front.go: -------------------------------------------------------------------------------- 1 | package front 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "goa.design/clue/example/weather/services/front/clients/forecaster" 8 | "goa.design/clue/example/weather/services/front/clients/locator" 9 | "goa.design/clue/example/weather/services/front/clients/tester" 10 | genfront "goa.design/clue/example/weather/services/front/gen/front" 11 | ) 12 | 13 | type ( 14 | // Service is the front service implementation. 15 | Service struct { 16 | fc forecaster.Client 17 | lc locator.Client 18 | tc tester.Client 19 | } 20 | ) 21 | 22 | // New instantiates a new front service. 23 | func New(fc forecaster.Client, lc locator.Client, tc tester.Client) *Service { 24 | return &Service{fc: fc, lc: lc, tc: tc} 25 | } 26 | 27 | // Forecast returns the forecast for the location at the given IP. 28 | func (s *Service) Forecast(ctx context.Context, ip string) (*genfront.Forecast2, error) { 29 | l, err := s.lc.GetLocation(ctx, ip) 30 | if err != nil { 31 | return nil, err 32 | } 33 | if l.Country != "United States" { 34 | return nil, genfront.MakeNotUsa(fmt.Errorf("IP not in the US (%s)", l.Country)) 35 | } 36 | f, err := s.fc.GetForecast(ctx, l.Lat, l.Long) 37 | if err != nil { 38 | return nil, err 39 | } 40 | loc := genfront.Location{ 41 | Lat: l.Lat, 42 | Long: l.Long, 43 | City: l.City, 44 | State: l.Region, 45 | } 46 | ps := make([]*genfront.Period, len(f.Periods)) 47 | for i, p := range f.Periods { 48 | pval := genfront.Period(*p) 49 | ps[i] = &pval 50 | } 51 | return &genfront.Forecast2{Location: &loc, Periods: ps}, nil 52 | } 53 | 54 | // Runs ALL API Integration Tests from the Tester service, allowing for filtering on included or excluded tests 55 | func (s *Service) TestAll(ctx context.Context, payload *genfront.TestAllPayload) (*genfront.TestResults, error) { 56 | return s.tc.TestAll(ctx, payload.Include, payload.Exclude) 57 | } 58 | 59 | // Runs API Integration Tests' Smoke Tests ONLY from the Tester service 60 | func (s *Service) TestSmoke(ctx context.Context) (*genfront.TestResults, error) { 61 | return s.tc.TestSmoke(ctx) 62 | } 63 | -------------------------------------------------------------------------------- /example/weather/services/front/gen/front/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // front client 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/front/design -o 7 | // services/front 8 | 9 | package front 10 | 11 | import ( 12 | "context" 13 | 14 | goa "goa.design/goa/v3/pkg" 15 | ) 16 | 17 | // Client is the "front" service client. 18 | type Client struct { 19 | ForecastEndpoint goa.Endpoint 20 | TestAllEndpoint goa.Endpoint 21 | TestSmokeEndpoint goa.Endpoint 22 | } 23 | 24 | // NewClient initializes a "front" service client given the endpoints. 25 | func NewClient(forecast, testAll, testSmoke goa.Endpoint) *Client { 26 | return &Client{ 27 | ForecastEndpoint: forecast, 28 | TestAllEndpoint: testAll, 29 | TestSmokeEndpoint: testSmoke, 30 | } 31 | } 32 | 33 | // Forecast calls the "forecast" endpoint of the "front" service. 34 | // Forecast may return the following errors: 35 | // - "not_usa" (type *goa.ServiceError): IP address is not in the US 36 | // - error: internal error 37 | func (c *Client) Forecast(ctx context.Context, p string) (res *Forecast2, err error) { 38 | var ires any 39 | ires, err = c.ForecastEndpoint(ctx, p) 40 | if err != nil { 41 | return 42 | } 43 | return ires.(*Forecast2), nil 44 | } 45 | 46 | // TestAll calls the "test_all" endpoint of the "front" service. 47 | func (c *Client) TestAll(ctx context.Context, p *TestAllPayload) (res *TestResults, err error) { 48 | var ires any 49 | ires, err = c.TestAllEndpoint(ctx, p) 50 | if err != nil { 51 | return 52 | } 53 | return ires.(*TestResults), nil 54 | } 55 | 56 | // TestSmoke calls the "test_smoke" endpoint of the "front" service. 57 | func (c *Client) TestSmoke(ctx context.Context) (res *TestResults, err error) { 58 | var ires any 59 | ires, err = c.TestSmokeEndpoint(ctx, nil) 60 | if err != nil { 61 | return 62 | } 63 | return ires.(*TestResults), nil 64 | } 65 | -------------------------------------------------------------------------------- /example/weather/services/front/gen/front/endpoints.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // front endpoints 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/front/design -o 7 | // services/front 8 | 9 | package front 10 | 11 | import ( 12 | "context" 13 | 14 | goa "goa.design/goa/v3/pkg" 15 | ) 16 | 17 | // Endpoints wraps the "front" service endpoints. 18 | type Endpoints struct { 19 | Forecast goa.Endpoint 20 | TestAll goa.Endpoint 21 | TestSmoke goa.Endpoint 22 | } 23 | 24 | // NewEndpoints wraps the methods of the "front" service with endpoints. 25 | func NewEndpoints(s Service) *Endpoints { 26 | return &Endpoints{ 27 | Forecast: NewForecastEndpoint(s), 28 | TestAll: NewTestAllEndpoint(s), 29 | TestSmoke: NewTestSmokeEndpoint(s), 30 | } 31 | } 32 | 33 | // Use applies the given middleware to all the "front" service endpoints. 34 | func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { 35 | e.Forecast = m(e.Forecast) 36 | e.TestAll = m(e.TestAll) 37 | e.TestSmoke = m(e.TestSmoke) 38 | } 39 | 40 | // NewForecastEndpoint returns an endpoint function that calls the method 41 | // "forecast" of service "front". 42 | func NewForecastEndpoint(s Service) goa.Endpoint { 43 | return func(ctx context.Context, req any) (any, error) { 44 | p := req.(string) 45 | return s.Forecast(ctx, p) 46 | } 47 | } 48 | 49 | // NewTestAllEndpoint returns an endpoint function that calls the method 50 | // "test_all" of service "front". 51 | func NewTestAllEndpoint(s Service) goa.Endpoint { 52 | return func(ctx context.Context, req any) (any, error) { 53 | p := req.(*TestAllPayload) 54 | return s.TestAll(ctx, p) 55 | } 56 | } 57 | 58 | // NewTestSmokeEndpoint returns an endpoint function that calls the method 59 | // "test_smoke" of service "front". 60 | func NewTestSmokeEndpoint(s Service) goa.Endpoint { 61 | return func(ctx context.Context, req any) (any, error) { 62 | return s.TestSmoke(ctx) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /example/weather/services/front/gen/http/front/client/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // front HTTP client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/front/design -o 7 | // services/front 8 | 9 | package client 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | 15 | front "goa.design/clue/example/weather/services/front/gen/front" 16 | ) 17 | 18 | // BuildTestAllPayload builds the payload for the front test_all endpoint from 19 | // CLI flags. 20 | func BuildTestAllPayload(frontTestAllBody string) (*front.TestAllPayload, error) { 21 | var err error 22 | var body TestAllRequestBody 23 | { 24 | err = json.Unmarshal([]byte(frontTestAllBody), &body) 25 | if err != nil { 26 | return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"exclude\": [\n \"Veritatis vel inventore voluptatem ab nulla.\",\n \"In optio sed id.\",\n \"Tenetur repellendus commodi asperiores.\",\n \"Quaerat omnis vel quia ab dolorem qui.\"\n ],\n \"include\": [\n \"Consectetur eos nihil accusamus reiciendis eligendi.\",\n \"Aut autem non.\",\n \"Aperiam possimus assumenda commodi aut facilis provident.\",\n \"Aspernatur voluptatem et placeat deserunt.\"\n ]\n }'") 27 | } 28 | } 29 | v := &front.TestAllPayload{} 30 | if body.Include != nil { 31 | v.Include = make([]string, len(body.Include)) 32 | for i, val := range body.Include { 33 | v.Include[i] = val 34 | } 35 | } 36 | if body.Exclude != nil { 37 | v.Exclude = make([]string, len(body.Exclude)) 38 | for i, val := range body.Exclude { 39 | v.Exclude[i] = val 40 | } 41 | } 42 | 43 | return v, nil 44 | } 45 | -------------------------------------------------------------------------------- /example/weather/services/front/gen/http/front/client/paths.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // HTTP request path constructors for the front service. 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/front/design -o 7 | // services/front 8 | 9 | package client 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // ForecastFrontPath returns the URL path to the front service forecast HTTP endpoint. 16 | func ForecastFrontPath(ip string) string { 17 | return fmt.Sprintf("/forecast/%v", ip) 18 | } 19 | 20 | // TestAllFrontPath returns the URL path to the front service test_all HTTP endpoint. 21 | func TestAllFrontPath() string { 22 | return "/tester/all" 23 | } 24 | 25 | // TestSmokeFrontPath returns the URL path to the front service test_smoke HTTP endpoint. 26 | func TestSmokeFrontPath() string { 27 | return "/tester/smoke" 28 | } 29 | -------------------------------------------------------------------------------- /example/weather/services/front/gen/http/front/server/paths.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // HTTP request path constructors for the front service. 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/front/design -o 7 | // services/front 8 | 9 | package server 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // ForecastFrontPath returns the URL path to the front service forecast HTTP endpoint. 16 | func ForecastFrontPath(ip string) string { 17 | return fmt.Sprintf("/forecast/%v", ip) 18 | } 19 | 20 | // TestAllFrontPath returns the URL path to the front service test_all HTTP endpoint. 21 | func TestAllFrontPath() string { 22 | return "/tester/all" 23 | } 24 | 25 | // TestSmokeFrontPath returns the URL path to the front service test_smoke HTTP endpoint. 26 | func TestSmokeFrontPath() string { 27 | return "/tester/smoke" 28 | } 29 | -------------------------------------------------------------------------------- /example/weather/services/locator/clients/ipapi/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator v0.18.2, DO NOT EDIT. 2 | // 3 | // Command: 4 | // $ cmg gen goa.design/clue/example/weather/services/locator/clients/ipapi 5 | 6 | package mockipapi 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "testing" 12 | 13 | "goa.design/clue/mock" 14 | 15 | "goa.design/clue/example/weather/services/locator/clients/ipapi" 16 | ) 17 | 18 | type ( 19 | Client struct { 20 | m *mock.Mock 21 | t *testing.T 22 | } 23 | 24 | ClientGetLocationFunc func(ctx context.Context, ip string) (*ipapi.WorldLocation, error) 25 | ClientNameFunc func() string 26 | ClientPingFunc func(ctx context.Context) error 27 | 28 | Doer struct { 29 | m *mock.Mock 30 | t *testing.T 31 | } 32 | 33 | DoerDoFunc func(req *http.Request) (*http.Response, error) 34 | ) 35 | 36 | func NewClient(t *testing.T) *Client { 37 | var ( 38 | m = &Client{mock.New(), t} 39 | _ ipapi.Client = m 40 | ) 41 | return m 42 | } 43 | 44 | func (m *Client) AddGetLocation(f ClientGetLocationFunc) { 45 | m.m.Add("GetLocation", f) 46 | } 47 | 48 | func (m *Client) SetGetLocation(f ClientGetLocationFunc) { 49 | m.m.Set("GetLocation", f) 50 | } 51 | 52 | func (m *Client) GetLocation(ctx context.Context, ip string) (*ipapi.WorldLocation, error) { 53 | if f := m.m.Next("GetLocation"); f != nil { 54 | return f.(ClientGetLocationFunc)(ctx, ip) 55 | } 56 | m.t.Helper() 57 | m.t.Error("unexpected GetLocation call") 58 | return nil, nil 59 | } 60 | 61 | func (m *Client) AddName(f ClientNameFunc) { 62 | m.m.Add("Name", f) 63 | } 64 | 65 | func (m *Client) SetName(f ClientNameFunc) { 66 | m.m.Set("Name", f) 67 | } 68 | 69 | func (m *Client) Name() string { 70 | if f := m.m.Next("Name"); f != nil { 71 | return f.(ClientNameFunc)() 72 | } 73 | m.t.Helper() 74 | m.t.Error("unexpected Name call") 75 | return "" 76 | } 77 | 78 | func (m *Client) AddPing(f ClientPingFunc) { 79 | m.m.Add("Ping", f) 80 | } 81 | 82 | func (m *Client) SetPing(f ClientPingFunc) { 83 | m.m.Set("Ping", f) 84 | } 85 | 86 | func (m *Client) Ping(ctx context.Context) error { 87 | if f := m.m.Next("Ping"); f != nil { 88 | return f.(ClientPingFunc)(ctx) 89 | } 90 | m.t.Helper() 91 | m.t.Error("unexpected Ping call") 92 | return nil 93 | } 94 | 95 | func (m *Client) HasMore() bool { 96 | return m.m.HasMore() 97 | } 98 | 99 | func NewDoer(t *testing.T) *Doer { 100 | var ( 101 | m = &Doer{mock.New(), t} 102 | _ ipapi.Doer = m 103 | ) 104 | return m 105 | } 106 | 107 | func (m *Doer) AddDo(f DoerDoFunc) { 108 | m.m.Add("Do", f) 109 | } 110 | 111 | func (m *Doer) SetDo(f DoerDoFunc) { 112 | m.m.Set("Do", f) 113 | } 114 | 115 | func (m *Doer) Do(req *http.Request) (*http.Response, error) { 116 | if f := m.m.Next("Do"); f != nil { 117 | return f.(DoerDoFunc)(req) 118 | } 119 | m.t.Helper() 120 | m.t.Error("unexpected Do call") 121 | return nil, nil 122 | } 123 | 124 | func (m *Doer) HasMore() bool { 125 | return m.m.HasMore() 126 | } 127 | -------------------------------------------------------------------------------- /example/weather/services/locator/design/design.go: -------------------------------------------------------------------------------- 1 | package design 2 | 3 | import ( 4 | . "goa.design/goa/v3/dsl" 5 | ) 6 | 7 | var _ = API("IP Location API", func() { 8 | Title("IP Location Service API") 9 | Description("A fully instrumented IP location service API") 10 | Version("1.0.0") 11 | }) 12 | 13 | var _ = Service("locator", func() { 14 | Description("Public HTTP frontend") 15 | 16 | Method("get_location", func() { 17 | Description("Retrieve location information for a given IP address") 18 | Payload(String, "IP address", func() { 19 | Format(FormatIP) 20 | }) 21 | Result(WorldLocation) 22 | GRPC(func() {}) 23 | }) 24 | }) 25 | 26 | var WorldLocation = Type("WorldLocation", func() { 27 | Description("Geographical location") 28 | Field(1, "lat", Float64, "Latitude", func() { 29 | Example(37.8267) 30 | }) 31 | Field(2, "long", Float64, "Longitude", func() { 32 | Example(-122.4233) 33 | }) 34 | Field(3, "city", String, "City", func() { 35 | Example("San Francisco") 36 | }) 37 | Field(4, "region", String, "State, region etc.", func() { 38 | Example("CA") 39 | }) 40 | Field(5, "country", String, "Country", func() { 41 | Example("USA") 42 | }) 43 | Required("lat", "long", "city", "region", "country") 44 | }) 45 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/client/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator gRPC client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package client 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | 15 | locatorpb "goa.design/clue/example/weather/services/locator/gen/grpc/locator/pb" 16 | ) 17 | 18 | // BuildGetLocationPayload builds the payload for the locator get_location 19 | // endpoint from CLI flags. 20 | func BuildGetLocationPayload(locatorGetLocationMessage string) (string, error) { 21 | var err error 22 | var message locatorpb.GetLocationRequest 23 | { 24 | if locatorGetLocationMessage != "" { 25 | err = json.Unmarshal([]byte(locatorGetLocationMessage), &message) 26 | if err != nil { 27 | var zero string 28 | return zero, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"field\": \"Perspiciatis vel blanditiis tenetur blanditiis.\"\n }'") 29 | } 30 | } 31 | } 32 | v := message.Field 33 | return v, nil 34 | } 35 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/client/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator gRPC client 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package client 10 | 11 | import ( 12 | "context" 13 | 14 | locatorpb "goa.design/clue/example/weather/services/locator/gen/grpc/locator/pb" 15 | goagrpc "goa.design/goa/v3/grpc" 16 | goa "goa.design/goa/v3/pkg" 17 | "google.golang.org/grpc" 18 | ) 19 | 20 | // Client lists the service endpoint gRPC clients. 21 | type Client struct { 22 | grpccli locatorpb.LocatorClient 23 | opts []grpc.CallOption 24 | } // NewClient instantiates gRPC client for all the locator service servers. 25 | func NewClient(cc *grpc.ClientConn, opts ...grpc.CallOption) *Client { 26 | return &Client{ 27 | grpccli: locatorpb.NewLocatorClient(cc), 28 | opts: opts, 29 | } 30 | } // GetLocation calls the "GetLocation" function in locatorpb.LocatorClient 31 | // interface. 32 | func (c *Client) GetLocation() goa.Endpoint { 33 | return func(ctx context.Context, v any) (any, error) { 34 | inv := goagrpc.NewInvoker( 35 | BuildGetLocationFunc(c.grpccli, c.opts...), 36 | EncodeGetLocationRequest, 37 | DecodeGetLocationResponse) 38 | res, err := inv.Invoke(ctx, v) 39 | if err != nil { 40 | return nil, goa.Fault("%s", err.Error()) 41 | } 42 | return res, nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/client/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator gRPC client encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package client 10 | 11 | import ( 12 | "context" 13 | 14 | locatorpb "goa.design/clue/example/weather/services/locator/gen/grpc/locator/pb" 15 | goagrpc "goa.design/goa/v3/grpc" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/metadata" 18 | ) 19 | 20 | // BuildGetLocationFunc builds the remote method to invoke for "locator" 21 | // service "get_location" endpoint. 22 | func BuildGetLocationFunc(grpccli locatorpb.LocatorClient, cliopts ...grpc.CallOption) goagrpc.RemoteFunc { 23 | return func(ctx context.Context, reqpb any, opts ...grpc.CallOption) (any, error) { 24 | for _, opt := range cliopts { 25 | opts = append(opts, opt) 26 | } 27 | if reqpb != nil { 28 | return grpccli.GetLocation(ctx, reqpb.(*locatorpb.GetLocationRequest), opts...) 29 | } 30 | return grpccli.GetLocation(ctx, &locatorpb.GetLocationRequest{}, opts...) 31 | } 32 | } 33 | 34 | // EncodeGetLocationRequest encodes requests sent to locator get_location 35 | // endpoint. 36 | func EncodeGetLocationRequest(ctx context.Context, v any, md *metadata.MD) (any, error) { 37 | payload, ok := v.(string) 38 | if !ok { 39 | return nil, goagrpc.ErrInvalidType("locator", "get_location", "string", v) 40 | } 41 | return NewProtoGetLocationRequest(payload), nil 42 | } 43 | 44 | // DecodeGetLocationResponse decodes responses from the locator get_location 45 | // endpoint. 46 | func DecodeGetLocationResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { 47 | message, ok := v.(*locatorpb.GetLocationResponse) 48 | if !ok { 49 | return nil, goagrpc.ErrInvalidType("locator", "get_location", "*locatorpb.GetLocationResponse", v) 50 | } 51 | res := NewGetLocationResult(message) 52 | return res, nil 53 | } 54 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/client/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator gRPC client types 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package client 10 | 11 | import ( 12 | locatorpb "goa.design/clue/example/weather/services/locator/gen/grpc/locator/pb" 13 | locator "goa.design/clue/example/weather/services/locator/gen/locator" 14 | ) 15 | 16 | // NewProtoGetLocationRequest builds the gRPC request type from the payload of 17 | // the "get_location" endpoint of the "locator" service. 18 | func NewProtoGetLocationRequest(payload string) *locatorpb.GetLocationRequest { 19 | message := &locatorpb.GetLocationRequest{} 20 | message.Field = payload 21 | return message 22 | } 23 | 24 | // NewGetLocationResult builds the result type of the "get_location" endpoint 25 | // of the "locator" service from the gRPC response type. 26 | func NewGetLocationResult(message *locatorpb.GetLocationResponse) *locator.WorldLocation { 27 | result := &locator.WorldLocation{ 28 | Lat: message.Lat, 29 | Long: message.Long, 30 | City: message.City, 31 | Region: message.Region, 32 | Country: message.Country, 33 | } 34 | return result 35 | } 36 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.proto: -------------------------------------------------------------------------------- 1 | // Code generated with goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator protocol buffer definition 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | syntax = "proto3"; 10 | 11 | package locator; 12 | 13 | option go_package = "/locatorpb"; 14 | 15 | // Public HTTP frontend 16 | service Locator { 17 | // Retrieve location information for a given IP address 18 | rpc GetLocation (GetLocationRequest) returns (GetLocationResponse); 19 | } 20 | 21 | message GetLocationRequest { 22 | string field = 1; 23 | } 24 | 25 | message GetLocationResponse { 26 | // Latitude 27 | double lat = 1; 28 | // Longitude 29 | double long = 2; 30 | // City 31 | string city = 3; 32 | // State, region etc. 33 | string region = 4; 34 | // Country 35 | string country = 5; 36 | } 37 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/server/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator gRPC server encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package server 10 | 11 | import ( 12 | "context" 13 | 14 | locatorpb "goa.design/clue/example/weather/services/locator/gen/grpc/locator/pb" 15 | locator "goa.design/clue/example/weather/services/locator/gen/locator" 16 | goagrpc "goa.design/goa/v3/grpc" 17 | "google.golang.org/grpc/metadata" 18 | ) 19 | 20 | // EncodeGetLocationResponse encodes responses from the "locator" service 21 | // "get_location" endpoint. 22 | func EncodeGetLocationResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 23 | result, ok := v.(*locator.WorldLocation) 24 | if !ok { 25 | return nil, goagrpc.ErrInvalidType("locator", "get_location", "*locator.WorldLocation", v) 26 | } 27 | resp := NewProtoGetLocationResponse(result) 28 | return resp, nil 29 | } 30 | 31 | // DecodeGetLocationRequest decodes requests sent to "locator" service 32 | // "get_location" endpoint. 33 | func DecodeGetLocationRequest(ctx context.Context, v any, md metadata.MD) (any, error) { 34 | var ( 35 | message *locatorpb.GetLocationRequest 36 | ok bool 37 | ) 38 | { 39 | if message, ok = v.(*locatorpb.GetLocationRequest); !ok { 40 | return nil, goagrpc.ErrInvalidType("locator", "get_location", "*locatorpb.GetLocationRequest", v) 41 | } 42 | } 43 | var payload string 44 | { 45 | payload = NewGetLocationPayload(message) 46 | } 47 | return payload, nil 48 | } 49 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/server/server.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator gRPC server 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package server 10 | 11 | import ( 12 | "context" 13 | 14 | locatorpb "goa.design/clue/example/weather/services/locator/gen/grpc/locator/pb" 15 | locator "goa.design/clue/example/weather/services/locator/gen/locator" 16 | goagrpc "goa.design/goa/v3/grpc" 17 | goa "goa.design/goa/v3/pkg" 18 | ) 19 | 20 | // Server implements the locatorpb.LocatorServer interface. 21 | type Server struct { 22 | GetLocationH goagrpc.UnaryHandler 23 | locatorpb.UnimplementedLocatorServer 24 | } 25 | 26 | // New instantiates the server struct with the locator service endpoints. 27 | func New(e *locator.Endpoints, uh goagrpc.UnaryHandler) *Server { 28 | return &Server{ 29 | GetLocationH: NewGetLocationHandler(e.GetLocation, uh), 30 | } 31 | } 32 | 33 | // NewGetLocationHandler creates a gRPC handler which serves the "locator" 34 | // service "get_location" endpoint. 35 | func NewGetLocationHandler(endpoint goa.Endpoint, h goagrpc.UnaryHandler) goagrpc.UnaryHandler { 36 | if h == nil { 37 | h = goagrpc.NewUnaryHandler(endpoint, DecodeGetLocationRequest, EncodeGetLocationResponse) 38 | } 39 | return h 40 | } 41 | 42 | // GetLocation implements the "GetLocation" method in locatorpb.LocatorServer 43 | // interface. 44 | func (s *Server) GetLocation(ctx context.Context, message *locatorpb.GetLocationRequest) (*locatorpb.GetLocationResponse, error) { 45 | ctx = context.WithValue(ctx, goa.MethodKey, "get_location") 46 | ctx = context.WithValue(ctx, goa.ServiceKey, "locator") 47 | resp, err := s.GetLocationH.Handle(ctx, message) 48 | if err != nil { 49 | return nil, goagrpc.EncodeError(err) 50 | } 51 | return resp.(*locatorpb.GetLocationResponse), nil 52 | } 53 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/grpc/locator/server/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator gRPC server types 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package server 10 | 11 | import ( 12 | locatorpb "goa.design/clue/example/weather/services/locator/gen/grpc/locator/pb" 13 | locator "goa.design/clue/example/weather/services/locator/gen/locator" 14 | ) 15 | 16 | // NewGetLocationPayload builds the payload of the "get_location" endpoint of 17 | // the "locator" service from the gRPC request type. 18 | func NewGetLocationPayload(message *locatorpb.GetLocationRequest) string { 19 | v := message.Field 20 | return v 21 | } 22 | 23 | // NewProtoGetLocationResponse builds the gRPC response type from the result of 24 | // the "get_location" endpoint of the "locator" service. 25 | func NewProtoGetLocationResponse(result *locator.WorldLocation) *locatorpb.GetLocationResponse { 26 | message := &locatorpb.GetLocationResponse{ 27 | Lat: result.Lat, 28 | Long: result.Long, 29 | City: result.City, 30 | Region: result.Region, 31 | Country: result.Country, 32 | } 33 | return message 34 | } 35 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/locator/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator client 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package locator 10 | 11 | import ( 12 | "context" 13 | 14 | goa "goa.design/goa/v3/pkg" 15 | ) 16 | 17 | // Client is the "locator" service client. 18 | type Client struct { 19 | GetLocationEndpoint goa.Endpoint 20 | } 21 | 22 | // NewClient initializes a "locator" service client given the endpoints. 23 | func NewClient(getLocation goa.Endpoint) *Client { 24 | return &Client{ 25 | GetLocationEndpoint: getLocation, 26 | } 27 | } 28 | 29 | // GetLocation calls the "get_location" endpoint of the "locator" service. 30 | func (c *Client) GetLocation(ctx context.Context, p string) (res *WorldLocation, err error) { 31 | var ires any 32 | ires, err = c.GetLocationEndpoint(ctx, p) 33 | if err != nil { 34 | return 35 | } 36 | return ires.(*WorldLocation), nil 37 | } 38 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/locator/endpoints.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator endpoints 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package locator 10 | 11 | import ( 12 | "context" 13 | 14 | goa "goa.design/goa/v3/pkg" 15 | ) 16 | 17 | // Endpoints wraps the "locator" service endpoints. 18 | type Endpoints struct { 19 | GetLocation goa.Endpoint 20 | } 21 | 22 | // NewEndpoints wraps the methods of the "locator" service with endpoints. 23 | func NewEndpoints(s Service) *Endpoints { 24 | return &Endpoints{ 25 | GetLocation: NewGetLocationEndpoint(s), 26 | } 27 | } 28 | 29 | // Use applies the given middleware to all the "locator" service endpoints. 30 | func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { 31 | e.GetLocation = m(e.GetLocation) 32 | } 33 | 34 | // NewGetLocationEndpoint returns an endpoint function that calls the method 35 | // "get_location" of service "locator". 36 | func NewGetLocationEndpoint(s Service) goa.Endpoint { 37 | return func(ctx context.Context, req any) (any, error) { 38 | p := req.(string) 39 | return s.GetLocation(ctx, p) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/weather/services/locator/gen/locator/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // locator service 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/locator/design -o 7 | // services/locator 8 | 9 | package locator 10 | 11 | import ( 12 | "context" 13 | ) 14 | 15 | // Public HTTP frontend 16 | type Service interface { 17 | // Retrieve location information for a given IP address 18 | GetLocation(context.Context, string) (res *WorldLocation, err error) 19 | } 20 | 21 | // APIName is the name of the API as defined in the design. 22 | const APIName = "IP Location API" 23 | 24 | // APIVersion is the version of the API as defined in the design. 25 | const APIVersion = "1.0.0" 26 | 27 | // ServiceName is the name of the service as defined in the design. This is the 28 | // same value that is set in the endpoint request contexts under the ServiceKey 29 | // key. 30 | const ServiceName = "locator" 31 | 32 | // MethodNames lists the service method names as defined in the design. These 33 | // are the same values that are set in the endpoint request contexts under the 34 | // MethodKey key. 35 | var MethodNames = [1]string{"get_location"} 36 | 37 | // WorldLocation is the result type of the locator service get_location method. 38 | type WorldLocation struct { 39 | // Latitude 40 | Lat float64 41 | // Longitude 42 | Long float64 43 | // City 44 | City string 45 | // State, region etc. 46 | Region string 47 | // Country 48 | Country string 49 | } 50 | -------------------------------------------------------------------------------- /example/weather/services/locator/locator.go: -------------------------------------------------------------------------------- 1 | package locator 2 | 3 | import ( 4 | "context" 5 | 6 | "goa.design/clue/example/weather/services/locator/clients/ipapi" 7 | genlocator "goa.design/clue/example/weather/services/locator/gen/locator" 8 | ) 9 | 10 | type ( 11 | // Service is the locator service implementation. 12 | Service struct { 13 | ipc ipapi.Client 14 | } 15 | ) 16 | 17 | // New instantiates a new locator service. 18 | func New(ipc ipapi.Client) *Service { 19 | return &Service{ipc: ipc} 20 | } 21 | 22 | // GetLocation returns the location for the given IP address. 23 | func (s *Service) GetLocation(ctx context.Context, ip string) (*genlocator.WorldLocation, error) { 24 | l, err := s.ipc.GetLocation(ctx, ip) 25 | if err != nil { 26 | return nil, err 27 | } 28 | lval := genlocator.WorldLocation(*l) 29 | return &lval, nil 30 | } 31 | -------------------------------------------------------------------------------- /example/weather/services/locator/locator_test.go: -------------------------------------------------------------------------------- 1 | package locator 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "goa.design/clue/example/weather/services/locator/clients/ipapi" 9 | mockipapi "goa.design/clue/example/weather/services/locator/clients/ipapi/mocks" 10 | ) 11 | 12 | func TestGetIPLocation(t *testing.T) { 13 | var ( 14 | testLat = 37.4224764 15 | testLong = -122.0842499 16 | testCity = "Mountain View" 17 | testRegion = "CA" 18 | testCountry = "United States" 19 | ) 20 | // Create mock call sequence with first successful call returning an IP 21 | // location then failing. 22 | ipc := mockipapi.NewClient(t) 23 | ipc.AddGetLocation(func(ctx context.Context, ip string) (*ipapi.WorldLocation, error) { 24 | return &ipapi.WorldLocation{Lat: testLat, Long: testLong, City: testCity, Region: testRegion, Country: testCountry}, nil 25 | }) 26 | ipc.AddGetLocation(func(ctx context.Context, ip string) (*ipapi.WorldLocation, error) { 27 | return nil, fmt.Errorf("test failure") 28 | }) 29 | 30 | // Create locator service. 31 | s := New(ipc) 32 | 33 | // Call service, first call should succeed. 34 | l, err := s.GetLocation(context.Background(), "8.8.8.8") 35 | if err != nil { 36 | t.Errorf("got error %v, expected nil", err) 37 | } 38 | if l.Lat != testLat { 39 | t.Errorf("got lat %v, expected %f", l.Lat, testLat) 40 | } 41 | if l.Long != testLong { 42 | t.Errorf("got long %v, expected %f", l.Long, testLong) 43 | } 44 | if l.City != testCity { 45 | t.Errorf("got city %q, expected %q", l.City, testCity) 46 | } 47 | if l.Region != testRegion { 48 | t.Errorf("got region code %q, expected %q", l.Region, testRegion) 49 | } 50 | 51 | // Call service, second call should fail. 52 | _, err = s.GetLocation(context.Background(), "8.8.8.8") 53 | if err == nil { 54 | t.Errorf("got nil, expected error") 55 | } 56 | 57 | // Make sure all calls were made. 58 | if ipc.HasMore() { 59 | t.Errorf("expected all calls to be made") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/weather/services/tester/clients/forecaster/client.go: -------------------------------------------------------------------------------- 1 | package forecaster 2 | 3 | import ( 4 | "context" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | 9 | "goa.design/clue/debug" 10 | genforecast "goa.design/clue/example/weather/services/forecaster/gen/forecaster" 11 | genclient "goa.design/clue/example/weather/services/forecaster/gen/grpc/forecaster/client" 12 | ) 13 | 14 | type ( 15 | // Client is a client for the forecast service. 16 | Client interface { 17 | // GetForecast gets the forecast for the given location. 18 | GetForecast(ctx context.Context, lat, long float64) (*Forecast, error) 19 | } 20 | 21 | // Forecast represents the forecast for a given location. 22 | Forecast struct { 23 | // Location is the location of the forecast. 24 | Location *Location 25 | // Periods is the forecast for the location. 26 | Periods []*Period 27 | } 28 | 29 | // Location represents the geographical location of a forecast. 30 | Location struct { 31 | // Lat is the latitude of the location. 32 | Lat float64 33 | // Long is the longitude of the location. 34 | Long float64 35 | // City is the city of the location. 36 | City string 37 | // State is the state of the location. 38 | State string 39 | } 40 | 41 | // Period represents a forecast period. 42 | Period struct { 43 | // Name is the name of the forecast period. 44 | Name string 45 | // StartTime is the start time of the forecast period in RFC3339 format. 46 | StartTime string 47 | // EndTime is the end time of the forecast period in RFC3339 format. 48 | EndTime string 49 | // Temperature is the temperature of the forecast period. 50 | Temperature int 51 | // TemperatureUnit is the temperature unit of the forecast period. 52 | TemperatureUnit string 53 | // Summary is the summary of the forecast period. 54 | Summary string 55 | } 56 | 57 | // client is the client implementation. 58 | client struct { 59 | forecast goa.Endpoint 60 | } 61 | ) 62 | 63 | // New instantiates a new forecast service client. 64 | func New(cc *grpc.ClientConn) Client { 65 | c := genclient.NewClient(cc, grpc.WaitForReady(true)) 66 | return &client{debug.LogPayloads(debug.WithClient())(c.Forecast())} 67 | } 68 | 69 | // Forecast returns the forecast for the given location or current location if 70 | // lat or long are nil. 71 | func (c *client) GetForecast(ctx context.Context, lat, long float64) (*Forecast, error) { 72 | res, err := c.forecast(ctx, &genforecast.ForecastPayload{Lat: lat, Long: long}) 73 | if err != nil { 74 | return nil, err 75 | } 76 | f := res.(*genforecast.Forecast2) 77 | l := Location(*f.Location) 78 | ps := make([]*Period, len(f.Periods)) 79 | for i, p := range f.Periods { 80 | pval := Period(*p) 81 | ps[i] = &pval 82 | } 83 | return &Forecast{&l, ps}, nil 84 | } 85 | -------------------------------------------------------------------------------- /example/weather/services/tester/clients/forecaster/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator v0.18.2, DO NOT EDIT. 2 | // 3 | // Command: 4 | // $ cmg gen goa.design/clue/example/weather/services/tester/clients/forecaster 5 | 6 | package mockforecaster 7 | 8 | import ( 9 | "context" 10 | "testing" 11 | 12 | "goa.design/clue/mock" 13 | 14 | "goa.design/clue/example/weather/services/tester/clients/forecaster" 15 | ) 16 | 17 | type ( 18 | Client struct { 19 | m *mock.Mock 20 | t *testing.T 21 | } 22 | 23 | ClientGetForecastFunc func(ctx context.Context, lat, long float64) (*forecaster.Forecast, error) 24 | ) 25 | 26 | func NewClient(t *testing.T) *Client { 27 | var ( 28 | m = &Client{mock.New(), t} 29 | _ forecaster.Client = m 30 | ) 31 | return m 32 | } 33 | 34 | func (m *Client) AddGetForecast(f ClientGetForecastFunc) { 35 | m.m.Add("GetForecast", f) 36 | } 37 | 38 | func (m *Client) SetGetForecast(f ClientGetForecastFunc) { 39 | m.m.Set("GetForecast", f) 40 | } 41 | 42 | func (m *Client) GetForecast(ctx context.Context, lat, long float64) (*forecaster.Forecast, error) { 43 | if f := m.m.Next("GetForecast"); f != nil { 44 | return f.(ClientGetForecastFunc)(ctx, lat, long) 45 | } 46 | m.t.Helper() 47 | m.t.Error("unexpected GetForecast call") 48 | return nil, nil 49 | } 50 | 51 | func (m *Client) HasMore() bool { 52 | return m.m.HasMore() 53 | } 54 | -------------------------------------------------------------------------------- /example/weather/services/tester/clients/locator/client.go: -------------------------------------------------------------------------------- 1 | package locator 2 | 3 | import ( 4 | "context" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | 9 | "goa.design/clue/debug" 10 | genclient "goa.design/clue/example/weather/services/locator/gen/grpc/locator/client" 11 | genlocator "goa.design/clue/example/weather/services/locator/gen/locator" 12 | ) 13 | 14 | type ( 15 | // Client is a client for the locator service. 16 | Client interface { 17 | // GetLocation gets the location for the given ip 18 | GetLocation(ctx context.Context, ip string) (*WorldLocation, error) 19 | } 20 | 21 | // WorldLocation represents the location for the given IP address. 22 | WorldLocation struct { 23 | // Lat is the latitude of the location. 24 | Lat float64 25 | // Long is the longitude of the location. 26 | Long float64 27 | // City is the city of the location. 28 | City string 29 | // Region is the region/state of the location. 30 | Region string 31 | // Country is the country of the location. 32 | Country string 33 | } 34 | 35 | // client is the client implementation. 36 | client struct { 37 | locator goa.Endpoint 38 | } 39 | ) 40 | 41 | // New instantiates a new locator service client. 42 | func New(cc *grpc.ClientConn) Client { 43 | c := genclient.NewClient(cc, grpc.WaitForReady(true)) 44 | return &client{debug.LogPayloads(debug.WithClient())(c.GetLocation())} 45 | } 46 | 47 | // GetLocation returns the location for the given IP address. 48 | func (c *client) GetLocation(ctx context.Context, ip string) (*WorldLocation, error) { 49 | res, err := c.locator(ctx, ip) 50 | if err != nil { 51 | return nil, err 52 | } 53 | ipl := res.(*genlocator.WorldLocation) 54 | l := WorldLocation(*ipl) 55 | return &l, nil 56 | } 57 | -------------------------------------------------------------------------------- /example/weather/services/tester/clients/locator/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator v0.18.2, DO NOT EDIT. 2 | // 3 | // Command: 4 | // $ cmg gen goa.design/clue/example/weather/services/tester/clients/locator 5 | 6 | package mocklocator 7 | 8 | import ( 9 | "context" 10 | "testing" 11 | 12 | "goa.design/clue/mock" 13 | 14 | "goa.design/clue/example/weather/services/tester/clients/locator" 15 | ) 16 | 17 | type ( 18 | Client struct { 19 | m *mock.Mock 20 | t *testing.T 21 | } 22 | 23 | ClientGetLocationFunc func(ctx context.Context, ip string) (*locator.WorldLocation, error) 24 | ) 25 | 26 | func NewClient(t *testing.T) *Client { 27 | var ( 28 | m = &Client{mock.New(), t} 29 | _ locator.Client = m 30 | ) 31 | return m 32 | } 33 | 34 | func (m *Client) AddGetLocation(f ClientGetLocationFunc) { 35 | m.m.Add("GetLocation", f) 36 | } 37 | 38 | func (m *Client) SetGetLocation(f ClientGetLocationFunc) { 39 | m.m.Set("GetLocation", f) 40 | } 41 | 42 | func (m *Client) GetLocation(ctx context.Context, ip string) (*locator.WorldLocation, error) { 43 | if f := m.m.Next("GetLocation"); f != nil { 44 | return f.(ClientGetLocationFunc)(ctx, ip) 45 | } 46 | m.t.Helper() 47 | m.t.Error("unexpected GetLocation call") 48 | return nil, nil 49 | } 50 | 51 | func (m *Client) HasMore() bool { 52 | return m.m.HasMore() 53 | } 54 | -------------------------------------------------------------------------------- /example/weather/services/tester/gen/grpc/tester/client/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // tester gRPC client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/tester/design -o 7 | // services/tester 8 | 9 | package client 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | 15 | testerpb "goa.design/clue/example/weather/services/tester/gen/grpc/tester/pb" 16 | tester "goa.design/clue/example/weather/services/tester/gen/tester" 17 | ) 18 | 19 | // BuildTestAllPayload builds the payload for the tester test_all endpoint from 20 | // CLI flags. 21 | func BuildTestAllPayload(testerTestAllMessage string) (*tester.TesterPayload, error) { 22 | var err error 23 | var message testerpb.TestAllRequest 24 | { 25 | if testerTestAllMessage != "" { 26 | err = json.Unmarshal([]byte(testerTestAllMessage), &message) 27 | if err != nil { 28 | return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"Exclude\": [\n \"TestNameToExclude\"\n ],\n \"Include\": [\n \"TestNameToInclude\"\n ]\n }'") 29 | } 30 | } 31 | } 32 | v := &tester.TesterPayload{} 33 | if message.Include != nil { 34 | v.Include = make([]string, len(message.Include)) 35 | for i, val := range message.Include { 36 | v.Include[i] = val 37 | } 38 | } 39 | if message.Exclude != nil { 40 | v.Exclude = make([]string, len(message.Exclude)) 41 | for i, val := range message.Exclude { 42 | v.Exclude[i] = val 43 | } 44 | } 45 | 46 | return v, nil 47 | } 48 | -------------------------------------------------------------------------------- /example/weather/services/tester/gen/grpc/tester/server/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // tester gRPC server encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/tester/design -o 7 | // services/tester 8 | 9 | package server 10 | 11 | import ( 12 | "context" 13 | 14 | testerpb "goa.design/clue/example/weather/services/tester/gen/grpc/tester/pb" 15 | tester "goa.design/clue/example/weather/services/tester/gen/tester" 16 | goagrpc "goa.design/goa/v3/grpc" 17 | "google.golang.org/grpc/metadata" 18 | ) 19 | 20 | // EncodeTestAllResponse encodes responses from the "tester" service "test_all" 21 | // endpoint. 22 | func EncodeTestAllResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 23 | result, ok := v.(*tester.TestResults) 24 | if !ok { 25 | return nil, goagrpc.ErrInvalidType("tester", "test_all", "*tester.TestResults", v) 26 | } 27 | resp := NewProtoTestAllResponse(result) 28 | return resp, nil 29 | } 30 | 31 | // DecodeTestAllRequest decodes requests sent to "tester" service "test_all" 32 | // endpoint. 33 | func DecodeTestAllRequest(ctx context.Context, v any, md metadata.MD) (any, error) { 34 | var ( 35 | message *testerpb.TestAllRequest 36 | ok bool 37 | ) 38 | { 39 | if message, ok = v.(*testerpb.TestAllRequest); !ok { 40 | return nil, goagrpc.ErrInvalidType("tester", "test_all", "*testerpb.TestAllRequest", v) 41 | } 42 | } 43 | var payload *tester.TesterPayload 44 | { 45 | payload = NewTestAllPayload(message) 46 | } 47 | return payload, nil 48 | } 49 | 50 | // EncodeTestSmokeResponse encodes responses from the "tester" service 51 | // "test_smoke" endpoint. 52 | func EncodeTestSmokeResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 53 | result, ok := v.(*tester.TestResults) 54 | if !ok { 55 | return nil, goagrpc.ErrInvalidType("tester", "test_smoke", "*tester.TestResults", v) 56 | } 57 | resp := NewProtoTestSmokeResponse(result) 58 | return resp, nil 59 | } 60 | 61 | // EncodeTestForecasterResponse encodes responses from the "tester" service 62 | // "test_forecaster" endpoint. 63 | func EncodeTestForecasterResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 64 | result, ok := v.(*tester.TestResults) 65 | if !ok { 66 | return nil, goagrpc.ErrInvalidType("tester", "test_forecaster", "*tester.TestResults", v) 67 | } 68 | resp := NewProtoTestForecasterResponse(result) 69 | return resp, nil 70 | } 71 | 72 | // EncodeTestLocatorResponse encodes responses from the "tester" service 73 | // "test_locator" endpoint. 74 | func EncodeTestLocatorResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 75 | result, ok := v.(*tester.TestResults) 76 | if !ok { 77 | return nil, goagrpc.ErrInvalidType("tester", "test_locator", "*tester.TestResults", v) 78 | } 79 | resp := NewProtoTestLocatorResponse(result) 80 | return resp, nil 81 | } 82 | -------------------------------------------------------------------------------- /example/weather/services/tester/gen/tester/endpoints.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // tester endpoints 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/example/weather/services/tester/design -o 7 | // services/tester 8 | 9 | package tester 10 | 11 | import ( 12 | "context" 13 | 14 | goa "goa.design/goa/v3/pkg" 15 | ) 16 | 17 | // Endpoints wraps the "tester" service endpoints. 18 | type Endpoints struct { 19 | TestAll goa.Endpoint 20 | TestSmoke goa.Endpoint 21 | TestForecaster goa.Endpoint 22 | TestLocator goa.Endpoint 23 | } 24 | 25 | // NewEndpoints wraps the methods of the "tester" service with endpoints. 26 | func NewEndpoints(s Service) *Endpoints { 27 | return &Endpoints{ 28 | TestAll: NewTestAllEndpoint(s), 29 | TestSmoke: NewTestSmokeEndpoint(s), 30 | TestForecaster: NewTestForecasterEndpoint(s), 31 | TestLocator: NewTestLocatorEndpoint(s), 32 | } 33 | } 34 | 35 | // Use applies the given middleware to all the "tester" service endpoints. 36 | func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { 37 | e.TestAll = m(e.TestAll) 38 | e.TestSmoke = m(e.TestSmoke) 39 | e.TestForecaster = m(e.TestForecaster) 40 | e.TestLocator = m(e.TestLocator) 41 | } 42 | 43 | // NewTestAllEndpoint returns an endpoint function that calls the method 44 | // "test_all" of service "tester". 45 | func NewTestAllEndpoint(s Service) goa.Endpoint { 46 | return func(ctx context.Context, req any) (any, error) { 47 | p := req.(*TesterPayload) 48 | return s.TestAll(ctx, p) 49 | } 50 | } 51 | 52 | // NewTestSmokeEndpoint returns an endpoint function that calls the method 53 | // "test_smoke" of service "tester". 54 | func NewTestSmokeEndpoint(s Service) goa.Endpoint { 55 | return func(ctx context.Context, req any) (any, error) { 56 | return s.TestSmoke(ctx) 57 | } 58 | } 59 | 60 | // NewTestForecasterEndpoint returns an endpoint function that calls the method 61 | // "test_forecaster" of service "tester". 62 | func NewTestForecasterEndpoint(s Service) goa.Endpoint { 63 | return func(ctx context.Context, req any) (any, error) { 64 | return s.TestForecaster(ctx) 65 | } 66 | } 67 | 68 | // NewTestLocatorEndpoint returns an endpoint function that calls the method 69 | // "test_locator" of service "tester". 70 | func NewTestLocatorEndpoint(s Service) goa.Endpoint { 71 | return func(ctx context.Context, req any) (any, error) { 72 | return s.TestLocator(ctx) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /example/weather/services/tester/locator_integration.go: -------------------------------------------------------------------------------- 1 | package tester 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | gentester "goa.design/clue/example/weather/services/tester/gen/tester" 9 | ) 10 | 11 | func (svc *Service) TestLocatorValidIP(ctx context.Context, tc *TestCollection) { 12 | results := []*gentester.TestResult{} 13 | start := time.Now() 14 | name := "TestLocatorValidIP" 15 | passed := true 16 | testRes := gentester.TestResult{ 17 | Name: name, 18 | Passed: passed, 19 | } 20 | // Start Test Logic 21 | locatorReturn, err := svc.lc.GetLocation(ctx, "8.8.8.8") 22 | if err != nil { 23 | testRes.Passed = false 24 | errorDescription := fmt.Sprintf("error getting location for valid ip: %v", err) 25 | testRes.Error = &errorDescription 26 | endTest(&testRes, start, tc, results) 27 | return 28 | } 29 | if locatorReturn == nil { 30 | testRes.Passed = false 31 | errorDescription := fmt.Sprintf("location for valid ip is nil: %v", err) 32 | testRes.Error = &errorDescription 33 | endTest(&testRes, start, tc, results) 34 | return 35 | } 36 | // End Test Logic 37 | endTest(&testRes, start, tc, results) 38 | } 39 | 40 | func (svc *Service) TestLocatorInvalidIP(ctx context.Context, tc *TestCollection) { 41 | results := []*gentester.TestResult{} 42 | start := time.Now() 43 | name := "TestLocatorInvalidIP" 44 | passed := true 45 | testRes := gentester.TestResult{ 46 | Name: name, 47 | Passed: passed, 48 | } 49 | // Start Test Logic 50 | locatorReturn, err := svc.lc.GetLocation(ctx, "999") 51 | if err != nil { 52 | testRes.Passed = false 53 | errorDescription := fmt.Sprintf("no error getting location for invalid ip: %v", err) 54 | testRes.Error = &errorDescription 55 | endTest(&testRes, start, tc, results) 56 | return 57 | } 58 | if locatorReturn == nil { 59 | testRes.Passed = false 60 | errorDescription := fmt.Sprintf("location for valid ip is not nil: %v", err) 61 | testRes.Error = &errorDescription 62 | endTest(&testRes, start, tc, results) 63 | return 64 | } 65 | // End Test Logic 66 | endTest(&testRes, start, tc, results) 67 | } 68 | -------------------------------------------------------------------------------- /example/weather/services/tester/test_methods.go: -------------------------------------------------------------------------------- 1 | package tester 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | gentester "goa.design/clue/example/weather/services/tester/gen/tester" 8 | ) 9 | 10 | // Alias type for gen/tester.TestCollection to allow for adding an Append func 11 | type TestCollection gentester.TestCollection 12 | 13 | // Appends a slice of TestResults to the TestCollection 14 | func (t *TestCollection) AppendTestResult(tr ...*gentester.TestResult) { 15 | mutex := sync.Mutex{} 16 | mutex.Lock() 17 | defer mutex.Unlock() 18 | t.Results = append(t.Results, tr...) 19 | } 20 | 21 | // Runs all test collections EXCEPT smoke tests (those are in their own collections as well) 22 | func (svc *Service) TestAll(ctx context.Context, p *gentester.TesterPayload) (res *gentester.TestResults, err error) { 23 | retval := gentester.TestResults{} 24 | 25 | // Forecaster tests 26 | forecasterCollection := TestCollection{ 27 | Name: "Forecaster Tests", 28 | } 29 | forecasterResults, err := svc.runTests(ctx, p, &forecasterCollection, svc.forecasterTestMap, false) 30 | if err != nil { 31 | _ = logError(ctx, err) 32 | return nil, err 33 | } 34 | 35 | // Locator tests 36 | locatorCollection := TestCollection{ 37 | Name: "Locator Tests", 38 | } 39 | locatorResults, err := svc.runTests(ctx, p, &locatorCollection, svc.locatorTestMap, true) 40 | if err != nil { 41 | _ = logError(ctx, err) 42 | return nil, err 43 | } 44 | 45 | //Merge Disparate Collection Results 46 | allResults := []*gentester.TestResults{} 47 | allResults = append(allResults, forecasterResults) 48 | allResults = append(allResults, locatorResults) 49 | for _, r := range allResults { 50 | retval.Collections = append(retval.Collections, r.Collections...) 51 | retval.Duration += r.Duration 52 | retval.PassCount += r.PassCount 53 | retval.FailCount += r.FailCount 54 | } 55 | 56 | return &retval, nil 57 | } 58 | 59 | // Runs the Smoke tests as a collection in parallel 60 | func (svc *Service) TestSmoke(ctx context.Context) (res *gentester.TestResults, err error) { 61 | // Smoke tests 62 | smokeCollection := TestCollection{ 63 | Name: "Smoke Tests", 64 | } 65 | return svc.runTests(ctx, nil, &smokeCollection, svc.smokeTestMap, false) 66 | } 67 | 68 | // Runs the Forecaster Service tests as a collection in parallel 69 | func (svc *Service) TestForecaster(ctx context.Context) (res *gentester.TestResults, err error) { 70 | // Forecaster tests 71 | forecasterCollection := TestCollection{ 72 | Name: "Forecaster Tests", 73 | } 74 | return svc.runTests(ctx, nil, &forecasterCollection, svc.forecasterTestMap, false) 75 | } 76 | 77 | // Runs the Locator Service tests as a collection synchronously 78 | func (svc *Service) TestLocator(ctx context.Context) (res *gentester.TestResults, err error) { 79 | // Locator tests 80 | locatorCollection := TestCollection{ 81 | Name: "Locator Tests", 82 | } 83 | return svc.runTests(ctx, nil, &locatorCollection, svc.locatorTestMap, true) 84 | } 85 | -------------------------------------------------------------------------------- /example/weather/services/tester/testing.go: -------------------------------------------------------------------------------- 1 | package tester 2 | 3 | import ( 4 | "context" 5 | 6 | "goa.design/clue/log" 7 | 8 | "goa.design/clue/example/weather/services/tester/clients/forecaster" 9 | "goa.design/clue/example/weather/services/tester/clients/locator" 10 | ) 11 | 12 | type ( 13 | Service struct { 14 | lc locator.Client 15 | fc forecaster.Client 16 | // Define test func maps for each collection tested (except TestAll) 17 | smokeTestMap map[string]func(context.Context, *TestCollection) 18 | forecasterTestMap map[string]func(context.Context, *TestCollection) 19 | locatorTestMap map[string]func(context.Context, *TestCollection) 20 | } 21 | ) 22 | 23 | // New instantiates a new tester service. 24 | func New(lc locator.Client, fc forecaster.Client) *Service { 25 | svc := &Service{ 26 | lc: lc, 27 | fc: fc, 28 | } 29 | // initalize the test func maps with test function names as keys to funcs to be run. 30 | svc.smokeTestMap = map[string]func(context.Context, *TestCollection){ 31 | "TestForecasterValidLatLong": svc.TestForecasterValidLatLong, 32 | "TestLocatorValidIP": svc.TestLocatorValidIP, 33 | } 34 | svc.forecasterTestMap = map[string]func(context.Context, *TestCollection){ 35 | "TestForecasterValidLatLong": svc.TestForecasterValidLatLong, 36 | "TestForecasterInvalidLat": svc.TestForecasterInvalidLat, 37 | "TestForecasterInvalidLong": svc.TestForecasterInvalidLong, 38 | } 39 | svc.locatorTestMap = map[string]func(context.Context, *TestCollection){ 40 | "TestLocatorValidIP": svc.TestLocatorValidIP, 41 | "TestLocatorInvalidIP": svc.TestLocatorInvalidIP, 42 | } 43 | 44 | return svc 45 | } 46 | 47 | func logError(ctx context.Context, err error) error { 48 | log.Error(ctx, err) 49 | return err 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module goa.design/clue 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/aws/smithy-go v1.22.3 7 | github.com/go-logr/logr v1.4.3 8 | github.com/stretchr/testify v1.10.0 9 | go.opentelemetry.io/otel v1.36.0 10 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 11 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 12 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 13 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 14 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 15 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 16 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 17 | go.opentelemetry.io/otel/metric v1.36.0 18 | go.opentelemetry.io/otel/sdk v1.36.0 19 | go.opentelemetry.io/otel/sdk/metric v1.36.0 20 | go.opentelemetry.io/otel/trace v1.36.0 21 | goa.design/goa/v3 v3.21.0 22 | golang.org/x/term v0.32.0 23 | golang.org/x/tools v0.33.0 24 | google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 25 | google.golang.org/grpc v1.73.0 26 | google.golang.org/protobuf v1.36.6 27 | ) 28 | 29 | require ( 30 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 31 | github.com/cenkalti/backoff/v5 v5.0.2 // indirect 32 | github.com/davecgh/go-spew v1.1.1 // indirect 33 | github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect 34 | github.com/go-chi/chi/v5 v5.2.1 // indirect 35 | github.com/go-logr/stdr v1.2.2 // indirect 36 | github.com/gohugoio/hashstructure v0.5.0 // indirect 37 | github.com/google/uuid v1.6.0 // indirect 38 | github.com/gorilla/websocket v1.5.3 // indirect 39 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect 40 | github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect 41 | github.com/pmezard/go-difflib v1.0.0 // indirect 42 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 43 | go.opentelemetry.io/proto/otlp v1.6.0 // indirect 44 | golang.org/x/mod v0.24.0 // indirect 45 | golang.org/x/net v0.40.0 // indirect 46 | golang.org/x/sync v0.14.0 // indirect 47 | golang.org/x/sys v0.33.0 // indirect 48 | golang.org/x/text v0.25.0 // indirect 49 | google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect 50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.24.0 2 | 3 | use ( 4 | . 5 | ./example/weather 6 | ./mock/cmd/cmg/pkg/generate/_tests 7 | ./mock/cmd/cmg/pkg/parse/_tests 8 | ) 9 | -------------------------------------------------------------------------------- /health/checker.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import ( 4 | "context" 5 | "encoding/xml" 6 | "sort" 7 | "time" 8 | 9 | "goa.design/clue/log" 10 | ) 11 | 12 | type ( 13 | // Checker exposes a health check. 14 | Checker interface { 15 | // Check that all dependencies are healthy. Check returns true 16 | // if the service is healthy. The returned Health struct 17 | // contains the health status of each dependency. 18 | Check(context.Context) (*Health, bool) 19 | } 20 | 21 | // Health status of a service. 22 | Health struct { 23 | // Uptime of service in seconds. 24 | Uptime int64 `json:"uptime"` 25 | // Version of service. 26 | Version string `json:"version"` 27 | // Status of each dependency indexed by service name. 28 | // "OK" if dependency is healthy, "NOT OK" otherwise. 29 | Status map[string]string `json:"status,omitempty"` 30 | } 31 | 32 | // checker is a Checker that checks the health of the given 33 | // dependencies. 34 | checker struct { 35 | deps []Pinger 36 | } 37 | 38 | // mp is used to marshal a map to xml. 39 | mp map[string]string 40 | ) 41 | 42 | func (h Health) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 43 | return e.Encode(struct { 44 | XMLName xml.Name `xml:"health"` 45 | Uptime int64 `xml:"uptime"` 46 | Version string `xml:"version"` 47 | Status mp `xml:"status"` 48 | }{ 49 | Uptime: h.Uptime, 50 | Version: h.Version, 51 | Status: h.Status, 52 | }) 53 | } 54 | 55 | func (m mp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 56 | if len(m) == 0 { 57 | return nil 58 | } 59 | if err := e.EncodeToken(start); err != nil { 60 | return err 61 | } 62 | var keys []string 63 | for k := range m { 64 | keys = append(keys, k) 65 | } 66 | sort.Strings(keys) 67 | for _, k := range keys { 68 | if err := e.EncodeElement(m[k], xml.StartElement{Name: xml.Name{Local: k}}); err != nil { 69 | return err 70 | } 71 | } 72 | return e.EncodeToken(start.End()) 73 | } 74 | 75 | // Version of service, initialized at compiled time. 76 | var Version string 77 | 78 | // StartedAt is the time the service was started. 79 | var StartedAt = time.Now() 80 | 81 | // Create a Checker that checks the health of the given dependencies. 82 | func NewChecker(deps ...Pinger) Checker { 83 | return &checker{ 84 | deps: deps, 85 | } 86 | } 87 | 88 | func (c *checker) Check(ctx context.Context) (*Health, bool) { 89 | res := &Health{ 90 | Uptime: int64(time.Since(StartedAt).Seconds()), 91 | Version: Version, 92 | Status: make(map[string]string), 93 | } 94 | healthy := true 95 | for _, dep := range c.deps { 96 | res.Status[dep.Name()] = "OK" 97 | // Note: need to create a new context for each dependency So that one 98 | // dependency canceling the context will not affect the other checks. 99 | logCtx := log.With(context.Background(), log.KV{K: "dep", V: dep.Name()}) 100 | if err := dep.Ping(logCtx); err != nil { 101 | res.Status[dep.Name()] = "NOT OK" 102 | healthy = false 103 | log.Error(ctx, err, log.KV{K: "msg", V: "ping failed"}) 104 | } 105 | } 106 | return res, healthy 107 | } 108 | -------------------------------------------------------------------------------- /health/handler.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | goahttp "goa.design/goa/v3/http" 8 | ) 9 | 10 | // Handler returns a HTTP handler that serves health check requests. The 11 | // response body is the health status returned by chk.Check(). By default 12 | // it's encoded as JSON, but you can specify a different encoding in the 13 | // HTTP Accept header. The response status is 200 if chk.Check() returns 14 | // a nil error, 503 otherwise. 15 | func Handler(chk Checker) http.HandlerFunc { 16 | encoder := goahttp.ResponseEncoder 17 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 18 | ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) 19 | enc := encoder(ctx, w) 20 | h, healthy := chk.Check(ctx) 21 | if healthy { 22 | w.WriteHeader(http.StatusOK) 23 | } else { 24 | w.WriteHeader(http.StatusServiceUnavailable) 25 | } 26 | enc.Encode(h) // nolint: errcheck 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /health/pinger.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | type ( 12 | // Pinger makes it possible to ping a service. 13 | Pinger interface { 14 | // Name of remote service. 15 | Name() string 16 | // Ping the remote service, return a non nil error if the 17 | // service is not available. 18 | Ping(context.Context) error 19 | } 20 | 21 | // Option configures a Pinger. 22 | Option func(o *options) 23 | 24 | client struct { 25 | name string 26 | req *http.Request 27 | httpClient *http.Client 28 | } 29 | 30 | options struct { 31 | scheme string 32 | path string 33 | timeout time.Duration 34 | } 35 | ) 36 | 37 | // NewPinger returns a new health-check client for the given service. It panics 38 | // if the given host address is malformed. The default scheme is "http" and the 39 | // default path is "/livez". Both can be overridden via options. 40 | func NewPinger(name, addr string, opts ...Option) Pinger { 41 | options := &options{scheme: "http", path: "/livez"} 42 | for _, o := range opts { 43 | o(options) 44 | } 45 | u := url.URL{Scheme: options.scheme, Host: addr, Path: options.path} 46 | req, err := http.NewRequest("GET", u.String(), nil) 47 | if err != nil { 48 | panic(err) 49 | } 50 | return &client{ 51 | name: name, 52 | req: req, 53 | httpClient: &http.Client{Timeout: options.timeout}, 54 | } 55 | } 56 | 57 | func (c *client) Name() string { 58 | return c.name 59 | } 60 | 61 | func (c *client) Ping(ctx context.Context) error { 62 | resp, err := c.httpClient.Do(c.req.WithContext(ctx)) 63 | if err != nil { 64 | return fmt.Errorf("failed to make health check request to %q: %v", c.name, err) 65 | } 66 | defer resp.Body.Close() // nolint: errcheck 67 | if resp.StatusCode < 200 || resp.StatusCode > 300 { 68 | return fmt.Errorf("health-check for %q returned status %d", c.name, resp.StatusCode) 69 | } 70 | return nil 71 | } 72 | 73 | // WithScheme sets the scheme used to ping the service. 74 | // Default scheme is "http". 75 | func WithScheme(scheme string) Option { 76 | return func(o *options) { 77 | o.scheme = scheme 78 | } 79 | } 80 | 81 | // WithPath sets the path used to ping the service. 82 | // Default path is "/livez". 83 | func WithPath(path string) Option { 84 | return func(o *options) { 85 | o.path = path 86 | } 87 | } 88 | 89 | // WithTimeout sets the timeout used to ping the service. 90 | // Default is no timeout. 91 | func WithTimeout(timeout time.Duration) Option { 92 | return func(o *options) { 93 | o.timeout = timeout 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /internal/testsvc/README.md: -------------------------------------------------------------------------------- 1 | # Test Service 2 | 3 | This directory contains a service used by the `metrics` and `tracing` tests. 4 | This code is not intended to be used outside of this repo. 5 | -------------------------------------------------------------------------------- /internal/testsvc/design/design.go: -------------------------------------------------------------------------------- 1 | package design 2 | 3 | import ( 4 | . "goa.design/goa/v3/dsl" 5 | ) 6 | 7 | var _ = API("itest", func() { 8 | Description("metrics test service") 9 | }) 10 | 11 | var _ = Service("test", func() { 12 | Method("http_method", func() { 13 | Payload(Fields) 14 | Result(Fields) 15 | HTTP(func() { 16 | POST("/{i}") 17 | }) 18 | }) 19 | 20 | Method("grpc_method", func() { 21 | Payload(Fields) 22 | Result(Fields) 23 | GRPC(func() {}) 24 | }) 25 | 26 | Method("grpc_stream", func() { 27 | StreamingPayload(Fields) 28 | StreamingResult(Fields) 29 | GRPC(func() {}) 30 | }) 31 | }) 32 | 33 | var Fields = Type("Fields", func() { 34 | Field(1, "s", String, "String operand") 35 | Field(2, "i", Int, "Int operand") 36 | }) 37 | -------------------------------------------------------------------------------- /internal/testsvc/gen/grpc/test/client/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test gRPC client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | 14 | testpb "goa.design/clue/internal/testsvc/gen/grpc/test/pb" 15 | test "goa.design/clue/internal/testsvc/gen/test" 16 | ) 17 | 18 | // BuildGrpcMethodPayload builds the payload for the test grpc_method endpoint 19 | // from CLI flags. 20 | func BuildGrpcMethodPayload(testGrpcMethodMessage string) (*test.Fields, error) { 21 | var err error 22 | var message testpb.GrpcMethodRequest 23 | { 24 | if testGrpcMethodMessage != "" { 25 | err = json.Unmarshal([]byte(testGrpcMethodMessage), &message) 26 | if err != nil { 27 | return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"i\": 4434351785751264939,\n \"s\": \"Corrupti repellat autem sit architecto ut.\"\n }'") 28 | } 29 | } 30 | } 31 | v := &test.Fields{ 32 | S: message.S, 33 | } 34 | if message.I != nil { 35 | i := int(*message.I) 36 | v.I = &i 37 | } 38 | 39 | return v, nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/testsvc/gen/grpc/test/client/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test gRPC client encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | "context" 12 | 13 | testpb "goa.design/clue/internal/testsvc/gen/grpc/test/pb" 14 | test "goa.design/clue/internal/testsvc/gen/test" 15 | goagrpc "goa.design/goa/v3/grpc" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/metadata" 18 | ) 19 | 20 | // BuildGrpcMethodFunc builds the remote method to invoke for "test" service 21 | // "grpc_method" endpoint. 22 | func BuildGrpcMethodFunc(grpccli testpb.TestClient, cliopts ...grpc.CallOption) goagrpc.RemoteFunc { 23 | return func(ctx context.Context, reqpb any, opts ...grpc.CallOption) (any, error) { 24 | for _, opt := range cliopts { 25 | opts = append(opts, opt) 26 | } 27 | if reqpb != nil { 28 | return grpccli.GrpcMethod(ctx, reqpb.(*testpb.GrpcMethodRequest), opts...) 29 | } 30 | return grpccli.GrpcMethod(ctx, &testpb.GrpcMethodRequest{}, opts...) 31 | } 32 | } 33 | 34 | // EncodeGrpcMethodRequest encodes requests sent to test grpc_method endpoint. 35 | func EncodeGrpcMethodRequest(ctx context.Context, v any, md *metadata.MD) (any, error) { 36 | payload, ok := v.(*test.Fields) 37 | if !ok { 38 | return nil, goagrpc.ErrInvalidType("test", "grpc_method", "*test.Fields", v) 39 | } 40 | return NewProtoGrpcMethodRequest(payload), nil 41 | } 42 | 43 | // DecodeGrpcMethodResponse decodes responses from the test grpc_method 44 | // endpoint. 45 | func DecodeGrpcMethodResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { 46 | message, ok := v.(*testpb.GrpcMethodResponse) 47 | if !ok { 48 | return nil, goagrpc.ErrInvalidType("test", "grpc_method", "*testpb.GrpcMethodResponse", v) 49 | } 50 | res := NewGrpcMethodResult(message) 51 | return res, nil 52 | } // BuildGrpcStreamFunc builds the remote method to invoke for "test" service 53 | // "grpc_stream" endpoint. 54 | func BuildGrpcStreamFunc(grpccli testpb.TestClient, cliopts ...grpc.CallOption) goagrpc.RemoteFunc { 55 | return func(ctx context.Context, reqpb any, opts ...grpc.CallOption) (any, error) { 56 | for _, opt := range cliopts { 57 | opts = append(opts, opt) 58 | } 59 | if reqpb != nil { 60 | return grpccli.GrpcStream(ctx, opts...) 61 | } 62 | return grpccli.GrpcStream(ctx, opts...) 63 | } 64 | } 65 | 66 | // DecodeGrpcStreamResponse decodes responses from the test grpc_stream 67 | // endpoint. 68 | func DecodeGrpcStreamResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { 69 | return &GrpcStreamClientStream{ 70 | stream: v.(testpb.Test_GrpcStreamClient), 71 | }, nil 72 | } 73 | -------------------------------------------------------------------------------- /internal/testsvc/gen/grpc/test/client/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test gRPC client types 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | testpb "goa.design/clue/internal/testsvc/gen/grpc/test/pb" 12 | test "goa.design/clue/internal/testsvc/gen/test" 13 | ) 14 | 15 | // NewProtoGrpcMethodRequest builds the gRPC request type from the payload of 16 | // the "grpc_method" endpoint of the "test" service. 17 | func NewProtoGrpcMethodRequest(payload *test.Fields) *testpb.GrpcMethodRequest { 18 | message := &testpb.GrpcMethodRequest{ 19 | S: payload.S, 20 | } 21 | if payload.I != nil { 22 | i := int32(*payload.I) 23 | message.I = &i 24 | } 25 | return message 26 | } 27 | 28 | // NewGrpcMethodResult builds the result type of the "grpc_method" endpoint of 29 | // the "test" service from the gRPC response type. 30 | func NewGrpcMethodResult(message *testpb.GrpcMethodResponse) *test.Fields { 31 | result := &test.Fields{ 32 | S: message.S, 33 | } 34 | if message.I != nil { 35 | i := int(*message.I) 36 | result.I = &i 37 | } 38 | return result 39 | } 40 | 41 | func NewGrpcStreamResponseFields(v *testpb.GrpcStreamResponse) *test.Fields { 42 | result := &test.Fields{ 43 | S: v.S, 44 | } 45 | if v.I != nil { 46 | i := int(*v.I) 47 | result.I = &i 48 | } 49 | return result 50 | } 51 | 52 | func NewProtoFieldsGrpcStreamStreamingRequest(spayload *test.Fields) *testpb.GrpcStreamStreamingRequest { 53 | v := &testpb.GrpcStreamStreamingRequest{ 54 | S: spayload.S, 55 | } 56 | if spayload.I != nil { 57 | i := int32(*spayload.I) 58 | v.I = &i 59 | } 60 | return v 61 | } 62 | -------------------------------------------------------------------------------- /internal/testsvc/gen/grpc/test/pb/goagen_testsvc_test.proto: -------------------------------------------------------------------------------- 1 | // Code generated with goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test protocol buffer definition 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | syntax = "proto3"; 9 | 10 | package test; 11 | 12 | option go_package = "/testpb"; 13 | 14 | // Service is the test service interface. 15 | service Test { 16 | // GrpcMethod implements grpc_method. 17 | rpc GrpcMethod (GrpcMethodRequest) returns (GrpcMethodResponse); 18 | // GrpcStream implements grpc_stream. 19 | rpc GrpcStream (stream GrpcStreamStreamingRequest) returns (stream GrpcStreamResponse); 20 | } 21 | 22 | message GrpcMethodRequest { 23 | // String operand 24 | optional string s = 1; 25 | // Int operand 26 | optional sint32 i = 2; 27 | } 28 | 29 | message GrpcMethodResponse { 30 | // String operand 31 | optional string s = 1; 32 | // Int operand 33 | optional sint32 i = 2; 34 | } 35 | 36 | message GrpcStreamStreamingRequest { 37 | // String operand 38 | optional string s = 1; 39 | // Int operand 40 | optional sint32 i = 2; 41 | } 42 | 43 | message GrpcStreamResponse { 44 | // String operand 45 | optional string s = 1; 46 | // Int operand 47 | optional sint32 i = 2; 48 | } 49 | -------------------------------------------------------------------------------- /internal/testsvc/gen/grpc/test/server/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test gRPC server encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package server 9 | 10 | import ( 11 | "context" 12 | 13 | testpb "goa.design/clue/internal/testsvc/gen/grpc/test/pb" 14 | test "goa.design/clue/internal/testsvc/gen/test" 15 | goagrpc "goa.design/goa/v3/grpc" 16 | "google.golang.org/grpc/metadata" 17 | ) 18 | 19 | // EncodeGrpcMethodResponse encodes responses from the "test" service 20 | // "grpc_method" endpoint. 21 | func EncodeGrpcMethodResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 22 | result, ok := v.(*test.Fields) 23 | if !ok { 24 | return nil, goagrpc.ErrInvalidType("test", "grpc_method", "*test.Fields", v) 25 | } 26 | resp := NewProtoGrpcMethodResponse(result) 27 | return resp, nil 28 | } 29 | 30 | // DecodeGrpcMethodRequest decodes requests sent to "test" service 31 | // "grpc_method" endpoint. 32 | func DecodeGrpcMethodRequest(ctx context.Context, v any, md metadata.MD) (any, error) { 33 | var ( 34 | message *testpb.GrpcMethodRequest 35 | ok bool 36 | ) 37 | { 38 | if message, ok = v.(*testpb.GrpcMethodRequest); !ok { 39 | return nil, goagrpc.ErrInvalidType("test", "grpc_method", "*testpb.GrpcMethodRequest", v) 40 | } 41 | } 42 | var payload *test.Fields 43 | { 44 | payload = NewGrpcMethodPayload(message) 45 | } 46 | return payload, nil 47 | } 48 | 49 | // EncodeGrpcStreamResponse encodes responses from the "test" service 50 | // "grpc_stream" endpoint. 51 | func EncodeGrpcStreamResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { 52 | result, ok := v.(*test.Fields) 53 | if !ok { 54 | return nil, goagrpc.ErrInvalidType("test", "grpc_stream", "*test.Fields", v) 55 | } 56 | resp := NewProtoGrpcStreamResponse(result) 57 | return resp, nil 58 | } 59 | -------------------------------------------------------------------------------- /internal/testsvc/gen/grpc/test/server/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test gRPC server types 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package server 9 | 10 | import ( 11 | testpb "goa.design/clue/internal/testsvc/gen/grpc/test/pb" 12 | test "goa.design/clue/internal/testsvc/gen/test" 13 | ) 14 | 15 | // NewGrpcMethodPayload builds the payload of the "grpc_method" endpoint of the 16 | // "test" service from the gRPC request type. 17 | func NewGrpcMethodPayload(message *testpb.GrpcMethodRequest) *test.Fields { 18 | v := &test.Fields{ 19 | S: message.S, 20 | } 21 | if message.I != nil { 22 | i := int(*message.I) 23 | v.I = &i 24 | } 25 | return v 26 | } 27 | 28 | // NewProtoGrpcMethodResponse builds the gRPC response type from the result of 29 | // the "grpc_method" endpoint of the "test" service. 30 | func NewProtoGrpcMethodResponse(result *test.Fields) *testpb.GrpcMethodResponse { 31 | message := &testpb.GrpcMethodResponse{ 32 | S: result.S, 33 | } 34 | if result.I != nil { 35 | i := int32(*result.I) 36 | message.I = &i 37 | } 38 | return message 39 | } 40 | 41 | // NewProtoGrpcStreamResponse builds the gRPC response type from the result of 42 | // the "grpc_stream" endpoint of the "test" service. 43 | func NewProtoGrpcStreamResponse(result *test.Fields) *testpb.GrpcStreamResponse { 44 | message := &testpb.GrpcStreamResponse{ 45 | S: result.S, 46 | } 47 | if result.I != nil { 48 | i := int32(*result.I) 49 | message.I = &i 50 | } 51 | return message 52 | } 53 | 54 | func NewProtoFieldsGrpcStreamResponse(result *test.Fields) *testpb.GrpcStreamResponse { 55 | v := &testpb.GrpcStreamResponse{ 56 | S: result.S, 57 | } 58 | if result.I != nil { 59 | i := int32(*result.I) 60 | v.I = &i 61 | } 62 | return v 63 | } 64 | 65 | func NewGrpcStreamStreamingRequestFields(v *testpb.GrpcStreamStreamingRequest) *test.Fields { 66 | spayload := &test.Fields{ 67 | S: v.S, 68 | } 69 | if v.I != nil { 70 | i := int(*v.I) 71 | spayload.I = &i 72 | } 73 | return spayload 74 | } 75 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/openapi.json: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","description":"metrics test service","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/{i}":{"post":{"tags":["test"],"summary":"http_method test","operationId":"test#http_method","parameters":[{"name":"i","in":"path","description":"Int operand","required":true,"type":"integer"},{"name":"http_method_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/Fields"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Fields"}}},"schemes":["http"]}}},"definitions":{"Fields":{"title":"Fields","type":"object","properties":{"i":{"type":"integer","description":"Int operand","example":8526503336960817370,"format":"int64"},"s":{"type":"string","description":"String operand","example":"Esse perspiciatis officiis a reprehenderit quam consequatur."}},"example":{"i":3221232481350240723,"s":"Vel esse illo quos doloremque provident consequatur."}}}} -------------------------------------------------------------------------------- /internal/testsvc/gen/http/openapi.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | description: metrics test service 5 | version: 0.0.1 6 | host: localhost:80 7 | consumes: 8 | - application/json 9 | - application/xml 10 | - application/gob 11 | produces: 12 | - application/json 13 | - application/xml 14 | - application/gob 15 | paths: 16 | /{i}: 17 | post: 18 | tags: 19 | - test 20 | summary: http_method test 21 | operationId: test#http_method 22 | parameters: 23 | - name: i 24 | in: path 25 | description: Int operand 26 | required: true 27 | type: integer 28 | - name: http_method_request_body 29 | in: body 30 | required: true 31 | schema: 32 | $ref: '#/definitions/Fields' 33 | responses: 34 | "200": 35 | description: OK response. 36 | schema: 37 | $ref: '#/definitions/Fields' 38 | schemes: 39 | - http 40 | definitions: 41 | Fields: 42 | title: Fields 43 | type: object 44 | properties: 45 | i: 46 | type: integer 47 | description: Int operand 48 | example: 8526503336960817370 49 | format: int64 50 | s: 51 | type: string 52 | description: String operand 53 | example: Esse perspiciatis officiis a reprehenderit quam consequatur. 54 | example: 55 | i: 3221232481350240723 56 | s: Vel esse illo quos doloremque provident consequatur. 57 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/openapi3.json: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","description":"metrics test service","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for itest"}],"paths":{"/{i}":{"post":{"tags":["test"],"summary":"http_method test","operationId":"test#http_method","parameters":[{"name":"i","in":"path","description":"Int operand","required":true,"schema":{"type":"integer","description":"Int operand","example":5686343733477757409,"format":"int64"},"example":5571735705013562983}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Fields2"},"example":{"s":"Voluptas exercitationem vitae."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Fields"},"example":{"i":4403340894134448357,"s":"In modi et."}}}}}}}},"components":{"schemas":{"Fields":{"type":"object","properties":{"i":{"type":"integer","description":"Int operand","example":506856245359991540,"format":"int64"},"s":{"type":"string","description":"String operand","example":"Eos nulla ut doloremque."}},"example":{"i":8161197411161146199,"s":"Quam dolores est corporis nulla."}},"Fields2":{"type":"object","properties":{"s":{"type":"string","description":"String operand","example":"Voluptatem dolor inventore possimus delectus minima ipsa."}},"example":{"s":"Quia laborum et distinctio dolores."}}}},"tags":[{"name":"test"}]} -------------------------------------------------------------------------------- /internal/testsvc/gen/http/openapi3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | description: metrics test service 5 | version: 0.0.1 6 | servers: 7 | - url: http://localhost:80 8 | description: Default server for itest 9 | paths: 10 | /{i}: 11 | post: 12 | tags: 13 | - test 14 | summary: http_method test 15 | operationId: test#http_method 16 | parameters: 17 | - name: i 18 | in: path 19 | description: Int operand 20 | required: true 21 | schema: 22 | type: integer 23 | description: Int operand 24 | example: 5686343733477757409 25 | format: int64 26 | example: 5571735705013562983 27 | requestBody: 28 | required: true 29 | content: 30 | application/json: 31 | schema: 32 | $ref: '#/components/schemas/Fields2' 33 | example: 34 | s: Voluptas exercitationem vitae. 35 | responses: 36 | "200": 37 | description: OK response. 38 | content: 39 | application/json: 40 | schema: 41 | $ref: '#/components/schemas/Fields' 42 | example: 43 | i: 4403340894134448357 44 | s: In modi et. 45 | components: 46 | schemas: 47 | Fields: 48 | type: object 49 | properties: 50 | i: 51 | type: integer 52 | description: Int operand 53 | example: 506856245359991540 54 | format: int64 55 | s: 56 | type: string 57 | description: String operand 58 | example: Eos nulla ut doloremque. 59 | example: 60 | i: 8161197411161146199 61 | s: Quam dolores est corporis nulla. 62 | Fields2: 63 | type: object 64 | properties: 65 | s: 66 | type: string 67 | description: String operand 68 | example: Voluptatem dolor inventore possimus delectus minima ipsa. 69 | example: 70 | s: Quia laborum et distinctio dolores. 71 | tags: 72 | - name: test 73 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/client/cli.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test HTTP client CLI support package 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | "strconv" 14 | 15 | test "goa.design/clue/internal/testsvc/gen/test" 16 | ) 17 | 18 | // BuildHTTPMethodPayload builds the payload for the test http_method endpoint 19 | // from CLI flags. 20 | func BuildHTTPMethodPayload(testHTTPMethodBody string, testHTTPMethodI string) (*test.Fields, error) { 21 | var err error 22 | var body HTTPMethodRequestBody 23 | { 24 | err = json.Unmarshal([]byte(testHTTPMethodBody), &body) 25 | if err != nil { 26 | return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"s\": \"Voluptas exercitationem vitae.\"\n }'") 27 | } 28 | } 29 | var i int 30 | { 31 | var v int64 32 | v, err = strconv.ParseInt(testHTTPMethodI, 10, strconv.IntSize) 33 | i = int(v) 34 | if err != nil { 35 | return nil, fmt.Errorf("invalid value for i, must be INT") 36 | } 37 | } 38 | v := &test.Fields{ 39 | S: body.S, 40 | } 41 | v.I = &i 42 | 43 | return v, nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/client/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test client HTTP transport 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | "context" 12 | "net/http" 13 | 14 | goahttp "goa.design/goa/v3/http" 15 | goa "goa.design/goa/v3/pkg" 16 | ) 17 | 18 | // Client lists the test service endpoint HTTP clients. 19 | type Client struct { 20 | // HTTPMethod Doer is the HTTP client used to make requests to the http_method 21 | // endpoint. 22 | HTTPMethodDoer goahttp.Doer 23 | 24 | // RestoreResponseBody controls whether the response bodies are reset after 25 | // decoding so they can be read again. 26 | RestoreResponseBody bool 27 | 28 | scheme string 29 | host string 30 | encoder func(*http.Request) goahttp.Encoder 31 | decoder func(*http.Response) goahttp.Decoder 32 | } 33 | 34 | // NewClient instantiates HTTP clients for all the test service servers. 35 | func NewClient( 36 | scheme string, 37 | host string, 38 | doer goahttp.Doer, 39 | enc func(*http.Request) goahttp.Encoder, 40 | dec func(*http.Response) goahttp.Decoder, 41 | restoreBody bool, 42 | ) *Client { 43 | return &Client{ 44 | HTTPMethodDoer: doer, 45 | RestoreResponseBody: restoreBody, 46 | scheme: scheme, 47 | host: host, 48 | decoder: dec, 49 | encoder: enc, 50 | } 51 | } 52 | 53 | // HTTPMethod returns an endpoint that makes HTTP requests to the test service 54 | // http_method server. 55 | func (c *Client) HTTPMethod() goa.Endpoint { 56 | var ( 57 | encodeRequest = EncodeHTTPMethodRequest(c.encoder) 58 | decodeResponse = DecodeHTTPMethodResponse(c.decoder, c.RestoreResponseBody) 59 | ) 60 | return func(ctx context.Context, v any) (any, error) { 61 | req, err := c.BuildHTTPMethodRequest(ctx, v) 62 | if err != nil { 63 | return nil, err 64 | } 65 | err = encodeRequest(req, v) 66 | if err != nil { 67 | return nil, err 68 | } 69 | resp, err := c.HTTPMethodDoer.Do(req) 70 | if err != nil { 71 | return nil, goahttp.ErrRequestError("test", "http_method", err) 72 | } 73 | return decodeResponse(resp) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/client/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test HTTP client encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | "bytes" 12 | "context" 13 | "io" 14 | "net/http" 15 | "net/url" 16 | 17 | test "goa.design/clue/internal/testsvc/gen/test" 18 | goahttp "goa.design/goa/v3/http" 19 | ) 20 | 21 | // BuildHTTPMethodRequest instantiates a HTTP request object with method and 22 | // path set to call the "test" service "http_method" endpoint 23 | func (c *Client) BuildHTTPMethodRequest(ctx context.Context, v any) (*http.Request, error) { 24 | var ( 25 | i int 26 | ) 27 | { 28 | p, ok := v.(*test.Fields) 29 | if !ok { 30 | return nil, goahttp.ErrInvalidType("test", "http_method", "*test.Fields", v) 31 | } 32 | if p.I != nil { 33 | i = *p.I 34 | } 35 | } 36 | u := &url.URL{Scheme: c.scheme, Host: c.host, Path: HTTPMethodTestPath(i)} 37 | req, err := http.NewRequest("POST", u.String(), nil) 38 | if err != nil { 39 | return nil, goahttp.ErrInvalidURL("test", "http_method", u.String(), err) 40 | } 41 | if ctx != nil { 42 | req = req.WithContext(ctx) 43 | } 44 | 45 | return req, nil 46 | } 47 | 48 | // EncodeHTTPMethodRequest returns an encoder for requests sent to the test 49 | // http_method server. 50 | func EncodeHTTPMethodRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { 51 | return func(req *http.Request, v any) error { 52 | p, ok := v.(*test.Fields) 53 | if !ok { 54 | return goahttp.ErrInvalidType("test", "http_method", "*test.Fields", v) 55 | } 56 | body := NewHTTPMethodRequestBody(p) 57 | if err := encoder(req).Encode(&body); err != nil { 58 | return goahttp.ErrEncodingError("test", "http_method", err) 59 | } 60 | return nil 61 | } 62 | } 63 | 64 | // DecodeHTTPMethodResponse returns a decoder for responses returned by the 65 | // test http_method endpoint. restoreBody controls whether the response body 66 | // should be restored after having been read. 67 | func DecodeHTTPMethodResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { 68 | return func(resp *http.Response) (any, error) { 69 | if restoreBody { 70 | b, err := io.ReadAll(resp.Body) 71 | if err != nil { 72 | return nil, err 73 | } 74 | resp.Body = io.NopCloser(bytes.NewBuffer(b)) 75 | defer func() { 76 | resp.Body = io.NopCloser(bytes.NewBuffer(b)) 77 | }() 78 | } else { 79 | defer resp.Body.Close() 80 | } 81 | switch resp.StatusCode { 82 | case http.StatusOK: 83 | var ( 84 | body HTTPMethodResponseBody 85 | err error 86 | ) 87 | err = decoder(resp).Decode(&body) 88 | if err != nil { 89 | return nil, goahttp.ErrDecodingError("test", "http_method", err) 90 | } 91 | res := NewHTTPMethodFieldsOK(&body) 92 | return res, nil 93 | default: 94 | body, _ := io.ReadAll(resp.Body) 95 | return nil, goahttp.ErrInvalidResponse("test", "http_method", resp.StatusCode, string(body)) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/client/paths.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // HTTP request path constructors for the test service. 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | "fmt" 12 | ) 13 | 14 | // HTTPMethodTestPath returns the URL path to the test service http_method HTTP endpoint. 15 | func HTTPMethodTestPath(i int) string { 16 | return fmt.Sprintf("/%v", i) 17 | } 18 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/client/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test HTTP client types 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package client 9 | 10 | import ( 11 | test "goa.design/clue/internal/testsvc/gen/test" 12 | ) 13 | 14 | // HTTPMethodRequestBody is the type of the "test" service "http_method" 15 | // endpoint HTTP request body. 16 | type HTTPMethodRequestBody struct { 17 | // String operand 18 | S *string `form:"s,omitempty" json:"s,omitempty" xml:"s,omitempty"` 19 | } 20 | 21 | // HTTPMethodResponseBody is the type of the "test" service "http_method" 22 | // endpoint HTTP response body. 23 | type HTTPMethodResponseBody struct { 24 | // String operand 25 | S *string `form:"s,omitempty" json:"s,omitempty" xml:"s,omitempty"` 26 | // Int operand 27 | I *int `form:"i,omitempty" json:"i,omitempty" xml:"i,omitempty"` 28 | } 29 | 30 | // NewHTTPMethodRequestBody builds the HTTP request body from the payload of 31 | // the "http_method" endpoint of the "test" service. 32 | func NewHTTPMethodRequestBody(p *test.Fields) *HTTPMethodRequestBody { 33 | body := &HTTPMethodRequestBody{ 34 | S: p.S, 35 | } 36 | return body 37 | } 38 | 39 | // NewHTTPMethodFieldsOK builds a "test" service "http_method" endpoint result 40 | // from a HTTP "OK" response. 41 | func NewHTTPMethodFieldsOK(body *HTTPMethodResponseBody) *test.Fields { 42 | v := &test.Fields{ 43 | S: body.S, 44 | I: body.I, 45 | } 46 | 47 | return v 48 | } 49 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/server/encode_decode.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test HTTP server encoders and decoders 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package server 9 | 10 | import ( 11 | "context" 12 | "errors" 13 | "io" 14 | "net/http" 15 | "strconv" 16 | 17 | test "goa.design/clue/internal/testsvc/gen/test" 18 | goahttp "goa.design/goa/v3/http" 19 | goa "goa.design/goa/v3/pkg" 20 | ) 21 | 22 | // EncodeHTTPMethodResponse returns an encoder for responses returned by the 23 | // test http_method endpoint. 24 | func EncodeHTTPMethodResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { 25 | return func(ctx context.Context, w http.ResponseWriter, v any) error { 26 | res, _ := v.(*test.Fields) 27 | enc := encoder(ctx, w) 28 | body := NewHTTPMethodResponseBody(res) 29 | w.WriteHeader(http.StatusOK) 30 | return enc.Encode(body) 31 | } 32 | } 33 | 34 | // DecodeHTTPMethodRequest returns a decoder for requests sent to the test 35 | // http_method endpoint. 36 | func DecodeHTTPMethodRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { 37 | return func(r *http.Request) (any, error) { 38 | var ( 39 | body HTTPMethodRequestBody 40 | err error 41 | ) 42 | err = decoder(r).Decode(&body) 43 | if err != nil { 44 | if err == io.EOF { 45 | return nil, goa.MissingPayloadError() 46 | } 47 | var gerr *goa.ServiceError 48 | if errors.As(err, &gerr) { 49 | return nil, gerr 50 | } 51 | return nil, goa.DecodePayloadError(err.Error()) 52 | } 53 | 54 | var ( 55 | i int 56 | 57 | params = mux.Vars(r) 58 | ) 59 | { 60 | iRaw := params["i"] 61 | v, err2 := strconv.ParseInt(iRaw, 10, strconv.IntSize) 62 | if err2 != nil { 63 | err = goa.MergeErrors(err, goa.InvalidFieldTypeError("i", iRaw, "integer")) 64 | } 65 | i = int(v) 66 | } 67 | if err != nil { 68 | return nil, err 69 | } 70 | payload := NewHTTPMethodFields(&body, i) 71 | 72 | return payload, nil 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/server/paths.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // HTTP request path constructors for the test service. 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package server 9 | 10 | import ( 11 | "fmt" 12 | ) 13 | 14 | // HTTPMethodTestPath returns the URL path to the test service http_method HTTP endpoint. 15 | func HTTPMethodTestPath(i int) string { 16 | return fmt.Sprintf("/%v", i) 17 | } 18 | -------------------------------------------------------------------------------- /internal/testsvc/gen/http/test/server/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test HTTP server types 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package server 9 | 10 | import ( 11 | test "goa.design/clue/internal/testsvc/gen/test" 12 | ) 13 | 14 | // HTTPMethodRequestBody is the type of the "test" service "http_method" 15 | // endpoint HTTP request body. 16 | type HTTPMethodRequestBody struct { 17 | // String operand 18 | S *string `form:"s,omitempty" json:"s,omitempty" xml:"s,omitempty"` 19 | } 20 | 21 | // HTTPMethodResponseBody is the type of the "test" service "http_method" 22 | // endpoint HTTP response body. 23 | type HTTPMethodResponseBody struct { 24 | // String operand 25 | S *string `form:"s,omitempty" json:"s,omitempty" xml:"s,omitempty"` 26 | // Int operand 27 | I *int `form:"i,omitempty" json:"i,omitempty" xml:"i,omitempty"` 28 | } 29 | 30 | // NewHTTPMethodResponseBody builds the HTTP response body from the result of 31 | // the "http_method" endpoint of the "test" service. 32 | func NewHTTPMethodResponseBody(res *test.Fields) *HTTPMethodResponseBody { 33 | body := &HTTPMethodResponseBody{ 34 | S: res.S, 35 | I: res.I, 36 | } 37 | return body 38 | } 39 | 40 | // NewHTTPMethodFields builds a test service http_method endpoint payload. 41 | func NewHTTPMethodFields(body *HTTPMethodRequestBody, i int) *test.Fields { 42 | v := &test.Fields{ 43 | S: body.S, 44 | } 45 | v.I = &i 46 | 47 | return v 48 | } 49 | -------------------------------------------------------------------------------- /internal/testsvc/gen/test/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test client 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package test 9 | 10 | import ( 11 | "context" 12 | 13 | goa "goa.design/goa/v3/pkg" 14 | ) 15 | 16 | // Client is the "test" service client. 17 | type Client struct { 18 | HTTPMethodEndpoint goa.Endpoint 19 | GrpcMethodEndpoint goa.Endpoint 20 | GrpcStreamEndpoint goa.Endpoint 21 | } 22 | 23 | // NewClient initializes a "test" service client given the endpoints. 24 | func NewClient(hTTPMethod, grpcMethod, grpcStream goa.Endpoint) *Client { 25 | return &Client{ 26 | HTTPMethodEndpoint: hTTPMethod, 27 | GrpcMethodEndpoint: grpcMethod, 28 | GrpcStreamEndpoint: grpcStream, 29 | } 30 | } 31 | 32 | // HTTPMethod calls the "http_method" endpoint of the "test" service. 33 | func (c *Client) HTTPMethod(ctx context.Context, p *Fields) (res *Fields, err error) { 34 | var ires any 35 | ires, err = c.HTTPMethodEndpoint(ctx, p) 36 | if err != nil { 37 | return 38 | } 39 | return ires.(*Fields), nil 40 | } 41 | 42 | // GrpcMethod calls the "grpc_method" endpoint of the "test" service. 43 | func (c *Client) GrpcMethod(ctx context.Context, p *Fields) (res *Fields, err error) { 44 | var ires any 45 | ires, err = c.GrpcMethodEndpoint(ctx, p) 46 | if err != nil { 47 | return 48 | } 49 | return ires.(*Fields), nil 50 | } 51 | 52 | // GrpcStream calls the "grpc_stream" endpoint of the "test" service. 53 | func (c *Client) GrpcStream(ctx context.Context) (res GrpcStreamClientStream, err error) { 54 | var ires any 55 | ires, err = c.GrpcStreamEndpoint(ctx, nil) 56 | if err != nil { 57 | return 58 | } 59 | return ires.(GrpcStreamClientStream), nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/testsvc/gen/test/endpoints.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test endpoints 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package test 9 | 10 | import ( 11 | "context" 12 | 13 | goa "goa.design/goa/v3/pkg" 14 | ) 15 | 16 | // Endpoints wraps the "test" service endpoints. 17 | type Endpoints struct { 18 | HTTPMethod goa.Endpoint 19 | GrpcMethod goa.Endpoint 20 | GrpcStream goa.Endpoint 21 | } 22 | 23 | // GrpcStreamEndpointInput holds both the payload and the server stream of the 24 | // "grpc_stream" method. 25 | type GrpcStreamEndpointInput struct { 26 | // Stream is the server stream used by the "grpc_stream" method to send data. 27 | Stream GrpcStreamServerStream 28 | } 29 | 30 | // NewEndpoints wraps the methods of the "test" service with endpoints. 31 | func NewEndpoints(s Service) *Endpoints { 32 | return &Endpoints{ 33 | HTTPMethod: NewHTTPMethodEndpoint(s), 34 | GrpcMethod: NewGrpcMethodEndpoint(s), 35 | GrpcStream: NewGrpcStreamEndpoint(s), 36 | } 37 | } 38 | 39 | // Use applies the given middleware to all the "test" service endpoints. 40 | func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { 41 | e.HTTPMethod = m(e.HTTPMethod) 42 | e.GrpcMethod = m(e.GrpcMethod) 43 | e.GrpcStream = m(e.GrpcStream) 44 | } 45 | 46 | // NewHTTPMethodEndpoint returns an endpoint function that calls the method 47 | // "http_method" of service "test". 48 | func NewHTTPMethodEndpoint(s Service) goa.Endpoint { 49 | return func(ctx context.Context, req any) (any, error) { 50 | p := req.(*Fields) 51 | return s.HTTPMethod(ctx, p) 52 | } 53 | } 54 | 55 | // NewGrpcMethodEndpoint returns an endpoint function that calls the method 56 | // "grpc_method" of service "test". 57 | func NewGrpcMethodEndpoint(s Service) goa.Endpoint { 58 | return func(ctx context.Context, req any) (any, error) { 59 | p := req.(*Fields) 60 | return s.GrpcMethod(ctx, p) 61 | } 62 | } 63 | 64 | // NewGrpcStreamEndpoint returns an endpoint function that calls the method 65 | // "grpc_stream" of service "test". 66 | func NewGrpcStreamEndpoint(s Service) goa.Endpoint { 67 | return func(ctx context.Context, req any) (any, error) { 68 | ep := req.(*GrpcStreamEndpointInput) 69 | return nil, s.GrpcStream(ctx, ep.Stream) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/testsvc/gen/test/service.go: -------------------------------------------------------------------------------- 1 | // Code generated by goa v3.20.0, DO NOT EDIT. 2 | // 3 | // test service 4 | // 5 | // Command: 6 | // $ goa gen goa.design/clue/internal/testsvc/design 7 | 8 | package test 9 | 10 | import ( 11 | "context" 12 | ) 13 | 14 | // Service is the test service interface. 15 | type Service interface { 16 | // HTTPMethod implements http_method. 17 | HTTPMethod(context.Context, *Fields) (res *Fields, err error) 18 | // GrpcMethod implements grpc_method. 19 | GrpcMethod(context.Context, *Fields) (res *Fields, err error) 20 | // GrpcStream implements grpc_stream. 21 | GrpcStream(context.Context, GrpcStreamServerStream) (err error) 22 | } 23 | 24 | // APIName is the name of the API as defined in the design. 25 | const APIName = "itest" 26 | 27 | // APIVersion is the version of the API as defined in the design. 28 | const APIVersion = "0.0.1" 29 | 30 | // ServiceName is the name of the service as defined in the design. This is the 31 | // same value that is set in the endpoint request contexts under the ServiceKey 32 | // key. 33 | const ServiceName = "test" 34 | 35 | // MethodNames lists the service method names as defined in the design. These 36 | // are the same values that are set in the endpoint request contexts under the 37 | // MethodKey key. 38 | var MethodNames = [3]string{"http_method", "grpc_method", "grpc_stream"} 39 | 40 | // GrpcStreamServerStream is the interface a "grpc_stream" endpoint server 41 | // stream must satisfy. 42 | type GrpcStreamServerStream interface { 43 | // Send streams instances of "Fields". 44 | Send(*Fields) error 45 | // SendWithContext streams instances of "Fields" with context. 46 | SendWithContext(context.Context, *Fields) error 47 | // Recv reads instances of "Fields" from the stream. 48 | Recv() (*Fields, error) 49 | // RecvWithContext reads instances of "Fields" from the stream with context. 50 | RecvWithContext(context.Context) (*Fields, error) 51 | // Close closes the stream. 52 | Close() error 53 | } 54 | 55 | // GrpcStreamClientStream is the interface a "grpc_stream" endpoint client 56 | // stream must satisfy. 57 | type GrpcStreamClientStream interface { 58 | // Send streams instances of "Fields". 59 | Send(*Fields) error 60 | // SendWithContext streams instances of "Fields" with context. 61 | SendWithContext(context.Context, *Fields) error 62 | // Recv reads instances of "Fields" from the stream. 63 | Recv() (*Fields, error) 64 | // RecvWithContext reads instances of "Fields" from the stream with context. 65 | RecvWithContext(context.Context) (*Fields, error) 66 | // Close closes the stream. 67 | Close() error 68 | } 69 | 70 | // Fields is the payload type of the test service http_method method. 71 | type Fields struct { 72 | // String operand 73 | S *string 74 | // Int operand 75 | I *int 76 | } 77 | -------------------------------------------------------------------------------- /internal/testsvc/http_testing.go: -------------------------------------------------------------------------------- 1 | package testsvc 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "testing" 9 | 10 | goahttp "goa.design/goa/v3/http" 11 | 12 | "goa.design/clue/internal/testsvc/gen/http/test/client" 13 | "goa.design/clue/internal/testsvc/gen/http/test/server" 14 | "goa.design/clue/internal/testsvc/gen/test" 15 | ) 16 | 17 | type ( 18 | // HTTPClient is a test service HTTP client. 19 | HTTPClient interface { 20 | HTTPMethod(ctx context.Context, req *Fields) (res *Fields, err error) 21 | } 22 | 23 | // HTTPOption is a function that can be used to configure the HTTP server. 24 | HTTPOption func(*httpOptions) 25 | 26 | httpc struct { 27 | genc *client.Client 28 | } 29 | 30 | httpOptions struct { 31 | fn UnaryFunc 32 | middleware []func(http.Handler) http.Handler 33 | } 34 | ) 35 | 36 | // SetupHTTP instantiates the test gRPC service with the given options. 37 | // It returns a ready-to-use client and a function used to stop the server. 38 | func SetupHTTP(t *testing.T, opts ...HTTPOption) (c HTTPClient, stop func()) { 39 | t.Helper() 40 | 41 | // Configure options 42 | var options httpOptions 43 | for _, opt := range opts { 44 | opt(&options) 45 | } 46 | 47 | // Create test HTTP server 48 | svc := &Service{HTTPFunc: options.fn} 49 | endpoints := test.NewEndpoints(svc) 50 | mux := goahttp.NewMuxer() 51 | svr := server.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil) 52 | server.Mount(mux, svr) 53 | var handler http.Handler = mux 54 | for i := range options.middleware { 55 | handler = options.middleware[len(options.middleware)-(i+1)](handler) 56 | } 57 | httpsvr := httptest.NewServer(handler) 58 | 59 | // Create client 60 | u, _ := url.Parse(httpsvr.URL) 61 | c = httpc{client.NewClient("http", u.Host, http.DefaultClient, goahttp.RequestEncoder, goahttp.ResponseDecoder, false)} 62 | 63 | // Cleanup 64 | stop = func() { 65 | httpsvr.Close() 66 | } 67 | 68 | return 69 | } 70 | 71 | func WithHTTPFunc(fn UnaryFunc) HTTPOption { 72 | return func(opt *httpOptions) { 73 | opt.fn = fn 74 | } 75 | } 76 | 77 | func WithHTTPMiddleware(fn ...func(http.Handler) http.Handler) HTTPOption { 78 | return func(opt *httpOptions) { 79 | opt.middleware = fn 80 | } 81 | } 82 | 83 | // HTTPMethod implements HTTPClient.HTTPMethod using the generated client. 84 | func (c httpc) HTTPMethod(ctx context.Context, req *Fields) (res *Fields, err error) { 85 | rq := &test.Fields{} 86 | if req != nil { 87 | *rq = test.Fields(*req) 88 | } 89 | resp, err := c.genc.HTTPMethod()(ctx, rq) 90 | if err != nil { 91 | return nil, err 92 | } 93 | if resp != nil { 94 | res = &Fields{} 95 | *res = Fields(*(resp.(*test.Fields))) 96 | } 97 | return 98 | } 99 | -------------------------------------------------------------------------------- /internal/testsvc/service.go: -------------------------------------------------------------------------------- 1 | package testsvc 2 | 3 | import ( 4 | "context" 5 | 6 | "goa.design/clue/internal/testsvc/gen/test" 7 | ) 8 | 9 | type ( 10 | UnaryFunc func(context.Context, *Fields) (res *Fields, err error) 11 | StreamFunc func(context.Context, Stream) (err error) 12 | 13 | // Shadow generated type to avoid dependency creep. 14 | Fields test.Fields 15 | Stream interface { 16 | Send(*Fields) error 17 | Recv() (*Fields, error) 18 | Close() error 19 | } 20 | 21 | Service struct { 22 | HTTPFunc UnaryFunc 23 | GRPCFunc UnaryFunc 24 | StreamFunc StreamFunc 25 | } 26 | 27 | adapter struct { 28 | stream test.GrpcStreamServerStream 29 | } 30 | ) 31 | 32 | func (s *Service) HTTPMethod(ctx context.Context, req *test.Fields) (res *test.Fields, err error) { 33 | if s.HTTPFunc == nil { 34 | return 35 | } 36 | 37 | var r *Fields 38 | if req != nil { 39 | r = &Fields{} 40 | *r = Fields(*req) 41 | } 42 | var resp *Fields 43 | resp, err = s.HTTPFunc(ctx, r) 44 | if resp != nil { 45 | res = &test.Fields{} 46 | *res = test.Fields(*resp) 47 | } 48 | return 49 | } 50 | 51 | func (s *Service) GrpcMethod(ctx context.Context, req *test.Fields) (res *test.Fields, err error) { 52 | if s.GRPCFunc == nil { 53 | return 54 | } 55 | 56 | var r *Fields 57 | if req != nil { 58 | r = &Fields{} 59 | *r = Fields(*req) 60 | } 61 | var resp *Fields 62 | resp, err = s.GRPCFunc(ctx, r) 63 | if resp != nil { 64 | res = &test.Fields{} 65 | *res = test.Fields(*resp) 66 | } 67 | return 68 | } 69 | 70 | func (s *Service) GrpcStream(ctx context.Context, stream test.GrpcStreamServerStream) (err error) { 71 | if s.StreamFunc != nil { 72 | return s.StreamFunc(ctx, adapter{stream}) 73 | } 74 | return nil 75 | } 76 | 77 | func (a adapter) Send(fields *Fields) error { 78 | var f *test.Fields 79 | if fields != nil { 80 | f = &test.Fields{} 81 | *f = test.Fields(*fields) 82 | } 83 | return a.stream.Send(f) 84 | } 85 | 86 | func (a adapter) Recv() (*Fields, error) { 87 | f, err := a.stream.Recv() 88 | if f == nil { 89 | return nil, err 90 | } 91 | fields := &Fields{} 92 | *fields = Fields(*f) 93 | return fields, err 94 | } 95 | 96 | func (a adapter) Close() error { 97 | return a.stream.Close() 98 | } 99 | -------------------------------------------------------------------------------- /log/context.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "context" 4 | 5 | const ( 6 | ctxLogger ctxKey = iota + 1 7 | ) 8 | 9 | // Context initializes a context for logging. 10 | func Context(ctx context.Context, opts ...LogOption) context.Context { 11 | l, ok := ctx.Value(ctxLogger).(*logger) 12 | if !ok { 13 | l = &logger{options: defaultOptions()} 14 | } 15 | l.lock.Lock() 16 | defer l.lock.Unlock() 17 | for _, opt := range opts { 18 | opt(l.options) 19 | } 20 | if l.options.disableBuffering != nil && l.options.disableBuffering(ctx) { 21 | l.flush() 22 | } 23 | return context.WithValue(ctx, ctxLogger, l) 24 | } 25 | 26 | // WithContext will inject the second context in the given one. 27 | // 28 | // It is useful when building middleware handlers such as log.HTTP 29 | func WithContext(parentCtx, logCtx context.Context) context.Context { 30 | logger, ok := logCtx.Value(ctxLogger).(*logger) 31 | if !ok { 32 | return parentCtx 33 | } 34 | return context.WithValue(parentCtx, ctxLogger, logger) 35 | } 36 | 37 | // MustContainLogger will panic if the given context is missing the logger. 38 | // 39 | // It can be used during server initialisation when you have a function or 40 | // middleware that you want to ensure receives a context with a logger. 41 | func MustContainLogger(logCtx context.Context) { 42 | _, ok := logCtx.Value(ctxLogger).(*logger) 43 | if !ok { 44 | panic("provided a context without a logger. Use log.Context") 45 | } 46 | } 47 | 48 | // DebugEnabled returns true if the given context has debug logging enabled. 49 | func DebugEnabled(ctx context.Context) bool { 50 | v := ctx.Value(ctxLogger) 51 | if v == nil { 52 | return false 53 | } 54 | l := v.(*logger) 55 | l.lock.Lock() 56 | defer l.lock.Unlock() 57 | return l.options.debug 58 | } 59 | -------------------------------------------------------------------------------- /log/context_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDebugEnabled(t *testing.T) { 11 | ctx := Context(context.Background()) 12 | assert.False(t, DebugEnabled(ctx), "expected debug logs to be disabled") 13 | ctx = Context(ctx, WithDebug()) 14 | assert.True(t, DebugEnabled(ctx), "expected debug logs to be enabled") 15 | } 16 | -------------------------------------------------------------------------------- /log/fields.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type ( 4 | // KV represents a key/value pair. Values must be strings, numbers, 5 | // booleans, nil or a slice of these types. 6 | KV struct { 7 | K string 8 | V interface{} 9 | } 10 | 11 | // Fielder is an interface that will return a slice of KV 12 | Fielder interface { 13 | LogFields() []KV 14 | } 15 | 16 | // Fields allows to quickly define fields for cases where you are OK with 17 | // non-deterministic order of the fields 18 | Fields map[string]interface{} 19 | 20 | kvList []KV 21 | ) 22 | 23 | func (kv KV) LogFields() []KV { 24 | return []KV{kv} 25 | } 26 | 27 | func (f Fields) LogFields() []KV { 28 | fields := make([]KV, 0, len(f)) 29 | for k, v := range f { 30 | fields = append(fields, KV{k, v}) 31 | } 32 | return fields 33 | } 34 | 35 | func (kvs kvList) merge(fielders []Fielder) kvList { 36 | totalLen := len(kvs) 37 | cachedFields := make([][]KV, len(fielders)) 38 | for i, fielder := range fielders { 39 | if _, ok := fielder.(KV); ok { 40 | totalLen++ 41 | } else { 42 | fields := fielder.LogFields() 43 | cachedFields[i] = fields 44 | totalLen += len(fields) 45 | } 46 | } 47 | result := make(kvList, len(kvs), totalLen) 48 | copy(result, kvs) 49 | for i, fielder := range fielders { 50 | if kv, ok := fielder.(KV); ok { 51 | result = append(result, kv) 52 | } else { 53 | result = append(result, cachedFields[i]...) 54 | } 55 | } 56 | return result 57 | } 58 | 59 | func (kvs kvList) LogFields() []KV { 60 | return kvs 61 | } 62 | -------------------------------------------------------------------------------- /log/keys.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | var ( 4 | TraceIDKey = "trace_id" 5 | SpanIDKey = "span_id" 6 | RequestIDKey = "request_id" 7 | MessageKey = "msg" 8 | ErrorMessageKey = "err" 9 | TimestampKey = "time" 10 | SeverityKey = "level" 11 | HTTPMethodKey = "http.method" 12 | HTTPURLKey = "http.url" 13 | HTTPFromKey = "http.remote_addr" 14 | HTTPStatusKey = "http.status" 15 | HTTPDurationKey = "http.time_ms" 16 | HTTPBytesKey = "http.bytes" 17 | HTTPBodyKey = "http.body" 18 | GRPCServiceKey = "grpc.service" 19 | GRPCMethodKey = "grpc.method" 20 | GRPCCodeKey = "grpc.code" 21 | GRPCStatusKey = "grpc.status" 22 | GRPCDurationKey = "grpc.time_ms" 23 | GoaServiceKey = "goa.service" 24 | GoaMethodKey = "goa.method" 25 | ) 26 | -------------------------------------------------------------------------------- /log/options_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "os" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "go.opentelemetry.io/otel" 12 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 13 | "go.opentelemetry.io/otel/sdk/trace" 14 | ) 15 | 16 | func TestDefaultOptions(t *testing.T) { 17 | opts := defaultOptions() 18 | assert.Equal(t, fmt.Sprintf("%p", opts.disableBuffering), fmt.Sprintf("%p", IsTracing)) 19 | assert.False(t, opts.debug) 20 | assert.Equal(t, opts.w, os.Stdout) 21 | assert.Equal(t, fmt.Sprintf("%p", opts.format), fmt.Sprintf("%p", FormatText)) 22 | assert.Equal(t, opts.maxsize, DefaultMaxSize) 23 | } 24 | 25 | func TestWithDisableBuffering(t *testing.T) { 26 | opts := defaultOptions() 27 | disable := func(ctx context.Context) bool { return true } 28 | WithDisableBuffering(disable)(opts) 29 | assert.Equal(t, fmt.Sprintf("%p", opts.disableBuffering), fmt.Sprintf("%p", disable)) 30 | } 31 | 32 | func TestWithDebug(t *testing.T) { 33 | opts := defaultOptions() 34 | WithDebug()(opts) 35 | assert.True(t, opts.debug) 36 | } 37 | 38 | func TestWithNoDebug(t *testing.T) { 39 | opts := defaultOptions() 40 | WithDebug()(opts) 41 | WithNoDebug()(opts) 42 | assert.False(t, opts.debug) 43 | } 44 | 45 | func TestWithOutput(t *testing.T) { 46 | opts := defaultOptions() 47 | w := io.Discard 48 | WithOutput(w)(opts) 49 | assert.Equal(t, opts.w, w) 50 | } 51 | 52 | func TestWithFormat(t *testing.T) { 53 | opts := defaultOptions() 54 | WithFormat(FormatJSON)(opts) 55 | assert.Equal(t, fmt.Sprintf("%p", opts.format), fmt.Sprintf("%p", FormatJSON)) 56 | } 57 | 58 | func TestWithMaxSize(t *testing.T) { 59 | opts := defaultOptions() 60 | WithMaxSize(10)(opts) 61 | assert.Equal(t, opts.maxsize, 10) 62 | } 63 | 64 | func TestIsTracing(t *testing.T) { 65 | if IsTracing(context.Background()) { 66 | t.Errorf("expected IsTracing to return false") 67 | } 68 | exp, _ := stdouttrace.New(stdouttrace.WithWriter(io.Discard)) 69 | tp := trace.NewTracerProvider(trace.WithBatcher(exp)) 70 | defer tp.Shutdown(context.Background()) // nolint:errcheck 71 | otel.SetTracerProvider(tp) 72 | ctx, span := otel.Tracer("test").Start(context.Background(), "test") 73 | defer span.End() 74 | assert.True(t, IsTracing(ctx)) 75 | } 76 | 77 | func TestIsTerminal(t *testing.T) { 78 | if IsTerminal() { 79 | t.Errorf("expected IsTerminal to return false") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /log/span.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/otel/trace" 7 | ) 8 | 9 | // Span is a log key/value pair generator function that can be used to log trace 10 | // and span IDs. Usage: 11 | // 12 | // ctx := log.Context(ctx, WithFunc(log.Span)) 13 | // log.Printf(ctx, "message") 14 | // 15 | // Output: trace_id= span_id= message 16 | func Span(ctx context.Context) (kvs []KV) { 17 | spanContext := trace.SpanFromContext(ctx).SpanContext() 18 | if spanContext.IsValid() { 19 | kvs = append(kvs, KV{K: TraceIDKey, V: spanContext.TraceID().String()}) 20 | kvs = append(kvs, KV{K: SpanIDKey, V: spanContext.SpanID().String()}) 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /log/span_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "go.opentelemetry.io/otel/trace" 9 | ) 10 | 11 | func TestSpan(t *testing.T) { 12 | // Create a mock span context 13 | traceID := trace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 14 | spanID := trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8} 15 | spanContext := trace.NewSpanContext(trace.SpanContextConfig{ 16 | TraceID: traceID, 17 | SpanID: spanID, 18 | TraceFlags: trace.FlagsSampled, 19 | }) 20 | 21 | // Create a context with the mock span context 22 | ctx := trace.ContextWithSpanContext(context.Background(), spanContext) 23 | 24 | // Call the Span function 25 | kvs := Span(ctx) 26 | 27 | // Assert that the expected key-value pairs are returned 28 | assert.Equal(t, []KV{ 29 | {K: TraceIDKey, V: "0102030405060708090a0b0c0d0e0f10"}, 30 | {K: SpanIDKey, V: "0102030405060708"}, 31 | }, kvs) 32 | } 33 | -------------------------------------------------------------------------------- /mock/cmd/cmg/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "goa.design/clue/log" 10 | cluemockgen "goa.design/clue/mock/cmd/cmg/pkg" 11 | ) 12 | 13 | func main() { 14 | var ( 15 | gSet = flag.NewFlagSet("global", flag.ExitOnError) 16 | debug, testify, help, h *bool 17 | addGlobals = func(set *flag.FlagSet) { 18 | debug = set.Bool("debug", false, "Print debug output") 19 | testify = set.Bool("testify", false, "Use github.com/stretchr/testify for assertions") 20 | help = set.Bool("help", false, "Print help information") 21 | h = set.Bool("h", false, "Print help information") 22 | } 23 | 24 | genSet = flag.NewFlagSet("gen", flag.ExitOnError) 25 | 26 | versionSet = flag.NewFlagSet("version", flag.ExitOnError) 27 | 28 | showUsage = func(code int) { 29 | printUsage(gSet) 30 | os.Exit(code) 31 | } 32 | ) 33 | 34 | addGlobals(gSet) 35 | 36 | if len(os.Args) == 1 { 37 | showUsage(1) 38 | } 39 | 40 | var ( 41 | cmd = os.Args[1] 42 | args []string 43 | ) 44 | switch cmd { 45 | case "gen": 46 | addGlobals(genSet) 47 | _ = genSet.Parse(os.Args[2:]) 48 | args = genSet.Args() 49 | case "version": 50 | addGlobals(versionSet) 51 | _ = versionSet.Parse(os.Args[2:]) 52 | args = versionSet.Args() 53 | case "help": 54 | showUsage(0) 55 | default: 56 | _ = gSet.Parse(os.Args[1:]) 57 | } 58 | 59 | if *h || *help { 60 | showUsage(0) 61 | } 62 | 63 | switch cmd { 64 | case "gen": 65 | ctx := context.Background() 66 | if *debug { 67 | ctx = log.Context(ctx, log.WithDebug()) 68 | } else { 69 | ctx = log.Context(ctx, log.WithDisableBuffering(func(ctx context.Context) bool { return true })) 70 | } 71 | err := cluemockgen.Generate(ctx, args, "", *testify) 72 | if err != nil { 73 | os.Exit(1) 74 | } 75 | case "version": 76 | fmt.Println(os.Args[0], "version", cluemockgen.Version()) 77 | default: 78 | fmt.Fprintf(os.Stderr, `unknown command %q, use "--help" for usage`, cmd) 79 | os.Exit(1) 80 | } 81 | } 82 | 83 | func printUsage(fss ...*flag.FlagSet) { 84 | cmd := os.Args[0] 85 | fmt.Fprintf(os.Stderr, `%v is the Clue Mock Generation tool for the Goa framework. 86 | 87 | Usage: 88 | %v gen PACKAGE... 89 | %v version 90 | 91 | Commands: 92 | gen 93 | Generate mocks for interfaces in packages 94 | version 95 | Print version information 96 | 97 | Args: 98 | PACKAGE 99 | Go import path(s) to look for interfaces 100 | 101 | Flags: 102 | `, cmd, cmd, cmd) 103 | for _, fs := range fss { 104 | fs.PrintDefaults() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/_tests/conflicts/conflicts.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | type ( 4 | Conflicts interface { 5 | Simple(c *Conflicts) *Conflicts 6 | AddSimple() 7 | SetSimple() 8 | HasMore() 9 | HasMoreMock() 10 | } 11 | ) 12 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/_tests/extensive/aliased/imported.go: -------------------------------------------------------------------------------- 1 | package imported 2 | 3 | type ( 4 | Type byte 5 | 6 | Interface interface { 7 | Imported(Type) Type 8 | } 9 | 10 | Generic[T any] interface { 11 | ImportedGeneric(T) T 12 | } 13 | ) 14 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/_tests/extensive/extensive.go: -------------------------------------------------------------------------------- 1 | package extensive 2 | 3 | import ( 4 | "io" 5 | "unsafe" 6 | 7 | goa "goa.design/goa/v3/pkg" 8 | 9 | imported "example.com/c/d/extensive/aliased" 10 | ) 11 | 12 | type ( 13 | Extensive interface { 14 | Simple(int, string) float64 15 | NoResult() 16 | MultipleResults() (bool, complex64, complex128, string, unsafe.Pointer, error) 17 | NamedResult() (err error) 18 | RepeatedTypes(a, b int, c, d float64) (e, f int, g, h float64, err error) 19 | Variadic(args ...string) 20 | ComplexTypes([5]string, []string, map[string]string, *string, chan int, chan<- int, <-chan int) ([5]string, []string, map[string]string, *string, chan int, chan<- int, <-chan int) 21 | MoreComplexTypes(interface{}, interface { 22 | io.ReadWriter 23 | A(int) error 24 | B() 25 | }, struct { 26 | Struct 27 | A, B int 28 | C float64 29 | }, func(int) (bool, error)) (interface{}, interface { 30 | io.ReadWriter 31 | A(int) error 32 | B() 33 | }, struct { 34 | Struct 35 | A, B int 36 | C float64 37 | }, func(int) (bool, error)) 38 | NamedTypes(Struct, Array, io.Reader, imported.Type, goa.Endpoint, Generic[uint, string, Struct, Array]) (Struct, Array, io.Reader, imported.Type, goa.Endpoint, Generic[uint, string, Struct, Array]) 39 | FuncNamedTypes(func(Struct, Array, io.Reader, imported.Type, goa.Endpoint, Generic[uint, string, Struct, Array])) func(Struct, Array, io.Reader, imported.Type, goa.Endpoint, Generic[uint, string, Struct, Array]) 40 | VariableConflicts(f, m uint) 41 | AliasedTypes(IntAlias, ArrayAlias, StructAlias, IntSetAlias, SetAlias[string]) (IntAlias, ArrayAlias, StructAlias, IntSetAlias, SetAlias[string]) 42 | AliasedFuncTypes(func(IntAlias, ArrayAlias, StructAlias, IntSetAlias, SetAlias[string])) func(IntAlias, ArrayAlias, StructAlias, IntSetAlias, SetAlias[string]) 43 | 44 | Embedded 45 | imported.Interface 46 | 47 | EmbeddedGeneric[uint16, uint32] 48 | imported.Generic[rune] 49 | } 50 | 51 | Embedded interface { 52 | Embedded(int8) int8 53 | } 54 | 55 | EmbeddedGeneric[X, Y any] interface { 56 | EmbeddedGeneric(x X, y Y) (X, Y) 57 | } 58 | 59 | Generic[K comparable, V ~int | bool | string, X, Y any] interface { 60 | Simple(k K, v V, x X, y Y) (K, V, X, Y) 61 | Complex(map[K]V, []X, *Y, Set[K]) (map[K]V, []X, *Y, Set[K]) 62 | 63 | EmbeddedGeneric[X, Y] 64 | imported.Generic[Y] 65 | } 66 | 67 | Struct struct{ A, B int } 68 | Array [5]Struct 69 | Set[K comparable] map[K]Struct 70 | IntAlias = int 71 | ArrayAlias = [5]Struct 72 | StructAlias = struct{ A, B int } 73 | IntSetAlias = Set[int] 74 | SetAlias[K comparable] = Set[K] 75 | 76 | ExtensiveAlias = Extensive 77 | ImportedAlias = imported.Interface 78 | GenericAlias[K comparable, V ~int | bool | string, X, Y any] = Generic[K, V, X, Y] 79 | ConstrainedGenericAlias = Generic[string, IntAlias, float32, float64] 80 | ) 81 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/_tests/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/c/d 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.0 6 | 7 | require goa.design/goa/v3 v3.20.0 8 | 9 | require github.com/google/uuid v1.6.0 // indirect 10 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/_tests/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 2 | goa.design/goa/v3 v3.20.0 h1:mYYNqCBg9SSxe2jxvPJFOPmJqqKkSAUSU84jpczky3s= 3 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/_tests/testify/mocks/testify.go: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator TEST VERSION, DO NOT EDIT. 2 | // 3 | // Command: 4 | // $ cmg gen example.com/c/d/testify 5 | 6 | package mocktestify 7 | 8 | import ( 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "goa.design/clue/mock" 13 | 14 | "example.com/c/d/testify" 15 | ) 16 | 17 | type ( 18 | Testify struct { 19 | m *mock.Mock 20 | assert *assert.Assertions 21 | } 22 | 23 | TestifySimpleFunc func(a, b int) bool 24 | ) 25 | 26 | func NewTestify(t *testing.T) *Testify { 27 | var ( 28 | m = &Testify{mock.New(), assert.New(t)} 29 | _ testify.Testify = m 30 | ) 31 | return m 32 | } 33 | 34 | func (m *Testify) AddSimple(f TestifySimpleFunc) { 35 | m.m.Add("Simple", f) 36 | } 37 | 38 | func (m *Testify) SetSimple(f TestifySimpleFunc) { 39 | m.m.Set("Simple", f) 40 | } 41 | 42 | func (m *Testify) Simple(a, b int) bool { 43 | if f := m.m.Next("Simple"); f != nil { 44 | return f.(TestifySimpleFunc)(a, b) 45 | } 46 | m.assert.Fail("unexpected Simple call") 47 | return false 48 | } 49 | 50 | func (m *Testify) HasMore() bool { 51 | return m.m.HasMore() 52 | } 53 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/_tests/testify/testify.go: -------------------------------------------------------------------------------- 1 | package testify 2 | 3 | type ( 4 | Testify interface { 5 | Simple(a, b int) bool 6 | } 7 | ) 8 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/import.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "path" 7 | "strings" 8 | ) 9 | 10 | type ( 11 | Import interface { 12 | PkgName() string 13 | PkgPath() string 14 | Alias() string 15 | AliasOrPkgName() string 16 | } 17 | 18 | importMap map[string]Import 19 | 20 | importImpl struct { 21 | pkgPath, pkgName, alias string 22 | } 23 | ) 24 | 25 | func newImport(pkgPath string, args ...string) Import { 26 | var pkgName, alias string 27 | switch len(args) { 28 | case 0: 29 | pkgName = path.Base(pkgPath) 30 | case 1: 31 | pkgName = args[0] 32 | if pkgName != path.Base(pkgPath) { 33 | alias = pkgName 34 | } 35 | case 2: 36 | pkgName = args[0] 37 | alias = args[1] 38 | } 39 | return &importImpl{pkgPath: pkgPath, pkgName: pkgName, alias: alias} 40 | } 41 | 42 | func (i *importImpl) PkgName() string { 43 | return i.pkgName 44 | } 45 | 46 | func (i *importImpl) PkgPath() string { 47 | return i.pkgPath 48 | } 49 | 50 | func (i *importImpl) Alias() string { 51 | return i.alias 52 | } 53 | 54 | func (i *importImpl) AliasOrPkgName() string { 55 | if i.alias != "" { 56 | return i.alias 57 | } 58 | return i.pkgName 59 | } 60 | 61 | func addImport(pkgImport Import, stdImports, extImports, intImports importMap, modPath string) Import { 62 | var ( 63 | allImports = []map[string]Import{stdImports, extImports, intImports} 64 | pkgName = pkgImport.PkgName() 65 | pkgPath = pkgImport.PkgPath() 66 | dup = false 67 | ) 68 | for _, imports := range allImports { 69 | if i, ok := imports[pkgName]; ok { 70 | if i.PkgPath() == pkgPath { 71 | return i 72 | } 73 | dup = true 74 | } 75 | } 76 | alias := pkgName 77 | if dup { 78 | aliases: 79 | for i := 1; i <= math.MaxInt; i++ { 80 | alias = fmt.Sprintf("%v%v", pkgName, i) 81 | for _, imports := range allImports { 82 | if i, ok := imports[alias]; ok { 83 | if i.PkgPath() == pkgPath { 84 | return i 85 | } 86 | continue aliases 87 | } 88 | } 89 | pkgImport = newImport(pkgPath, pkgName, alias) 90 | break 91 | } 92 | } 93 | if !strings.Contains(pkgPath, ".") { 94 | stdImports[alias] = pkgImport 95 | } else if pkgPath == modPath || strings.HasPrefix(pkgPath, modPath+"/") { 96 | intImports[alias] = pkgImport 97 | } else { 98 | extImports[alias] = pkgImport 99 | } 100 | return pkgImport 101 | } 102 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/mocks.go.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by Clue Mock Generator {{ .ToolVersion }}, DO NOT EDIT. 2 | // 3 | // Command: 4 | // {{ .ToolCommandLine }} 5 | 6 | package {{ .PkgName }} 7 | 8 | import ( 9 | {{- range .StdImports }} 10 | {{ if .Alias }}{{ .Alias }} {{ end }}"{{ .PkgPath }}" 11 | {{- end }} 12 | {{ range .ExtImports }} 13 | {{ if .Alias }}{{ .Alias }} {{ end }}"{{ .PkgPath }}" 14 | {{- end }} 15 | {{ range .IntImports }} 16 | {{ if .Alias }}{{ .Alias }} {{ end }}"{{ .PkgPath }}" 17 | {{- end }} 18 | ) 19 | 20 | {{ $testify := .Testify -}} 21 | type ( 22 | {{- range $index, $interface := .Interfaces }} 23 | {{- if ge $index 1 }} 24 | {{ end }} 25 | {{ .Name }}{{ .TypeParameters }} struct { 26 | {{- if $testify }} 27 | m *mock.Mock 28 | assert *assert.Assertions 29 | {{- else }} 30 | m *mock.Mock 31 | t *testing.T 32 | {{- end }} 33 | } 34 | {{ range .Methods }} 35 | {{ printf "%v%v" .Func $interface.TypeParameters | printf $interface.MaxFuncLenFmt }} func({{ .Parameters }}){{ if .Results }} {{ .Results }}{{ end }} 36 | {{- end }} 37 | {{- end }} 38 | ) 39 | {{ $import := .PkgImport -}} 40 | {{- range $index, $interface := .Interfaces }} 41 | {{- if ge $index 1 }} 42 | {{ end }} 43 | func {{ .Constructor }}{{ .TypeParameters }}(t *testing.T) *{{ .Name }}{{ .TypeParameterVars }} { 44 | var ( 45 | {{ .Var | printf ($import.AliasOrPkgName | .ConstructorFmt) }} = &{{ .Name }}{{ .TypeParameterVars }}{mock.New(), {{ if $testify }}assert.New(t){{ else }}t{{ end }}} 46 | _ {{ $import.AliasOrPkgName }}.{{ .Name }}{{ .TypeParameterVars }} = m 47 | ) 48 | return {{ .Var }} 49 | } 50 | {{ range $index, $method := .Methods }} 51 | {{- if ge $index 1 }} 52 | {{ end }} 53 | func ({{ $interface.Var }} *{{ $interface.Name }}{{ $interface.TypeParameterVars }}) {{ .Add }}(f {{ .Func }}{{ $interface.TypeParameterVars }}) { 54 | {{ $interface.Var }}.m.Add("{{ .Name }}", f) 55 | } 56 | 57 | func ({{ $interface.Var }} *{{ $interface.Name }}{{ $interface.TypeParameterVars }}) {{ .Set }}(f {{ .Func }}{{ $interface.TypeParameterVars }}) { 58 | {{ $interface.Var }}.m.Set("{{ .Name }}", f) 59 | } 60 | 61 | func ({{ .InterfaceVar }} *{{ $interface.Name }}{{ $interface.TypeParameterVars }}) {{ .Name }}({{ .Parameters }}){{ if .Results }} {{ .Results }}{{ end }} { 62 | if {{ .FuncVar }} := {{ .InterfaceVar }}.m.Next("{{ .Name }}"); {{ .FuncVar }} != nil { 63 | {{ if .Results }}return {{ end }}{{ .FuncVar }}.({{ .Func }}{{ $interface.TypeParameterVars }})({{ .ParameterVars }}) 64 | {{- if not .Results }} 65 | return 66 | {{- end }} 67 | } 68 | {{- if $testify }} 69 | {{ .InterfaceVar }}.assert.Fail("unexpected {{ .Name }} call") 70 | {{- else }} 71 | {{ .InterfaceVar }}.t.Helper() 72 | {{ .InterfaceVar }}.t.Error("unexpected {{ .Name }} call") 73 | {{- end }} 74 | {{- if .ZeroResults }} 75 | return {{ .ZeroResults }} 76 | {{- end }} 77 | } 78 | {{- end }} 79 | 80 | func ({{ $interface.Var }} *{{ $interface.Name }}{{ $interface.TypeParameterVars }}) {{ .HasMore }}() bool { 81 | return {{ $interface.Var }}.m.HasMore() 82 | } 83 | {{- end }} 84 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/mocks_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | 13 | "goa.design/clue/mock/cmd/cmg/pkg/parse" 14 | ) 15 | 16 | var updateGolden = false 17 | 18 | func init() { 19 | flag.BoolVar(&updateGolden, "update-golden", false, "update golden files") 20 | } 21 | 22 | func TestMocks_Render(t *testing.T) { 23 | cases := []struct { 24 | Name, Pattern string 25 | ExpectedFiles []string 26 | Testify bool 27 | }{ 28 | { 29 | Name: "extensive", 30 | Pattern: "./extensive", 31 | ExpectedFiles: []string{"extensive.go"}, 32 | }, 33 | { 34 | Name: "conflicts", 35 | Pattern: "./conflicts", 36 | ExpectedFiles: []string{"conflicts.go"}, 37 | }, 38 | { 39 | Name: "testify", 40 | Pattern: "./testify", 41 | ExpectedFiles: []string{"testify.go"}, 42 | Testify: true, 43 | }, 44 | } 45 | 46 | for _, tc := range cases { 47 | t.Run(tc.Name, func(t *testing.T) { 48 | t.Parallel() 49 | 50 | assert := assert.New(t) 51 | require := require.New(t) 52 | 53 | ps, err := parse.LoadPackages([]string{tc.Pattern}, "_tests") 54 | require.NoError(err) 55 | require.Len(ps, 1) 56 | p := ps[0] 57 | 58 | is, err := p.Interfaces() 59 | require.NoError(err) 60 | 61 | interfacesByFile := make(map[string][]parse.Interface) 62 | for _, i := range is { 63 | f := filepath.Base(i.File()) 64 | interfacesByFile[f] = append(interfacesByFile[f], i) 65 | } 66 | 67 | mocksDir := filepath.Join("_tests", tc.Pattern, "mocks") 68 | 69 | if updateGolden { 70 | require.NoError(os.MkdirAll(mocksDir, 0750)) 71 | 72 | for f, is := range interfacesByFile { 73 | of, err := os.OpenFile(filepath.Join(mocksDir, f), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0640) 74 | require.NoError(err) 75 | t.Cleanup(func() { assert.NoError(of.Close()) }) 76 | 77 | m := NewMocks("mock", p, is, toolVersion, tc.Testify) 78 | require.NoError(m.Render(of)) 79 | } 80 | 81 | return 82 | } 83 | 84 | var files []string 85 | for f := range interfacesByFile { 86 | files = append(files, f) 87 | } 88 | assert.ElementsMatch(tc.ExpectedFiles, files) 89 | 90 | for f, is := range interfacesByFile { 91 | f := filepath.Join(mocksDir, f) 92 | m := NewMocks("mock", p, is, toolVersion, tc.Testify) 93 | b := &bytes.Buffer{} 94 | 95 | err := m.Render(b) 96 | if assert.NoError(err) && assert.FileExists(f) { 97 | expected, err := os.ReadFile(f) 98 | if assert.NoError(err) { 99 | assert.Equal(string(expected), b.String()) 100 | } 101 | } 102 | } 103 | }) 104 | } 105 | } 106 | 107 | func toolVersion() string { 108 | return "TEST VERSION" 109 | } 110 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/generate/type_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "go/types" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type ( 11 | fakeType struct { 12 | Name string 13 | } 14 | ) 15 | 16 | func (t *fakeType) Underlying() types.Type { 17 | return t 18 | } 19 | 20 | func (t *fakeType) String() string { 21 | return t.Name 22 | } 23 | 24 | func TestTypeAdder_name(t *testing.T) { 25 | cases := []struct { 26 | Name, ExpectedPanic string 27 | Type types.Type 28 | }{ 29 | { 30 | Name: "unhandled type", 31 | Type: &fakeType{"unhandled"}, 32 | ExpectedPanic: `unknown name for type: &generate.fakeType{Name:"unhandled"} (*generate.fakeType)`, 33 | }, 34 | } 35 | 36 | for _, tc := range cases { 37 | t.Run(tc.Name, func(t *testing.T) { 38 | t.Parallel() 39 | 40 | assert := assert.New(t) 41 | 42 | ta := typeAdder{} 43 | 44 | if tc.ExpectedPanic != "" { 45 | assert.PanicsWithError(tc.ExpectedPanic, func() { 46 | ta.name(tc.Type) 47 | }) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func TestTypeAdder_zero(t *testing.T) { 54 | cases := []struct { 55 | Name, ExpectedPanic string 56 | Type types.Type 57 | }{ 58 | { 59 | Name: "unhandled type", 60 | Type: &fakeType{"unhandled"}, 61 | ExpectedPanic: `unknown zero for type: &generate.fakeType{Name:"unhandled"} (*generate.fakeType)`, 62 | }, 63 | } 64 | 65 | for _, tc := range cases { 66 | t.Run(tc.Name, func(t *testing.T) { 67 | t.Parallel() 68 | 69 | assert := assert.New(t) 70 | 71 | ta := typeAdder{} 72 | 73 | if tc.ExpectedPanic != "" { 74 | assert.PanicsWithError(tc.ExpectedPanic, func() { 75 | ta.zero(tc.Type) 76 | }) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/_tests/doer/doer.go: -------------------------------------------------------------------------------- 1 | package doer 2 | 3 | import ( 4 | "example.com/a/b/external" 5 | ) 6 | 7 | type ( 8 | Doer interface { 9 | Do(a, b int, c float64) (d, e int, err error) 10 | } 11 | 12 | EmbeddedDoer interface { 13 | Doer 14 | } 15 | 16 | ExternalEmbeddedDoer interface { 17 | external.Doer 18 | } 19 | 20 | doer interface { //nolint:unused 21 | do(a, b int, c float64) (d, e int, err error) 22 | } 23 | 24 | DoerAlias = Doer 25 | EmbeddedDoerAlias = EmbeddedDoer 26 | ExternalEmbeddedDoerAlias = ExternalEmbeddedDoer 27 | ExternalDoerAlias = external.Doer 28 | DoerAliasAlias = DoerAlias 29 | ) 30 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/_tests/external/doer.go: -------------------------------------------------------------------------------- 1 | package external 2 | 3 | type ( 4 | Doer interface { 5 | Do(a, b int, c float64) (d, e int, err error) 6 | } 7 | ) 8 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/_tests/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/a/b 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/interface.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | 7 | "golang.org/x/tools/go/packages" 8 | ) 9 | 10 | type ( 11 | Interface interface { 12 | Name() string 13 | IsExported() bool 14 | File() string 15 | TypeParameters() []Type 16 | Methods() []Method 17 | } 18 | 19 | interfaceImpl struct { 20 | p *packages.Package 21 | file string 22 | typeSpec *ast.TypeSpec 23 | interfaceType *ast.InterfaceType 24 | } 25 | 26 | interfaceAlias struct { 27 | Interface 28 | aliasedInterface *types.Interface 29 | } 30 | ) 31 | 32 | func newInterface(p *packages.Package, file string, typeSpec *ast.TypeSpec, interfaceType *ast.InterfaceType) Interface { 33 | return &interfaceImpl{p: p, file: file, typeSpec: typeSpec, interfaceType: interfaceType} 34 | } 35 | 36 | func (i *interfaceImpl) Name() string { 37 | return i.typeSpec.Name.Name 38 | } 39 | 40 | func (i *interfaceImpl) IsExported() bool { 41 | return i.typeSpec.Name.IsExported() 42 | } 43 | 44 | func (i *interfaceImpl) File() string { 45 | return i.file 46 | } 47 | 48 | func (i *interfaceImpl) TypeParameters() (typeParameters []Type) { 49 | if i.typeSpec.TypeParams != nil { 50 | for _, tp := range i.typeSpec.TypeParams.List { 51 | for _, ident := range tp.Names { 52 | typeParameters = append(typeParameters, newType(i.p, ident, tp.Type)) 53 | } 54 | } 55 | } 56 | return 57 | } 58 | 59 | func (i *interfaceImpl) Methods() []Method { 60 | return i.methods(i.interfaceType) 61 | } 62 | 63 | func (i *interfaceImpl) methods(it *ast.InterfaceType) (methods []Method) { 64 | for _, m := range it.Methods.List { 65 | switch t := m.Type.(type) { 66 | case *ast.FuncType: 67 | for _, n := range m.Names { 68 | o, _, _ := types.LookupFieldOrMethod(i.p.Types.Scope().Lookup(i.Name()).Type(), true, i.p.Types, n.Name) 69 | methods = append(methods, newASTMethod(i.p, n, t, o.Type().Underlying().(*types.Signature).Variadic())) 70 | } 71 | case *ast.Ident: 72 | switch dt := t.Obj.Decl.(type) { 73 | case *ast.TypeSpec: 74 | switch t := dt.Type.(type) { 75 | case *ast.InterfaceType: 76 | methods = append(methods, i.methods(t)...) 77 | } 78 | } 79 | case *ast.SelectorExpr, *ast.IndexExpr, *ast.IndexListExpr: 80 | if tv, ok := i.p.TypesInfo.Types[t]; ok { 81 | if ti, ok := tv.Type.Underlying().(*types.Interface); ok { 82 | for m := range ti.Methods() { 83 | methods = append(methods, newTypesMethod(m)) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | return 90 | } 91 | 92 | func newInterfaceAlias(p *packages.Package, file string, typeSpec *ast.TypeSpec, aliasedInterface *types.Interface) Interface { 93 | return &interfaceAlias{ 94 | Interface: newInterface(p, file, typeSpec, nil), 95 | aliasedInterface: aliasedInterface, 96 | } 97 | } 98 | 99 | func (i *interfaceAlias) Methods() (methods []Method) { 100 | for m := range i.aliasedInterface.Methods() { 101 | methods = append(methods, newTypesMethod(m)) 102 | } 103 | return 104 | } 105 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/method.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | 7 | "golang.org/x/tools/go/packages" 8 | ) 9 | 10 | type ( 11 | Method interface { 12 | Name() string 13 | IsExported() bool 14 | Parameters() []Value 15 | Results() []Value 16 | Variadic() bool 17 | } 18 | 19 | astMethod struct { 20 | p *packages.Package 21 | ident *ast.Ident 22 | funcType *ast.FuncType 23 | variadic bool 24 | } 25 | 26 | typesMethod struct { 27 | f *types.Func 28 | s *types.Signature 29 | } 30 | ) 31 | 32 | func newASTMethod(p *packages.Package, ident *ast.Ident, funcType *ast.FuncType, variadic bool) Method { 33 | return &astMethod{p: p, ident: ident, funcType: funcType, variadic: variadic} 34 | } 35 | 36 | func (am *astMethod) Name() string { 37 | return am.ident.Name 38 | } 39 | 40 | func (am *astMethod) IsExported() bool { 41 | return am.ident.IsExported() 42 | } 43 | 44 | func (am *astMethod) Parameters() (parameters []Value) { 45 | for _, p := range am.funcType.Params.List { 46 | idents := []*ast.Ident{nil} 47 | if p.Names != nil { 48 | idents = p.Names 49 | } 50 | for _, ident := range idents { 51 | parameters = append(parameters, newASTValue(am.p, ident, p.Type)) 52 | } 53 | } 54 | return 55 | } 56 | 57 | func (am *astMethod) Results() (results []Value) { 58 | if am.funcType.Results != nil { 59 | for _, r := range am.funcType.Results.List { 60 | idents := []*ast.Ident{nil} 61 | if r.Names != nil { 62 | idents = r.Names 63 | } 64 | for _, ident := range idents { 65 | results = append(results, newASTValue(am.p, ident, r.Type)) 66 | } 67 | } 68 | } 69 | return 70 | } 71 | 72 | func (am *astMethod) Variadic() bool { 73 | return am.variadic 74 | } 75 | 76 | func newTypesMethod(f *types.Func) Method { 77 | return &typesMethod{f: f, s: f.Type().(*types.Signature)} 78 | } 79 | 80 | func (tm *typesMethod) Name() string { 81 | return tm.f.Name() 82 | } 83 | 84 | func (tm *typesMethod) IsExported() bool { 85 | return tm.f.Exported() 86 | } 87 | 88 | func (tm *typesMethod) Parameters() (parameters []Value) { 89 | for i := 0; i < tm.s.Params().Len(); i++ { 90 | parameters = append(parameters, newTypesValue(tm.s.Params().At(i))) 91 | } 92 | return 93 | } 94 | 95 | func (tm *typesMethod) Results() (results []Value) { 96 | for i := 0; i < tm.s.Results().Len(); i++ { 97 | results = append(results, newTypesValue(tm.s.Results().At(i))) 98 | } 99 | return 100 | } 101 | 102 | func (tm *typesMethod) Variadic() bool { 103 | return tm.s.Variadic() 104 | } 105 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/package.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "go/ast" 5 | "go/token" 6 | "go/types" 7 | 8 | "golang.org/x/tools/go/packages" 9 | ) 10 | 11 | type ( 12 | Package interface { 13 | Name() string 14 | PkgPath() string 15 | ModPath() string 16 | Interfaces() ([]Interface, error) 17 | } 18 | 19 | packageImpl struct { 20 | p *packages.Package 21 | } 22 | 23 | interfaceVisitor struct { 24 | p *packages.Package 25 | file string 26 | interfaces []Interface 27 | } 28 | ) 29 | 30 | func LoadPackages(patterns []string, dir string) ([]Package, error) { 31 | c := &packages.Config{ 32 | Dir: dir, 33 | Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedModule, 34 | } 35 | 36 | ps, err := packages.Load(c, patterns...) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | packages := make([]Package, len(ps)) 42 | for i, p := range ps { 43 | packages[i] = &packageImpl{p: p} 44 | } 45 | 46 | return packages, nil 47 | } 48 | 49 | func (p *packageImpl) Name() string { 50 | return p.p.Name 51 | } 52 | 53 | func (p *packageImpl) PkgPath() string { 54 | return p.p.PkgPath 55 | } 56 | 57 | func (p *packageImpl) ModPath() string { 58 | if p.p.Module != nil { 59 | return p.p.Module.Path 60 | } 61 | return "" 62 | } 63 | 64 | func (p *packageImpl) Interfaces() ([]Interface, error) { 65 | if len(p.p.Errors) > 0 { 66 | return nil, p.p.Errors[0] 67 | } 68 | 69 | var interfaces []Interface 70 | for i, gf := range p.p.GoFiles { 71 | iv := &interfaceVisitor{p: p.p, file: gf} 72 | ast.Walk(iv, p.p.Syntax[i]) 73 | interfaces = append(interfaces, iv.interfaces...) 74 | } 75 | 76 | return interfaces, nil 77 | } 78 | 79 | func (iv *interfaceVisitor) Visit(node ast.Node) ast.Visitor { 80 | if n, ok := node.(*ast.TypeSpec); ok { 81 | switch t := n.Type.(type) { 82 | case *ast.InterfaceType: 83 | iv.interfaces = append(iv.interfaces, newInterface(iv.p, iv.file, n, t)) 84 | default: 85 | if n.Assign != token.NoPos { 86 | underlying := iv.p.TypesInfo.Types[t].Type.Underlying() 87 | if u, ok := underlying.(*types.Interface); ok { 88 | iv.interfaces = append(iv.interfaces, newInterfaceAlias(iv.p, iv.file, n, u)) 89 | } 90 | } 91 | } 92 | } 93 | return iv 94 | } 95 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/type.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | 7 | "golang.org/x/tools/go/packages" 8 | ) 9 | 10 | type ( 11 | Type interface { 12 | Name() string 13 | Constraint() types.Type 14 | } 15 | 16 | typeImpl struct { 17 | p *packages.Package 18 | ident *ast.Ident 19 | typeType ast.Expr 20 | } 21 | ) 22 | 23 | func newType(p *packages.Package, ident *ast.Ident, typeType ast.Expr) Type { 24 | return &typeImpl{p: p, ident: ident, typeType: typeType} 25 | } 26 | 27 | func (t *typeImpl) Name() string { 28 | return t.ident.Name 29 | } 30 | 31 | func (t *typeImpl) Constraint() types.Type { 32 | return t.p.TypesInfo.Types[t.typeType].Type 33 | } 34 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/type_test.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "golang.org/x/tools/go/packages" 10 | ) 11 | 12 | func TestType_Name(t *testing.T) { 13 | cases := []struct { 14 | Name, Expected string 15 | Ident *ast.Ident 16 | }{ 17 | { 18 | Name: "success", 19 | Ident: ast.NewIdent("T"), 20 | Expected: "T", 21 | }, 22 | } 23 | 24 | for _, tc := range cases { 25 | t.Run(tc.Name, func(t *testing.T) { 26 | t.Parallel() 27 | 28 | typ := newType(nil, tc.Ident, nil) 29 | name := typ.Name() 30 | assert.Equal(t, tc.Expected, name) 31 | }) 32 | } 33 | } 34 | 35 | func TestType_Constraint(t *testing.T) { 36 | var ( 37 | comparableIdent = ast.NewIdent("comparable") 38 | comparableType types.Type = &fakeType{"comparable"} 39 | ) 40 | 41 | cases := []struct { 42 | Name string 43 | Package *packages.Package 44 | TypeType ast.Expr 45 | Expected types.Type 46 | }{ 47 | { 48 | Name: "success", 49 | Package: &packages.Package{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{ 50 | comparableIdent: {Type: comparableType}, 51 | }}}, 52 | TypeType: comparableIdent, 53 | Expected: comparableType, 54 | }, 55 | } 56 | 57 | for _, tc := range cases { 58 | t.Run(tc.Name, func(t *testing.T) { 59 | t.Parallel() 60 | 61 | value := newType(tc.Package, nil, tc.TypeType) 62 | constraint := value.Constraint() 63 | assert.Equal(t, tc.Expected, constraint) 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/value.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | 7 | "golang.org/x/tools/go/packages" 8 | ) 9 | 10 | type ( 11 | Value interface { 12 | Name() string 13 | Type() types.Type 14 | } 15 | 16 | astValue struct { 17 | p *packages.Package 18 | ident *ast.Ident 19 | valueType ast.Expr 20 | } 21 | 22 | typesValue struct { 23 | v *types.Var 24 | } 25 | ) 26 | 27 | func newASTValue(p *packages.Package, ident *ast.Ident, valueType ast.Expr) Value { 28 | return &astValue{p: p, ident: ident, valueType: valueType} 29 | } 30 | 31 | func (av *astValue) Name() string { 32 | if av.ident != nil { 33 | return av.ident.Name 34 | } 35 | return "" 36 | } 37 | 38 | func (av *astValue) Type() types.Type { 39 | return av.p.TypesInfo.Types[av.valueType].Type 40 | } 41 | 42 | func newTypesValue(v *types.Var) Value { 43 | return &typesValue{v: v} 44 | } 45 | 46 | func (tv *typesValue) Name() string { 47 | return tv.v.Name() 48 | } 49 | 50 | func (tv *typesValue) Type() types.Type { 51 | return tv.v.Type() 52 | } 53 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/parse/value_test.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "golang.org/x/tools/go/packages" 10 | ) 11 | 12 | type ( 13 | fakeType struct { 14 | Name string 15 | } 16 | ) 17 | 18 | func (t *fakeType) Underlying() types.Type { 19 | return t 20 | } 21 | 22 | func (t *fakeType) String() string { 23 | return t.Name 24 | } 25 | 26 | func TestValue_Name(t *testing.T) { 27 | cases := []struct { 28 | Name, Expected string 29 | Value Value 30 | }{ 31 | { 32 | Name: "AST empty", 33 | Value: newASTValue(nil, nil, nil), 34 | Expected: "", 35 | }, 36 | { 37 | Name: "AST success", 38 | Value: newASTValue(nil, ast.NewIdent("a"), nil), 39 | Expected: "a", 40 | }, 41 | { 42 | Name: "types success", 43 | Value: newTypesValue(types.NewVar(0, nil, "a", nil)), 44 | Expected: "a", 45 | }, 46 | } 47 | 48 | for _, tc := range cases { 49 | t.Run(tc.Name, func(t *testing.T) { 50 | t.Parallel() 51 | 52 | name := tc.Value.Name() 53 | assert.Equal(t, tc.Expected, name) 54 | }) 55 | } 56 | } 57 | 58 | func TestValue_Type(t *testing.T) { 59 | var ( 60 | stringIdent = ast.NewIdent("string") 61 | stringType types.Type = &fakeType{"string"} 62 | ) 63 | 64 | cases := []struct { 65 | Name string 66 | Value Value 67 | Expected types.Type 68 | }{ 69 | { 70 | Name: "AST success", 71 | Value: newASTValue(&packages.Package{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{ 72 | stringIdent: {Type: stringType}, 73 | }}}, nil, stringIdent), 74 | Expected: stringType, 75 | }, 76 | { 77 | Name: "types success", 78 | Value: newTypesValue(types.NewVar(0, nil, "", stringType)), 79 | Expected: stringType, 80 | }, 81 | } 82 | 83 | for _, tc := range cases { 84 | t.Run(tc.Name, func(t *testing.T) { 85 | t.Parallel() 86 | 87 | typ := tc.Value.Type() 88 | assert.Equal(t, tc.Expected, typ) 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /mock/cmd/cmg/pkg/version.go: -------------------------------------------------------------------------------- 1 | package cluemockgen 2 | 3 | import ( 4 | "runtime/debug" 5 | ) 6 | 7 | // Version returns the complete version number. 8 | func Version() string { 9 | bi, ok := debug.ReadBuildInfo() 10 | if !ok { 11 | return "(unknown)" 12 | } 13 | return bi.Main.Version 14 | } 15 | -------------------------------------------------------------------------------- /mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import "sync" 4 | 5 | type ( 6 | // Mock implementation of a service client. 7 | Mock struct { 8 | funcs map[string]interface{} 9 | seqs map[string][]interface{} 10 | indices []*index 11 | pos int 12 | lock sync.Mutex 13 | } 14 | 15 | // index identifies a mock in a sequence. 16 | index struct { 17 | name string 18 | pos int 19 | } 20 | ) 21 | 22 | // New returns a new mock client. 23 | func New() *Mock { 24 | return &Mock{ 25 | funcs: make(map[string]interface{}), 26 | seqs: make(map[string][]interface{}), 27 | } 28 | } 29 | 30 | // If there is no mock left in the sequence then Next returns the permanent mock 31 | // for name if any, nil otherwise. If there are mocks left in the sequence then 32 | // Next returns the next mock if its name is name, nil otherwise. 33 | func (m *Mock) Next(name string) interface{} { 34 | m.lock.Lock() 35 | defer m.lock.Unlock() 36 | 37 | if m.pos < len(m.indices) && len(m.seqs[name]) > 0 { 38 | idx := m.indices[m.pos] 39 | if idx.name != name || idx.pos >= len(m.seqs[name]) { 40 | // There is a sequence but the wrong method is being called 41 | return nil 42 | } 43 | f := m.seqs[name][idx.pos] 44 | idx.pos++ 45 | m.pos++ 46 | return f 47 | } 48 | // No sequence or sequence fully consumed - look for permanent mock 49 | if f, ok := m.funcs[name]; ok { 50 | return f 51 | } 52 | return nil 53 | } 54 | 55 | // Add adds f to the mock sequence. 56 | func (m *Mock) Add(name string, f interface{}) { 57 | m.lock.Lock() 58 | defer m.lock.Unlock() 59 | 60 | m.indices = append(m.indices, &index{name, len(m.seqs[name])}) 61 | m.seqs[name] = append(m.seqs[name], f) 62 | } 63 | 64 | // Set a permanent mock for the function with the given name. 65 | func (m *Mock) Set(name string, f interface{}) { 66 | m.lock.Lock() 67 | defer m.lock.Unlock() 68 | 69 | m.funcs[name] = f 70 | } 71 | 72 | // HasMore returns true if the mock sequence isn't fully consumed. 73 | func (m *Mock) HasMore() bool { 74 | m.lock.Lock() 75 | defer m.lock.Unlock() 76 | 77 | return m.pos < len(m.indices) 78 | } 79 | -------------------------------------------------------------------------------- /scripts/cibuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | GIT_ROOT=$(git rev-parse --show-toplevel) 6 | pushd ${GIT_ROOT} > /dev/null 7 | 8 | ./scripts/setup 9 | ./scripts/test 10 | 11 | popd > /dev/null 12 | -------------------------------------------------------------------------------- /scripts/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | GIT_ROOT=$(git rev-parse --show-toplevel) 6 | 7 | pushd ${GIT_ROOT} > /dev/null 8 | 9 | source ./scripts/utils/common.sh 10 | 11 | if [[ "$CI" == "" ]]; then 12 | check_required_cmd "tmux" 13 | fi 14 | 15 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest 16 | 17 | popd > /dev/null 18 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | GIT_ROOT=$(git rev-parse --show-toplevel) 6 | # Ensure we are in the correct directory for the following commands 7 | pushd ${GIT_ROOT} > /dev/null 8 | 9 | echo "Running static analysis..." 10 | golangci-lint run --timeout 5m --verbose 11 | 12 | echo "Running tests..." 13 | go test -race -coverprofile=coverage.out -covermode=atomic ./... 14 | 15 | popd > /dev/null 16 | -------------------------------------------------------------------------------- /scripts/utils/common.sh: -------------------------------------------------------------------------------- 1 | # Useful functions for re-use in different scripts 2 | 3 | [[ "$DEBUG" != "" ]] && set -x 4 | 5 | function is_mac { 6 | [[ "$OSTYPE" == "darwin"* ]] 7 | } 8 | 9 | function is_m1_mac { 10 | is_mac && [[ "$(uname -a)" == *"ARM64"* ]] 11 | } 12 | 13 | function check_required_cmd { 14 | cmd="$1" 15 | pkg="$2" 16 | # I feel like you could do this with substitution 17 | # but I didn't feel like fighting bash. 18 | [[ "$pkg" == "" ]] && pkg=$cmd 19 | 20 | if ! command -v $1 &> /dev/null; then 21 | echo "Unable to find '$cmd' in your PATH - cannot continue." 22 | if is_mac; then 23 | echo "Try 'brew install '$pkg'" 24 | else 25 | echo "Try 'apt-get install '$pkg'" 26 | fi 27 | exit 1 28 | fi 29 | } 30 | -------------------------------------------------------------------------------- /staticcheck.conf: -------------------------------------------------------------------------------- 1 | checks = ["-SA1029"] # SA1029: should not use built-in type string as key for value; define your own type to avoid collisions 2 | dot_import_whitelist = [ 3 | "goa.design/goa/v3/dsl", 4 | "goa.design/model/dsl", 5 | "goa.design/clue/example/weather/design", 6 | ] 7 | --------------------------------------------------------------------------------