From 0100a1224edf130bcb611575aedece3b354377d6 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Mon, 24 Aug 2026 18:04:18 -0700 Subject: [PATCH 01/30] Port plugins to Goa generation plans --- arnz/example/gen/arnz/service.go | 4 +- .../gen/http/arnz/client/encode_decode.go | 106 ++- arnz/example/gen/http/openapi.json | 2 +- arnz/example/gen/http/openapi.yaml | 4 +- arnz/example/gen/http/openapi3.2.json | 2 +- arnz/example/gen/http/openapi3.2.yaml | 14 +- arnz/example/gen/http/openapi3.json | 2 +- arnz/example/gen/http/openapi3.yaml | 14 +- cors/examples/calc/cmd/calc-cli/http.go | 20 +- cors/examples/calc/cmd/calc-cli/main.go | 42 +- cors/examples/calc/gen/calc/service.go | 4 +- .../calc/gen/http/calc/client/client.go | 4 - .../gen/http/calc/client/encode_decode.go | 22 +- .../calc/gen/http/calc/server/server.go | 8 +- cors/generate_test.go | 20 +- docs/examples/calc/cmd/calc-cli/http.go | 20 +- docs/examples/calc/cmd/calc-cli/main.go | 42 +- docs/examples/calc/gen/calc/service.go | 4 +- docs/examples/calc/gen/docs.json | 2 +- .../gen/http/calc/client/encode_decode.go | 22 +- .../calc/gen/http/calc/server/server.go | 8 +- docs/examples/calc/gen/http/cli/calc/cli.go | 4 +- docs/examples/calc/gen/http/openapi3.2.json | 2 +- docs/examples/calc/gen/http/openapi3.2.yaml | 12 +- docs/examples/calc/gen/http/openapi3.json | 2 +- docs/examples/calc/gen/http/openapi3.yaml | 12 +- docs/generate.go | 41 +- docs/generate_test.go | 4 - docs/genstate.go | 24 +- docs/testdata/array-payload-no-return.json | 2 +- docs/testdata/map-payload-no-return.json | 2 +- docs/testdata/no-payload-array-return.json | 2 +- docs/testdata/no-payload-map-return.json | 2 +- .../testdata/no-payload-primitive-return.json | 2 +- docs/testdata/no-payload-user-return.json | 2 +- .../testdata/primitive-payload-no-return.json | 2 +- docs/testdata/user-payload-no-return.json | 2 +- go.mod | 2 +- go.sum | 4 +- goakit/encode_decode_files.go | 99 ++- goakit/encode_decode_files_test.go | 22 +- goakit/examples/calc/cmd/calc-cli/http.go | 20 +- goakit/examples/calc/cmd/calc-cli/main.go | 42 +- goakit/examples/calc/gen/calc/service.go | 4 +- .../gen/http/calc/client/encode_decode.go | 22 +- .../fetcher/archiver/cmd/archiver-cli/http.go | 27 +- .../fetcher/archiver/cmd/archiver-cli/main.go | 43 +- .../archiver/gen/archiver/endpoints.go | 7 + .../archiver/gen/archiver/views/view.go | 4 +- .../fetcher/archiver/gen/health/service.go | 4 +- .../archiver/gen/http/archiver/client/cli.go | 2 +- .../gen/http/archiver/client/encode_decode.go | 43 +- .../gen/http/archiver/client/types.go | 4 +- .../archiver/gen/http/cli/archiver/cli.go | 6 +- .../gen/http/health/client/encode_decode.go | 22 +- .../fetcher/archiver/gen/http/openapi.json | 2 +- .../fetcher/archiver/gen/http/openapi.yaml | 32 +- .../fetcher/archiver/gen/http/openapi3.2.json | 2 +- .../fetcher/archiver/gen/http/openapi3.2.yaml | 40 +- .../fetcher/archiver/gen/http/openapi3.json | 2 +- .../fetcher/archiver/gen/http/openapi3.yaml | 40 +- .../fetcher/fetcher/gen/fetcher/endpoints.go | 4 + .../fetcher/fetcher/gen/fetcher/views/view.go | 4 +- .../fetcher/fetcher/gen/health/service.go | 4 +- .../fetcher/gen/http/cli/fetcher/cli.go | 4 +- .../gen/http/fetcher/client/encode_decode.go | 22 +- .../fetcher/gen/http/fetcher/client/types.go | 4 +- .../gen/http/health/client/encode_decode.go | 22 +- .../fetcher/fetcher/gen/http/openapi.json | 2 +- .../fetcher/fetcher/gen/http/openapi.yaml | 20 +- .../fetcher/fetcher/gen/http/openapi3.2.json | 2 +- .../fetcher/fetcher/gen/http/openapi3.2.yaml | 26 +- .../fetcher/fetcher/gen/http/openapi3.json | 2 +- .../fetcher/fetcher/gen/http/openapi3.yaml | 26 +- goakit/generate.go | 317 +++++++- goakit/generate_test.go | 51 +- goakit/mount_files.go | 50 +- i18n/examples/calc/cmd/calc-cli/http.go | 20 +- i18n/examples/calc/cmd/calc-cli/main.go | 42 +- i18n/examples/calc/gen/calc/service.go | 4 +- .../gen/http/calc/client/encode_decode.go | 22 +- i18n/generate.go | 135 +++- i18n/generate_test.go | 26 +- mains/generate.go | 748 +++++++++--------- mains/generate_test.go | 232 +++--- otel/generate_test.go | 13 +- testing/README.md | 3 +- testing/codegen/clients.go | 46 +- testing/codegen/clients_test.go | 22 +- testing/codegen/errors.go | 4 +- testing/codegen/generate.go | 84 +- testing/codegen/harness.go | 54 +- testing/codegen/harness_test.go | 5 +- testing/codegen/package_plan.go | 202 +++++ testing/codegen/plans_test.go | 151 ++++ testing/codegen/scenarios.go | 118 ++- testing/codegen/scenarios_test.go | 11 +- testing/codegen/suite.go | 86 +- testing/codegen/suite_test.go | 2 +- .../templates/example_scenarios.yaml.tpl | 12 +- .../codegen/templates/scenario_runner.go.tpl | 47 +- .../codegen/templates/scenario_types.go.tpl | 17 +- testing/codegen/testdata.go | 19 +- testing/codegen/testdata/code.go | 195 ++--- testing/codegen/testdata/dsls.go | 21 + testing/codegen/transports.go | 62 ++ .../calculator/calculatortest/scenarios.go | 56 +- .../gen/calculator/calculatortest/testdata.go | 22 +- .../calculator/gen/calculator/service.go | 4 +- .../gen/grpc/calculator/client/cli.go | 44 +- .../gen/grpc/calculator/client/client.go | 3 + .../grpc/calculator/client/encode_decode.go | 12 + .../gen/grpc/calculator/client/types.go | 205 ++++- .../pb/goagen_calculator_calculator.pb.go | 237 +++--- .../pb/goagen_calculator_calculator.proto | 44 +- .../goagen_calculator_calculator_grpc.pb.go | 2 +- .../grpc/calculator/server/encode_decode.go | 6 + .../gen/grpc/calculator/server/server.go | 5 +- .../gen/grpc/calculator/server/types.go | 140 ++-- .../calculator/gen/grpc/cli/calculator/cli.go | 10 +- .../gen/http/calculator/client/cli.go | 8 +- .../http/calculator/client/encode_decode.go | 106 ++- .../gen/http/calculator/client/types.go | 8 +- .../gen/http/calculator/server/types.go | 4 +- .../gen/http/calculator/server/websocket.go | 33 +- .../calculator/gen/http/cli/calculator/cli.go | 10 +- .../examples/calculator/gen/http/openapi.json | 2 +- .../examples/calculator/gen/http/openapi.yaml | 110 +-- .../calculator/gen/http/openapi3.2.json | 2 +- .../calculator/gen/http/openapi3.2.yaml | 154 ++-- .../calculator/gen/http/openapi3.json | 2 +- .../calculator/gen/http/openapi3.yaml | 154 ++-- testing/examples/calculator/go.mod | 4 +- testing/examples/calculator/go.sum | 89 +-- .../gen/grpc/cli/test_http_grpc/cli.go | 10 +- .../gen/grpc/test_http_grpc/client/cli.go | 44 +- .../gen/grpc/test_http_grpc/client/client.go | 21 + .../test_http_grpc/client/encode_decode.go | 9 + .../gen/grpc/test_http_grpc/client/types.go | 266 +++++-- .../pb/goagen_httpgrpc_test_http_grpc.pb.go | 234 +++--- .../pb/goagen_httpgrpc_test_http_grpc.proto | 38 +- .../goagen_httpgrpc_test_http_grpc_grpc.pb.go | 2 +- .../test_http_grpc/server/encode_decode.go | 9 + .../gen/grpc/test_http_grpc/server/server.go | 24 +- .../gen/grpc/test_http_grpc/server/types.go | 259 +++--- .../gen/http/cli/test_http_grpc/cli.go | 8 +- .../examples/httpgrpc/gen/http/openapi.json | 2 +- .../examples/httpgrpc/gen/http/openapi.yaml | 59 +- .../httpgrpc/gen/http/openapi3.2.json | 2 +- .../httpgrpc/gen/http/openapi3.2.yaml | 41 +- .../examples/httpgrpc/gen/http/openapi3.json | 2 +- .../examples/httpgrpc/gen/http/openapi3.yaml | 45 +- .../gen/http/test_http_grpc/client/cli.go | 6 +- .../gen/http/test_http_grpc/client/client.go | 15 +- .../test_http_grpc/client/encode_decode.go | 211 +++-- .../gen/http/test_http_grpc/client/sse.go | 24 +- .../gen/http/test_http_grpc/client/types.go | 22 +- .../gen/http/test_http_grpc/server/server.go | 14 + .../gen/http/test_http_grpc/server/sse.go | 144 +--- .../gen/http/test_http_grpc/server/types.go | 14 +- .../http/test_http_grpc/server/websocket.go | 99 ++- .../httpgrpc/gen/test_http_grpc/service.go | 4 +- .../test_http_grpctest/scenarios.go | 57 +- .../test_http_grpctest/testdata.go | 48 +- testing/examples/httpgrpc/go.mod | 4 +- testing/examples/httpgrpc/go.sum | 89 +-- .../gen/jsonrpc/cli/test_jsonrpc/cli.go | 6 +- .../gen/jsonrpc/test_jsonrpc/client/cli.go | 4 +- .../gen/jsonrpc/test_jsonrpc/client/client.go | 5 +- .../test_jsonrpc/client/encode_decode.go | 51 +- .../gen/jsonrpc/test_jsonrpc/client/types.go | 4 +- .../gen/jsonrpc/test_jsonrpc/server/server.go | 215 +++-- .../gen/jsonrpc/test_jsonrpc/server/types.go | 4 +- .../test_jsonrpctest/scenarios.go | 55 +- .../test_jsonrpc/test_jsonrpctest/testdata.go | 8 +- testing/examples/jsonrpc/go.mod | 4 +- testing/examples/jsonrpc/go.sum | 89 +-- testing/examples/jsonrpc/scenarios.yaml | 6 +- testing/generate.go | 157 +++- testing/generate_test.go | 35 + tscap/auth/auth.go | 4 +- tscap/example/cmd/example/main.go | 2 +- tscap/example/cmd/example/main_test.go | 2 +- .../example/gen/http/cli/tscap_example/cli.go | 4 +- tscap/example/gen/http/openapi.json | 2 +- tscap/example/gen/http/openapi.yaml | 6 +- .../example/gen/http/openapi/server/server.go | 8 +- tscap/example/gen/http/openapi3.2.json | 2 +- tscap/example/gen/http/openapi3.2.yaml | 30 +- tscap/example/gen/http/openapi3.json | 2 +- tscap/example/gen/http/openapi3.yaml | 30 +- tscap/example/gen/http/tscap/client/cli.go | 2 +- .../gen/http/tscap/client/encode_decode.go | 85 +- tscap/example/gen/tscap/service.go | 4 +- types/generate.go | 396 +++++++--- types/generate_test.go | 31 +- types/testdata/alias.go_ | 12 +- types/testdata/array.go_ | 14 +- types/testdata/dsls.go | 14 + types/testdata/empty.go_ | 9 +- types/testdata/example.go_ | 13 +- types/testdata/multiple.go_ | 48 +- types/testdata/noval.go_ | 9 +- types/testdata/recArray.go_ | 33 +- types/testdata/required.go_ | 14 +- types/testdata/validation.go_ | 14 +- types/testdata/validator_collision.go_ | 32 + zaplogger/examples/calc/cmd/calc-cli/http.go | 20 +- zaplogger/examples/calc/cmd/calc-cli/main.go | 42 +- zaplogger/examples/calc/gen/calc/service.go | 4 +- .../gen/http/calc/client/encode_decode.go | 22 +- .../calc/gen/http/calc/server/server.go | 8 +- .../examples/calc/gen/http/cli/calc/cli.go | 4 +- .../examples/calc/gen/http/openapi3.2.json | 2 +- .../examples/calc/gen/http/openapi3.2.yaml | 13 +- .../examples/calc/gen/http/openapi3.json | 2 +- .../examples/calc/gen/http/openapi3.yaml | 13 +- zaplogger/update.go | 17 +- zaplogger/update_test.go | 55 ++ 219 files changed, 5663 insertions(+), 3247 deletions(-) create mode 100644 testing/codegen/package_plan.go create mode 100644 testing/codegen/plans_test.go create mode 100644 testing/codegen/transports.go create mode 100644 testing/generate_test.go create mode 100644 types/testdata/validator_collision.go_ create mode 100644 zaplogger/update_test.go diff --git a/arnz/example/gen/arnz/service.go b/arnz/example/gen/arnz/service.go index c115ed15c..7edbdf604 100644 --- a/arnz/example/gen/arnz/service.go +++ b/arnz/example/gen/arnz/service.go @@ -8,9 +8,7 @@ package arnz -import ( - "context" -) +import "context" // Service is the Arnz service interface. type Service interface { diff --git a/arnz/example/gen/http/arnz/client/encode_decode.go b/arnz/example/gen/http/arnz/client/encode_decode.go index 4c42f6ef1..a61740944 100644 --- a/arnz/example/gen/http/arnz/client/encode_decode.go +++ b/arnz/example/gen/http/arnz/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -37,18 +38,24 @@ func (c *Client) BuildCreateRequest(ctx context.Context, v any) (*http.Request, // create endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeCreateResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "create", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("Arnz", "create", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -67,7 +74,10 @@ func DecodeCreateResponse(decoder func(*http.Response) goahttp.Decoder, restoreB res := NewCreateResponseBodyOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "create", err) + } return nil, goahttp.ErrInvalidResponse("Arnz", "create", resp.StatusCode, string(body)) } } @@ -92,18 +102,24 @@ func (c *Client) BuildReadRequest(ctx context.Context, v any) (*http.Request, er // endpoint. restoreBody controls whether the response body should be restored // after having been read. func DecodeReadResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "read", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("Arnz", "read", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -122,7 +138,10 @@ func DecodeReadResponse(decoder func(*http.Response) goahttp.Decoder, restoreBod res := NewReadResponseBodyOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "read", err) + } return nil, goahttp.ErrInvalidResponse("Arnz", "read", resp.StatusCode, string(body)) } } @@ -147,18 +166,24 @@ func (c *Client) BuildUpdateRequest(ctx context.Context, v any) (*http.Request, // update endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeUpdateResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "update", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("Arnz", "update", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -177,7 +202,10 @@ func DecodeUpdateResponse(decoder func(*http.Response) goahttp.Decoder, restoreB res := NewUpdateResponseBodyOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "update", err) + } return nil, goahttp.ErrInvalidResponse("Arnz", "update", resp.StatusCode, string(body)) } } @@ -202,18 +230,24 @@ func (c *Client) BuildDeleteRequest(ctx context.Context, v any) (*http.Request, // delete endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeDeleteResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "delete", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("Arnz", "delete", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -232,7 +266,10 @@ func DecodeDeleteResponse(decoder func(*http.Response) goahttp.Decoder, restoreB res := NewDeleteResponseBodyOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "delete", err) + } return nil, goahttp.ErrInvalidResponse("Arnz", "delete", resp.StatusCode, string(body)) } } @@ -257,18 +294,24 @@ func (c *Client) BuildHealthRequest(ctx context.Context, v any) (*http.Request, // health endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeHealthResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "health", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("Arnz", "health", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -287,7 +330,10 @@ func DecodeHealthResponse(decoder func(*http.Response) goahttp.Decoder, restoreB res := NewHealthResponseBodyOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("Arnz", "health", err) + } return nil, goahttp.ErrInvalidResponse("Arnz", "health", resp.StatusCode, string(body)) } } diff --git a/arnz/example/gen/http/openapi.json b/arnz/example/gen/http/openapi.json index c0c09c32d..f6a5e797a 100644 --- a/arnz/example/gen/http/openapi.json +++ b/arnz/example/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["Arnz"],"summary":"read Arnz","operationId":"Arnz#read","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]},"put":{"tags":["Arnz"],"summary":"update Arnz","operationId":"Arnz#update","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]},"post":{"tags":["Arnz"],"summary":"create Arnz","operationId":"Arnz#create","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]},"delete":{"tags":["Arnz"],"summary":"delete Arnz","operationId":"Arnz#delete","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]}},"/health":{"get":{"tags":["Arnz"],"summary":"health Arnz","operationId":"Arnz#health","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]}}},"definitions":{"ResponseBody":{"title":"ResponseBody","type":"object","properties":{"action":{"type":"string","example":"Autem eos aut quisquam."}},"example":{"action":"Autem eos aut quisquam."},"required":["action"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["Arnz"],"summary":"read Arnz","operationId":"Arnz#read","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]},"put":{"tags":["Arnz"],"summary":"update Arnz","operationId":"Arnz#update","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]},"post":{"tags":["Arnz"],"summary":"create Arnz","operationId":"Arnz#create","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]},"delete":{"tags":["Arnz"],"summary":"delete Arnz","operationId":"Arnz#delete","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]}},"/health":{"get":{"tags":["Arnz"],"summary":"health Arnz","operationId":"Arnz#health","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/ResponseBody","required":["action"]}}},"schemes":["http"]}}},"definitions":{"ResponseBody":{"title":"ResponseBody","type":"object","properties":{"action":{"type":"string","example":"Aut sit sit repellendus nesciunt minima."}},"example":{"action":"Aut sit sit repellendus nesciunt minima."},"required":["action"]}}} \ No newline at end of file diff --git a/arnz/example/gen/http/openapi.yaml b/arnz/example/gen/http/openapi.yaml index e72f33eb3..9851bf44d 100644 --- a/arnz/example/gen/http/openapi.yaml +++ b/arnz/example/gen/http/openapi.yaml @@ -91,8 +91,8 @@ definitions: properties: action: type: string - example: Autem eos aut quisquam. + example: Aut sit sit repellendus nesciunt minima. example: - action: Autem eos aut quisquam. + action: Aut sit sit repellendus nesciunt minima. required: - action diff --git a/arnz/example/gen/http/openapi3.2.json b/arnz/example/gen/http/openapi3.2.json index 876feb8c3..bf1f14085 100644 --- a/arnz/example/gen/http/openapi3.2.json +++ b/arnz/example/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"Arnz","description":"Default server for Arnz"}],"paths":{"/":{"delete":{"tags":["Arnz"],"summary":"delete Arnz","operationId":"Arnz#delete","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Quaerat neque commodi cupiditate dignissimos voluptates sed."}}}}}},"get":{"tags":["Arnz"],"summary":"read Arnz","operationId":"Arnz#read","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Nulla dolores est sunt autem et est."}}}}}},"post":{"tags":["Arnz"],"summary":"create Arnz","operationId":"Arnz#create","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Autem eos aut quisquam."}}}}}},"put":{"tags":["Arnz"],"summary":"update Arnz","operationId":"Arnz#update","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Id impedit dolores."}}}}}}},"/health":{"get":{"tags":["Arnz"],"summary":"health Arnz","operationId":"Arnz#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Voluptatem impedit sunt aliquam."}}}}}}}},"components":{"schemas":{"ResponseBody":{"type":"object","properties":{"action":{"type":"string","example":"Autem eos aut quisquam."}},"example":{"action":"Autem eos aut quisquam."},"required":["action"]}}},"tags":[{"name":"Arnz"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"Arnz","description":"Default server for Arnz"}],"paths":{"/":{"delete":{"tags":["Arnz"],"summary":"delete Arnz","operationId":"Arnz#delete","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Qui illo."}}}}}},"get":{"tags":["Arnz"],"summary":"read Arnz","operationId":"Arnz#read","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Accusantium est non molestiae."}}}}}},"post":{"tags":["Arnz"],"summary":"create Arnz","operationId":"Arnz#create","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Aut sit sit repellendus nesciunt minima."}}}}}},"put":{"tags":["Arnz"],"summary":"update Arnz","operationId":"Arnz#update","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Consectetur recusandae."}}}}}}},"/health":{"get":{"tags":["Arnz"],"summary":"health Arnz","operationId":"Arnz#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Culpa sed."}}}}}}}},"components":{"schemas":{"ResponseBody":{"type":"object","properties":{"action":{"type":"string","example":"Aut sit sit repellendus nesciunt minima."}},"example":{"action":"Aut sit sit repellendus nesciunt minima."},"required":["action"]}}},"tags":[{"name":"Arnz"}]} \ No newline at end of file diff --git a/arnz/example/gen/http/openapi3.2.yaml b/arnz/example/gen/http/openapi3.2.yaml index 106b900d4..a71d9a777 100644 --- a/arnz/example/gen/http/openapi3.2.yaml +++ b/arnz/example/gen/http/openapi3.2.yaml @@ -21,7 +21,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Quaerat neque commodi cupiditate dignissimos voluptates sed. + action: Qui illo. get: tags: - Arnz @@ -35,7 +35,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Nulla dolores est sunt autem et est. + action: Accusantium est non molestiae. post: tags: - Arnz @@ -49,7 +49,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Autem eos aut quisquam. + action: Aut sit sit repellendus nesciunt minima. put: tags: - Arnz @@ -63,7 +63,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Id impedit dolores. + action: Consectetur recusandae. /health: get: tags: @@ -78,7 +78,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Voluptatem impedit sunt aliquam. + action: Culpa sed. components: schemas: ResponseBody: @@ -86,9 +86,9 @@ components: properties: action: type: string - example: Autem eos aut quisquam. + example: Aut sit sit repellendus nesciunt minima. example: - action: Autem eos aut quisquam. + action: Aut sit sit repellendus nesciunt minima. required: - action tags: diff --git a/arnz/example/gen/http/openapi3.json b/arnz/example/gen/http/openapi3.json index eee51383f..18911b539 100644 --- a/arnz/example/gen/http/openapi3.json +++ b/arnz/example/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for Arnz"}],"paths":{"/":{"delete":{"tags":["Arnz"],"summary":"delete Arnz","operationId":"Arnz#delete","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Quaerat neque commodi cupiditate dignissimos voluptates sed."}}}}}},"get":{"tags":["Arnz"],"summary":"read Arnz","operationId":"Arnz#read","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Nulla dolores est sunt autem et est."}}}}}},"post":{"tags":["Arnz"],"summary":"create Arnz","operationId":"Arnz#create","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Autem eos aut quisquam."}}}}}},"put":{"tags":["Arnz"],"summary":"update Arnz","operationId":"Arnz#update","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Id impedit dolores."}}}}}}},"/health":{"get":{"tags":["Arnz"],"summary":"health Arnz","operationId":"Arnz#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Voluptatem impedit sunt aliquam."}}}}}}}},"components":{"schemas":{"ResponseBody":{"type":"object","properties":{"action":{"type":"string","example":"Autem eos aut quisquam."}},"example":{"action":"Autem eos aut quisquam."},"required":["action"]}}},"tags":[{"name":"Arnz"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for Arnz"}],"paths":{"/":{"delete":{"tags":["Arnz"],"summary":"delete Arnz","operationId":"Arnz#delete","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Qui illo."}}}}}},"get":{"tags":["Arnz"],"summary":"read Arnz","operationId":"Arnz#read","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Accusantium est non molestiae."}}}}}},"post":{"tags":["Arnz"],"summary":"create Arnz","operationId":"Arnz#create","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Aut sit sit repellendus nesciunt minima."}}}}}},"put":{"tags":["Arnz"],"summary":"update Arnz","operationId":"Arnz#update","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Consectetur recusandae."}}}}}}},"/health":{"get":{"tags":["Arnz"],"summary":"health Arnz","operationId":"Arnz#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponseBody"},"example":{"action":"Culpa sed."}}}}}}}},"components":{"schemas":{"ResponseBody":{"type":"object","properties":{"action":{"type":"string","example":"Aut sit sit repellendus nesciunt minima."}},"example":{"action":"Aut sit sit repellendus nesciunt minima."},"required":["action"]}}},"tags":[{"name":"Arnz"}]} \ No newline at end of file diff --git a/arnz/example/gen/http/openapi3.yaml b/arnz/example/gen/http/openapi3.yaml index cc39e72dd..e5f670ff6 100644 --- a/arnz/example/gen/http/openapi3.yaml +++ b/arnz/example/gen/http/openapi3.yaml @@ -20,7 +20,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Quaerat neque commodi cupiditate dignissimos voluptates sed. + action: Qui illo. get: tags: - Arnz @@ -34,7 +34,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Nulla dolores est sunt autem et est. + action: Accusantium est non molestiae. post: tags: - Arnz @@ -48,7 +48,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Autem eos aut quisquam. + action: Aut sit sit repellendus nesciunt minima. put: tags: - Arnz @@ -62,7 +62,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Id impedit dolores. + action: Consectetur recusandae. /health: get: tags: @@ -77,7 +77,7 @@ paths: schema: $ref: '#/components/schemas/ResponseBody' example: - action: Voluptatem impedit sunt aliquam. + action: Culpa sed. components: schemas: ResponseBody: @@ -85,9 +85,9 @@ components: properties: action: type: string - example: Autem eos aut quisquam. + example: Aut sit sit repellendus nesciunt minima. example: - action: Autem eos aut quisquam. + action: Aut sit sit repellendus nesciunt minima. required: - action tags: diff --git a/cors/examples/calc/cmd/calc-cli/http.go b/cors/examples/calc/cmd/calc-cli/http.go index f66cfa329..0bdfa7458 100644 --- a/cors/examples/calc/cmd/calc-cli/http.go +++ b/cors/examples/calc/cmd/calc-cli/http.go @@ -1,16 +1,18 @@ package main import ( + "context" + "flag" "fmt" + "io" "net/http" "time" goahttp "goa.design/goa/v3/http" - goa "goa.design/goa/v3/pkg" cli "goa.design/plugins/v3/cors/examples/calc/gen/http/cli/calc" ) -func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { +func doHTTP(ctx context.Context, scheme, host string, timeout int, debug bool, stdout io.Writer) error { var ( doer goahttp.Doer ) @@ -30,13 +32,17 @@ func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, er debug, ) if err != nil { - return nil, nil, fmt.Errorf("parse endpoint: %w", err) + return fmt.Errorf("parse endpoint: %w", err) } - return endpoint, payload, nil -} -func httpUsageCommands() []string { - return cli.UsageCommands() + switch flag.Arg(0) { + case "calc": + switch flag.Arg(1) { + case "add": + return writeEndpointResult(ctx, stdout, endpoint, payload) + } + } + panic("parsed HTTP command has no generated result writer") } func httpUsageExamples() string { diff --git a/cors/examples/calc/cmd/calc-cli/main.go b/cors/examples/calc/cmd/calc-cli/main.go index db45e50ae..3f4fb644a 100644 --- a/cors/examples/calc/cmd/calc-cli/main.go +++ b/cors/examples/calc/cmd/calc-cli/main.go @@ -6,10 +6,9 @@ import ( "errors" "flag" "fmt" + "io" "net/url" "os" - "slices" - "sort" "strings" goa "goa.design/goa/v3/pkg" @@ -62,14 +61,12 @@ func main() { } var ( - endpoint goa.Endpoint - payload any - err error + err error ) { switch scheme { case "http", "https": - endpoint, payload, err = doHTTP(scheme, host, timeout, debug) + err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) os.Exit(1) @@ -84,23 +81,36 @@ func main() { os.Exit(1) } - data, err := endpoint(context.Background(), payload) +} + +// writeEndpointResult calls one normal endpoint and writes its result as JSON. +func writeEndpointResult(ctx context.Context, stdout io.Writer, endpoint goa.Endpoint, payload any) error { + data, err := endpoint(ctx, payload) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return err } + return writeJSON(stdout, data) +} - if data != nil { - m, _ := json.MarshalIndent(data, "", " ") - fmt.Println(string(m)) +// writeJSON writes one indented JSON value followed by a newline. +func writeJSON(stdout io.Writer, data any) error { + if data == nil { + return nil } + encoded, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("encode result: %w", err) + } + if _, err := fmt.Fprintln(stdout, string(encoded)); err != nil { + return fmt.Errorf("write result: %w", err) + } + return nil } func usage() { - var usageCommands []string - usageCommands = append(usageCommands, httpUsageCommands()...) - sort.Strings(usageCommands) - usageCommands = slices.Compact(usageCommands) + usageCommands := []string{ + "calc add", + } fmt.Fprintf(os.Stderr, `%s is a command line client for the calc API. Usage: diff --git a/cors/examples/calc/gen/calc/service.go b/cors/examples/calc/gen/calc/service.go index 5caaeddcd..8633d4223 100644 --- a/cors/examples/calc/gen/calc/service.go +++ b/cors/examples/calc/gen/calc/service.go @@ -8,9 +8,7 @@ package calc -import ( - "context" -) +import "context" // The calc service exposes public endpoints that defines CORS policy. type Service interface { diff --git a/cors/examples/calc/gen/http/calc/client/client.go b/cors/examples/calc/gen/http/calc/client/client.go index 1c3d20afd..2825d2e6b 100644 --- a/cors/examples/calc/gen/http/calc/client/client.go +++ b/cors/examples/calc/gen/http/calc/client/client.go @@ -21,9 +21,6 @@ type Client struct { // Add Doer is the HTTP client used to make requests to the add endpoint. AddDoer goahttp.Doer - // CORS Doer is the HTTP client used to make requests to the endpoint. - CORSDoer goahttp.Doer - // RestoreResponseBody controls whether the response bodies are reset after // decoding so they can be read again. RestoreResponseBody bool @@ -45,7 +42,6 @@ func NewClient( ) *Client { return &Client{ AddDoer: doer, - CORSDoer: doer, RestoreResponseBody: restoreBody, scheme: scheme, host: host, diff --git a/cors/examples/calc/gen/http/calc/client/encode_decode.go b/cors/examples/calc/gen/http/calc/client/encode_decode.go index fad45df0d..5e109425e 100644 --- a/cors/examples/calc/gen/http/calc/client/encode_decode.go +++ b/cors/examples/calc/gen/http/calc/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -50,18 +51,24 @@ func (c *Client) BuildAddRequest(ctx context.Context, v any) (*http.Request, err // endpoint. restoreBody controls whether the response body should be restored // after having been read. func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calc", "add", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -75,7 +82,10 @@ func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) + } return nil, goahttp.ErrInvalidResponse("calc", "add", resp.StatusCode, string(body)) } } diff --git a/cors/examples/calc/gen/http/calc/server/server.go b/cors/examples/calc/gen/http/calc/server/server.go index 5d74afa69..70e6637b0 100644 --- a/cors/examples/calc/gen/http/calc/server/server.go +++ b/cors/examples/calc/gen/http/calc/server/server.go @@ -148,15 +148,14 @@ func NewAddHandler( }) } -// appendFS is a custom implementation of fs.FS that appends a specified prefix -// to the file paths before delegating the Open call to the underlying fs.FS. +// appendFS adds a fixed directory to file paths before opening them. type appendFS struct { prefix string fs http.FileSystem } // Open opens the named file, appending the prefix to the file path before -// passing it to the underlying fs.FS. +// passing it to the underlying file system. func (s appendFS) Open(name string) (http.File, error) { switch name { case "//": @@ -165,8 +164,7 @@ func (s appendFS) Open(name string) (http.File, error) { return s.fs.Open(path.Join(s.prefix, name)) } -// appendPrefix returns a new fs.FS that appends the specified prefix to file paths -// before delegating to the provided embed.FS. +// appendPrefix returns a file system that adds prefix before opening each path. func appendPrefix(fsys http.FileSystem, prefix string) http.FileSystem { return appendFS{prefix: prefix, fs: fsys} } diff --git a/cors/generate_test.go b/cors/generate_test.go index 964b37e7f..e66f1860d 100644 --- a/cors/generate_test.go +++ b/cors/generate_test.go @@ -9,6 +9,7 @@ import ( "goa.design/goa/v3/codegen" "goa.design/goa/v3/codegen/service" "goa.design/goa/v3/eval" + goaexpr "goa.design/goa/v3/expr" httpcodegen "goa.design/goa/v3/http/codegen" "goa.design/plugins/v3/cors" @@ -43,8 +44,7 @@ func NewCORSHandler() http.Handler { for _, c := range cases { t.Run(c.Name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) - services := httpcodegen.NewServicesData(service.NewServicesData(root), root.API.HTTP) - fs := httpcodegen.ServerFiles("", services) + fs := serverFiles(t, root) require.Len(t, fs, c.CodeGenCount) cors.Generate("", []eval.Root{root}, fs) expectedCodeIndex := -1 @@ -73,6 +73,22 @@ func NewCORSHandler() http.Handler { } } +// serverFiles builds the HTTP server files through the same planning steps as +// a Goa generation run. +func serverFiles(t *testing.T, root *goaexpr.RootExpr) []*codegen.File { + t.Helper() + generation, err := codegen.NewGeneration("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + servicePlan, err := service.NewPlan(root, generation, goaexpr.NewExampleGenerator(root.API.RandomizerFactory)) + require.NoError(t, err) + plans, err := httpcodegen.NewPlans(generation, httpcodegen.PlanInput{Root: root, Service: servicePlan}) + require.NoError(t, err) + require.NoError(t, generation.Freeze()) + require.NoError(t, servicePlan.Link()) + require.NoError(t, plans[0].Link()) + return plans[0].ServerFiles() +} + func testCode(t *testing.T, file *codegen.File, section, expCode string) { sections := file.Section(section) require.Greater(t, len(sections), 0) diff --git a/docs/examples/calc/cmd/calc-cli/http.go b/docs/examples/calc/cmd/calc-cli/http.go index 577a3617b..3956102bc 100644 --- a/docs/examples/calc/cmd/calc-cli/http.go +++ b/docs/examples/calc/cmd/calc-cli/http.go @@ -1,16 +1,18 @@ package main import ( + "context" + "flag" "fmt" + "io" "net/http" "time" goahttp "goa.design/goa/v3/http" - goa "goa.design/goa/v3/pkg" cli "goa.design/plugins/v3/docs/examples/calc/gen/http/cli/calc" ) -func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { +func doHTTP(ctx context.Context, scheme, host string, timeout int, debug bool, stdout io.Writer) error { var ( doer goahttp.Doer ) @@ -30,13 +32,17 @@ func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, er debug, ) if err != nil { - return nil, nil, fmt.Errorf("parse endpoint: %w", err) + return fmt.Errorf("parse endpoint: %w", err) } - return endpoint, payload, nil -} -func httpUsageCommands() []string { - return cli.UsageCommands() + switch flag.Arg(0) { + case "calc": + switch flag.Arg(1) { + case "add": + return writeEndpointResult(ctx, stdout, endpoint, payload) + } + } + panic("parsed HTTP command has no generated result writer") } func httpUsageExamples() string { diff --git a/docs/examples/calc/cmd/calc-cli/main.go b/docs/examples/calc/cmd/calc-cli/main.go index db45e50ae..3f4fb644a 100644 --- a/docs/examples/calc/cmd/calc-cli/main.go +++ b/docs/examples/calc/cmd/calc-cli/main.go @@ -6,10 +6,9 @@ import ( "errors" "flag" "fmt" + "io" "net/url" "os" - "slices" - "sort" "strings" goa "goa.design/goa/v3/pkg" @@ -62,14 +61,12 @@ func main() { } var ( - endpoint goa.Endpoint - payload any - err error + err error ) { switch scheme { case "http", "https": - endpoint, payload, err = doHTTP(scheme, host, timeout, debug) + err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) os.Exit(1) @@ -84,23 +81,36 @@ func main() { os.Exit(1) } - data, err := endpoint(context.Background(), payload) +} + +// writeEndpointResult calls one normal endpoint and writes its result as JSON. +func writeEndpointResult(ctx context.Context, stdout io.Writer, endpoint goa.Endpoint, payload any) error { + data, err := endpoint(ctx, payload) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return err } + return writeJSON(stdout, data) +} - if data != nil { - m, _ := json.MarshalIndent(data, "", " ") - fmt.Println(string(m)) +// writeJSON writes one indented JSON value followed by a newline. +func writeJSON(stdout io.Writer, data any) error { + if data == nil { + return nil } + encoded, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("encode result: %w", err) + } + if _, err := fmt.Fprintln(stdout, string(encoded)); err != nil { + return fmt.Errorf("write result: %w", err) + } + return nil } func usage() { - var usageCommands []string - usageCommands = append(usageCommands, httpUsageCommands()...) - sort.Strings(usageCommands) - usageCommands = slices.Compact(usageCommands) + usageCommands := []string{ + "calc add", + } fmt.Fprintf(os.Stderr, `%s is a command line client for the calc API. Usage: diff --git a/docs/examples/calc/gen/calc/service.go b/docs/examples/calc/gen/calc/service.go index 2e77d8ff8..e24425c21 100644 --- a/docs/examples/calc/gen/calc/service.go +++ b/docs/examples/calc/gen/calc/service.go @@ -8,9 +8,7 @@ package calc -import ( - "context" -) +import "context" // The calc service performs additions on numbers type Service interface { diff --git a/docs/examples/calc/gen/docs.json b/docs/examples/calc/gen/docs.json index daaf1fdee..da36550fe 100644 --- a/docs/examples/calc/gen/docs.json +++ b/docs/examples/calc/gen/docs.json @@ -1 +1 @@ -{"api":{"name":"calc","title":"Calculator Service","version":"0.0.1","servers":{"calc":{"name":"calc","description":"Default server for calc","services":["calc"],"hosts":{"localhost":{"name":"localhost","server":"calc","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"calc":{"name":"calc","description":"The calc service performs additions on numbers","methods":{"add":{"name":"add","payload":{"type":{"$ref":"#/$defs/AddPayload","required":["left","right"]},"example":{"left":1292269682895376051,"right":7045955346828763739}},"result":{"type":{"type":"integer","format":"int64"},"example":7837387407375911615}}}}},"definitions":{"AddPayload":{"title":"AddPayload","type":"object","properties":{"left":{"type":"integer","description":"Left operand","example":1292269682895376051,"format":"int64"},"right":{"type":"integer","description":"Right operand","example":7045955346828763739,"format":"int64"}},"example":{"left":1292269682895376051,"right":7045955346828763739},"required":["left","right"]}}} \ No newline at end of file +{"api":{"name":"calc","title":"Calculator Service","version":"0.0.1","servers":{"calc":{"name":"calc","description":"Default server for calc","services":["calc"],"hosts":{"localhost":{"name":"localhost","server":"calc","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"calc":{"name":"calc","description":"The calc service performs additions on numbers","methods":{"add":{"name":"add","payload":{"type":{"$ref":"#/$defs/AddPayload","required":["left","right"]},"example":{"left":4214196853188143223,"right":1249315168498320761}},"result":{"type":{"type":"integer","format":"int64"},"example":8142676430951835252}}}}},"definitions":{"AddPayload":{"title":"AddPayload","type":"object","properties":{"left":{"type":"integer","description":"Left operand","example":4214196853188143223,"format":"int64"},"right":{"type":"integer","description":"Right operand","example":1249315168498320761,"format":"int64"}},"example":{"left":4214196853188143223,"right":1249315168498320761},"required":["left","right"]}}} \ No newline at end of file diff --git a/docs/examples/calc/gen/http/calc/client/encode_decode.go b/docs/examples/calc/gen/http/calc/client/encode_decode.go index 432500cdc..6b7cf171a 100644 --- a/docs/examples/calc/gen/http/calc/client/encode_decode.go +++ b/docs/examples/calc/gen/http/calc/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -50,18 +51,24 @@ func (c *Client) BuildAddRequest(ctx context.Context, v any) (*http.Request, err // endpoint. restoreBody controls whether the response body should be restored // after having been read. func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calc", "add", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -75,7 +82,10 @@ func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) + } return nil, goahttp.ErrInvalidResponse("calc", "add", resp.StatusCode, string(body)) } } diff --git a/docs/examples/calc/gen/http/calc/server/server.go b/docs/examples/calc/gen/http/calc/server/server.go index 8da65339d..e7e5adea5 100644 --- a/docs/examples/calc/gen/http/calc/server/server.go +++ b/docs/examples/calc/gen/http/calc/server/server.go @@ -140,15 +140,14 @@ func NewAddHandler( }) } -// appendFS is a custom implementation of fs.FS that appends a specified prefix -// to the file paths before delegating the Open call to the underlying fs.FS. +// appendFS adds a fixed directory to file paths before opening them. type appendFS struct { prefix string fs http.FileSystem } // Open opens the named file, appending the prefix to the file path before -// passing it to the underlying fs.FS. +// passing it to the underlying file system. func (s appendFS) Open(name string) (http.File, error) { switch name { case "/swagger.json": @@ -157,8 +156,7 @@ func (s appendFS) Open(name string) (http.File, error) { return s.fs.Open(path.Join(s.prefix, name)) } -// appendPrefix returns a new fs.FS that appends the specified prefix to file paths -// before delegating to the provided embed.FS. +// appendPrefix returns a file system that adds prefix before opening each path. func appendPrefix(fsys http.FileSystem, prefix string) http.FileSystem { return appendFS{prefix: prefix, fs: fsys} } diff --git a/docs/examples/calc/gen/http/cli/calc/cli.go b/docs/examples/calc/gen/http/cli/calc/cli.go index 4a490d03a..a8a4caf95 100644 --- a/docs/examples/calc/gen/http/cli/calc/cli.go +++ b/docs/examples/calc/gen/http/cli/calc/cli.go @@ -30,7 +30,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "calc add --left 1292269682895376051 --right 7045955346828763739" + "\n" + + return os.Args[0] + " " + "calc add --left 4214196853188143223 --right 1249315168498320761" + "\n" + "" } @@ -155,5 +155,5 @@ func calcAddUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calc add --left 1292269682895376051 --right 7045955346828763739") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calc add --left 4214196853188143223 --right 1249315168498320761") } diff --git a/docs/examples/calc/gen/http/openapi3.2.json b/docs/examples/calc/gen/http/openapi3.2.json index e558f3343..3146fcb04 100644 --- a/docs/examples/calc/gen/http/openapi3.2.json +++ b/docs/examples/calc/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"Calculator Service","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"calc","description":"Default server for calc"}],"paths":{"/add/{left}/{right}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"left","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":1292269682895376051,"format":"int64"},"example":7929060683978000797},{"name":"right","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":7045955346828763739,"format":"int64"},"example":7586702564123934160}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":6252541931587653406,"format":"int64"},"example":6252541931587653406}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs additions on numbers"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"Calculator Service","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"calc","description":"Default server for calc"}],"paths":{"/add/{left}/{right}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"left","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":4214196853188143223,"format":"int64"},"example":4214196853188143223},{"name":"right","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":1249315168498320761,"format":"int64"},"example":1249315168498320761}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":7371373013862544951,"format":"int64"},"example":7371373013862544951}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs additions on numbers"}]} \ No newline at end of file diff --git a/docs/examples/calc/gen/http/openapi3.2.yaml b/docs/examples/calc/gen/http/openapi3.2.yaml index b1d700b93..9dccefe3a 100644 --- a/docs/examples/calc/gen/http/openapi3.2.yaml +++ b/docs/examples/calc/gen/http/openapi3.2.yaml @@ -21,9 +21,9 @@ paths: schema: type: integer description: Left operand - example: 1292269682895376051 + example: 4214196853188143223 format: int64 - example: 7929060683978000797 + example: 4214196853188143223 - name: right in: path description: Right operand @@ -31,9 +31,9 @@ paths: schema: type: integer description: Right operand - example: 7045955346828763739 + example: 1249315168498320761 format: int64 - example: 7586702564123934160 + example: 1249315168498320761 responses: "200": description: OK response. @@ -41,9 +41,9 @@ paths: application/json: schema: type: integer - example: 6252541931587653406 + example: 7371373013862544951 format: int64 - example: 6252541931587653406 + example: 7371373013862544951 /swagger.json: get: tags: diff --git a/docs/examples/calc/gen/http/openapi3.json b/docs/examples/calc/gen/http/openapi3.json index f941faa4a..760fde0db 100644 --- a/docs/examples/calc/gen/http/openapi3.json +++ b/docs/examples/calc/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Calculator Service","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for calc"}],"paths":{"/add/{left}/{right}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"left","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":1292269682895376051,"format":"int64"},"example":7929060683978000797},{"name":"right","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":7045955346828763739,"format":"int64"},"example":7586702564123934160}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":6252541931587653406,"format":"int64"},"example":6252541931587653406}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs additions on numbers"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Calculator Service","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for calc"}],"paths":{"/add/{left}/{right}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"left","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":4214196853188143223,"format":"int64"},"example":4214196853188143223},{"name":"right","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":1249315168498320761,"format":"int64"},"example":1249315168498320761}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":7371373013862544951,"format":"int64"},"example":7371373013862544951}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs additions on numbers"}]} \ No newline at end of file diff --git a/docs/examples/calc/gen/http/openapi3.yaml b/docs/examples/calc/gen/http/openapi3.yaml index 8207881dd..2f156e6ef 100644 --- a/docs/examples/calc/gen/http/openapi3.yaml +++ b/docs/examples/calc/gen/http/openapi3.yaml @@ -20,9 +20,9 @@ paths: schema: type: integer description: Left operand - example: 1292269682895376051 + example: 4214196853188143223 format: int64 - example: 7929060683978000797 + example: 4214196853188143223 - name: right in: path description: Right operand @@ -30,9 +30,9 @@ paths: schema: type: integer description: Right operand - example: 7045955346828763739 + example: 1249315168498320761 format: int64 - example: 7586702564123934160 + example: 1249315168498320761 responses: "200": description: OK response. @@ -40,9 +40,9 @@ paths: application/json: schema: type: integer - example: 6252541931587653406 + example: 7371373013862544951 format: int64 - example: 6252541931587653406 + example: 7371373013862544951 /swagger.json: get: tags: diff --git a/docs/generate.go b/docs/generate.go index 1c1ddb57c..f17691131 100644 --- a/docs/generate.go +++ b/docs/generate.go @@ -101,14 +101,8 @@ func Generate(_ string, roots []eval.Root, files []*codegen.File) ([]*codegen.Fi return files, nil } -// docsDataForRoot builds the docs data for a single root while scoping -// openapi.Definitions to that root only. The global definitions map is restored -// before returning. +// docsDataForRoot builds the documentation for one evaluated API. func docsDataForRoot(r *goaexpr.RootExpr) *data { - prev := openapi.Definitions - openapi.Definitions = make(map[string]*openapi.Schema) - defer func() { openapi.Definitions = prev }() - f := docsFile(r) if len(f.SectionTemplates) == 0 || f.SectionTemplates[0] == nil { return nil @@ -118,17 +112,14 @@ func docsDataForRoot(r *goaexpr.RootExpr) *data { } func docsFile(r *goaexpr.RootExpr) *codegen.File { - // Per-run generation state - state := newGenState() + state := newGenState(r.API) docs := &data{ API: apiDocs(r.API), Services: servicesDocs(r, state), } - // Default behavior: use root-local OpenAPI definitions produced during - // payload/result schema generation to preserve golden stability. - defs := make(map[string]*openapi.Schema, len(openapi.Definitions)) - for n, d := range openapi.Definitions { + defs := make(map[string]*openapi.Schema, len(state.definitions)) + for n, d := range state.definitions { defs[n] = dupSchema(d) } @@ -209,7 +200,8 @@ func forcedDefinitions(api *goaexpr.APIExpr, uts []goaexpr.UserType, state *genS out := make(map[string]*openapi.Schema, len(seen)) for name, ute := range seen { - sch := schemaForAttribute(api, ute.AttributeExpr, state) + examples := state.examples.At(goaexpr.UserTypeExampleIdentity(ute)) + sch := schemaForAttribute(api, ute.AttributeExpr, examples, state) out[name] = dupSchema(sch) } return out @@ -494,17 +486,17 @@ func generateMethod(api *goaexpr.APIExpr, meth *goaexpr.MethodExpr, state *genSt m := &methodData{ Name: meth.Name, Description: meth.Description, - Payload: generatePayload(api, meth.Payload, state), - StreamingPayload: generatePayload(api, meth.StreamingPayload, state), + Payload: generatePayload(api, meth.Payload, state.examples.At(goaexpr.MethodPayloadExampleIdentity(meth)), state), + StreamingPayload: generatePayload(api, meth.StreamingPayload, state.examples.At(goaexpr.MethodStreamingPayloadExampleIdentity(meth)), state), } if meth.Stream == goaexpr.BidirectionalStreamKind || meth.Stream == goaexpr.ServerStreamKind { - m.StreamingResult = generatePayload(api, meth.Result, state) + m.StreamingResult = generatePayload(api, meth.Result, state.examples.At(goaexpr.MethodStreamingResultExampleIdentity(meth)), state) } else { - m.Result = generatePayload(api, meth.Result, state) + m.Result = generatePayload(api, meth.Result, state.examples.At(goaexpr.MethodResultExampleIdentity(meth)), state) } m.Errors = make(map[string]*errorData, len(meth.Errors)) for _, er := range meth.Errors { - m.Errors[er.Name] = generateError(api, er, state) + m.Errors[er.Name] = generateError(api, meth, er, state) } m.Requirements = make([]*requirementData, len(meth.Requirements)) for i, req := range meth.Requirements { @@ -513,14 +505,14 @@ func generateMethod(api *goaexpr.APIExpr, meth *goaexpr.MethodExpr, state *genSt return m } -func generatePayload(api *goaexpr.APIExpr, att *goaexpr.AttributeExpr, state *genState) *payloadData { +func generatePayload(api *goaexpr.APIExpr, att *goaexpr.AttributeExpr, examples *goaexpr.ExampleGenerator, state *genState) *payloadData { // Do not generate payload for Empty if ut, ok := att.Type.(*goaexpr.UserTypeExpr); ok && ut == goaexpr.Empty { return nil } - schema := schemaForAttribute(api, att, state) - ex := att.Example(api.ExampleGenerator) + schema := schemaForAttribute(api, att, examples, state) + ex := att.Example(examples) if plugexpr.Root.UseJSONTags { // avoid mutating shared schema nodes schema = dupSchema(schema) @@ -533,11 +525,12 @@ func generatePayload(api *goaexpr.APIExpr, att *goaexpr.AttributeExpr, state *ge } } -func generateError(api *goaexpr.APIExpr, er *goaexpr.ErrorExpr, state *genState) *errorData { +func generateError(api *goaexpr.APIExpr, meth *goaexpr.MethodExpr, er *goaexpr.ErrorExpr, state *genState) *errorData { _, temporary := er.Meta["goa:error:temporary"] _, timeout := er.Meta["goa:error:timeout"] _, fault := er.Meta["goa:error:fault"] - sch := schemaForAttribute(api, er.AttributeExpr, state) + examples := state.examples.At(goaexpr.MethodErrorExampleIdentity(meth, er)) + sch := schemaForAttribute(api, er.AttributeExpr, examples, state) if plugexpr.Root.UseJSONTags { sch = dupSchema(sch) applyJSONTagsToSchema(er.AttributeExpr, sch) diff --git a/docs/generate_test.go b/docs/generate_test.go index 31959ac3c..56bde7b16 100644 --- a/docs/generate_test.go +++ b/docs/generate_test.go @@ -14,7 +14,6 @@ import ( "goa.design/goa/v3/codegen" . "goa.design/goa/v3/dsl" "goa.design/goa/v3/eval" - openapi "goa.design/goa/v3/http/codegen/openapi" "goa.design/plugins/v3/docs" . "goa.design/plugins/v3/docs/dsl" plugexpr "goa.design/plugins/v3/docs/expr" @@ -27,10 +26,7 @@ var update = flag.Bool("update", false, "update golden files") func genDocs(t *testing.T, dsl func()) map[string]any { t.Helper() root := codegen.RunDSL(t, dsl) - prev := openapi.Definitions - openapi.Definitions = make(map[string]*openapi.Schema) fs, err := docs.Generate("", []eval.Root{root}, nil) - openapi.Definitions = prev require.NoError(t, err) require.NotEmpty(t, fs) require.NotEmpty(t, fs[0].SectionTemplates) diff --git a/docs/genstate.go b/docs/genstate.go index 5eb4f803a..c5c5155b7 100644 --- a/docs/genstate.go +++ b/docs/genstate.go @@ -4,6 +4,7 @@ import ( "goa.design/goa/v3/codegen" "goa.design/goa/v3/expr" "goa.design/goa/v3/http/codegen/openapi" + openapiv2 "goa.design/goa/v3/http/codegen/openapi/v2" ) // genState stores per-generation state so we can avoid mutating @@ -11,13 +12,17 @@ import ( type genState struct { nameScope *codegen.NameScope assignedNames map[*expr.UserTypeExpr]string + examples *expr.ExampleGenerator + definitions map[string]*openapi.Schema } -// newGenState constructs a fresh generator state for one Generate run. -func newGenState() *genState { +// newGenState constructs the state used to document one API. +func newGenState(api *expr.APIExpr) *genState { return &genState{ nameScope: codegen.NewNameScope(), assignedNames: make(map[*expr.UserTypeExpr]string), + examples: expr.NewExampleGenerator(api.RandomizerFactory), + definitions: make(map[string]*openapi.Schema), } } @@ -32,10 +37,10 @@ func (s *genState) uniqueNameFor(ut *expr.UserTypeExpr) string { return n } -// schemaForAttribute computes the OpenAPI schema for the given attribute using -// a temporary, per-call unique name for user types. The original AST is restored -// before returning. -func schemaForAttribute(api *expr.APIExpr, att *expr.AttributeExpr, state *genState) *openapi.Schema { +// schemaForAttribute builds a schema and keeps its named definitions with the +// API currently being documented. It restores temporary type names before it +// returns so documentation generation does not change the evaluated design. +func schemaForAttribute(api *expr.APIExpr, att *expr.AttributeExpr, examples *expr.ExampleGenerator, state *genState) *openapi.Schema { if att == nil || att.Type == nil { return nil } @@ -44,5 +49,10 @@ func schemaForAttribute(api *expr.APIExpr, att *expr.AttributeExpr, state *genSt ut.TypeName = state.uniqueNameFor(ut) defer func() { ut.TypeName = orig }() } - return openapi.AttributeTypeSchema(api, att) + schema := openapiv2.BuildAttributeSchema(api, att, examples) + for name, definition := range schema.Defs { + state.definitions[name] = dupSchema(definition) + } + schema.Defs = nil + return schema } diff --git a/docs/testdata/array-payload-no-return.json b/docs/testdata/array-payload-no-return.json index d4fb07f11..cc2be6979 100644 --- a/docs/testdata/array-payload-no-return.json +++ b/docs/testdata/array-payload-no-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"type":"array","items":{"type":"string","example":"Sit porro quaerat excepturi mollitia."}},"example":["Sit porro quaerat excepturi mollitia.","Dolorem et atque mollitia autem.","Officiis molestiae deserunt magni.","Facere nisi."]}}}}},"definitions":{}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"type":"array","items":{"type":"string","example":"Quos commodi quaerat modi laborum."}},"example":["Quos commodi quaerat modi laborum.","Ullam quos nesciunt et.","Corporis a rem vel sed."]}}}}},"definitions":{}} \ No newline at end of file diff --git a/docs/testdata/map-payload-no-return.json b/docs/testdata/map-payload-no-return.json index 819a71ef5..0b2f55b8c 100644 --- a/docs/testdata/map-payload-no-return.json +++ b/docs/testdata/map-payload-no-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"type":"object","additionalProperties":{"type":"integer","example":1605133563,"format":"int32"}},"example":{"Autem quasi quia qui distinctio.":1509751893,"Ea doloremque dicta nihil nostrum natus exercitationem.":1175599502,"Et consectetur.":1605133563}}}}}},"definitions":{}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"type":"object","additionalProperties":{"type":"integer","example":1329254996,"format":"int32"}},"example":{"Omnis quia eum ut inventore.":134110743,"Sit nulla iure aperiam ipsam ex ullam.":1329254996}}}}}},"definitions":{}} \ No newline at end of file diff --git a/docs/testdata/no-payload-array-return.json b/docs/testdata/no-payload-array-return.json index 734b4feb3..da963995b 100644 --- a/docs/testdata/no-payload-array-return.json +++ b/docs/testdata/no-payload-array-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"type":"array","items":{"type":"string","example":"Sit porro quaerat excepturi mollitia."}},"example":["Sit porro quaerat excepturi mollitia.","Dolorem et atque mollitia autem.","Officiis molestiae deserunt magni.","Facere nisi."]}}}}},"definitions":{}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"type":"array","items":{"type":"string","example":"Possimus totam iusto voluptatem."}},"example":["Possimus totam iusto voluptatem.","Minima vel debitis repellat sapiente quo.","Occaecati perferendis neque debitis nobis."]}}}}},"definitions":{}} \ No newline at end of file diff --git a/docs/testdata/no-payload-map-return.json b/docs/testdata/no-payload-map-return.json index fdb3d67b8..a390cfa76 100644 --- a/docs/testdata/no-payload-map-return.json +++ b/docs/testdata/no-payload-map-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"type":"object","additionalProperties":{"type":"integer","example":1605133563,"format":"int32"}},"example":{"Autem quasi quia qui distinctio.":1509751893,"Ea doloremque dicta nihil nostrum natus exercitationem.":1175599502,"Et consectetur.":1605133563}}}}}},"definitions":{}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"type":"object","additionalProperties":{"type":"integer","example":1816827947,"format":"int32"}},"example":{"Sed aut ea consequatur sit.":1625527223,"Unde et magni laborum esse ducimus cumque.":1816827947}}}}}},"definitions":{}} \ No newline at end of file diff --git a/docs/testdata/no-payload-primitive-return.json b/docs/testdata/no-payload-primitive-return.json index 10bc18ab1..56dd158cc 100644 --- a/docs/testdata/no-payload-primitive-return.json +++ b/docs/testdata/no-payload-primitive-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"type":"string"},"example":"In voluptatem consectetur."}}}}},"definitions":{}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"type":"string"},"example":"Quibusdam rerum et nemo eveniet ex."}}}}},"definitions":{}} \ No newline at end of file diff --git a/docs/testdata/no-payload-user-return.json b/docs/testdata/no-payload-user-return.json index 9cb983084..2aba861b4 100644 --- a/docs/testdata/no-payload-user-return.json +++ b/docs/testdata/no-payload-user-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"$ref":"#/$defs/User"},"example":{"att1":"Tempora quisquam.","att2":5658933617810793426}}}}}},"definitions":{"User":{"title":"User","type":"object","properties":{"att1":{"type":"string","example":"Tempora quisquam."},"att2":{"type":"integer","example":5658933617810793426,"format":"int64"}},"example":{"att1":"Tempora quisquam.","att2":5658933617810793426}}}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","result":{"type":{"$ref":"#/$defs/User"},"example":{"att1":"Molestiae et necessitatibus.","att2":4293885742366814168}}}}}},"definitions":{"User":{"title":"User","type":"object","properties":{"att1":{"type":"string","example":"Molestiae et necessitatibus."},"att2":{"type":"integer","example":4293885742366814168,"format":"int64"}},"example":{"att1":"Molestiae et necessitatibus.","att2":4293885742366814168}}}} \ No newline at end of file diff --git a/docs/testdata/primitive-payload-no-return.json b/docs/testdata/primitive-payload-no-return.json index 11e69da2c..97efa4b1d 100644 --- a/docs/testdata/primitive-payload-no-return.json +++ b/docs/testdata/primitive-payload-no-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"type":"string"},"example":"In voluptatem consectetur."}}}}},"definitions":{}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"type":"string"},"example":"Porro labore qui sint libero non."}}}}},"definitions":{}} \ No newline at end of file diff --git a/docs/testdata/user-payload-no-return.json b/docs/testdata/user-payload-no-return.json index b65658fe0..e074621cb 100644 --- a/docs/testdata/user-payload-no-return.json +++ b/docs/testdata/user-payload-no-return.json @@ -1 +1 @@ -{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"$ref":"#/$defs/User"},"example":{"att1":"Tempora quisquam.","att2":5658933617810793426}}}}}},"definitions":{"User":{"title":"User","type":"object","properties":{"att1":{"type":"string","example":"Tempora quisquam."},"att2":{"type":"integer","example":5658933617810793426,"format":"int64"}},"example":{"att1":"Tempora quisquam.","att2":5658933617810793426}}}} \ No newline at end of file +{"api":{"name":"Test API","version":"0.0.1","servers":{"Test API":{"name":"Test API","description":"Default server for Test API","services":["Service"],"hosts":{"localhost":{"name":"localhost","server":"Test API","uris":["http://localhost:80","grpc://localhost:8080"]}}}}},"services":{"Service":{"name":"Service","methods":{"Method":{"name":"Method","payload":{"type":{"$ref":"#/$defs/User"},"example":{"att1":"Molestiae et necessitatibus.","att2":4293885742366814168}}}}}},"definitions":{"User":{"title":"User","type":"object","properties":{"att1":{"type":"string","example":"Molestiae et necessitatibus."},"att2":{"type":"integer","example":4293885742366814168,"format":"int64"}},"example":{"att1":"Molestiae et necessitatibus.","att2":4293885742366814168}}}} \ No newline at end of file diff --git a/go.mod b/go.mod index adc45470d..6fc66ca0a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.0 + goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 5ffe96445..003ba9a70 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.0 h1:KXfAR5qlUCJLVPlHTZ6pHdC5b013BB78PLbDf2fFhfY= -goa.design/goa/v3 v3.30.0/go.mod h1:Is2byRdJddS20jhGT3Ql80N5hQyAc4HgAl7H/A0ZmJ4= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/encode_decode_files.go b/goakit/encode_decode_files.go index 3d87ff6af..11d7b6d78 100644 --- a/goakit/encode_decode_files.go +++ b/goakit/encode_decode_files.go @@ -1,3 +1,5 @@ +// This file writes go-kit request and response wrappers using the names chosen +// while Goa planned the generated client and server packages. package goakit import ( @@ -5,29 +7,63 @@ import ( "path/filepath" "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/service" "goa.design/goa/v3/expr" httpcodegen "goa.design/goa/v3/http/codegen" ) +type ( + // goakitEndpointData contains the chosen names rendered by go-kit templates + // alongside the HTTP method data supplied by Goa. + goakitEndpointData struct { + *httpcodegen.EndpointData + MountHandler, RequestDecoder, ResponseEncoder string + ErrorEncoder, RequestEncoder, ResponseDecoder string + } +) + // EncodeDecodeFiles produces a set of go-kit transport encoders and decoders // that wrap the corresponding generated goa functions. func EncodeDecodeFiles(genpkg string, root *expr.RootExpr) []*codegen.File { - services := httpcodegen.NewServicesData(service.NewServicesData(root), root.API.HTTP) - fw := make([]*codegen.File, 2*len(root.API.HTTP.Services)) - for i, r := range root.API.HTTP.Services { - fw[i] = serverEncodeDecode(genpkg, r, services) + plan, err := planHTTP(genpkg, root) + if err != nil { + panic(err) } - for i, r := range root.API.HTTP.Services { - fw[i+len(root.API.HTTP.Services)] = clientEncodeDecode(genpkg, r, services) + return encodeDecodeFiles(genpkg, plan) +} + +// encodeDecodeFiles builds go-kit codecs from the HTTP names chosen for the +// current generation run. +func encodeDecodeFiles(genpkg string, plan *goakitRootPlan) []*codegen.File { + fw := make([]*codegen.File, 2*len(plan.root.API.HTTP.Services)) + for i, service := range plan.root.API.HTTP.Services { + data := httpServiceData(plan.http, service) + fw[i] = serverEncodeDecode(genpkg, service, data, plan.services[service]) + } + for i, service := range plan.root.API.HTTP.Services { + data := httpServiceData(plan.http, service) + fw[i+len(plan.root.API.HTTP.Services)] = clientEncodeDecode(genpkg, service, data, plan.services[service]) } return fw } +// httpServiceData returns the linked HTTP data for a service already listed +// by the same design root. Its absence means the Goa plan is inconsistent. +func httpServiceData(plan *httpcodegen.Plan, service *expr.HTTPServiceExpr) *httpcodegen.ServiceData { + data, ok := plan.Service(service) + if !ok { + panic(fmt.Sprintf("goakit: HTTP service %q is missing from its plan", service.Name())) + } + return data +} + // serverEncodeDecode returns the file defining the go-kit HTTP server encoding // and decoding logic. -func serverEncodeDecode(genpkg string, svc *expr.HTTPServiceExpr, services *httpcodegen.ServicesData) *codegen.File { - data := services.Get(svc.Name()) +func serverEncodeDecode( + genpkg string, + svc *expr.HTTPServiceExpr, + data *httpcodegen.ServiceData, + names *goakitServicePlan, +) *codegen.File { svcName := data.Service.PathName path := filepath.Join(codegen.Gendir, "http", svcName, "kitserver", "encode_decode.go") title := fmt.Sprintf("%s go-kit HTTP server encoders and decoders", svc.Name()) @@ -44,17 +80,18 @@ func serverEncodeDecode(genpkg string, svc *expr.HTTPServiceExpr, services *http } for _, e := range data.Endpoints { + endpoint := plannedEndpointData(e, names.endpoints[e.Method.Name]) sections = append(sections, &codegen.SectionTemplate{ Name: "goakit-response-encoder", Source: responseEncoderT, - Data: e, + Data: endpoint, }) if e.Payload.Ref != "" { sections = append(sections, &codegen.SectionTemplate{ Name: "goakit-request-decoder", Source: requestDecoderT, - Data: e, + Data: endpoint, }) } @@ -62,7 +99,7 @@ func serverEncodeDecode(genpkg string, svc *expr.HTTPServiceExpr, services *http sections = append(sections, &codegen.SectionTemplate{ Name: "goakit-error-encoder", Source: errorEncoderT, - Data: e, + Data: endpoint, }) } } @@ -72,8 +109,12 @@ func serverEncodeDecode(genpkg string, svc *expr.HTTPServiceExpr, services *http // clientEncodeDecode returns the file defining the go-kit HTTP client encoding // and decoding logic. -func clientEncodeDecode(genpkg string, svc *expr.HTTPServiceExpr, services *httpcodegen.ServicesData) *codegen.File { - data := services.Get(svc.Name()) +func clientEncodeDecode( + genpkg string, + svc *expr.HTTPServiceExpr, + data *httpcodegen.ServiceData, + names *goakitServicePlan, +) *codegen.File { svcName := data.Service.PathName path := filepath.Join(codegen.Gendir, "http", svcName, "kitclient", "encode_decode.go") title := fmt.Sprintf("%s go-kit HTTP client encoders and decoders", svc.Name()) @@ -90,18 +131,19 @@ func clientEncodeDecode(genpkg string, svc *expr.HTTPServiceExpr, services *http } for _, e := range data.Endpoints { + endpoint := plannedEndpointData(e, names.endpoints[e.Method.Name]) if e.RequestEncoder != "" { sections = append(sections, &codegen.SectionTemplate{ Name: "goakit-request-encoder", Source: requestEncoderT, - Data: e, + Data: endpoint, }) } if e.Result != nil || len(e.Errors) > 0 { sections = append(sections, &codegen.SectionTemplate{ Name: "goakit-response-decoder", Source: responseDecoderT, - Data: e, + Data: endpoint, }) } } @@ -109,6 +151,31 @@ func clientEncodeDecode(genpkg string, svc *expr.HTTPServiceExpr, services *http return &codegen.File{Path: path, SectionTemplates: sections} } +// plannedEndpointData combines Goa's linked HTTP values with the wrapper names +// declared by this plugin before generation was frozen. +func plannedEndpointData(endpoint *httpcodegen.EndpointData, names *goakitEndpointNames) *goakitEndpointData { + data := &goakitEndpointData{EndpointData: endpoint} + if names.mountHandler != nil { + data.MountHandler = names.mountHandler.Name() + } + if names.requestDecoder != nil { + data.RequestDecoder = names.requestDecoder.Name() + } + if names.responseEncoder != nil { + data.ResponseEncoder = names.responseEncoder.Name() + } + if names.errorEncoder != nil { + data.ErrorEncoder = names.errorEncoder.Name() + } + if names.requestEncoder != nil { + data.RequestEncoder = names.requestEncoder.Name() + } + if names.responseDecoder != nil { + data.ResponseDecoder = names.responseDecoder.Name() + } + return data +} + // input: EndpointData const requestEncoderT = `{{ printf "%s returns a go-kit EncodeRequestFunc suitable for encoding %s %s requests." .RequestEncoder .ServiceName .Method.Name | comment }} func {{ .RequestEncoder }}(encoder func(*http.Request) goahttp.Encoder) kithttp.EncodeRequestFunc { diff --git a/goakit/encode_decode_files_test.go b/goakit/encode_decode_files_test.go index 641fcad96..aa213b7d0 100644 --- a/goakit/encode_decode_files_test.go +++ b/goakit/encode_decode_files_test.go @@ -24,7 +24,7 @@ func TestServerEncodeDecode(t *testing.T) { "goakit-request-decoder": {}, "goakit-error-encoder": {}, }, - Import: "/http/simple_service/server", + Import: "generated.local/gen/http/simple_service/server", }, "with-payload": { DSL: testdata.WithPayloadDSL, @@ -33,7 +33,7 @@ func TestServerEncodeDecode(t *testing.T) { "goakit-request-decoder": {testdata.WithPayloadMethodGoakitRequestDecoderCode}, "goakit-error-encoder": {}, }, - Import: "/http/with_payload_service/server", + Import: "generated.local/gen/http/with_payload_service/server", }, "with-error": { DSL: testdata.WithErrorDSL, @@ -42,7 +42,7 @@ func TestServerEncodeDecode(t *testing.T) { "goakit-request-decoder": {}, "goakit-error-encoder": {testdata.WithErrorMethodGoakitErrorEncoderCode}, }, - Import: "/http/with_error_service/server", + Import: "generated.local/gen/http/with_error_service/server", }, "multi-endpoints": { DSL: testdata.MultiEndpointDSL, @@ -51,7 +51,7 @@ func TestServerEncodeDecode(t *testing.T) { "goakit-request-decoder": {testdata.Endpoint1GoakitRequestDecoderCode}, "goakit-error-encoder": {testdata.Endpoint1GoakitErrorEncoderCode, testdata.Endpoint2GoakitErrorEncoderCode}, }, - Import: "/http/multi_endpoint_service/server", + Import: "generated.local/gen/http/multi_endpoint_service/server", }, "goifyable-service": { DSL: testdata.GoifyableServiceDSL, @@ -60,13 +60,13 @@ func TestServerEncodeDecode(t *testing.T) { "goakit-request-decoder": {}, "goakit-error-encoder": {}, }, - Import: "/http/goifyable_service/server", + Import: "generated.local/gen/http/goifyable_service/server", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) - fs := EncodeDecodeFiles("", root) + fs := EncodeDecodeFiles("generated.local/gen", root) require.Len(t, fs, 2) var found bool for _, f := range fs { @@ -95,7 +95,7 @@ func TestClientEncodeDecode(t *testing.T) { "goakit-response-decoder": {testdata.SimpleMethodGoakitResponseDecoderCode}, "goakit-request-encoder": {}, }, - Import: "/http/simple_service/client", + Import: "generated.local/gen/http/simple_service/client", }, "with-payload": { DSL: testdata.WithPayloadDSL, @@ -103,7 +103,7 @@ func TestClientEncodeDecode(t *testing.T) { "goakit-response-decoder": {testdata.WithPayloadMethodGoakitResponseDecoderCode}, "goakit-request-encoder": {testdata.WithPayloadMethodGoakitRequestEncoderCode}, }, - Import: "/http/with_payload_service/client", + Import: "generated.local/gen/http/with_payload_service/client", }, "with-error": { DSL: testdata.WithErrorDSL, @@ -111,7 +111,7 @@ func TestClientEncodeDecode(t *testing.T) { "goakit-response-decoder": {testdata.WithErrorMethodGoakitResponseDecoderCode}, "goakit-request-encoder": {}, }, - Import: "/http/with_error_service/client", + Import: "generated.local/gen/http/with_error_service/client", }, "multi-endpoints": { DSL: testdata.MultiEndpointDSL, @@ -119,13 +119,13 @@ func TestClientEncodeDecode(t *testing.T) { "goakit-response-decoder": {testdata.Endpoint1GoakitResponseDecoderCode, testdata.Endpoint2GoakitResponseDecoderCode}, "goakit-request-encoder": {testdata.Endpoint1GoakitRequestEncoderCode}, }, - Import: "/http/multi_endpoint_service/client", + Import: "generated.local/gen/http/multi_endpoint_service/client", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) - fs := EncodeDecodeFiles("", root) + fs := EncodeDecodeFiles("generated.local/gen", root) require.Len(t, fs, 2) var found bool for _, f := range fs { diff --git a/goakit/examples/calc/cmd/calc-cli/http.go b/goakit/examples/calc/cmd/calc-cli/http.go index 2028554c4..b586af945 100644 --- a/goakit/examples/calc/cmd/calc-cli/http.go +++ b/goakit/examples/calc/cmd/calc-cli/http.go @@ -1,16 +1,18 @@ package main import ( + "context" + "flag" "fmt" + "io" "net/http" "time" - "github.com/go-kit/kit/endpoint" goahttp "goa.design/goa/v3/http" cli "goa.design/plugins/v3/goakit/examples/calc/gen/http/cli/calc" ) -func doHTTP(scheme, host string, timeout int, debug bool) (endpoint.Endpoint, any, error) { +func doHTTP(ctx context.Context, scheme, host string, timeout int, debug bool, stdout io.Writer) error { var ( doer goahttp.Doer ) @@ -30,13 +32,17 @@ func doHTTP(scheme, host string, timeout int, debug bool) (endpoint.Endpoint, an debug, ) if err != nil { - return nil, nil, fmt.Errorf("parse endpoint: %w", err) + return fmt.Errorf("parse endpoint: %w", err) } - return endpoint, payload, nil -} -func httpUsageCommands() []string { - return cli.UsageCommands() + switch flag.Arg(0) { + case "calc": + switch flag.Arg(1) { + case "add": + return writeEndpointResult(ctx, stdout, endpoint, payload) + } + } + panic("parsed HTTP command has no generated result writer") } func httpUsageExamples() string { diff --git a/goakit/examples/calc/cmd/calc-cli/main.go b/goakit/examples/calc/cmd/calc-cli/main.go index 0c1f3a231..072841dc2 100644 --- a/goakit/examples/calc/cmd/calc-cli/main.go +++ b/goakit/examples/calc/cmd/calc-cli/main.go @@ -6,10 +6,9 @@ import ( "errors" "flag" "fmt" + "io" "net/url" "os" - "slices" - "sort" "strings" "github.com/go-kit/kit/endpoint" @@ -62,14 +61,12 @@ func main() { } var ( - endpoint endpoint.Endpoint - payload any - err error + err error ) { switch scheme { case "http", "https": - endpoint, payload, err = doHTTP(scheme, host, timeout, debug) + err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) os.Exit(1) @@ -84,23 +81,36 @@ func main() { os.Exit(1) } - data, err := endpoint(context.Background(), payload) +} + +// writeEndpointResult calls one normal endpoint and writes its result as JSON. +func writeEndpointResult(ctx context.Context, stdout io.Writer, endpoint endpoint.Endpoint, payload any) error { + data, err := endpoint(ctx, payload) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return err } + return writeJSON(stdout, data) +} - if data != nil { - m, _ := json.MarshalIndent(data, "", " ") - fmt.Println(string(m)) +// writeJSON writes one indented JSON value followed by a newline. +func writeJSON(stdout io.Writer, data any) error { + if data == nil { + return nil } + encoded, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("encode result: %w", err) + } + if _, err := fmt.Fprintln(stdout, string(encoded)); err != nil { + return fmt.Errorf("write result: %w", err) + } + return nil } func usage() { - var usageCommands []string - usageCommands = append(usageCommands, httpUsageCommands()...) - sort.Strings(usageCommands) - usageCommands = slices.Compact(usageCommands) + usageCommands := []string{ + "calc add", + } fmt.Fprintf(os.Stderr, `%s is a command line client for the calc API. Usage: diff --git a/goakit/examples/calc/gen/calc/service.go b/goakit/examples/calc/gen/calc/service.go index 09f2723a0..9ab077c3a 100644 --- a/goakit/examples/calc/gen/calc/service.go +++ b/goakit/examples/calc/gen/calc/service.go @@ -8,9 +8,7 @@ package calc -import ( - "context" -) +import "context" // The calc service exposes public endpoints that uses go-kit. type Service interface { diff --git a/goakit/examples/calc/gen/http/calc/client/encode_decode.go b/goakit/examples/calc/gen/http/calc/client/encode_decode.go index 91b86c10e..2f8272de7 100644 --- a/goakit/examples/calc/gen/http/calc/client/encode_decode.go +++ b/goakit/examples/calc/gen/http/calc/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -50,18 +51,24 @@ func (c *Client) BuildAddRequest(ctx context.Context, v any) (*http.Request, err // endpoint. restoreBody controls whether the response body should be restored // after having been read. func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calc", "add", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -75,7 +82,10 @@ func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) + } return nil, goahttp.ErrInvalidResponse("calc", "add", resp.StatusCode, string(body)) } } diff --git a/goakit/examples/fetcher/archiver/cmd/archiver-cli/http.go b/goakit/examples/fetcher/archiver/cmd/archiver-cli/http.go index c4de9ed3c..2aff82331 100644 --- a/goakit/examples/fetcher/archiver/cmd/archiver-cli/http.go +++ b/goakit/examples/fetcher/archiver/cmd/archiver-cli/http.go @@ -1,16 +1,18 @@ package main import ( + "context" + "flag" "fmt" + "io" "net/http" "time" - "github.com/go-kit/kit/endpoint" goahttp "goa.design/goa/v3/http" cli "goa.design/plugins/v3/goakit/examples/fetcher/archiver/gen/http/cli/archiver" ) -func doHTTP(scheme, host string, timeout int, debug bool) (endpoint.Endpoint, any, error) { +func doHTTP(ctx context.Context, scheme, host string, timeout int, debug bool, stdout io.Writer) error { var ( doer goahttp.Doer ) @@ -30,13 +32,24 @@ func doHTTP(scheme, host string, timeout int, debug bool) (endpoint.Endpoint, an debug, ) if err != nil { - return nil, nil, fmt.Errorf("parse endpoint: %w", err) + return fmt.Errorf("parse endpoint: %w", err) } - return endpoint, payload, nil -} -func httpUsageCommands() []string { - return cli.UsageCommands() + switch flag.Arg(0) { + case "archiver": + switch flag.Arg(1) { + case "archive": + return writeEndpointResult(ctx, stdout, endpoint, payload) + case "read": + return writeEndpointResult(ctx, stdout, endpoint, payload) + } + case "health": + switch flag.Arg(1) { + case "show": + return writeEndpointResult(ctx, stdout, endpoint, payload) + } + } + panic("parsed HTTP command has no generated result writer") } func httpUsageExamples() string { diff --git a/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go b/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go index a65b0218d..dbf0fc6a2 100644 --- a/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go +++ b/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go @@ -6,10 +6,9 @@ import ( "errors" "flag" "fmt" + "io" "net/url" "os" - "slices" - "sort" "strings" "github.com/go-kit/kit/endpoint" @@ -62,14 +61,12 @@ func main() { } var ( - endpoint endpoint.Endpoint - payload any - err error + err error ) { switch scheme { case "http", "https": - endpoint, payload, err = doHTTP(scheme, host, timeout, debug) + err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) os.Exit(1) @@ -84,23 +81,37 @@ func main() { os.Exit(1) } - data, err := endpoint(context.Background(), payload) +} + +// writeEndpointResult calls one normal endpoint and writes its result as JSON. +func writeEndpointResult(ctx context.Context, stdout io.Writer, endpoint endpoint.Endpoint, payload any) error { + data, err := endpoint(ctx, payload) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return err } + return writeJSON(stdout, data) +} - if data != nil { - m, _ := json.MarshalIndent(data, "", " ") - fmt.Println(string(m)) +// writeJSON writes one indented JSON value followed by a newline. +func writeJSON(stdout io.Writer, data any) error { + if data == nil { + return nil } + encoded, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("encode result: %w", err) + } + if _, err := fmt.Fprintln(stdout, string(encoded)); err != nil { + return fmt.Errorf("write result: %w", err) + } + return nil } func usage() { - var usageCommands []string - usageCommands = append(usageCommands, httpUsageCommands()...) - sort.Strings(usageCommands) - usageCommands = slices.Compact(usageCommands) + usageCommands := []string{ + "archiver (archive|read)", + "health show", + } fmt.Fprintf(os.Stderr, `%s is a command line client for the archiver API. Usage: diff --git a/goakit/examples/fetcher/archiver/gen/archiver/endpoints.go b/goakit/examples/fetcher/archiver/gen/archiver/endpoints.go index 8253f9730..5a684614e 100644 --- a/goakit/examples/fetcher/archiver/gen/archiver/endpoints.go +++ b/goakit/examples/fetcher/archiver/gen/archiver/endpoints.go @@ -12,6 +12,7 @@ import ( "context" "github.com/go-kit/kit/endpoint" + archiverviews "goa.design/plugins/v3/goakit/examples/fetcher/archiver/gen/archiver/views" ) // Endpoints wraps the "archiver" service endpoints. @@ -44,6 +45,9 @@ func NewArchiveEndpoint(s Service) endpoint.Endpoint { return nil, err } vres := NewViewedArchiveMedia(res, "default") + if err := archiverviews.ValidateArchiveMedia(vres); err != nil { + return nil, err + } return vres, nil } } @@ -58,6 +62,9 @@ func NewReadEndpoint(s Service) endpoint.Endpoint { return nil, err } vres := NewViewedArchiveMedia(res, "default") + if err := archiverviews.ValidateArchiveMedia(vres); err != nil { + return nil, err + } return vres, nil } } diff --git a/goakit/examples/fetcher/archiver/gen/archiver/views/view.go b/goakit/examples/fetcher/archiver/gen/archiver/views/view.go index 0f13ecda3..fc7d8b923 100644 --- a/goakit/examples/fetcher/archiver/gen/archiver/views/view.go +++ b/goakit/examples/fetcher/archiver/gen/archiver/views/view.go @@ -8,9 +8,7 @@ package views -import ( - goa "goa.design/goa/v3/pkg" -) +import goa "goa.design/goa/v3/pkg" // ArchiveMedia is the viewed result type that is projected based on a view. type ArchiveMedia struct { diff --git a/goakit/examples/fetcher/archiver/gen/health/service.go b/goakit/examples/fetcher/archiver/gen/health/service.go index c96ff8949..ccd452935 100644 --- a/goakit/examples/fetcher/archiver/gen/health/service.go +++ b/goakit/examples/fetcher/archiver/gen/health/service.go @@ -8,9 +8,7 @@ package health -import ( - "context" -) +import "context" // Service is the health service interface. type Service interface { diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go b/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go index f2fbf2be4..0a2f480a7 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go @@ -25,7 +25,7 @@ func BuildArchivePayload(archiverArchiveBody string) (*archiver.ArchivePayload, { err = json.Unmarshal([]byte(archiverArchiveBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"body\": \"Neque sunt.\",\n \"status\": 200\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"body\": \"In nisi sit nihil totam dolorum.\",\n \"status\": 200\n }'") } if body.Status < 0 { err = goa.MergeErrors(err, goa.InvalidRangeError("body.status", body.Status, 0, true)) diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go b/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go index 96ef20feb..b7bed7ba9 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -55,18 +56,24 @@ func EncodeArchiveRequest(encoder func(*http.Request) goahttp.Encoder) func(*htt // archiver archive endpoint. restoreBody controls whether the response body // should be restored after having been read. func DecodeArchiveResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("archiver", "archive", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("archiver", "archive", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -87,7 +94,10 @@ func DecodeArchiveResponse(decoder func(*http.Response) goahttp.Decoder, restore res := archiver.NewArchiveMedia(vres) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("archiver", "archive", err) + } return nil, goahttp.ErrInvalidResponse("archiver", "archive", resp.StatusCode, string(body)) } } @@ -126,18 +136,24 @@ func (c *Client) BuildReadRequest(ctx context.Context, v any) (*http.Request, er // - "bad_request" (type *goa.ServiceError): http.StatusBadRequest // - error: internal error func DecodeReadResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("archiver", "read", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("archiver", "read", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -186,7 +202,10 @@ func DecodeReadResponse(decoder func(*http.Response) goahttp.Decoder, restoreBod } return nil, NewReadBadRequest(&body) default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("archiver", "read", err) + } return nil, goahttp.ErrInvalidResponse("archiver", "read", resp.StatusCode, string(body)) } } diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go b/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go index 2bbc162f9..e0d7f84b6 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go @@ -144,7 +144,7 @@ func NewReadBadRequest(body *ReadBadRequestResponseBody) *goa.ServiceError { } // ValidateReadNotFoundResponseBody runs the validations defined on -// read_not_found_response_body +// ReadNotFoundResponseBody func ValidateReadNotFoundResponseBody(body *ReadNotFoundResponseBody) (err error) { if body.Name == nil { err = goa.MergeErrors(err, goa.MissingFieldError("name", "body")) @@ -168,7 +168,7 @@ func ValidateReadNotFoundResponseBody(body *ReadNotFoundResponseBody) (err error } // ValidateReadBadRequestResponseBody runs the validations defined on -// read_bad_request_response_body +// ReadBadRequestResponseBody func ValidateReadBadRequestResponseBody(body *ReadBadRequestResponseBody) (err error) { if body.Name == nil { err = goa.MergeErrors(err, goa.MissingFieldError("name", "body")) diff --git a/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go b/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go index 2f651b5fc..861e6b1c0 100644 --- a/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go +++ b/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go @@ -32,7 +32,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "archiver archive --body '{\n \"body\": \"Neque sunt.\",\n \"status\": 200\n }'" + "\n" + + return os.Args[0] + " " + "archiver archive --body '{\n \"body\": \"In nisi sit nihil totam dolorum.\",\n \"status\": 200\n }'" + "\n" + os.Args[0] + " " + "health show" + "\n" + "" } @@ -188,7 +188,7 @@ func archiverArchiveUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "archiver archive --body '{\n \"body\": \"Neque sunt.\",\n \"status\": 200\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "archiver archive --body '{\n \"body\": \"In nisi sit nihil totam dolorum.\",\n \"status\": 200\n }'") } func archiverReadUsage() { @@ -206,7 +206,7 @@ func archiverReadUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "archiver read --id 1362141423305610375") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "archiver read --id 4229100771459457146") } // healthUsage displays the usage of the health command and its subcommands. diff --git a/goakit/examples/fetcher/archiver/gen/http/health/client/encode_decode.go b/goakit/examples/fetcher/archiver/gen/http/health/client/encode_decode.go index 21ccfd9de..6abc92a20 100644 --- a/goakit/examples/fetcher/archiver/gen/http/health/client/encode_decode.go +++ b/goakit/examples/fetcher/archiver/gen/http/health/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -37,18 +38,24 @@ func (c *Client) BuildShowRequest(ctx context.Context, v any) (*http.Request, er // show endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeShowResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("health", "show", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("health", "show", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -62,7 +69,10 @@ func DecodeShowResponse(decoder func(*http.Response) goahttp.Decoder, restoreBod } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("health", "show", err) + } return nil, goahttp.ErrInvalidResponse("health", "show", resp.StatusCode, string(body)) } } diff --git a/goakit/examples/fetcher/archiver/gen/http/openapi.json b/goakit/examples/fetcher/archiver/gen/http/openapi.json index 546ed99e4..b8299115d 100644 --- a/goakit/examples/fetcher/archiver/gen/http/openapi.json +++ b/goakit/examples/fetcher/archiver/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"The goakit example downstream service","description":"Archiver is a service that manages the content of HTTP responses","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/archive":{"post":{"tags":["archiver"],"summary":"archive archiver","description":"Archive HTTP response","operationId":"archiver#archive","parameters":[{"name":"ArchiveRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/ArchivePayload","required":["status","body"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoaArchive"}}},"schemes":["http"]}},"/archive/{id}":{"get":{"tags":["archiver"],"summary":"read archiver","description":"Read HTTP response from archive","operationId":"archiver#read","parameters":[{"name":"id","in":"path","description":"ID of archive","required":true,"type":"integer","minimum":0}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoaArchive"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/ArchiverReadBadRequestResponseBody"}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/ArchiverReadNotFoundResponseBody"}}},"schemes":["http"]}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["http"]}}},"definitions":{"ArchivePayload":{"title":"ArchivePayload","type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Neque sunt."},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"example":{"body":"Neque sunt.","status":200},"required":["status","body"]},"ArchiverReadBadRequestResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":false},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type (default view)","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"ArchiverReadNotFoundResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":false},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type (default view)","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"GoaArchive":{"title":"Mediatype identifier: application/vnd.goa.archive; view=default","type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Consectetur rem atque quod temporibus vero."},"href":{"type":"string","description":"The archive resouce href","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Archive is an HTTP response archive (default view)","example":{"body":"Consectetur rem atque quod temporibus vero.","href":"/archive/1","status":200},"required":["href","status","body"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"The goakit example downstream service","description":"Archiver is a service that manages the content of HTTP responses","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/archive":{"post":{"tags":["archiver"],"summary":"archive archiver","description":"Archive HTTP response","operationId":"archiver#archive","parameters":[{"name":"ArchiveRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/ArchivePayload","required":["status","body"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoaArchive"}}},"schemes":["http"]}},"/archive/{id}":{"get":{"tags":["archiver"],"summary":"read archiver","description":"Read HTTP response from archive","operationId":"archiver#read","parameters":[{"name":"id","in":"path","description":"ID of archive","required":true,"type":"integer","minimum":0}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoaArchive"}},"400":{"description":"bad_request: Bad Request response.","schema":{"$ref":"#/definitions/ArchiverReadBadRequestResponseBody"},"examples":{"application/vnd.goa.error":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":false,"timeout":false}}},"404":{"description":"not_found: Not Found response.","schema":{"$ref":"#/definitions/ArchiverReadNotFoundResponseBody"},"examples":{"application/vnd.goa.error":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"not_found","temporary":false,"timeout":false}}}},"schemes":["http"]}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["http"]}}},"definitions":{"ArchivePayload":{"title":"ArchivePayload","type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"In nisi sit nihil totam dolorum."},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"example":{"body":"In nisi sit nihil totam dolorum.","status":200},"required":["status","body"]},"ArchiverReadBadRequestResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":false},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type (default view)","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"ArchiverReadNotFoundResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":false},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"Error response result type (default view)","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"GoaArchive":{"title":"Mediatype identifier: application/vnd.goa.archive; view=default","type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Sint maiores."},"href":{"type":"string","description":"The archive resouce href","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Archive is an HTTP response archive (default view)","example":{"body":"Sint maiores.","href":"/archive/1","status":200},"required":["href","status","body"]}}} \ No newline at end of file diff --git a/goakit/examples/fetcher/archiver/gen/http/openapi.yaml b/goakit/examples/fetcher/archiver/gen/http/openapi.yaml index 1b200fd07..9fc2ac475 100644 --- a/goakit/examples/fetcher/archiver/gen/http/openapi.yaml +++ b/goakit/examples/fetcher/archiver/gen/http/openapi.yaml @@ -56,13 +56,29 @@ paths: schema: $ref: '#/definitions/GoaArchive' "400": - description: Bad Request response. + description: 'bad_request: Bad Request response.' schema: $ref: '#/definitions/ArchiverReadBadRequestResponseBody' + examples: + application/vnd.goa.error: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: bad_request + temporary: false + timeout: false "404": - description: Not Found response. + description: 'not_found: Not Found response.' schema: $ref: '#/definitions/ArchiverReadNotFoundResponseBody' + examples: + application/vnd.goa.error: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: not_found + temporary: false + timeout: false schemes: - http /health: @@ -89,7 +105,7 @@ definitions: body: type: string description: HTTP response body content - example: Neque sunt. + example: In nisi sit nihil totam dolorum. status: type: integer description: HTTP status @@ -97,7 +113,7 @@ definitions: format: int64 minimum: 0 example: - body: Neque sunt. + body: In nisi sit nihil totam dolorum. status: 200 required: - status @@ -172,7 +188,7 @@ definitions: timeout: type: boolean description: Is the error a timeout? - example: true + example: false description: Error response result type (default view) example: fault: false @@ -180,7 +196,7 @@ definitions: message: parameter 'p' must be an integer name: bad_request temporary: true - timeout: true + timeout: false required: - name - id @@ -195,7 +211,7 @@ definitions: body: type: string description: HTTP response body content - example: Consectetur rem atque quod temporibus vero. + example: Sint maiores. href: type: string description: The archive resouce href @@ -209,7 +225,7 @@ definitions: minimum: 0 description: Archive is an HTTP response archive (default view) example: - body: Consectetur rem atque quod temporibus vero. + body: Sint maiores. href: /archive/1 status: 200 required: diff --git a/goakit/examples/fetcher/archiver/gen/http/openapi3.2.json b/goakit/examples/fetcher/archiver/gen/http/openapi3.2.json index a48fcb24a..9c369f91c 100644 --- a/goakit/examples/fetcher/archiver/gen/http/openapi3.2.json +++ b/goakit/examples/fetcher/archiver/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"The goakit example downstream service","description":"Archiver is a service that manages the content of HTTP responses","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"archiver","description":"Default server for archiver"}],"paths":{"/archive":{"post":{"tags":["archiver"],"summary":"archive archiver","description":"Archive HTTP response","operationId":"archiver#archive","requestBody":{"description":"Request body for archive.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchivePayload"},"example":{"body":"Neque sunt.","status":200}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Rerum rerum aliquid dolores.","href":"/archive/1","status":200}}}}}}},"/archive/{id}":{"get":{"tags":["archiver"],"summary":"read archiver","description":"Read HTTP response from archive","operationId":"archiver#read","parameters":[{"name":"id","in":"path","description":"ID of archive","required":true,"schema":{"type":"integer","description":"ID of archive","example":1362141423305610375,"format":"int64","minimum":0},"example":1557694296985387844}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Sit placeat culpa labore eaque.","href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: Not Found response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Voluptatibus omnis sed est."},"example":"Voluptatibus omnis sed est."}}}}}}},"components":{"schemas":{"ArchivePayload":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Neque sunt."},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Request body for archive.","example":{"body":"Neque sunt.","status":200},"required":["status","body"]},"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"Error response result type","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"GoaArchive":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Consequatur nisi et rerum ipsum accusantium."},"href":{"type":"string","description":"The archive resouce href","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Archive is an HTTP response archive","example":{"body":"Consequatur nisi et rerum ipsum accusantium.","href":"/archive/1","status":200},"required":["href","status","body"]}}},"tags":[{"name":"archiver"},{"name":"health"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"The goakit example downstream service","description":"Archiver is a service that manages the content of HTTP responses","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"archiver","description":"Default server for archiver"}],"paths":{"/archive":{"post":{"tags":["archiver"],"summary":"archive archiver","description":"Archive HTTP response","operationId":"archiver#archive","requestBody":{"description":"Request body for archive.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchivePayload"},"example":{"body":"In nisi sit nihil totam dolorum.","status":200}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Sint maiores.","href":"/archive/1","status":200}}}}}}},"/archive/{id}":{"get":{"tags":["archiver"],"summary":"read archiver","description":"Read HTTP response from archive","operationId":"archiver#read","parameters":[{"name":"id","in":"path","description":"ID of archive","required":true,"schema":{"type":"integer","description":"ID of archive","example":4229100771459457146,"format":"int64","minimum":0},"example":4229100771459457146}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Et enim.","href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":false,"timeout":false}}}},"404":{"description":"not_found: Not Found response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"not_found","temporary":false,"timeout":false}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Ut ipsa atque ad."},"example":"Ut ipsa atque ad."}}}}}}},"components":{"schemas":{"ArchivePayload":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"In nisi sit nihil totam dolorum."},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Request body for archive.","example":{"body":"In nisi sit nihil totam dolorum.","status":200},"required":["status","body"]},"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":false},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"Error response result type","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"GoaArchive":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Sint maiores."},"href":{"type":"string","description":"The archive resouce href","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Archive is an HTTP response archive","example":{"body":"Sint maiores.","href":"/archive/1","status":200},"required":["href","status","body"]}}},"tags":[{"name":"archiver"},{"name":"health"}]} \ No newline at end of file diff --git a/goakit/examples/fetcher/archiver/gen/http/openapi3.2.yaml b/goakit/examples/fetcher/archiver/gen/http/openapi3.2.yaml index 19bfafb29..bdf2f0b40 100644 --- a/goakit/examples/fetcher/archiver/gen/http/openapi3.2.yaml +++ b/goakit/examples/fetcher/archiver/gen/http/openapi3.2.yaml @@ -23,7 +23,7 @@ paths: schema: $ref: '#/components/schemas/ArchivePayload' example: - body: Neque sunt. + body: In nisi sit nihil totam dolorum. status: 200 responses: "200": @@ -33,7 +33,7 @@ paths: schema: $ref: '#/components/schemas/GoaArchive' example: - body: Rerum rerum aliquid dolores. + body: Sint maiores. href: /archive/1 status: 200 /archive/{id}: @@ -51,10 +51,10 @@ paths: schema: type: integer description: ID of archive - example: 1362141423305610375 + example: 4229100771459457146 format: int64 minimum: 0 - example: 1557694296985387844 + example: 4229100771459457146 responses: "200": description: OK response. @@ -63,7 +63,7 @@ paths: schema: $ref: '#/components/schemas/GoaArchive' example: - body: Sit placeat culpa labore eaque. + body: Et enim. href: /archive/1 status: 200 "400": @@ -72,12 +72,26 @@ paths: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: bad_request + temporary: false + timeout: false "404": description: 'not_found: Not Found response.' content: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: not_found + temporary: false + timeout: false /health: get: tags: @@ -92,8 +106,8 @@ paths: text/plain: schema: type: string - example: Voluptatibus omnis sed est. - example: Voluptatibus omnis sed est. + example: Ut ipsa atque ad. + example: Ut ipsa atque ad. components: schemas: ArchivePayload: @@ -102,7 +116,7 @@ components: body: type: string description: HTTP response body content - example: Neque sunt. + example: In nisi sit nihil totam dolorum. status: type: integer description: HTTP status @@ -111,7 +125,7 @@ components: minimum: 0 description: Request body for archive. example: - body: Neque sunt. + body: In nisi sit nihil totam dolorum. status: 200 required: - status @@ -122,7 +136,7 @@ components: fault: type: boolean description: Is the error a server-side fault? - example: true + example: false id: type: string description: ID is a unique identifier for this particular occurrence of the problem. @@ -145,7 +159,7 @@ components: example: false description: Error response result type example: - fault: true + fault: false id: 123abc message: parameter 'p' must be an integer name: bad_request @@ -164,7 +178,7 @@ components: body: type: string description: HTTP response body content - example: Consequatur nisi et rerum ipsum accusantium. + example: Sint maiores. href: type: string description: The archive resouce href @@ -178,7 +192,7 @@ components: minimum: 0 description: Archive is an HTTP response archive example: - body: Consequatur nisi et rerum ipsum accusantium. + body: Sint maiores. href: /archive/1 status: 200 required: diff --git a/goakit/examples/fetcher/archiver/gen/http/openapi3.json b/goakit/examples/fetcher/archiver/gen/http/openapi3.json index 8cdfcb4f9..012920314 100644 --- a/goakit/examples/fetcher/archiver/gen/http/openapi3.json +++ b/goakit/examples/fetcher/archiver/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"The goakit example downstream service","description":"Archiver is a service that manages the content of HTTP responses","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for archiver"}],"paths":{"/archive":{"post":{"tags":["archiver"],"summary":"archive archiver","description":"Archive HTTP response","operationId":"archiver#archive","requestBody":{"description":"Request body for archive.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchivePayload"},"example":{"body":"Neque sunt.","status":200}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Rerum rerum aliquid dolores.","href":"/archive/1","status":200}}}}}}},"/archive/{id}":{"get":{"tags":["archiver"],"summary":"read archiver","description":"Read HTTP response from archive","operationId":"archiver#read","parameters":[{"name":"id","in":"path","description":"ID of archive","required":true,"schema":{"type":"integer","description":"ID of archive","example":1362141423305610375,"format":"int64","minimum":0},"example":1557694296985387844}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Sit placeat culpa labore eaque.","href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: Not Found response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Voluptatibus omnis sed est."},"example":"Voluptatibus omnis sed est."}}}}}}},"components":{"schemas":{"ArchivePayload":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Neque sunt."},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Request body for archive.","example":{"body":"Neque sunt.","status":200},"required":["status","body"]},"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"Error response result type","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"GoaArchive":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Consequatur nisi et rerum ipsum accusantium."},"href":{"type":"string","description":"The archive resouce href","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Archive is an HTTP response archive","example":{"body":"Consequatur nisi et rerum ipsum accusantium.","href":"/archive/1","status":200},"required":["href","status","body"]}}},"tags":[{"name":"archiver"},{"name":"health"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"The goakit example downstream service","description":"Archiver is a service that manages the content of HTTP responses","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for archiver"}],"paths":{"/archive":{"post":{"tags":["archiver"],"summary":"archive archiver","description":"Archive HTTP response","operationId":"archiver#archive","requestBody":{"description":"Request body for archive.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchivePayload"},"example":{"body":"In nisi sit nihil totam dolorum.","status":200}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Sint maiores.","href":"/archive/1","status":200}}}}}}},"/archive/{id}":{"get":{"tags":["archiver"],"summary":"read archiver","description":"Read HTTP response from archive","operationId":"archiver#read","parameters":[{"name":"id","in":"path","description":"ID of archive","required":true,"schema":{"type":"integer","description":"ID of archive","example":4229100771459457146,"format":"int64","minimum":0},"example":4229100771459457146}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaArchive"},"example":{"body":"Et enim.","href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":false,"timeout":false}}}},"404":{"description":"not_found: Not Found response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"not_found","temporary":false,"timeout":false}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Ut ipsa atque ad."},"example":"Ut ipsa atque ad."}}}}}}},"components":{"schemas":{"ArchivePayload":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"In nisi sit nihil totam dolorum."},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Request body for archive.","example":{"body":"In nisi sit nihil totam dolorum.","status":200},"required":["status","body"]},"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":false},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"Error response result type","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"GoaArchive":{"type":"object","properties":{"body":{"type":"string","description":"HTTP response body content","example":"Sint maiores."},"href":{"type":"string","description":"The archive resouce href","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status","example":200,"format":"int64","minimum":0}},"description":"Archive is an HTTP response archive","example":{"body":"Sint maiores.","href":"/archive/1","status":200},"required":["href","status","body"]}}},"tags":[{"name":"archiver"},{"name":"health"}]} \ No newline at end of file diff --git a/goakit/examples/fetcher/archiver/gen/http/openapi3.yaml b/goakit/examples/fetcher/archiver/gen/http/openapi3.yaml index a70889a80..74be84119 100644 --- a/goakit/examples/fetcher/archiver/gen/http/openapi3.yaml +++ b/goakit/examples/fetcher/archiver/gen/http/openapi3.yaml @@ -22,7 +22,7 @@ paths: schema: $ref: '#/components/schemas/ArchivePayload' example: - body: Neque sunt. + body: In nisi sit nihil totam dolorum. status: 200 responses: "200": @@ -32,7 +32,7 @@ paths: schema: $ref: '#/components/schemas/GoaArchive' example: - body: Rerum rerum aliquid dolores. + body: Sint maiores. href: /archive/1 status: 200 /archive/{id}: @@ -50,10 +50,10 @@ paths: schema: type: integer description: ID of archive - example: 1362141423305610375 + example: 4229100771459457146 format: int64 minimum: 0 - example: 1557694296985387844 + example: 4229100771459457146 responses: "200": description: OK response. @@ -62,7 +62,7 @@ paths: schema: $ref: '#/components/schemas/GoaArchive' example: - body: Sit placeat culpa labore eaque. + body: Et enim. href: /archive/1 status: 200 "400": @@ -71,12 +71,26 @@ paths: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: bad_request + temporary: false + timeout: false "404": description: 'not_found: Not Found response.' content: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: not_found + temporary: false + timeout: false /health: get: tags: @@ -91,8 +105,8 @@ paths: text/plain: schema: type: string - example: Voluptatibus omnis sed est. - example: Voluptatibus omnis sed est. + example: Ut ipsa atque ad. + example: Ut ipsa atque ad. components: schemas: ArchivePayload: @@ -101,7 +115,7 @@ components: body: type: string description: HTTP response body content - example: Neque sunt. + example: In nisi sit nihil totam dolorum. status: type: integer description: HTTP status @@ -110,7 +124,7 @@ components: minimum: 0 description: Request body for archive. example: - body: Neque sunt. + body: In nisi sit nihil totam dolorum. status: 200 required: - status @@ -121,7 +135,7 @@ components: fault: type: boolean description: Is the error a server-side fault? - example: true + example: false id: type: string description: ID is a unique identifier for this particular occurrence of the problem. @@ -144,7 +158,7 @@ components: example: false description: Error response result type example: - fault: true + fault: false id: 123abc message: parameter 'p' must be an integer name: bad_request @@ -163,7 +177,7 @@ components: body: type: string description: HTTP response body content - example: Consequatur nisi et rerum ipsum accusantium. + example: Sint maiores. href: type: string description: The archive resouce href @@ -177,7 +191,7 @@ components: minimum: 0 description: Archive is an HTTP response archive example: - body: Consequatur nisi et rerum ipsum accusantium. + body: Sint maiores. href: /archive/1 status: 200 required: diff --git a/goakit/examples/fetcher/fetcher/gen/fetcher/endpoints.go b/goakit/examples/fetcher/fetcher/gen/fetcher/endpoints.go index 1d77fe70f..7d18bf8a4 100644 --- a/goakit/examples/fetcher/fetcher/gen/fetcher/endpoints.go +++ b/goakit/examples/fetcher/fetcher/gen/fetcher/endpoints.go @@ -12,6 +12,7 @@ import ( "context" "github.com/go-kit/kit/endpoint" + fetcherviews "goa.design/plugins/v3/goakit/examples/fetcher/fetcher/gen/fetcher/views" ) // Endpoints wraps the "fetcher" service endpoints. @@ -41,6 +42,9 @@ func NewFetchEndpoint(s Service) endpoint.Endpoint { return nil, err } vres := NewViewedFetchMedia(res, "default") + if err := fetcherviews.ValidateFetchMedia(vres); err != nil { + return nil, err + } return vres, nil } } diff --git a/goakit/examples/fetcher/fetcher/gen/fetcher/views/view.go b/goakit/examples/fetcher/fetcher/gen/fetcher/views/view.go index ea299dfa8..1728160ce 100644 --- a/goakit/examples/fetcher/fetcher/gen/fetcher/views/view.go +++ b/goakit/examples/fetcher/fetcher/gen/fetcher/views/view.go @@ -8,9 +8,7 @@ package views -import ( - goa "goa.design/goa/v3/pkg" -) +import goa "goa.design/goa/v3/pkg" // FetchMedia is the viewed result type that is projected based on a view. type FetchMedia struct { diff --git a/goakit/examples/fetcher/fetcher/gen/health/service.go b/goakit/examples/fetcher/fetcher/gen/health/service.go index 123c0d126..510e57695 100644 --- a/goakit/examples/fetcher/fetcher/gen/health/service.go +++ b/goakit/examples/fetcher/fetcher/gen/health/service.go @@ -8,9 +8,7 @@ package health -import ( - "context" -) +import "context" // Service is the health service interface. type Service interface { diff --git a/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go b/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go index 00e701ef6..d782c4ea4 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go +++ b/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go @@ -32,7 +32,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "fetcher fetch --url \"http://pfeffer.biz/lucile\"" + "\n" + + return os.Args[0] + " " + "fetcher fetch --url \"http://deckow.org/eric_nolan\"" + "\n" + os.Args[0] + " " + "health show" + "\n" + "" } @@ -177,7 +177,7 @@ func fetcherFetchUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "fetcher fetch --url \"http://pfeffer.biz/lucile\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "fetcher fetch --url \"http://deckow.org/eric_nolan\"") } // healthUsage displays the usage of the health command and its subcommands. diff --git a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go index c93d9e8b1..82d56ae3d 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go +++ b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -53,18 +54,24 @@ func (c *Client) BuildFetchRequest(ctx context.Context, v any) (*http.Request, e // - "internal_error" (type *goa.ServiceError): http.StatusInternalServerError // - error: internal error func DecodeFetchResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("fetcher", "fetch", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("fetcher", "fetch", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -113,7 +120,10 @@ func DecodeFetchResponse(decoder func(*http.Response) goahttp.Decoder, restoreBo } return nil, NewFetchInternalError(&body) default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("fetcher", "fetch", err) + } return nil, goahttp.ErrInvalidResponse("fetcher", "fetch", resp.StatusCode, string(body)) } } diff --git a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go index b77abe7ac..7c01e9588 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go +++ b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go @@ -99,7 +99,7 @@ func NewFetchInternalError(body *FetchInternalErrorResponseBody) *goa.ServiceErr } // ValidateFetchBadRequestResponseBody runs the validations defined on -// fetch_bad_request_response_body +// FetchBadRequestResponseBody func ValidateFetchBadRequestResponseBody(body *FetchBadRequestResponseBody) (err error) { if body.Name == nil { err = goa.MergeErrors(err, goa.MissingFieldError("name", "body")) @@ -123,7 +123,7 @@ func ValidateFetchBadRequestResponseBody(body *FetchBadRequestResponseBody) (err } // ValidateFetchInternalErrorResponseBody runs the validations defined on -// fetch_internal_error_response_body +// FetchInternalErrorResponseBody func ValidateFetchInternalErrorResponseBody(body *FetchInternalErrorResponseBody) (err error) { if body.Name == nil { err = goa.MergeErrors(err, goa.MissingFieldError("name", "body")) diff --git a/goakit/examples/fetcher/fetcher/gen/http/health/client/encode_decode.go b/goakit/examples/fetcher/fetcher/gen/http/health/client/encode_decode.go index b1a45e78f..010a32c42 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/health/client/encode_decode.go +++ b/goakit/examples/fetcher/fetcher/gen/http/health/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -37,18 +38,24 @@ func (c *Client) BuildShowRequest(ctx context.Context, v any) (*http.Request, er // show endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeShowResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("health", "show", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("health", "show", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -62,7 +69,10 @@ func DecodeShowResponse(decoder func(*http.Response) goahttp.Decoder, restoreBod } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("health", "show", err) + } return nil, goahttp.ErrInvalidResponse("health", "show", resp.StatusCode, string(body)) } } diff --git a/goakit/examples/fetcher/fetcher/gen/http/openapi.json b/goakit/examples/fetcher/fetcher/gen/http/openapi.json index 816d532a8..42607fac5 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/openapi.json +++ b/goakit/examples/fetcher/fetcher/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"The goakit example upstream service","description":"Fetcher is a service that makes GET requests to arbitrary URLs and stores the results in the downstream 'archiver' service.","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/fetch/{url}":{"get":{"tags":["fetcher"],"summary":"fetch fetcher","description":"Fetch makes a GET request to the given URL and stores the results in the archiver service which must be running or the request fails","operationId":"fetcher#fetch","parameters":[{"name":"url","in":"path","description":"URL to be fetched","required":true,"type":"string","format":"uri"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoaFetch"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/FetcherFetchBadRequestResponseBody"}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/FetcherFetchInternalErrorResponseBody"}}},"schemes":["http"]}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["http"]}}},"definitions":{"FetcherFetchBadRequestResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type (default view)","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"FetcherFetchInternalErrorResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type (default view)","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"GoaFetch":{"title":"Mediatype identifier: application/vnd.goa.fetch; view=default","type":"object","properties":{"archive_href":{"type":"string","description":"The href to the corresponding archive in the archiver service","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status code returned by fetched service","example":200,"format":"int64","minimum":0}},"description":"FetchResponse contains the HTTP status code returned by the fetched service and the href to the archived results in the archiver service (default view)","example":{"archive_href":"/archive/1","status":200},"required":["status","archive_href"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"The goakit example upstream service","description":"Fetcher is a service that makes GET requests to arbitrary URLs and stores the results in the downstream 'archiver' service.","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/fetch/{url}":{"get":{"tags":["fetcher"],"summary":"fetch fetcher","description":"Fetch makes a GET request to the given URL and stores the results in the archiver service which must be running or the request fails","operationId":"fetcher#fetch","parameters":[{"name":"url","in":"path","description":"URL to be fetched","required":true,"type":"string","format":"uri"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GoaFetch"}},"400":{"description":"bad_request: Bad Request response.","schema":{"$ref":"#/definitions/FetcherFetchBadRequestResponseBody"},"examples":{"application/vnd.goa.error":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":false,"timeout":false}}},"500":{"description":"internal_error: Internal Server Error response.","schema":{"$ref":"#/definitions/FetcherFetchInternalErrorResponseBody"},"examples":{"application/vnd.goa.error":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"internal_error","temporary":false,"timeout":false}}}},"schemes":["http"]}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["http"]}}},"definitions":{"FetcherFetchBadRequestResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type (default view)","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"FetcherFetchInternalErrorResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type (default view)","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"GoaFetch":{"title":"Mediatype identifier: application/vnd.goa.fetch; view=default","type":"object","properties":{"archive_href":{"type":"string","description":"The href to the corresponding archive in the archiver service","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status code returned by fetched service","example":200,"format":"int64","minimum":0}},"description":"FetchResponse contains the HTTP status code returned by the fetched service and the href to the archived results in the archiver service (default view)","example":{"archive_href":"/archive/1","status":200},"required":["status","archive_href"]}}} \ No newline at end of file diff --git a/goakit/examples/fetcher/fetcher/gen/http/openapi.yaml b/goakit/examples/fetcher/fetcher/gen/http/openapi.yaml index aa9bb8df5..98c05a366 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/openapi.yaml +++ b/goakit/examples/fetcher/fetcher/gen/http/openapi.yaml @@ -33,13 +33,29 @@ paths: schema: $ref: '#/definitions/GoaFetch' "400": - description: Bad Request response. + description: 'bad_request: Bad Request response.' schema: $ref: '#/definitions/FetcherFetchBadRequestResponseBody' + examples: + application/vnd.goa.error: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: bad_request + temporary: false + timeout: false "500": - description: Internal Server Error response. + description: 'internal_error: Internal Server Error response.' schema: $ref: '#/definitions/FetcherFetchInternalErrorResponseBody' + examples: + application/vnd.goa.error: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: internal_error + temporary: false + timeout: false schemes: - http /health: diff --git a/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.json b/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.json index 2f53435a6..ef38a80cb 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.json +++ b/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"The goakit example upstream service","description":"Fetcher is a service that makes GET requests to arbitrary URLs and stores the results in the downstream 'archiver' service.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"fetcher","description":"Default server for fetcher"}],"paths":{"/fetch/{url}":{"get":{"tags":["fetcher"],"summary":"fetch fetcher","description":"Fetch makes a GET request to the given URL and stores the results in the archiver service which must be running or the request fails","operationId":"fetcher#fetch","parameters":[{"name":"url","in":"path","description":"URL to be fetched","required":true,"schema":{"type":"string","description":"URL to be fetched","example":"http://pfeffer.biz/lucile","format":"uri"},"example":"http://nikolaus.com/adan.kemmer"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaFetch"},"example":{"archive_href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error: Internal Server Error response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Deserunt rerum."},"example":"Deserunt rerum."}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"Error response result type","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"GoaFetch":{"type":"object","properties":{"archive_href":{"type":"string","description":"The href to the corresponding archive in the archiver service","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status code returned by fetched service","example":200,"format":"int64","minimum":0}},"description":"FetchResponse contains the HTTP status code returned by the fetched service and the href to the archived results in the archiver service","example":{"archive_href":"/archive/1","status":200},"required":["status","archive_href"]}}},"tags":[{"name":"health"},{"name":"fetcher"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"The goakit example upstream service","description":"Fetcher is a service that makes GET requests to arbitrary URLs and stores the results in the downstream 'archiver' service.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"fetcher","description":"Default server for fetcher"}],"paths":{"/fetch/{url}":{"get":{"tags":["fetcher"],"summary":"fetch fetcher","description":"Fetch makes a GET request to the given URL and stores the results in the archiver service which must be running or the request fails","operationId":"fetcher#fetch","parameters":[{"name":"url","in":"path","description":"URL to be fetched","required":true,"schema":{"type":"string","description":"URL to be fetched","example":"http://deckow.org/eric_nolan","format":"uri"},"example":"http://deckow.org/eric_nolan"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaFetch"},"example":{"archive_href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":false,"timeout":false}}}},"500":{"description":"internal_error: Internal Server Error response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"internal_error","temporary":false,"timeout":false}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Veritatis nihil."},"example":"Veritatis nihil."}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"GoaFetch":{"type":"object","properties":{"archive_href":{"type":"string","description":"The href to the corresponding archive in the archiver service","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status code returned by fetched service","example":200,"format":"int64","minimum":0}},"description":"FetchResponse contains the HTTP status code returned by the fetched service and the href to the archived results in the archiver service","example":{"archive_href":"/archive/1","status":200},"required":["status","archive_href"]}}},"tags":[{"name":"health"},{"name":"fetcher"}]} \ No newline at end of file diff --git a/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.yaml b/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.yaml index 6a25c14bf..e5b7ed389 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.yaml +++ b/goakit/examples/fetcher/fetcher/gen/http/openapi3.2.yaml @@ -23,9 +23,9 @@ paths: schema: type: string description: URL to be fetched - example: http://pfeffer.biz/lucile + example: http://deckow.org/eric_nolan format: uri - example: http://nikolaus.com/adan.kemmer + example: http://deckow.org/eric_nolan responses: "200": description: OK response. @@ -42,12 +42,26 @@ paths: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: bad_request + temporary: false + timeout: false "500": description: 'internal_error: Internal Server Error response.' content: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: internal_error + temporary: false + timeout: false /health: get: tags: @@ -62,8 +76,8 @@ paths: text/plain: schema: type: string - example: Deserunt rerum. - example: Deserunt rerum. + example: Veritatis nihil. + example: Veritatis nihil. components: schemas: Error: @@ -92,7 +106,7 @@ components: timeout: type: boolean description: Is the error a timeout? - example: false + example: true description: Error response result type example: fault: true @@ -100,7 +114,7 @@ components: message: parameter 'p' must be an integer name: bad_request temporary: true - timeout: false + timeout: true required: - name - id diff --git a/goakit/examples/fetcher/fetcher/gen/http/openapi3.json b/goakit/examples/fetcher/fetcher/gen/http/openapi3.json index 1f8050067..3c1df0c35 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/openapi3.json +++ b/goakit/examples/fetcher/fetcher/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"The goakit example upstream service","description":"Fetcher is a service that makes GET requests to arbitrary URLs and stores the results in the downstream 'archiver' service.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for fetcher"}],"paths":{"/fetch/{url}":{"get":{"tags":["fetcher"],"summary":"fetch fetcher","description":"Fetch makes a GET request to the given URL and stores the results in the archiver service which must be running or the request fails","operationId":"fetcher#fetch","parameters":[{"name":"url","in":"path","description":"URL to be fetched","required":true,"schema":{"type":"string","description":"URL to be fetched","example":"http://pfeffer.biz/lucile","format":"uri"},"example":"http://nikolaus.com/adan.kemmer"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaFetch"},"example":{"archive_href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error: Internal Server Error response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Deserunt rerum."},"example":"Deserunt rerum."}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"Error response result type","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"GoaFetch":{"type":"object","properties":{"archive_href":{"type":"string","description":"The href to the corresponding archive in the archiver service","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status code returned by fetched service","example":200,"format":"int64","minimum":0}},"description":"FetchResponse contains the HTTP status code returned by the fetched service and the href to the archived results in the archiver service","example":{"archive_href":"/archive/1","status":200},"required":["status","archive_href"]}}},"tags":[{"name":"health"},{"name":"fetcher"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"The goakit example upstream service","description":"Fetcher is a service that makes GET requests to arbitrary URLs and stores the results in the downstream 'archiver' service.","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for fetcher"}],"paths":{"/fetch/{url}":{"get":{"tags":["fetcher"],"summary":"fetch fetcher","description":"Fetch makes a GET request to the given URL and stores the results in the archiver service which must be running or the request fails","operationId":"fetcher#fetch","parameters":[{"name":"url","in":"path","description":"URL to be fetched","required":true,"schema":{"type":"string","description":"URL to be fetched","example":"http://deckow.org/eric_nolan","format":"uri"},"example":"http://deckow.org/eric_nolan"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoaFetch"},"example":{"archive_href":"/archive/1","status":200}}}},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":false,"timeout":false}}}},"500":{"description":"internal_error: Internal Server Error response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"internal_error","temporary":false,"timeout":false}}}}}}},"/health":{"get":{"tags":["health"],"summary":"show health","description":"Health check endpoint","operationId":"health#show","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"Veritatis nihil."},"example":"Veritatis nihil."}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"Error response result type","example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"GoaFetch":{"type":"object","properties":{"archive_href":{"type":"string","description":"The href to the corresponding archive in the archiver service","example":"/archive/1","pattern":"^/archive/[0-9]+$"},"status":{"type":"integer","description":"HTTP status code returned by fetched service","example":200,"format":"int64","minimum":0}},"description":"FetchResponse contains the HTTP status code returned by the fetched service and the href to the archived results in the archiver service","example":{"archive_href":"/archive/1","status":200},"required":["status","archive_href"]}}},"tags":[{"name":"health"},{"name":"fetcher"}]} \ No newline at end of file diff --git a/goakit/examples/fetcher/fetcher/gen/http/openapi3.yaml b/goakit/examples/fetcher/fetcher/gen/http/openapi3.yaml index 99794f808..9a4be0b79 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/openapi3.yaml +++ b/goakit/examples/fetcher/fetcher/gen/http/openapi3.yaml @@ -22,9 +22,9 @@ paths: schema: type: string description: URL to be fetched - example: http://pfeffer.biz/lucile + example: http://deckow.org/eric_nolan format: uri - example: http://nikolaus.com/adan.kemmer + example: http://deckow.org/eric_nolan responses: "200": description: OK response. @@ -41,12 +41,26 @@ paths: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: bad_request + temporary: false + timeout: false "500": description: 'internal_error: Internal Server Error response.' content: application/vnd.goa.error: schema: $ref: '#/components/schemas/Error' + example: + fault: false + id: 123abc + message: parameter 'p' must be an integer + name: internal_error + temporary: false + timeout: false /health: get: tags: @@ -61,8 +75,8 @@ paths: text/plain: schema: type: string - example: Deserunt rerum. - example: Deserunt rerum. + example: Veritatis nihil. + example: Veritatis nihil. components: schemas: Error: @@ -91,7 +105,7 @@ components: timeout: type: boolean description: Is the error a timeout? - example: false + example: true description: Error response result type example: fault: true @@ -99,7 +113,7 @@ components: message: parameter 'p' must be an integer name: bad_request temporary: true - timeout: false + timeout: true required: - name - id diff --git a/goakit/generate.go b/goakit/generate.go index fd5e93aa8..2eafc35de 100644 --- a/goakit/generate.go +++ b/goakit/generate.go @@ -1,19 +1,71 @@ +// This file registers the go-kit generator and records every package name it +// adds before Goa chooses final names for generated code. package goakit import ( + "cmp" "path" "regexp" "strings" "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/generator" + "goa.design/goa/v3/codegen/service" "goa.design/goa/v3/eval" "goa.design/goa/v3/expr" httpcodegen "goa.design/goa/v3/http/codegen" ) +type ( + // goakitRootPlan keeps the Goa HTTP plan and the go-kit names declared for + // one design root during the same generation run. + goakitRootPlan struct { + root *expr.RootExpr + http *httpcodegen.Plan + services map[*expr.HTTPServiceExpr]*goakitServicePlan + } + + // goakitServicePlan keeps the function names emitted in the go-kit client + // and server packages for one service. + goakitServicePlan struct { + endpoints map[string]*goakitEndpointNames + fileServers []*codegen.NameDeclaration + } + + // goakitEndpointNames keeps the wrapper names emitted for one method. + goakitEndpointNames struct { + mountHandler *codegen.NameDeclaration + requestDecoder *codegen.NameDeclaration + responseEncoder *codegen.NameDeclaration + errorEncoder *codegen.NameDeclaration + requestEncoder *codegen.NameDeclaration + responseDecoder *codegen.NameDeclaration + } + + // goakitNameOrder gives every generated go-kit function a stable position + // when two plugins ask for the same name. + goakitNameOrder struct { + api, service, method, subject string + role goakitNameRole + } + + // goakitNameRole identifies the function written by one go-kit template. + goakitNameRole uint8 +) + +const ( + goakitMountHandlerRole goakitNameRole = iota + 1 + goakitRequestDecoderRole + goakitResponseEncoderRole + goakitErrorEncoderRole + goakitRequestEncoderRole + goakitResponseDecoderRole + goakitFileServerRole +) + // Register the plugin Generator functions. func init() { - codegen.RegisterPluginFirst("goakit", "gen", nil, Generate) + generator.RegisterPluginFirst("goakit", "gen", newPlugin) codegen.RegisterPluginLast("goakit-goakitify", "gen", nil, Goakitify) codegen.RegisterPluginLast("goakit-goakitify-example", "example", nil, GoakitifyExample) } @@ -22,13 +74,248 @@ func init() { func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { for _, root := range roots { if r, ok := root.(*expr.RootExpr); ok { - files = append(files, EncodeDecodeFiles(genpkg, r)...) - files = append(files, MountFiles(r)...) + plan, err := planHTTP(genpkg, r) + if err != nil { + return nil, err + } + files = append(files, encodeDecodeFiles(genpkg, plan)...) + files = append(files, mountFiles(plan)...) } } return files, nil } +// newPlugin creates the HTTP package additions used by one Goa generation. +func newPlugin() generator.Plugin { + var plans []*goakitRootPlan + return generator.Plugin{ + Plan: func(plan *generator.Plan) error { + for _, candidate := range plan.Generation().Roots() { + root, ok := candidate.(*expr.RootExpr) + if !ok { + continue + } + httpPlan, ok := plan.HTTP(root) + if !ok { + continue + } + services, err := planPackages(plan.Generation(), plan.Service(root), root) + if err != nil { + return err + } + plans = append(plans, &goakitRootPlan{root: root, http: httpPlan, services: services}) + } + return nil + }, + Generate: func(plan *generator.Plan, files []*codegen.File) ([]*codegen.File, error) { + for _, planned := range plans { + files = append(files, encodeDecodeFiles(plan.Generation().GenPkg(), planned)...) + files = append(files, mountFiles(planned)...) + } + return files, nil + }, + } +} + +// planPackages records every package, import, and function emitted by the +// go-kit files before Goa chooses names for the generation run. +func planPackages(generation *codegen.Generation, servicePlan *service.Plan, root *expr.RootExpr) (map[*expr.HTTPServiceExpr]*goakitServicePlan, error) { + plans := make(map[*expr.HTTPServiceExpr]*goakitServicePlan, len(root.API.HTTP.Services)) + for _, transportService := range root.API.HTTP.Services { + serviceImport, _, err := servicePlan.ServicePackageImports(transportService.ServiceExpr) + if err != nil { + return nil, err + } + servicePath := path.Base(serviceImport.Path) + serverPath := path.Join(generation.GenPkg(), "http", servicePath, "kitserver") + serverPackage, err := generation.ClaimPackage(serverPath) + if err != nil { + return nil, err + } + if err := requireImports(serverPackage, []*codegen.ImportSpec{ + {Path: "context"}, + {Path: "net/http"}, + {Path: "strings"}, + {Path: "github.com/go-kit/kit/transport/http", Name: "kithttp"}, + {Path: "goa.design/goa/v3", Name: "goa"}, + {Path: "goa.design/goa/v3/http", Name: "goahttp"}, + {Path: path.Join(generation.GenPkg(), "http", servicePath, "server")}, + }); err != nil { + return nil, err + } + clientPath := path.Join(generation.GenPkg(), "http", servicePath, "kitclient") + clientPackage, err := generation.ClaimPackage(clientPath) + if err != nil { + return nil, err + } + if err := requireImports(clientPackage, []*codegen.ImportSpec{ + {Path: "context"}, + {Path: "net/http"}, + {Path: "strings"}, + {Path: "github.com/go-kit/kit/transport/http", Name: "kithttp"}, + {Path: "goa.design/goa/v3", Name: "goa"}, + {Path: "goa.design/goa/v3/http", Name: "goahttp"}, + {Path: path.Join(generation.GenPkg(), "http", servicePath, "client")}, + }); err != nil { + return nil, err + } + planned, err := planServiceNames(root, servicePlan, transportService, serverPackage, clientPackage) + if err != nil { + return nil, err + } + plans[transportService] = planned + } + return plans, nil +} + +// planServiceNames declares every wrapper and mount function emitted for one +// HTTP service. +func planServiceNames(root *expr.RootExpr, servicePlan *service.Plan, transportService *expr.HTTPServiceExpr, serverPackage, clientPackage *codegen.GeneratedPackage) (*goakitServicePlan, error) { + planned := &goakitServicePlan{endpoints: make(map[string]*goakitEndpointNames, len(transportService.HTTPEndpoints))} + for _, endpoint := range transportService.HTTPEndpoints { + methodNames, err := servicePlan.HTTPMethodNames(endpoint.MethodExpr) + if err != nil { + return nil, err + } + order := goakitNameOrder{api: root.API.Name, service: transportService.Name(), method: endpoint.MethodExpr.Name} + names := new(goakitEndpointNames) + if names.mountHandler, err = declareGoakitName(serverPackage, "Mount"+methodNames.Method+"Handler", order.withRole(goakitMountHandlerRole)); err != nil { + return nil, err + } + if names.responseEncoder, err = declareGoakitName(serverPackage, "Encode"+methodNames.Method+"Response", order.withRole(goakitResponseEncoderRole)); err != nil { + return nil, err + } + if endpoint.MethodExpr.Payload.Type != expr.Empty { + if names.requestDecoder, err = declareGoakitName(serverPackage, "Decode"+methodNames.Method+"Request", order.withRole(goakitRequestDecoderRole)); err != nil { + return nil, err + } + } + if len(endpoint.HTTPErrors) > 0 { + if names.errorEncoder, err = declareGoakitName(serverPackage, "Encode"+methodNames.Method+"Error", order.withRole(goakitErrorEncoderRole)); err != nil { + return nil, err + } + } + if goakitClientRequestEncoderSelected(endpoint) { + if names.requestEncoder, err = declareGoakitName(clientPackage, "Encode"+methodNames.Method+"Request", order.withRole(goakitRequestEncoderRole)); err != nil { + return nil, err + } + } + if names.responseDecoder, err = declareGoakitName(clientPackage, "Decode"+methodNames.Method+"Response", order.withRole(goakitResponseDecoderRole)); err != nil { + return nil, err + } + planned.endpoints[endpoint.MethodExpr.Name] = names + } + for _, fileServer := range transportService.FileServers { + order := goakitNameOrder{ + api: root.API.Name, + service: transportService.Name(), + subject: fileServer.FilePath, + role: goakitFileServerRole, + } + declaration, err := declareGoakitName(serverPackage, "Mount"+codegen.Goify(fileServer.FilePath, true), order) + if err != nil { + return nil, err + } + planned.fileServers = append(planned.fileServers, declaration) + } + return planned, nil +} + +// declareGoakitName records one public function that a go-kit template writes. +func declareGoakitName(pkg *codegen.GeneratedPackage, preferred string, order goakitNameOrder) (*codegen.NameDeclaration, error) { + declaration := codegen.NewPreferredName(codegen.NameFunction, preferred, codegen.ExportedName, order) + if err := pkg.DeclareName(declaration); err != nil { + return nil, err + } + return declaration, nil +} + +// goakitClientRequestEncoderSelected reports whether the HTTP client writes a +// request encoder that the go-kit client must wrap. +func goakitClientRequestEncoderSelected(endpoint *expr.HTTPEndpointExpr) bool { + if endpoint.IsJSONRPC() { + return true + } + if (!endpoint.SkipRequestBodyEncodeDecode && endpoint.Body.Type != expr.Empty) || + endpoint.MapQueryParams != nil || + len(*expr.AsObject(endpoint.QueryParams().Type)) > 0 || + len(*expr.AsObject(endpoint.Headers.Type)) > 0 || + len(*expr.AsObject(endpoint.Cookies.Type)) > 0 { + return true + } + for _, requirement := range endpoint.Requirements { + for _, scheme := range requirement.Schemes { + if scheme.Kind == expr.BasicAuthKind { + return true + } + } + } + return false +} + +// requireImports records import names already written in go-kit templates. +func requireImports(pkg *codegen.GeneratedPackage, imports []*codegen.ImportSpec) error { + for _, spec := range imports { + if err := pkg.RequireImport(spec); err != nil { + return err + } + } + return nil +} + +// planHTTP creates linked HTTP data for callers that invoke Generate directly. +func planHTTP(genpkg string, root *expr.RootExpr) (*goakitRootPlan, error) { + generation, err := codegen.NewGeneration(genpkg, []eval.Root{root}) + if err != nil { + return nil, err + } + servicePlan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + if err != nil { + return nil, err + } + plans, err := httpcodegen.NewPlans(generation, httpcodegen.PlanInput{Root: root, Service: servicePlan}) + if err != nil { + return nil, err + } + services, err := planPackages(generation, servicePlan, root) + if err != nil { + return nil, err + } + if err := generation.Freeze(); err != nil { + return nil, err + } + if err := servicePlan.Link(); err != nil { + return nil, err + } + if err := plans[0].Link(); err != nil { + return nil, err + } + return &goakitRootPlan{root: root, http: plans[0], services: services}, nil +} + +// ComparePackageName orders go-kit names by their complete design identity. +func (o goakitNameOrder) ComparePackageName(other codegen.PackageNameOrder) int { + right := other.(goakitNameOrder) + for _, compared := range []int{ + cmp.Compare(o.api, right.api), + cmp.Compare(o.service, right.service), + cmp.Compare(o.method, right.method), + cmp.Compare(o.subject, right.subject), + cmp.Compare(o.role, right.role), + } { + if compared != 0 { + return compared + } + } + return 0 +} + +// withRole returns the same name identity for another generated function. +func (o goakitNameOrder) withRole(role goakitNameRole) goakitNameOrder { + o.role = role + return o +} + // Goakitify modifies all the previously generated files by adding go-kit // imports and replacing the following instances "goa.Endpoint" with // "github.com/go-kit/kit/endpoint".Endpoint @@ -89,8 +376,12 @@ func gokitifyExampleServer(genpkg string, file *codegen.File) { deleteImports(file.SectionTemplates[0]) codegen.AddImport(file.SectionTemplates[0], &codegen.ImportSpec{Name: "kitlog", Path: "github.com/go-kit/log"}) codegen.AddImport(file.SectionTemplates[0], &codegen.ImportSpec{Path: "goa.design/clue/log"}) - oldinit := "{{ .VarName }}Svc = {{ $.APIPkg }}.New{{ .StructName }}()" - section.Source = strings.Replace(section.Source, oldinit, initT, 1) + section.Source = strings.Replace( + section.Source, + "{{ .ServiceVar }} = {{ $.APIPkg }}.{{ .ExampleConstructorDeclaration.Name }}()", + initT, + 1, + ) case "basic-service-struct": deleteImports(file.SectionTemplates[0]) codegen.AddImport(file.SectionTemplates[0], &codegen.ImportSpec{Path: "github.com/go-kit/log"}) @@ -108,14 +399,14 @@ func gokitifyExampleServer(genpkg string, file *codegen.File) { hasGoaMiddleware = true section.Source = strings.Replace( section.Source, - `{{ .VarName }}Endpoints.Use(debug.LogPayloads())`, - `{{ .VarName }}Endpoints.Use(wrapMiddleware(debug.LogPayloads()))`, + `{{ .EndpointsVar }}.Use(debug.LogPayloads())`, + `{{ .EndpointsVar }}.Use(wrapMiddleware(debug.LogPayloads()))`, 1, ) section.Source = strings.Replace( section.Source, - `{{ .VarName }}Endpoints.Use(log.Endpoint)`, - `{{ .VarName }}Endpoints.Use(wrapMiddleware(log.Endpoint))`, + `{{ .EndpointsVar }}.Use(log.Endpoint)`, + `{{ .EndpointsVar }}.Use(wrapMiddleware(log.Endpoint))`, 1, ) case "server-http-init": @@ -177,21 +468,21 @@ const initT = `{ logger = kitlog.With(logger, "ts", kitlog.DefaultTimestampUTC) logger = kitlog.With(logger, "caller", kitlog.DefaultCaller) logger = kitlog.With(logger, "service", {{ printf "%q" .Name }}) - {{ .VarName }}Svc = {{ $.APIPkg }}.New{{ .StructName }}(logger) + {{ .ServiceVar }} = {{ $.APIPkg }}.{{ .ExampleConstructorDeclaration.Name }}(logger) } ` const basicServiceStructT = ` {{ printf "%s service example implementation.\nThe example methods log the requests and return zero values." .Name | comment }} -type {{ .VarName }}srvc struct { +type {{ .ExampleStructDeclaration.Name }} struct { logger log.Logger } ` const basicServiceInitT = ` {{ printf "New%s returns the %s service implementation." .StructName .Name | comment }} -func New{{ .StructName }}(logger log.Logger) {{ .PkgName }}.Service { - return &{{ .VarName }}srvc{ +func {{ .ExampleConstructorDeclaration.Name }}(logger log.Logger) {{ .ServicePkg }}.{{ .ServiceDeclaration.Name }} { + return &{{ .ExampleStructDeclaration.Name }}{ logger: logger, } } diff --git a/goakit/generate_test.go b/goakit/generate_test.go index 65c798562..659c721db 100644 --- a/goakit/generate_test.go +++ b/goakit/generate_test.go @@ -9,7 +9,10 @@ import ( "github.com/stretchr/testify/require" "goa.design/goa/v3/codegen" "goa.design/goa/v3/codegen/generator" + "goa.design/goa/v3/codegen/service" "goa.design/goa/v3/eval" + "goa.design/goa/v3/expr" + httpcodegen "goa.design/goa/v3/http/codegen" "goa.design/plugins/v3/goakit/testdata" ) @@ -26,7 +29,7 @@ func TestGenerate(t *testing.T) { root := codegen.RunDSL(t, c.DSL) roots := []eval.Root{root} files := generateFiles(t, roots) - newFiles, err := Generate("", roots, files) + newFiles, err := Generate("generated.local/gen", roots, files) if err != nil { t.Fatalf("generate error: %v", err) } @@ -38,6 +41,33 @@ func TestGenerate(t *testing.T) { } } +// TestPlannedNamesAvoidPackageCollisions verifies that go-kit wrappers use +// the final function names selected for their generated package. +func TestPlannedNamesAvoidPackageCollisions(t *testing.T) { + root := codegen.RunDSL(t, testdata.SimpleServiceDSL) + generation, err := codegen.NewGeneration("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + servicePlan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + require.NoError(t, err) + httpPlans, err := httpcodegen.NewPlans(generation, httpcodegen.PlanInput{Root: root, Service: servicePlan}) + require.NoError(t, err) + serverPackage, err := generation.ClaimPackage("generated.local/gen/http/simple_service/kitserver") + require.NoError(t, err) + require.NoError(t, serverPackage.DeclareName(codegen.NewExactName(codegen.NameFunction, "EncodeSimpleMethodResponse"))) + services, err := planPackages(generation, servicePlan, root) + require.NoError(t, err) + require.NoError(t, generation.Freeze()) + require.NoError(t, servicePlan.Link()) + require.NoError(t, httpPlans[0].Link()) + + planned := &goakitRootPlan{root: root, http: httpPlans[0], services: services} + files := encodeDecodeFiles(generation.GenPkg(), planned) + sections := findSections(files, "goakit-response-encoder") + require.Len(t, sections, 1) + code := codegen.SectionCode(t, sections[0]) + require.Contains(t, code, "func EncodeSimpleMethodResponse2(") +} + func TestGoakitify(t *testing.T) { // Map all files containing goaEndpoint indexed by file path goaEndpointFiles := map[string]bool{} @@ -139,16 +169,16 @@ func TestGoakitifyExample(t *testing.T) { } func generateFiles(t *testing.T, roots []eval.Root) []*codegen.File { - files, err := generator.Service("", roots) + files, err := generator.Service("generated.local/gen", roots) require.NoError(t, err) - httpFiles, err := generator.Transport("", roots) + httpFiles, err := generator.Transport("generated.local/gen", roots) require.NoError(t, err) files = append(files, httpFiles...) return files } func generateExamples(t *testing.T, roots []eval.Root) []*codegen.File { - files, err := generator.Example("", roots) + files, err := generator.Example("generated.local/gen", roots) require.NoError(t, err) return files } @@ -172,3 +202,16 @@ func containsStdlibLogger(t *testing.T, f *codegen.File) (string, bool) { } return "", false } + +// findSections returns every generated section with the requested name. +func findSections(files []*codegen.File, name string) []*codegen.SectionTemplate { + var matches []*codegen.SectionTemplate + for _, file := range files { + for _, section := range file.SectionTemplates { + if section.Name == name { + matches = append(matches, section) + } + } + } + return matches +} diff --git a/goakit/mount_files.go b/goakit/mount_files.go index fd3efa8c9..8691bfc37 100644 --- a/goakit/mount_files.go +++ b/goakit/mount_files.go @@ -1,3 +1,5 @@ +// This file writes go-kit mount functions using the names chosen while Goa +// planned the generated server package. package goakit import ( @@ -6,26 +8,47 @@ import ( "strings" "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/service" "goa.design/goa/v3/expr" httpcodegen "goa.design/goa/v3/http/codegen" ) +type ( + // goakitFileServerData contains the chosen mount name rendered by the go-kit + // template alongside the file server values supplied by Goa. + goakitFileServerData struct { + *httpcodegen.FileServerData + MountHandler string + } +) + // MountFiles produces the files containing the HTTP handler mount functions // that configure the mux to serve the requests. func MountFiles(root *expr.RootExpr) []*codegen.File { - services := httpcodegen.NewServicesData(service.NewServicesData(root), root.API.HTTP) - fw := make([]*codegen.File, len(root.API.HTTP.Services)) - for i, svc := range root.API.HTTP.Services { - fw[i] = mountFile(svc, services) + plan, err := planHTTP("generated.local/gen", root) + if err != nil { + panic(err) + } + return mountFiles(plan) +} + +// mountFiles builds mount functions from the HTTP names chosen for the current +// generation run. +func mountFiles(plan *goakitRootPlan) []*codegen.File { + fw := make([]*codegen.File, len(plan.root.API.HTTP.Services)) + for i, service := range plan.root.API.HTTP.Services { + data := httpServiceData(plan.http, service) + fw[i] = mountFile(service, data, plan.services[service]) } return fw } // mountFile returns the file defining the mount handler functions for the given // service. -func mountFile(svc *expr.HTTPServiceExpr, services *httpcodegen.ServicesData) *codegen.File { - data := services.Get(svc.Name()) +func mountFile( + svc *expr.HTTPServiceExpr, + data *httpcodegen.ServiceData, + names *goakitServicePlan, +) *codegen.File { path := filepath.Join(codegen.Gendir, "http", data.Service.PathName, "kitserver", "mount.go") title := fmt.Sprintf("%s go-kit HTTP server encoders and decoders", svc.Name()) sections := []*codegen.SectionTemplate{ @@ -38,16 +61,19 @@ func mountFile(svc *expr.HTTPServiceExpr, services *httpcodegen.ServicesData) *c sections = append(sections, &codegen.SectionTemplate{ Name: "goakit-mount-handler", Source: mountHandlerT, - Data: e, + Data: plannedEndpointData(e, names.endpoints[e.Method.Name]), }) } fm := codegen.TemplateFuncs() fm["join"] = strings.Join - for _, fs := range data.FileServers { + for index, fs := range data.FileServers { sections = append(sections, &codegen.SectionTemplate{ - Name: "goakit-mount-file-server", - Source: mountFileServerT, - Data: fs, + Name: "goakit-mount-file-server", + Source: mountFileServerT, + Data: &goakitFileServerData{ + FileServerData: fs, + MountHandler: names.fileServers[index].Name(), + }, FuncMap: fm, }) } diff --git a/i18n/examples/calc/cmd/calc-cli/http.go b/i18n/examples/calc/cmd/calc-cli/http.go index 429130989..f7a142146 100644 --- a/i18n/examples/calc/cmd/calc-cli/http.go +++ b/i18n/examples/calc/cmd/calc-cli/http.go @@ -1,16 +1,18 @@ package main import ( + "context" + "flag" "fmt" + "io" "net/http" "time" goahttp "goa.design/goa/v3/http" - goa "goa.design/goa/v3/pkg" cli "goa.design/plugins/v3/i18n/examples/calc/gen/http/cli/calc" ) -func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { +func doHTTP(ctx context.Context, scheme, host string, timeout int, debug bool, stdout io.Writer) error { var ( doer goahttp.Doer ) @@ -30,13 +32,17 @@ func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, er debug, ) if err != nil { - return nil, nil, fmt.Errorf("parse endpoint: %w", err) + return fmt.Errorf("parse endpoint: %w", err) } - return endpoint, payload, nil -} -func httpUsageCommands() []string { - return cli.UsageCommands() + switch flag.Arg(0) { + case "calc": + switch flag.Arg(1) { + case "add": + return writeEndpointResult(ctx, stdout, endpoint, payload) + } + } + panic("parsed HTTP command has no generated result writer") } func httpUsageExamples() string { diff --git a/i18n/examples/calc/cmd/calc-cli/main.go b/i18n/examples/calc/cmd/calc-cli/main.go index db45e50ae..3f4fb644a 100644 --- a/i18n/examples/calc/cmd/calc-cli/main.go +++ b/i18n/examples/calc/cmd/calc-cli/main.go @@ -6,10 +6,9 @@ import ( "errors" "flag" "fmt" + "io" "net/url" "os" - "slices" - "sort" "strings" goa "goa.design/goa/v3/pkg" @@ -62,14 +61,12 @@ func main() { } var ( - endpoint goa.Endpoint - payload any - err error + err error ) { switch scheme { case "http", "https": - endpoint, payload, err = doHTTP(scheme, host, timeout, debug) + err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) os.Exit(1) @@ -84,23 +81,36 @@ func main() { os.Exit(1) } - data, err := endpoint(context.Background(), payload) +} + +// writeEndpointResult calls one normal endpoint and writes its result as JSON. +func writeEndpointResult(ctx context.Context, stdout io.Writer, endpoint goa.Endpoint, payload any) error { + data, err := endpoint(ctx, payload) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return err } + return writeJSON(stdout, data) +} - if data != nil { - m, _ := json.MarshalIndent(data, "", " ") - fmt.Println(string(m)) +// writeJSON writes one indented JSON value followed by a newline. +func writeJSON(stdout io.Writer, data any) error { + if data == nil { + return nil } + encoded, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("encode result: %w", err) + } + if _, err := fmt.Fprintln(stdout, string(encoded)); err != nil { + return fmt.Errorf("write result: %w", err) + } + return nil } func usage() { - var usageCommands []string - usageCommands = append(usageCommands, httpUsageCommands()...) - sort.Strings(usageCommands) - usageCommands = slices.Compact(usageCommands) + usageCommands := []string{ + "calc add", + } fmt.Fprintf(os.Stderr, `%s is a command line client for the calc API. Usage: diff --git a/i18n/examples/calc/gen/calc/service.go b/i18n/examples/calc/gen/calc/service.go index 1aaa3d157..7b7fc7608 100644 --- a/i18n/examples/calc/gen/calc/service.go +++ b/i18n/examples/calc/gen/calc/service.go @@ -8,9 +8,7 @@ package calc -import ( - "context" -) +import "context" // The calc service exposes public endpoints to do basic mathematical // calculations. diff --git a/i18n/examples/calc/gen/http/calc/client/encode_decode.go b/i18n/examples/calc/gen/http/calc/client/encode_decode.go index 781a85d03..4bb298198 100644 --- a/i18n/examples/calc/gen/http/calc/client/encode_decode.go +++ b/i18n/examples/calc/gen/http/calc/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -50,18 +51,24 @@ func (c *Client) BuildAddRequest(ctx context.Context, v any) (*http.Request, err // endpoint. restoreBody controls whether the response body should be restored // after having been read. func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calc", "add", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -75,7 +82,10 @@ func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) + } return nil, goahttp.ErrInvalidResponse("calc", "add", resp.StatusCode, string(body)) } } diff --git a/i18n/generate.go b/i18n/generate.go index 1d637d696..d84c4895a 100644 --- a/i18n/generate.go +++ b/i18n/generate.go @@ -3,20 +3,21 @@ package i18n import ( "fmt" "os" - "path/filepath" "strings" "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/generator" goadsl "goa.design/goa/v3/dsl" "goa.design/goa/v3/eval" goaexpr "goa.design/goa/v3/expr" httpcodegen "goa.design/goa/v3/http/codegen" + "goa.design/goa/v3/http/codegen/openapi" "goa.design/plugins/v3/i18n/expr" ) func init() { - codegen.RegisterPlugin("i18n", "gen", Prepare, Generate) + generator.RegisterPlugin("i18n", "gen", newPlugin) } // ENVKEY is the key used to lookup locales to use when producing translation openapi specs @@ -83,30 +84,122 @@ func handleTitleTranslation(p eval.Expression, e []string) { }, p) } -// Generate produces additional openapi files for locales configured via -// the system environment variable GOA_I18N +// Generate produces additional OpenAPI files for locales configured through +// GOA_I18N. It remains available to callers that invoke the plugin directly. func Generate(_ string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { - locales, _ := getLocales() - - if len(locales) <= 1 { - // Nothing to generate, default already contains translations of default locale + plans, err := openAPIPlans(roots) + if err != nil { + return nil, err + } + if len(plans) <= 1 { return files, nil } - defaultLocale := locales[0] - restLocales := locales[1:] + for _, plan := range plans[1:] { + files = append(files, plan.Files()...) + } + return files, nil +} + +// newPlugin creates the state used by one Goa generation run. +func newPlugin() generator.Plugin { + return generator.Plugin{ + Prepare: Prepare, + Plan: func(plan *generator.Plan) error { + plans, err := openAPIPlans(plan.Generation().Roots()) + if err != nil { + return err + } + if len(plans) <= 1 { + return nil + } + root := applicationRoot(plan.Generation().Roots()) + return plan.ReplaceOpenAPI(root, plans...) + }, + } +} - for _, locale := range restLocales { - walkTranslations(roots, locale) +// openAPIPlans builds the default OpenAPI documents followed by one copy for +// every additional locale. Each copy reads alternate values without changing +// the evaluated design used by other generators. +func openAPIPlans(roots []eval.Root) ([]*httpcodegen.OpenAPIPlan, error) { + locales, err := getLocales() + if err != nil { + return nil, err + } + if len(locales) <= 1 { + return nil, nil + } + root := applicationRoot(roots) + examples := goaexpr.NewExampleGenerator(root.API.RandomizerFactory) + base, err := httpcodegen.NewOpenAPIPlan(root, examples) + if err != nil { + return nil, err + } + plans := []*httpcodegen.OpenAPIPlan{base} + for _, locale := range locales[1:] { + specs, err := localizedSpecs(root, locale) + if err != nil { + return nil, err + } + localized, err := httpcodegen.NewOpenAPIPlanFromSpecs( + root, + goaexpr.NewExampleGenerator(root.API.RandomizerFactory), + specs, + localizedValues(roots, locale), + ) + if err != nil { + return nil, err + } + plans = append(plans, localized) + } + return plans, nil +} - fs, _ := httpcodegen.OpenAPIFiles(goaexpr.Root) - // Rename the files - for _, file := range fs { - ext := filepath.Ext(file.Path) - file.Path = fmt.Sprintf("%s_%s%s", strings.TrimSuffix(file.Path, ext), locale, ext) +// applicationRoot returns the Goa design root that owns the OpenAPI files. +func applicationRoot(roots []eval.Root) *goaexpr.RootExpr { + for _, root := range roots { + if result, ok := root.(*goaexpr.RootExpr); ok { + return result } - files = append(files, fs...) } - // Not sure if needed, but reset messages to defaultLocale - walkTranslations(roots, defaultLocale) - return files, nil + panic("i18n: Goa design root is missing") +} + +// localizedSpecs adds the locale before each OpenAPI filename extension. +func localizedSpecs(root *goaexpr.RootExpr, locale string) ([]openapi.Spec, error) { + specs, err := openapi.Specs(root.API.Meta) + if err != nil { + return nil, err + } + for index := range specs { + specs[index].Path += "_" + locale + } + return specs, nil +} + +// localizedValues returns the translated text and examples for one OpenAPI +// document without changing the design read by other generators. +func localizedValues(roots []eval.Root, locale string) openapi.Values { + values := openapi.Values{} + for _, root := range roots { + root.WalkSets(func(expressions eval.ExpressionSet) { + for _, target := range expressions { + if translation, ok := expr.Root.Description[target]; ok { + values = values.WithDescription(target, translation.Messages(locale)[0]) + } + if translation, ok := expr.Root.Title[target]; ok { + values = values.WithTitle(target, translation.Messages(locale)[0]) + } + if translation, ok := expr.Root.Example[target]; ok { + if attribute, ok := target.(*goaexpr.AttributeExpr); ok { + values = values.WithExamples(attribute, []*goaexpr.ExampleExpr{{ + Summary: "default", + Value: translation.Messages(locale), + }}) + } + } + } + }) + } + return values } diff --git a/i18n/generate_test.go b/i18n/generate_test.go index 225e63caa..570158994 100644 --- a/i18n/generate_test.go +++ b/i18n/generate_test.go @@ -1,7 +1,6 @@ package i18n_test import ( - "os" "reflect" "slices" "testing" @@ -27,7 +26,7 @@ func TestPrepare(t *testing.T) { } for _, c := range cases { t.Run(c.Name, func(t *testing.T) { - os.Setenv("GOA_I18N", c.Locales) + t.Setenv("GOA_I18N", c.Locales) root := codegen.RunDSL(t, c.DSL) roots := []eval.Root{root} @@ -63,13 +62,17 @@ func checkExpr(roots []eval.Root, t interface{}, cb func(se interface{})) { } func TestGenerate(t *testing.T) { - os.Setenv("GOA_I18N", "en,nl") + t.Setenv("GOA_I18N", "en,nl") root := codegen.RunDSL(t, testdata.SimpleI18nDSL) roots := []eval.Root{root} i18n.Prepare("", roots) - fs, _ := httpcodegen.OpenAPIFiles(root) + plan, err := httpcodegen.NewOpenAPIPlan(root, expr.NewExampleGenerator(root.API.RandomizerFactory)) + if err != nil { + t.Fatal(err) + } + fs := plan.Files() gfs, _ := i18n.Generate("", roots, fs) if len(gfs) != 12 { @@ -88,3 +91,18 @@ func TestGenerate(t *testing.T) { } } } + +func TestGenerateWithOneLocalePreservesFiles(t *testing.T) { + t.Setenv("GOA_I18N", "en") + root := codegen.RunDSL(t, testdata.SimpleI18nDSL) + files := []*codegen.File{{Path: "existing.go"}} + + generated, err := i18n.Generate("generated.local/gen", []eval.Root{root}, files) + + if err != nil { + t.Fatal(err) + } + if !slices.Equal(generated, files) { + t.Fatalf("Generate returned %v, want the original files %v", generated, files) + } +} diff --git a/mains/generate.go b/mains/generate.go index a7bdfcac9..74e619be4 100644 --- a/mains/generate.go +++ b/mains/generate.go @@ -6,427 +6,455 @@ package mains import ( - "path" - "path/filepath" - "strings" + "path" + "path/filepath" + "strings" - "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/example" - "goa.design/goa/v3/codegen/service" - "goa.design/goa/v3/eval" - "goa.design/goa/v3/expr" - httpcodegen "goa.design/goa/v3/http/codegen" + "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/service" + "goa.design/goa/v3/eval" + "goa.design/goa/v3/expr" + httpcodegen "goa.design/goa/v3/http/codegen" ) const ( - // pluginName is the registered plugin name. - pluginName = "mains" - // pluginCmd is the goa CLI command the plugin integrates with. - pluginCmd = "example" + // pluginName is the registered plugin name. + pluginName = "mains" + // pluginCmd is the goa CLI command the plugin integrates with. + pluginCmd = "example" ) // srvInfo stores server-level data derived from generated example files. type srvInfo struct { - Dir string - APIPkg string - Services []*service.Data - HasWS bool - HasHTTP bool - HasGRPC bool - ServerName string - // FSCounts maps service name to the number of HTTP file servers. - FSCounts map[string]int + Dir string + APIPkg string + Services []*service.Data + HasWS bool + HasHTTP bool + HasGRPC bool + ServerName string + // FSCounts maps service name to the number of HTTP file servers. + FSCounts map[string]int } // svcT provides template data for each service imported by a server. type svcT struct { - Name string - StructName string - SvcVar string - EpVar string - SrvVar string - GenPkg string - GenHTTPPkg string - GenGRPCPkg string - GenGRPCPbPkg string - HasWebSocket bool - HasHTTP bool - HasGRPC bool - // FileServerNils is used by the template to emit one trailing - // nil argument per HTTP file server in the service. - FileServerNils []int + Name string + StructName string + SvcVar string + EpVar string + SrvVar string + GenPkg string + GenHTTPPkg string + GenGRPCPkg string + GenGRPCPbPkg string + HasWebSocket bool + HasHTTP bool + HasGRPC bool + // FileServerNils is used by the template to emit one trailing + // nil argument per HTTP file server in the service. + FileServerNils []int } // Register the plugin for the example phase. func init() { - codegen.RegisterPluginLast(pluginName, pluginCmd, nil, Generate) + codegen.RegisterPluginLast(pluginName, pluginCmd, nil, Generate) } // Generate produces golden-path mains that follow the Goa conventions and // pulse weather layout: -// - For servers with a single service: services//cmd//main.go -// - For servers with multiple services: cmd//main.go +// - For servers with a single service: services//cmd//main.go +// - For servers with multiple services: cmd//main.go +// // It replaces the default example main and http.go files. func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { - return generateExample(genpkg, roots, files) + return generateExample(genpkg, roots, files) } func generateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { - // Collect per-server services/APIPkg from example main files first - srvMap := map[string]*srvInfo{} - for _, f := range files { - if filepath.Base(f.Path) != "main.go" { continue } - segs := strings.Split(filepath.ToSlash(f.Path), "/") - if len(segs) < 3 || segs[0] != "cmd" { continue } - dir := segs[1] - var svcs []*service.Data - var apipkg string - for _, s := range f.SectionTemplates { - switch s.Name { - case "server-main-services": - if dm, ok := s.Data.(map[string]any); ok { - if v, ok := dm["Services"].([]*service.Data); ok { svcs = v } - } - case "server-main-logger": - if dm, ok := s.Data.(map[string]any); ok { - if v, ok := dm["APIPkg"].(string); ok { apipkg = v } - } - } - } - if len(svcs) == 0 { continue } - if apipkg == "" { apipkg = apiPkgAlias(genpkg, roots) } - if _, exists := srvMap[dir]; !exists { - srvMap[dir] = &srvInfo{Dir: dir, APIPkg: apipkg, Services: svcs} - } - } - // Complement with HTTP server data (WebSocket detection) and fallback if needed - for _, f := range files { - if filepath.Base(f.Path) != "http.go" { continue } - segs := strings.Split(filepath.ToSlash(f.Path), "/") - if len(segs) < 3 || segs[0] != "cmd" { continue } - dir := segs[1] - var httpSvcs []*httpcodegen.ServiceData - for _, s := range f.SectionTemplates { - if s.Name == "server-http-start" { - if dm, ok := s.Data.(map[string]any); ok { - if v, ok := dm["Services"].([]*httpcodegen.ServiceData); ok { httpSvcs = v; break } - } - } - } - if len(httpSvcs) == 0 { continue } - var svcs []*service.Data - fsCounts := map[string]int{} - for _, sd := range httpSvcs { - if sd == nil || sd.Service == nil { - continue - } - svcs = append(svcs, sd.Service) - if sd.FileServers != nil { - fsCounts[sd.Service.Name] = len(sd.FileServers) - } - } - // Detect WebSocket usage from HTTP endpoints (streaming without SSE). - // NeedDialer() is for client dialers, but here we're generating server mains - // and need to know whether to import gorilla/websocket for the upgrader. - hasWS := false - wsBySvc := httpWebSocketByService(roots) - for _, sd := range httpSvcs { - if sd != nil && sd.Service != nil && wsBySvc[sd.Service.Name] { - hasWS = true - break - } - } - apipkg := apiPkgAlias(genpkg, roots) - if info, ok := srvMap[dir]; ok { - info.HasWS = hasWS - info.HasHTTP = true - if info.APIPkg == "" { info.APIPkg = apipkg } - if info.FSCounts == nil { info.FSCounts = map[string]int{} } - for k, v := range fsCounts { info.FSCounts[k] = v } - } else { - srvMap[dir] = &srvInfo{Dir: dir, APIPkg: apipkg, Services: svcs, HasWS: hasWS, HasHTTP: true, FSCounts: fsCounts} - } - } - // Detect gRPC servers from grpc.go files - for _, f := range files { - if filepath.Base(f.Path) != "grpc.go" { continue } - segs := strings.Split(filepath.ToSlash(f.Path), "/") - if len(segs) < 3 || segs[0] != "cmd" { continue } - dir := segs[1] - if info, ok := srvMap[dir]; ok { - info.HasGRPC = true - } - } + // Collect per-server services/APIPkg from example main files first + srvMap := map[string]*srvInfo{} + for _, f := range files { + if filepath.Base(f.Path) != "main.go" { + continue + } + segs := strings.Split(filepath.ToSlash(f.Path), "/") + if len(segs) < 3 || segs[0] != "cmd" { + continue + } + dir := segs[1] + var svcs []*service.Data + var apipkg string + for _, s := range f.SectionTemplates { + switch s.Name { + case "server-main-services": + if dm, ok := s.Data.(map[string]any); ok { + if v, ok := dm["Services"].([]*service.Data); ok { + svcs = v + } + } + case "server-main-logger": + if dm, ok := s.Data.(map[string]any); ok { + if v, ok := dm["APIPkg"].(string); ok { + apipkg = v + } + } + } + } + if len(svcs) == 0 { + continue + } + if apipkg == "" { + apipkg = apiPkgAlias(genpkg, roots) + } + if _, exists := srvMap[dir]; !exists { + srvMap[dir] = &srvInfo{Dir: dir, APIPkg: apipkg, Services: svcs} + } + } + // Complement with HTTP server data (WebSocket detection) and fallback if needed + for _, f := range files { + if filepath.Base(f.Path) != "http.go" { + continue + } + segs := strings.Split(filepath.ToSlash(f.Path), "/") + if len(segs) < 3 || segs[0] != "cmd" { + continue + } + dir := segs[1] + var httpSvcs []*httpcodegen.ServiceData + for _, s := range f.SectionTemplates { + if s.Name == "server-http-start" { + if dm, ok := s.Data.(map[string]any); ok { + if v, ok := dm["Services"].([]*httpcodegen.ServiceData); ok { + httpSvcs = v + break + } + } + } + } + if len(httpSvcs) == 0 { + continue + } + var svcs []*service.Data + fsCounts := map[string]int{} + for _, sd := range httpSvcs { + if sd == nil || sd.Service == nil { + continue + } + svcs = append(svcs, sd.Service) + if sd.FileServers != nil { + fsCounts[sd.Service.Name] = len(sd.FileServers) + } + } + // Detect WebSocket usage from HTTP endpoints (streaming without SSE). + // NeedDialer() is for client dialers, but here we're generating server mains + // and need to know whether to import gorilla/websocket for the upgrader. + hasWS := false + wsBySvc := httpWebSocketByService(roots) + for _, sd := range httpSvcs { + if sd != nil && sd.Service != nil && wsBySvc[sd.Service.Name] { + hasWS = true + break + } + } + apipkg := apiPkgAlias(genpkg, roots) + if info, ok := srvMap[dir]; ok { + info.HasWS = hasWS + info.HasHTTP = true + if info.APIPkg == "" { + info.APIPkg = apipkg + } + if info.FSCounts == nil { + info.FSCounts = map[string]int{} + } + for k, v := range fsCounts { + info.FSCounts[k] = v + } + } else { + srvMap[dir] = &srvInfo{Dir: dir, APIPkg: apipkg, Services: svcs, HasWS: hasWS, HasHTTP: true, FSCounts: fsCounts} + } + } + // Detect gRPC servers from grpc.go files + for _, f := range files { + if filepath.Base(f.Path) != "grpc.go" { + continue + } + segs := strings.Split(filepath.ToSlash(f.Path), "/") + if len(segs) < 3 || segs[0] != "cmd" { + continue + } + dir := segs[1] + if info, ok := srvMap[dir]; ok { + info.HasGRPC = true + } + } - if len(srvMap) == 0 { - return files, nil - } + if len(srvMap) == 0 { + return files, nil + } - // Filter out default example mains, http.go, and grpc.go; we'll add our own mains. - var out []*codegen.File - for _, f := range files { - base := filepath.Base(f.Path) - if strings.HasPrefix(f.Path, "cmd/") && (base == "main.go" || base == "http.go" || base == "grpc.go") { - continue - } - out = append(out, f) - } + // Filter out default example mains, http.go, and grpc.go; we'll add our own mains. + var out []*codegen.File + for _, f := range files { + base := filepath.Base(f.Path) + if strings.HasPrefix(f.Path, "cmd/") && (base == "main.go" || base == "http.go" || base == "grpc.go") { + continue + } + out = append(out, f) + } - // Create mains per server - for _, info := range srvMap { - if len(info.Services) == 0 { - continue - } - specs := []*codegen.ImportSpec{ - {Path: "context"}, - {Path: "flag"}, - {Path: "fmt"}, - {Path: "net/http"}, - {Path: "net/http/httptrace"}, - {Path: "os"}, - {Path: "os/signal"}, - {Path: "sync"}, - {Path: "syscall"}, - {Path: "time"}, - {Path: "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"}, - {Path: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"}, - {Path: "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"}, - {Path: "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"}, - {Path: "goa.design/clue/clue"}, - {Path: "goa.design/clue/debug"}, - {Path: "goa.design/clue/health"}, - {Path: "goa.design/clue/log"}, - codegen.GoaNamedImport("http", "goahttp"), - {Path: "google.golang.org/grpc/credentials/insecure"}, - } - if info.HasGRPC { - specs = append(specs, - &codegen.ImportSpec{Path: "net"}, - &codegen.ImportSpec{Path: "google.golang.org/grpc"}, - &codegen.ImportSpec{Path: "google.golang.org/grpc/reflection"}, - &codegen.ImportSpec{Path: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"}, - ) - } - if info.HasWS { - specs = append(specs, &codegen.ImportSpec{Path: "github.com/gorilla/websocket"}) - } - rootPath := moduleRootFromGenpkg(genpkg) - specs = append(specs, &codegen.ImportSpec{Path: rootPath, Name: info.APIPkg}) + // Create mains per server + for _, info := range srvMap { + if len(info.Services) == 0 { + continue + } + specs := []*codegen.ImportSpec{ + {Path: "context"}, + {Path: "flag"}, + {Path: "fmt"}, + {Path: "net/http"}, + {Path: "net/http/httptrace"}, + {Path: "os"}, + {Path: "os/signal"}, + {Path: "sync"}, + {Path: "syscall"}, + {Path: "time"}, + {Path: "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"}, + {Path: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"}, + {Path: "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"}, + {Path: "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"}, + {Path: "goa.design/clue/clue"}, + {Path: "goa.design/clue/debug"}, + {Path: "goa.design/clue/health"}, + {Path: "goa.design/clue/log"}, + codegen.GoaNamedImport("http", "goahttp"), + {Path: "google.golang.org/grpc/credentials/insecure"}, + } + if info.HasGRPC { + specs = append(specs, + &codegen.ImportSpec{Path: "net"}, + &codegen.ImportSpec{Path: "google.golang.org/grpc"}, + &codegen.ImportSpec{Path: "google.golang.org/grpc/reflection"}, + &codegen.ImportSpec{Path: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"}, + ) + } + if info.HasWS { + specs = append(specs, &codegen.ImportSpec{Path: "github.com/gorilla/websocket"}) + } + rootPath := moduleRootFromGenpkg(genpkg) + specs = append(specs, &codegen.ImportSpec{Path: rootPath, Name: info.APIPkg}) - scope := codegen.NewNameScope() - var svcsData []svcT - httpBySvc := httpServicesByName(roots) - grpcBySvc := grpcServicesByName(roots) - wsBySvc := httpWebSocketByService(roots) - hasAnyWS := false - hasAnyHTTP := false - hasAnyGRPC := false - for _, sd := range info.Services { - genAlias := scope.Unique(sd.PkgName, "svc") - hasHTTP := httpBySvc[sd.Name] - hasGRPC := grpcBySvc[sd.Name] - hws := wsBySvc[sd.Name] + scope := codegen.NewNameScope() + var svcsData []svcT + httpBySvc := httpServicesByName(roots) + grpcBySvc := grpcServicesByName(roots) + wsBySvc := httpWebSocketByService(roots) + hasAnyWS := false + hasAnyHTTP := false + hasAnyGRPC := false + for _, sd := range info.Services { + genAlias := scope.Unique(sd.PkgName, "svc") + hasHTTP := httpBySvc[sd.Name] + hasGRPC := grpcBySvc[sd.Name] + hws := wsBySvc[sd.Name] - var httpAlias, grpcAlias, grpcPbAlias string + var httpAlias, grpcAlias, grpcPbAlias string - // Always add the base service package - specs = append(specs, &codegen.ImportSpec{Path: path.Join(genpkg, sd.PathName), Name: genAlias}) + // Always add the base service package + specs = append(specs, &codegen.ImportSpec{Path: path.Join(genpkg, sd.PathName), Name: genAlias}) - // Conditionally add HTTP server imports - if hasHTTP { - httpAlias = scope.Unique(sd.PkgName+"svr", "svr") - specs = append(specs, &codegen.ImportSpec{Path: path.Join(genpkg, "http", sd.PathName, "server"), Name: httpAlias}) - hasAnyHTTP = true - } + // Conditionally add HTTP server imports + if hasHTTP { + httpAlias = scope.Unique(sd.PkgName+"svr", "svr") + specs = append(specs, &codegen.ImportSpec{Path: path.Join(genpkg, "http", sd.PathName, "server"), Name: httpAlias}) + hasAnyHTTP = true + } - // Conditionally add gRPC server imports - if hasGRPC { - grpcAlias = scope.Unique(sd.PkgName+"grpc", "grpcsvc") - grpcPbAlias = scope.Unique(sd.PkgName+"pb", "pb") - specs = append(specs, - &codegen.ImportSpec{Path: path.Join(genpkg, "grpc", sd.PathName, "server"), Name: grpcAlias}, - &codegen.ImportSpec{Path: path.Join(genpkg, "grpc", sd.PathName, "pb"), Name: grpcPbAlias}, - ) - hasAnyGRPC = true - } + // Conditionally add gRPC server imports + if hasGRPC { + grpcAlias = scope.Unique(sd.PkgName+"grpc", "grpcsvc") + grpcPbAlias = scope.Unique(sd.PkgName+"pb", "pb") + specs = append(specs, + &codegen.ImportSpec{Path: path.Join(genpkg, "grpc", sd.PathName, "server"), Name: grpcAlias}, + &codegen.ImportSpec{Path: path.Join(genpkg, "grpc", sd.PathName, "pb"), Name: grpcPbAlias}, + ) + hasAnyGRPC = true + } - if hws { - hasAnyWS = true - } + if hws { + hasAnyWS = true + } - // Determine file server count: prefer extracted counts from example - // HTTP files, fallback to design counts when missing. - fsn, ok := info.FSCounts[sd.Name] - if !ok { - fsn = httpFileServerCounts(roots)[sd.Name] - } - svcsData = append(svcsData, svcT{ - Name: sd.Name, - StructName: sd.StructName, - SvcVar: sd.VarName + "Svc", - EpVar: sd.VarName + "Endpoints", - SrvVar: sd.VarName + "Server", - GenPkg: genAlias, - GenHTTPPkg: httpAlias, - GenGRPCPkg: grpcAlias, - GenGRPCPbPkg: grpcPbAlias, - HasWebSocket: hws, - HasHTTP: hasHTTP, - HasGRPC: hasGRPC, - FileServerNils: func(n int) []int { if n <= 0 { return nil }; s := make([]int, n); for i := range s { s[i] = i }; return s }(fsn), - }) - } + // Determine file server count: prefer extracted counts from example + // HTTP files, fallback to design counts when missing. + fsn, ok := info.FSCounts[sd.Name] + if !ok { + fsn = httpFileServerCounts(roots)[sd.Name] + } + svcsData = append(svcsData, svcT{ + Name: sd.Name, + StructName: sd.StructName, + SvcVar: sd.VarName + "Svc", + EpVar: sd.VarName + "Endpoints", + SrvVar: sd.VarName + "Server", + GenPkg: genAlias, + GenHTTPPkg: httpAlias, + GenGRPCPkg: grpcAlias, + GenGRPCPbPkg: grpcPbAlias, + HasWebSocket: hws, + HasHTTP: hasHTTP, + HasGRPC: hasGRPC, + FileServerNils: func(n int) []int { + if n <= 0 { + return nil + } + s := make([]int, n) + for i := range s { + s[i] = i + } + return s + }(fsn), + }) + } - sections := []*codegen.SectionTemplate{ - codegen.Header("", "main", specs), - {Name: "mains-main", Source: tmpl.Read("main"), Data: map[string]any{ - "APIPkg": info.APIPkg, - "Services": svcsData, - "HasAnyWebSocket": hasAnyWS, - "HasHTTP": hasAnyHTTP, - "HasGRPC": hasAnyGRPC, - "ServiceCount": len(svcsData), - "ServerLabel": serverLabel(roots), - }}, - } + sections := []*codegen.SectionTemplate{ + codegen.Header("", "main", specs), + {Name: "mains-main", Source: tmpl.Read("main"), Data: map[string]any{ + "APIPkg": info.APIPkg, + "Services": svcsData, + "HasAnyWebSocket": hasAnyWS, + "HasHTTP": hasAnyHTTP, + "HasGRPC": hasAnyGRPC, + "ServiceCount": len(svcsData), + "ServerLabel": serverLabel(roots), + }}, + } - var fpath string - if len(info.Services) == 1 { - svc := info.Services[0] - fpath = filepath.ToSlash(filepath.Join("services", svc.PathName, "cmd", svc.PathName, "main.go")) - } else { - // Use the example server directory so people can still `go run ./cmd/` when multiple services - // are served from a single process. - // Derive server directory using same logic as example generator. - fpath = filepath.ToSlash(filepath.Join("cmd", example.Servers.Get(rootServer(roots), roots[0].(*expr.RootExpr)).Dir, "main.go")) - // But we already filtered out the default http.go/main.go, so only our main remains. - } + var fpath string + if len(info.Services) == 1 { + svc := info.Services[0] + fpath = filepath.ToSlash(filepath.Join("services", svc.PathName, "cmd", svc.PathName, "main.go")) + } else { + // Use the example server directory so people can still `go run ./cmd/` when multiple services + // are served from a single process. + // Derive server directory using same logic as example generator. + fpath = filepath.ToSlash(filepath.Join("cmd", info.Dir, "main.go")) + // But we already filtered out the default http.go/main.go, so only our main remains. + } - out = append(out, &codegen.File{Path: fpath, SectionTemplates: sections, SkipExist: true}) - } - return out, nil + out = append(out, &codegen.File{Path: fpath, SectionTemplates: sections, SkipExist: true}) + } + return out, nil } func apiPkgAlias(genpkg string, roots []eval.Root) string { - var apiName string - for _, r := range roots { - if root, ok := r.(*expr.RootExpr); ok { - if root != nil && root.API != nil { - apiName = root.API.Name - break - } - } - } - if apiName == "" { - apiName = "api" - } - scope := codegen.NewNameScope() - return scope.Unique(strings.ToLower(codegen.Goify(apiName, false)), "api") + var apiName string + for _, r := range roots { + if root, ok := r.(*expr.RootExpr); ok { + if root != nil && root.API != nil { + apiName = root.API.Name + break + } + } + } + if apiName == "" { + apiName = "api" + } + scope := codegen.NewNameScope() + return scope.Unique(strings.ToLower(codegen.Goify(apiName, false)), "api") } func serverLabel(roots []eval.Root) string { - for _, r := range roots { - if root, ok := r.(*expr.RootExpr); ok { - if root != nil && root.API != nil { - return strings.ToLower(codegen.Goify(root.API.Name, false)) - } - } - } - return "goa-service" + for _, r := range roots { + if root, ok := r.(*expr.RootExpr); ok { + if root != nil && root.API != nil { + return strings.ToLower(codegen.Goify(root.API.Name, false)) + } + } + } + return "goa-service" } func moduleRootFromGenpkg(genpkg string) string { - idx := strings.LastIndex(genpkg, "/") - if idx <= 0 { - return "." - } - return genpkg[:idx] + idx := strings.LastIndex(genpkg, "/") + if idx <= 0 { + return "." + } + return genpkg[:idx] } func httpWebSocketByService(roots []eval.Root) map[string]bool { - hasWS := map[string]bool{} - for _, r := range roots { - root, ok := r.(*expr.RootExpr) - if !ok || root.API == nil || root.API.HTTP == nil { - continue - } - for _, svc := range root.API.HTTP.Services { - for _, e := range svc.HTTPEndpoints { - if e.SSE != nil { - continue - } - // Stream is 0 when no streaming is defined, and >= NoStreamKind (1) when streaming is used - if e.MethodExpr != nil && e.MethodExpr.Stream != 0 { - hasWS[svc.Name()] = true - break - } - } - } - } - return hasWS -} - -// rootServer returns the first server expression if any. -func rootServer(roots []eval.Root) *expr.ServerExpr { - for _, r := range roots { - if root, ok := r.(*expr.RootExpr); ok { - if root.API != nil && len(root.API.Servers) > 0 { - return root.API.Servers[0] - } - } - } - return nil + hasWS := map[string]bool{} + for _, r := range roots { + root, ok := r.(*expr.RootExpr) + if !ok || root.API == nil || root.API.HTTP == nil { + continue + } + for _, svc := range root.API.HTTP.Services { + for _, e := range svc.HTTPEndpoints { + if e.SSE != nil { + continue + } + // Stream is 0 when no streaming is defined, and >= NoStreamKind (1) when streaming is used + if e.MethodExpr != nil && e.MethodExpr.Stream != 0 { + hasWS[svc.Name()] = true + break + } + } + } + } + return hasWS } // httpServicesByName returns map of service names that have HTTP endpoints. func httpServicesByName(roots []eval.Root) map[string]bool { - hasHTTP := map[string]bool{} - for _, r := range roots { - root, ok := r.(*expr.RootExpr) - if !ok || root.API == nil || root.API.HTTP == nil { - continue - } - for _, svc := range root.API.HTTP.Services { - if len(svc.HTTPEndpoints) > 0 || len(svc.FileServers) > 0 { - hasHTTP[svc.Name()] = true - } - } - } - return hasHTTP + hasHTTP := map[string]bool{} + for _, r := range roots { + root, ok := r.(*expr.RootExpr) + if !ok || root.API == nil || root.API.HTTP == nil { + continue + } + for _, svc := range root.API.HTTP.Services { + if len(svc.HTTPEndpoints) > 0 || len(svc.FileServers) > 0 { + hasHTTP[svc.Name()] = true + } + } + } + return hasHTTP } // grpcServicesByName returns map of service names that have gRPC endpoints. func grpcServicesByName(roots []eval.Root) map[string]bool { - hasGRPC := map[string]bool{} - for _, r := range roots { - root, ok := r.(*expr.RootExpr) - if !ok || root.API == nil || root.API.GRPC == nil { - continue - } - for _, svc := range root.API.GRPC.Services { - if len(svc.GRPCEndpoints) > 0 { - hasGRPC[svc.Name()] = true - } - } - } - return hasGRPC + hasGRPC := map[string]bool{} + for _, r := range roots { + root, ok := r.(*expr.RootExpr) + if !ok || root.API == nil || root.API.GRPC == nil { + continue + } + for _, svc := range root.API.GRPC.Services { + if len(svc.GRPCEndpoints) > 0 { + hasGRPC[svc.Name()] = true + } + } + } + return hasGRPC } // httpFileServerCounts returns a map from service name to the number of // HTTP Files() endpoints defined for that service. func httpFileServerCounts(roots []eval.Root) map[string]int { - counts := map[string]int{} - for _, r := range roots { - root, ok := r.(*expr.RootExpr) - if !ok || root.API == nil || root.API.HTTP == nil { - continue - } - for _, svc := range root.API.HTTP.Services { - if svc == nil { - continue - } - counts[svc.Name()] = len(svc.FileServers) - } - } - return counts + counts := map[string]int{} + for _, r := range roots { + root, ok := r.(*expr.RootExpr) + if !ok || root.API == nil || root.API.HTTP == nil { + continue + } + for _, svc := range root.API.HTTP.Services { + if svc == nil { + continue + } + counts[svc.Name()] = len(svc.FileServers) + } + } + return counts } diff --git a/mains/generate_test.go b/mains/generate_test.go index aca43857f..e8203f43e 100644 --- a/mains/generate_test.go +++ b/mains/generate_test.go @@ -1,135 +1,129 @@ package mains import ( - "bytes" - "regexp" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/example" - "goa.design/goa/v3/codegen/service" - "goa.design/goa/v3/eval" - httpcodegen "goa.design/goa/v3/http/codegen" - "goa.design/plugins/v3/mains/testdata" + "bytes" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/generator" + "goa.design/goa/v3/eval" + "goa.design/plugins/v3/mains/testdata" ) func TestSingleServiceMainRelocation(t *testing.T) { - root := codegen.RunDSL(t, testdata.SingleServiceDSL) - svcs := service.NewServicesData(root) - httpSvcs := httpcodegen.NewServicesData(svcs, root.API.HTTP) - files := append(example.ServerFiles("gen", root, svcs), httpcodegen.ExampleServerFiles("gen", httpSvcs)...) - - // Sanity: default example mains produce cmd//main.go and cmd//http.go - require.GreaterOrEqual(t, len(files), 2) - - out, err := Generate("gen", []eval.Root{root}, files) - require.NoError(t, err) - - // Expect relocated main under services/calc/cmd/calc/main.go - var hasRelocated bool - var hasOldMain, hasOldHTTP bool - for _, f := range out { - switch f.Path { - case "services/calc/cmd/calc/main.go": - hasRelocated = true - case "cmd/edge/main.go": - hasOldMain = true - case "cmd/edge/http.go": - hasOldHTTP = true - } - } - assert.True(t, hasRelocated, "expected relocated main not found") - assert.False(t, hasOldMain, "default example main should be removed") - assert.False(t, hasOldHTTP, "default http.go should be removed") + root := codegen.RunDSL(t, testdata.SingleServiceDSL) + files, err := generator.Example("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + + // Sanity: default example mains produce cmd//main.go and cmd//http.go + require.GreaterOrEqual(t, len(files), 2) + + out, err := Generate("generated.local/gen", []eval.Root{root}, files) + require.NoError(t, err) + + // Expect relocated main under services/calc/cmd/calc/main.go + var hasRelocated bool + var hasOldMain, hasOldHTTP bool + for _, f := range out { + switch f.Path { + case "services/calc/cmd/calc/main.go": + hasRelocated = true + case "cmd/edge/main.go": + hasOldMain = true + case "cmd/edge/http.go": + hasOldHTTP = true + } + } + assert.True(t, hasRelocated, "expected relocated main not found") + assert.False(t, hasOldMain, "default example main should be removed") + assert.False(t, hasOldHTTP, "default http.go should be removed") } func TestMultiServiceMainStaysUnderCmd(t *testing.T) { - root := codegen.RunDSL(t, testdata.MultiServiceDSL) - svcs := service.NewServicesData(root) - httpSvcs := httpcodegen.NewServicesData(svcs, root.API.HTTP) - files := append(example.ServerFiles("gen", root, svcs), httpcodegen.ExampleServerFiles("gen", httpSvcs)...) - - out, err := Generate("gen", []eval.Root{root}, files) - require.NoError(t, err) - - var hasCmdMain, hasHTTP bool - for _, f := range out { - if f.Path == "cmd/api/main.go" { - hasCmdMain = true - } - if f.Path == "cmd/api/http.go" { - hasHTTP = true - } - } - assert.True(t, hasCmdMain, "expected cmd/api/main.go for multi-service server") - assert.False(t, hasHTTP, "default http.go should be removed") + root := codegen.RunDSL(t, testdata.MultiServiceDSL) + files, err := generator.Example("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + + out, err := Generate("generated.local/gen", []eval.Root{root}, files) + require.NoError(t, err) + + var hasCmdMain, hasHTTP bool + for _, f := range out { + if f.Path == "cmd/api/main.go" { + hasCmdMain = true + } + if f.Path == "cmd/api/http.go" { + hasHTTP = true + } + } + assert.True(t, hasCmdMain, "expected cmd/api/main.go for multi-service server") + assert.False(t, hasHTTP, "default http.go should be removed") } func TestWebSocketMainIncludesUpgrader(t *testing.T) { - root := codegen.RunDSL(t, testdata.WebSocketServiceDSL) - svcs := service.NewServicesData(root) - httpSvcs := httpcodegen.NewServicesData(svcs, root.API.HTTP) - files := append(example.ServerFiles("gen", root, svcs), httpcodegen.ExampleServerFiles("gen", httpSvcs)...) - - out, err := Generate("gen", []eval.Root{root}, files) - require.NoError(t, err) - - // Find relocated main - var mainFile *codegen.File - for _, f := range out { - if f.Path == "services/chat/cmd/chat/main.go" { - mainFile = f - break - } - } - require.NotNil(t, mainFile) - - // Assert WS import is present in the generated header. - header := mainFile.Section("source-header") - require.Greater(t, len(header), 0) - var hbuf bytes.Buffer - require.NoError(t, header[0].Write(&hbuf)) - assert.Contains(t, hbuf.String(), "github.com/gorilla/websocket") - - // Assert upgrader usage is present in the main body. - body := mainFile.Section("mains-main") - require.Greater(t, len(body), 0) - var bbuf bytes.Buffer - require.NoError(t, body[0].Write(&bbuf)) - assert.Contains(t, bbuf.String(), "websocket.Upgrader") + root := codegen.RunDSL(t, testdata.WebSocketServiceDSL) + files, err := generator.Example("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + + out, err := Generate("generated.local/gen", []eval.Root{root}, files) + require.NoError(t, err) + + // Find relocated main + var mainFile *codegen.File + for _, f := range out { + if f.Path == "services/chat/cmd/chat/main.go" { + mainFile = f + break + } + } + require.NotNil(t, mainFile) + + // Assert WS import is present in the generated header. + header := mainFile.Section("source-header") + require.Greater(t, len(header), 0) + var hbuf bytes.Buffer + require.NoError(t, header[0].Write(&hbuf)) + assert.Contains(t, hbuf.String(), "github.com/gorilla/websocket") + + // Assert upgrader usage is present in the main body. + body := mainFile.Section("mains-main") + require.Greater(t, len(body), 0) + var bbuf bytes.Buffer + require.NoError(t, body[0].Write(&bbuf)) + assert.Contains(t, bbuf.String(), "websocket.Upgrader") } func TestMainsAddsFileServerNils(t *testing.T) { - root := codegen.RunDSL(t, testdata.FileServerServiceDSL) - svcs := service.NewServicesData(root) - httpSvcs := httpcodegen.NewServicesData(svcs, root.API.HTTP) - files := append(example.ServerFiles("gen", root, svcs), httpcodegen.ExampleServerFiles("gen", httpSvcs)...) - - out, err := Generate("gen", []eval.Root{root}, files) - require.NoError(t, err) - - // Expect relocated main under services/static/cmd/static/main.go - var mainFile *codegen.File - for _, f := range out { - if f.Path == "services/static/cmd/static/main.go" { - mainFile = f - break - } - } - require.NotNil(t, mainFile) - - // Render the mains section and look for exactly 2 (errhandler, formatter) - // + 3 (file servers) nil arguments at the end of the New(...) call. - sections := mainFile.Section("mains-main") - require.Greater(t, len(sections), 0) - var buf bytes.Buffer - require.NoError(t, sections[0].Write(&buf)) - code := buf.String() - - // Match a New(...) call that ends with five consecutive `, nil` args - // (2 standard + 3 file servers) - re := regexp.MustCompile(`New\([\s\S]*,\s*nil(?:,\s*nil){4}\)`) // total 5 nils - assert.Regexp(t, re, code) + root := codegen.RunDSL(t, testdata.FileServerServiceDSL) + files, err := generator.Example("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + + out, err := Generate("generated.local/gen", []eval.Root{root}, files) + require.NoError(t, err) + + // Expect relocated main under services/static/cmd/static/main.go + var mainFile *codegen.File + for _, f := range out { + if f.Path == "services/static/cmd/static/main.go" { + mainFile = f + break + } + } + require.NotNil(t, mainFile) + + // Render the mains section and look for exactly 2 (errhandler, formatter) + // + 3 (file servers) nil arguments at the end of the New(...) call. + sections := mainFile.Section("mains-main") + require.Greater(t, len(sections), 0) + var buf bytes.Buffer + require.NoError(t, sections[0].Write(&buf)) + code := buf.String() + + // Match a New(...) call that ends with five consecutive `, nil` args + // (2 standard + 3 file servers) + re := regexp.MustCompile(`New\([\s\S]*,\s*nil(?:,\s*nil){4}\)`) // total 5 nils + assert.Regexp(t, re, code) } diff --git a/otel/generate_test.go b/otel/generate_test.go index f2e9142d1..b714826c2 100644 --- a/otel/generate_test.go +++ b/otel/generate_test.go @@ -14,6 +14,7 @@ import ( "goa.design/goa/v3/codegen" "goa.design/goa/v3/codegen/service" "goa.design/goa/v3/eval" + "goa.design/goa/v3/expr" httpcodegen "goa.design/goa/v3/http/codegen" "goa.design/plugins/v3/otel/testdata" ) @@ -31,8 +32,16 @@ func TestOtel(t *testing.T) { for _, c := range cases { t.Run(c.Name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) - services := httpcodegen.NewServicesData(service.NewServicesData(root), root.API.HTTP) - serverFiles := httpcodegen.ServerFiles("gen", services) + generation, err := codegen.NewGeneration("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + servicePlan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + require.NoError(t, err) + plans, err := httpcodegen.NewPlans(generation, httpcodegen.PlanInput{Root: root, Service: servicePlan}) + require.NoError(t, err) + require.NoError(t, generation.Freeze()) + require.NoError(t, servicePlan.Link()) + require.NoError(t, plans[0].Link()) + serverFiles := plans[0].ServerFiles() require.Len(t, serverFiles, 2) fs, err := Generate("", []eval.Root{root}, serverFiles) assert.NoError(t, err) diff --git a/testing/README.md b/testing/README.md index 7413bab7d..f9186eb8b 100644 --- a/testing/README.md +++ b/testing/README.md @@ -257,7 +257,7 @@ scenarios: # Required: List of test scenarios |-------|------|----------|-------------| | `name` | string | ✅ | Unique identifier for the scenario | | `description` | string | | Human-readable description for documentation | -| `transport` | string | | Default transport: `auto`, `http`, `grpc`, `jsonrpc`, `http-ws`, `http-sse`, `jsonrpc-ws`, `jsonrpc-sse` | +| `transport` | string | | Default transport: `auto`, `http`, `grpc`, `jsonrpc`, `http-ws`, `http-sse`, `jsonrpc-sse` | | `timeout` | duration | | Default timeout for all steps (e.g., `10s`, `500ms`, `1m`) | | `steps` | []Step | ✅ | Ordered list of test steps to execute | @@ -291,7 +291,6 @@ scenarios: # Required: List of test scenarios - **`jsonrpc`**: JSON-RPC over HTTP - **`http-ws`**: HTTP WebSocket (for streaming) - **`http-sse`**: HTTP Server-Sent Events -- **`jsonrpc-ws`**: JSON-RPC over WebSocket - **`jsonrpc-sse`**: JSON-RPC over SSE ### Running YAML Scenarios diff --git a/testing/codegen/clients.go b/testing/codegen/clients.go index 54fc83708..087eb78fd 100644 --- a/testing/codegen/clients.go +++ b/testing/codegen/clients.go @@ -1,3 +1,5 @@ +// This file renders a typed test client with one method branch for each +// transport selected by the Goa design. package codegen import ( @@ -46,44 +48,44 @@ type ( ) // generateClient generates the test client file for a service. -func generateClient(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { - path := filepath.Join(testingPath(genpkg, svc), "client.go") +func generateClient(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *codegen.File { + path := filepath.Join(testingPath(svcData), "client.go") if svcData == nil { return nil } - data := buildClientData(svcData, root, svc) + data := buildClientData(svcData, root, svc, transports) specs := []*codegen.ImportSpec{ {Path: "context"}, {Path: "fmt"}, {Path: "testing"}, {Path: "time"}, - {Path: filepath.Join(genpkg, codegen.SnakeCase(svc.Name)), Name: svcData.PkgName}, + {Path: filepath.Join(genpkg, svcData.PathName), Name: svcData.PkgName}, } // Add transport-specific imports based on what the service uses if data.HasHTTP { specs = append(specs, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "http", codegen.SnakeCase(svc.Name), "client"), Name: "httpcli"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "http", svcData.PathName, "client"), Name: "httpcli"}, &codegen.ImportSpec{Path: "goa.design/goa/v3/http", Name: "goahttp"}, ) } if data.HasGRPC { specs = append(specs, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", codegen.SnakeCase(svc.Name), "client"), Name: "grpccli"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", svcData.PathName, "client"), Name: "grpccli"}, &codegen.ImportSpec{Path: "google.golang.org/grpc"}, ) } if data.HasJSONRPC { specs = append(specs, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "jsonrpc", codegen.SnakeCase(svc.Name), "client"), Name: "jsonrpccli"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "jsonrpc", svcData.PathName, "client"), Name: "jsonrpccli"}, ) } sections := []*codegen.SectionTemplate{ - codegen.Header(fmt.Sprintf("Test client for %s service", svc.Name), codegen.SnakeCase(svc.Name)+"test", specs), + codegen.Header(fmt.Sprintf("Test client for %s service", svc.Name), svcData.PathName+"test", specs), { Name: "client-struct", Source: testingTemplates.Read("client_struct"), @@ -113,7 +115,7 @@ func generateClient(genpkg string, svcData *service.Data, root *expr.RootExpr, s } // buildClientData builds the template data for the client. -func buildClientData(svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) *clientData { +func buildClientData(svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *clientData { // Create client data using Goa's service.Data directly data := &clientData{ Data: svcData, @@ -133,7 +135,7 @@ func buildClientData(svcData *service.Data, root *expr.RootExpr, svc *expr.Servi md := svcData.Methods[i] // Build targets for this method - targets := buildMethodTargets(root, svc, m, md) + targets := buildMethodTargets(root, svc, m, transports) // Create client method data cmd := &clientMethodData{ @@ -156,7 +158,7 @@ func buildClientData(svcData *service.Data, root *expr.RootExpr, svc *expr.Servi if target.IsHTTPPlain || target.IsHTTPServerSent || target.IsHTTPWebSocket { cmd.HasHTTP = true } - if target.IsJSONRPCPlain || target.IsJSONRPCSSE || target.IsJSONRPCWS { + if target.IsJSONRPCPlain || target.IsJSONRPCSSE { cmd.HasJSONRPC = true } } @@ -169,7 +171,7 @@ func buildClientData(svcData *service.Data, root *expr.RootExpr, svc *expr.Servi // buildMethodTargets builds the transport targets for a method. // This is shared between harness and client generation. -func buildMethodTargets(root *expr.RootExpr, svc *expr.ServiceExpr, m *expr.MethodExpr, md *service.MethodData) []*suiteTarget { +func buildMethodTargets(root *expr.RootExpr, svc *expr.ServiceExpr, m *expr.MethodExpr, transports *methodTransports) []*suiteTarget { var targets []*suiteTarget // Check gRPC transport @@ -224,24 +226,10 @@ func buildMethodTargets(root *expr.RootExpr, svc *expr.ServiceExpr, m *expr.Meth } // Check JSON-RPC transport - if hasMethodJSONRPC(root, svc, m) && md != nil { - switch { - case md.IsJSONRPCSSE: + if transport, ok := transports.jsonRPC[m]; ok { + if transport.serverSentEvents { targets = append(targets, &suiteTarget{IsJSONRPCSSE: true, IsServerStream: true}) - } - if md.IsJSONRPCWebSocket { - ws := &suiteTarget{IsJSONRPCWS: true} - switch m.Stream { - case expr.ClientStreamKind: - ws.IsClientStream = true - case expr.ServerStreamKind: - ws.IsServerStream = true - case expr.BidirectionalStreamKind: - ws.IsBidirectional = true - } - targets = append(targets, ws) - } - if !md.IsJSONRPCSSE && !md.IsJSONRPCWebSocket { + } else { targets = append(targets, &suiteTarget{IsJSONRPCPlain: true, IsNoStream: true}) } } diff --git a/testing/codegen/clients_test.go b/testing/codegen/clients_test.go index 6b593c751..1a81ff10a 100644 --- a/testing/codegen/clients_test.go +++ b/testing/codegen/clients_test.go @@ -5,7 +5,6 @@ import ( "github.com/stretchr/testify/assert" goacodegen "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/service" "goa.design/plugins/v3/testing/codegen/testdata" ) @@ -40,10 +39,10 @@ func TestGenerateClient(t *testing.T) { for name, c := range cases { t.Run(name, func(t *testing.T) { root := goacodegen.RunDSL(t, c.DSL) - services := service.NewServicesData(root) + services := plannedServiceData(t, root) svc := root.Services[0] svcData := services.Get(svc.Name) - f := generateClient("", svcData, root, svc) + f := generateClient("", svcData, root, svc, designedMethodTransports(root)) assert.Equal(t, c.Path, f.Path) for sec, secCode := range c.Code { testCode(t, f, sec, secCode) @@ -51,3 +50,20 @@ func TestGenerateClient(t *testing.T) { }) } } + +func TestBuildMethodTargetsUsesPlannedJSONRPCMode(t *testing.T) { + root := goacodegen.RunDSL(t, testdata.JSONRPCTransportsDSL) + _, jsonPlan := plannedJSONRPCData(t, root) + svc := root.Services[0] + transports := plannedMethodTransports(root, jsonPlan) + + plain := buildMethodTargets(root, svc, svc.Method("Plain"), transports) + assert.Len(t, plain, 1) + assert.True(t, plain[0].IsJSONRPCPlain) + assert.True(t, plain[0].IsNoStream) + + events := buildMethodTargets(root, svc, svc.Method("Events"), transports) + assert.Len(t, events, 1) + assert.True(t, events[0].IsJSONRPCSSE) + assert.True(t, events[0].IsServerStream) +} diff --git a/testing/codegen/errors.go b/testing/codegen/errors.go index 9d64f7020..ff2c9ce48 100644 --- a/testing/codegen/errors.go +++ b/testing/codegen/errors.go @@ -34,7 +34,7 @@ func generateErrors(genpkg string, svcData *service.Data, root *expr.RootExpr, s return nil } - path := filepath.Join(testingPath(genpkg, svc), "errors.go") + path := filepath.Join(testingPath(svcData), "errors.go") if svcData == nil { return nil @@ -48,7 +48,7 @@ func generateErrors(genpkg string, svcData *service.Data, root *expr.RootExpr, s } sections := []*codegen.SectionTemplate{ - codegen.Header(fmt.Sprintf("Error test helpers for %s service", svc.Name), codegen.SnakeCase(svc.Name)+"test", specs), + codegen.Header(fmt.Sprintf("Error test helpers for %s service", svc.Name), svcData.PathName+"test", specs), { Name: "error-helpers", Source: testingTemplates.Read(errorHelpersT), diff --git a/testing/codegen/generate.go b/testing/codegen/generate.go index 68cca5045..4f5b40a69 100644 --- a/testing/codegen/generate.go +++ b/testing/codegen/generate.go @@ -1,21 +1,73 @@ +// This file assembles the generated testing package and preserves the released +// helper functions for applications that call the plugin directly. package codegen import ( "goa.design/goa/v3/codegen" "goa.design/goa/v3/codegen/service" + "goa.design/goa/v3/eval" "goa.design/goa/v3/expr" + jsonrpccodegen "goa.design/goa/v3/jsonrpc/codegen" ) // Generate produces testing files (client, harness, scenarios, errors, testdata) for the given service. func Generate(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) []*codegen.File { + return generate(genpkg, svcData, root, svc, designedMethodTransports(root)) +} + +// GeneratePlanned produces testing files from the service and JSON-RPC values +// finalized by the current Goa generation run. +func GeneratePlanned(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, jsonPlan *jsonrpccodegen.Plan) []*codegen.File { + return generate(genpkg, svcData, root, svc, plannedMethodTransports(root, jsonPlan)) +} + +// GenerateSuiteTopLevel returns the top-level example suite file. +func GenerateSuiteTopLevel(genpkg, examplePkg string, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { + svcData := isolatedServiceData(genpkg, root, svc) + return GenerateSuiteTopLevelFromData(genpkg, examplePkg, svcData, root, svc) +} + +// GenerateSuiteTopLevelFromData returns the top-level example suite from +// service values already finalized by a Goa service plan. +func GenerateSuiteTopLevelFromData(genpkg, examplePkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { + return generateSuiteTopLevel(genpkg, examplePkg, svcData, root, svc, designedMethodTransports(root)) +} + +// GeneratePlannedSuiteTopLevel returns an example suite using the transport +// choices finalized by the current Goa generation run. +func GeneratePlannedSuiteTopLevel(genpkg, examplePkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, jsonPlan *jsonrpccodegen.Plan) *codegen.File { + return generateSuiteTopLevel(genpkg, examplePkg, svcData, root, svc, plannedMethodTransports(root, jsonPlan)) +} + +// GenerateExampleScenarios returns an example scenarios.yaml file. +func GenerateExampleScenarios(genpkg string, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { + svcData := isolatedServiceData(genpkg, root, svc) + return GenerateExampleScenariosFromData(genpkg, svcData, root, svc) +} + +// GenerateExampleScenariosFromData returns an example scenarios.yaml file +// from service values already finalized by a Goa service plan. +func GenerateExampleScenariosFromData(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { + return generateExampleScenarios(genpkg, svcData, root, svc, designedMethodTransports(root)) +} + +// GeneratePlannedExampleScenarios returns example scenarios using the +// transport choices finalized by the current Goa generation run. +func GeneratePlannedExampleScenarios(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, jsonPlan *jsonrpccodegen.Plan) *codegen.File { + return generateExampleScenarios(genpkg, svcData, root, svc, plannedMethodTransports(root, jsonPlan)) +} + +// generate produces the test files after transport choices have been reduced +// to the branches that this service can use. +func generate(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) []*codegen.File { files := make([]*codegen.File, 0, 5) - if f := generateClient(genpkg, svcData, root, svc); f != nil { + if f := generateClient(genpkg, svcData, root, svc, transports); f != nil { files = append(files, f) } - if f := generateHarness(genpkg, svcData, root, svc); f != nil { + if f := generateHarness(genpkg, svcData, root, svc, transports); f != nil { files = append(files, f) } - if fs := generateScenarios(genpkg, svcData, root, svc); fs != nil { + if fs := generateScenarios(genpkg, svcData, root, svc, transports); fs != nil { files = append(files, fs...) } if f := generateErrors(genpkg, svcData, root, svc); f != nil { @@ -27,12 +79,22 @@ func Generate(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *ex return files } -// GenerateSuiteTopLevel returns the top-level example suite file. -func GenerateSuiteTopLevel(genpkg, examplePkg string, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { - return generateSuiteTopLevel(genpkg, examplePkg, root, svc) -} - -// GenerateExampleScenarios returns an example scenarios.yaml file. -func GenerateExampleScenarios(genpkg string, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { - return generateExampleScenarios(genpkg, root, svc) +// isolatedServiceData runs Goa's current service planner for callers of the +// older helper API, which supplies a design but not a generation plan. +func isolatedServiceData(genpkg string, root *expr.RootExpr, svc *expr.ServiceExpr) *service.Data { + generation, err := codegen.NewGeneration(genpkg, []eval.Root{root}) + if err != nil { + panic(err) + } + plan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + if err != nil { + panic(err) + } + if err := generation.Freeze(); err != nil { + panic(err) + } + if err := plan.Link(); err != nil { + panic(err) + } + return plan.Services().Get(svc.Name) } diff --git a/testing/codegen/harness.go b/testing/codegen/harness.go index 17364e5a0..097a24009 100644 --- a/testing/codegen/harness.go +++ b/testing/codegen/harness.go @@ -1,3 +1,5 @@ +// This file renders the in-process servers and clients used by generated +// service tests for HTTP, gRPC, and JSON-RPC transports. package codegen import ( @@ -40,14 +42,14 @@ type ( ) // generateHarness generates the test harness file for a service. -func generateHarness(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { - path := filepath.Join(testingPath(genpkg, svc), "harness.go") +func generateHarness(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *codegen.File { + path := filepath.Join(testingPath(svcData), "harness.go") if svcData == nil { return nil } - data := buildHarnessData(svcData, root, svc) + data := buildHarnessData(svcData, root, svc, transports) specs := []*codegen.ImportSpec{ {Path: "context"}, @@ -59,7 +61,7 @@ func generateHarness(genpkg string, svcData *service.Data, root *expr.RootExpr, {Path: "strings"}, {Path: "testing"}, {Path: "time"}, - {Path: filepath.Join(genpkg, codegen.SnakeCase(svc.Name)), Name: svcData.PkgName}, + {Path: filepath.Join(genpkg, svcData.PathName), Name: svcData.PkgName}, {Path: "goa.design/goa/v3/pkg"}, } @@ -68,8 +70,8 @@ func generateHarness(genpkg string, svcData *service.Data, root *expr.RootExpr, specs = append(specs, &codegen.ImportSpec{Path: "bytes"}, &codegen.ImportSpec{Path: "bufio"}, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "http", codegen.SnakeCase(svc.Name), "server"), Name: "httpsvr"}, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "http", codegen.SnakeCase(svc.Name), "client"), Name: "httpcli"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "http", svcData.PathName, "server"), Name: "httpsvr"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "http", svcData.PathName, "client"), Name: "httpcli"}, &codegen.ImportSpec{Path: "goa.design/goa/v3/http", Name: "goahttp"}, &codegen.ImportSpec{Path: "net/url"}, &codegen.ImportSpec{Path: "github.com/gorilla/websocket"}, @@ -81,23 +83,23 @@ func generateHarness(genpkg string, svcData *service.Data, root *expr.RootExpr, &codegen.ImportSpec{Path: "google.golang.org/grpc"}, &codegen.ImportSpec{Path: "google.golang.org/grpc/test/bufconn"}, &codegen.ImportSpec{Path: "goa.design/goa/v3/grpc", Name: "goagrpc"}, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", codegen.SnakeCase(svc.Name), "server"), Name: "grpcsvr"}, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", codegen.SnakeCase(svc.Name), "client"), Name: "grpccli"}, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", codegen.SnakeCase(svc.Name), "pb"), Name: svcData.PkgName + "pb"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", svcData.PathName, "server"), Name: "grpcsvr"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", svcData.PathName, "client"), Name: "grpccli"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "grpc", svcData.PathName, "pb"), Name: svcData.PkgName + "pb"}, ) } if data.HasJSONRPC { specs = append(specs, &codegen.ImportSpec{Path: "net/url"}, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "jsonrpc", codegen.SnakeCase(svc.Name), "server"), Name: "jsonrpcsvr"}, - &codegen.ImportSpec{Path: filepath.Join(genpkg, "jsonrpc", codegen.SnakeCase(svc.Name), "client"), Name: "jsonrpccli"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "jsonrpc", svcData.PathName, "server"), Name: "jsonrpcsvr"}, + &codegen.ImportSpec{Path: filepath.Join(genpkg, "jsonrpc", svcData.PathName, "client"), Name: "jsonrpccli"}, &codegen.ImportSpec{Path: "goa.design/goa/v3/jsonrpc", Name: "jsonrpc"}, &codegen.ImportSpec{Path: "goa.design/goa/v3/http", Name: "goahttp"}, ) } sections := []*codegen.SectionTemplate{ - codegen.Header(fmt.Sprintf("Test harness for %s service", svc.Name), codegen.SnakeCase(svc.Name)+"test", specs), + codegen.Header(fmt.Sprintf("Test harness for %s service", svc.Name), svcData.PathName+"test", specs), { Name: "harness-struct", Source: testingTemplates.Read(harnessStructT), @@ -128,7 +130,7 @@ func generateHarness(genpkg string, svcData *service.Data, root *expr.RootExpr, } // buildHarnessData builds the template data for the harness. -func buildHarnessData(svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) *harnessData { +func buildHarnessData(svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *harnessData { // Create harness data using Goa's service.Data directly data := &harnessData{ Data: svcData, @@ -145,7 +147,7 @@ func buildHarnessData(svcData *service.Data, root *expr.RootExpr, svc *expr.Serv md := svcData.Methods[i] // Build targets for this method using shared function - targets := buildMethodTargets(root, svc, m, md) + targets := buildMethodTargets(root, svc, m, transports) // Create harness method data hmd := &harnessMethodData{ @@ -197,10 +199,9 @@ func hasJSONRPCTransport(root *expr.RootExpr, svc *expr.ServiceExpr) bool { return false } -// testingPath returns the path to the testing package for the given service. -func testingPath(_ string, svc *expr.ServiceExpr) string { - // Use service-specific package name to avoid import collisions - return filepath.Join(codegen.Gendir, codegen.SnakeCase(svc.Name), codegen.SnakeCase(svc.Name)+"test") +// testingPath returns the output path next to the generated service package. +func testingPath(svc *service.Data) string { + return filepath.Join(codegen.Gendir, svc.PathName, svc.PathName+"test") } // hasStreams checks if the service has streaming methods. @@ -247,23 +248,6 @@ func hasMethodGRPC(root *expr.RootExpr, svc *expr.ServiceExpr, m *expr.MethodExp return false } -// hasMethodJSONRPC checks whether the given method is bound to JSON-RPC transport. -func hasMethodJSONRPC(root *expr.RootExpr, svc *expr.ServiceExpr, m *expr.MethodExpr) bool { - if root != nil && root.API != nil && root.API.JSONRPC != nil { - for _, js := range root.API.JSONRPC.Services { - if js.Name() != svc.Name { - continue - } - for _, hm := range js.HTTPEndpoints { - if hm.MethodExpr == m { - return true - } - } - } - } - return false -} - // hasPayloads checks if the service has methods with payloads. func hasPayloads(svc *expr.ServiceExpr) bool { for _, m := range svc.Methods { diff --git a/testing/codegen/harness_test.go b/testing/codegen/harness_test.go index 90f1b0299..e89b2088d 100644 --- a/testing/codegen/harness_test.go +++ b/testing/codegen/harness_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/service" "goa.design/plugins/v3/testing/codegen/testdata" ) @@ -34,10 +33,10 @@ func TestGenerateHarness(t *testing.T) { for name, c := range cases { t.Run(name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) - services := service.NewServicesData(root) + services := plannedServiceData(t, root) svc := root.Services[0] svcData := services.Get(svc.Name) - f := generateHarness("", svcData, root, svc) + f := generateHarness("", svcData, root, svc, designedMethodTransports(root)) assert.Equal(t, c.Path, f.Path) for sec, secCode := range c.Code { testCode(t, f, sec, secCode) diff --git a/testing/codegen/package_plan.go b/testing/codegen/package_plan.go new file mode 100644 index 000000000..bf89159ac --- /dev/null +++ b/testing/codegen/package_plan.go @@ -0,0 +1,202 @@ +// This file records every package, name, and import emitted by the testing +// plugin so Goa can report conflicts before it writes source files. +package codegen + +import ( + "path" + + "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/service" + "goa.design/goa/v3/expr" +) + +// PlanPackage records the package, imports, and fixed Go names emitted for one +// service test helper package. Public names stay fixed because applications +// refer to them from handwritten tests. +func PlanPackage( + generation *codegen.Generation, + servicePlan *service.Plan, + root *expr.RootExpr, + svc *expr.ServiceExpr, +) error { + serviceImport, _, err := servicePlan.ServicePackageImports(svc) + if err != nil { + return err + } + servicePath := path.Base(serviceImport.Path) + testPackage, err := generation.ClaimPackage(path.Join(serviceImport.Path, servicePath+"test")) + if err != nil { + return err + } + if err := planPackageImports(testPackage, generation.GenPkg(), root, svc, serviceImport, servicePath); err != nil { + return err + } + return planPackageNames(testPackage, servicePlan, root, svc, designedMethodTransports(root)) +} + +// planPackageImports records every import name written directly in testing +// templates for one service. +func planPackageImports( + pkg *codegen.GeneratedPackage, + genpkg string, + root *expr.RootExpr, + svc *expr.ServiceExpr, + serviceImport *codegen.ImportSpec, + servicePath string, +) error { + imports := []*codegen.ImportSpec{ + {Path: "context"}, + {Path: "encoding/json"}, + {Path: "fmt"}, + {Path: "io"}, + {Path: "net/http"}, + {Path: "net/http/httptest"}, + {Path: "os"}, + {Path: "strings"}, + {Path: "testing"}, + {Path: "time"}, + {Path: "goa.design/goa/v3/pkg"}, + {Path: "gopkg.in/yaml.v3", Name: "yaml"}, + {Path: serviceImport.Path, Name: serviceImport.Name}, + } + if hasHTTPTransport(root, svc) { + imports = append(imports, + &codegen.ImportSpec{Path: "bufio"}, + &codegen.ImportSpec{Path: "bytes"}, + &codegen.ImportSpec{Path: "net/url"}, + &codegen.ImportSpec{Path: "github.com/gorilla/websocket"}, + &codegen.ImportSpec{Path: "goa.design/goa/v3/http", Name: "goahttp"}, + &codegen.ImportSpec{Path: path.Join(genpkg, "http", servicePath, "client"), Name: "httpcli"}, + &codegen.ImportSpec{Path: path.Join(genpkg, "http", servicePath, "server"), Name: "httpsvr"}, + ) + } + if hasGRPCTransport(root, svc) { + imports = append(imports, + &codegen.ImportSpec{Path: "net"}, + &codegen.ImportSpec{Path: "google.golang.org/grpc"}, + &codegen.ImportSpec{Path: "google.golang.org/grpc/test/bufconn"}, + &codegen.ImportSpec{Path: "goa.design/goa/v3/grpc", Name: "goagrpc"}, + &codegen.ImportSpec{Path: path.Join(genpkg, "grpc", servicePath, "client"), Name: "grpccli"}, + &codegen.ImportSpec{Path: path.Join(genpkg, "grpc", servicePath, "server"), Name: "grpcsvr"}, + &codegen.ImportSpec{Path: path.Join(genpkg, "grpc", servicePath, "pb"), Name: serviceImport.Name + "pb"}, + ) + } + if hasJSONRPCTransport(root, svc) { + imports = append(imports, + &codegen.ImportSpec{Path: "net/url"}, + &codegen.ImportSpec{Path: "goa.design/goa/v3/http", Name: "goahttp"}, + &codegen.ImportSpec{Path: "goa.design/goa/v3/jsonrpc", Name: "jsonrpc"}, + &codegen.ImportSpec{Path: path.Join(genpkg, "jsonrpc", servicePath, "client"), Name: "jsonrpccli"}, + &codegen.ImportSpec{Path: path.Join(genpkg, "jsonrpc", servicePath, "server"), Name: "jsonrpcsvr"}, + ) + } + validator := ExtractValidatorsFromYAML() + if validator.Path != "" && validator.Package != "" && validator.Path != pkg.ImportPath() { + imports = append(imports, &codegen.ImportSpec{Path: validator.Path, Name: validator.Package}) + } + for _, spec := range imports { + if err := pkg.RequireImport(spec); err != nil { + return err + } + } + return nil +} + +// planPackageNames records every package-level declaration written by the +// testing templates. Receiver methods do not need separate package names. +func planPackageNames( + pkg *codegen.GeneratedPackage, + servicePlan *service.Plan, + root *expr.RootExpr, + svc *expr.ServiceExpr, + transports *methodTransports, +) error { + types := []string{ + "Transport", + "Client", + "Harness", + "Options", + "Option", + "Scenario", + "Step", + "Expectation", + "ScenarioConfig", + "Validators", + "ScenarioRunner", + "TestData", + } + if hasErrors(svc) { + types = append(types, "ErrorAsserter") + } + examples := expr.NewExampleGenerator(root.API.RandomizerFactory) + for _, method := range svc.Methods { + if methodHasPayloadExample(method, examples) { + types = append(types, codegen.Goify(method.Name, true)+"PayloadBuilder") + } + if transport, ok := transports.jsonRPC[method]; ok && transport.serverSentEvents && method.Stream == expr.ServerStreamKind { + names, err := servicePlan.HTTPMethodNames(method) + if err != nil { + return err + } + types = append(types, "jsonrpcSSE"+names.Method+"Wrapper") + } + } + for _, name := range types { + if err := pkg.DeclareName(codegen.NewExactName(codegen.NameType, name)); err != nil { + return err + } + } + + constants := []string{"AutoTransport"} + if hasHTTPTransport(root, svc) { + constants = append(constants, "HTTPTransport") + } + if hasGRPCTransport(root, svc) { + constants = append(constants, "GRPCTransport") + } + if hasJSONRPCTransport(root, svc) { + constants = append(constants, "JSONRPCTransport") + } + for _, name := range constants { + if err := pkg.DeclareName(codegen.NewExactName(codegen.NameConstant, name)); err != nil { + return err + } + } + + functions := []string{ + "WithContext", + "WithTimeout", + "NewHarness", + "LoadScenarios", + "NewScenarioRunner", + "defaultValidateResult", + "NewTestData", + } + if hasErrors(svc) { + functions = append(functions, "NewErrorAsserter") + } + for _, name := range functions { + if err := pkg.DeclareName(codegen.NewExactName(codegen.NameFunction, name)); err != nil { + return err + } + } + + for _, name := range []string{"ValidTransports", "TransportAvailability"} { + if err := pkg.DeclareName(codegen.NewExactName(codegen.NameVariable, name)); err != nil { + return err + } + } + return nil +} + +// methodHasPayloadExample reports whether the test data template writes a +// payload builder type for method. +func methodHasPayloadExample(method *expr.MethodExpr, examples *expr.ExampleGenerator) bool { + if method.StreamingPayload.Type != expr.Empty { + return method.StreamingPayload.Example(examples.At(expr.MethodStreamingPayloadExampleIdentity(method))) != nil + } + if method.Payload.Type != expr.Empty { + return method.Payload.Example(examples.At(expr.MethodPayloadExampleIdentity(method))) != nil + } + return false +} diff --git a/testing/codegen/plans_test.go b/testing/codegen/plans_test.go new file mode 100644 index 000000000..0e4732c79 --- /dev/null +++ b/testing/codegen/plans_test.go @@ -0,0 +1,151 @@ +// This file builds real Goa plans used by focused testing-plugin unit tests. +package codegen + +import ( + "os" + "os/exec" + "path" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + goacodegen "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/service" + "goa.design/goa/v3/dsl" + "goa.design/goa/v3/eval" + "goa.design/goa/v3/expr" + httpcodegen "goa.design/goa/v3/http/codegen" + jsonrpccodegen "goa.design/goa/v3/jsonrpc/codegen" + "goa.design/plugins/v3/testing/codegen/testdata" +) + +// TestJSONRPCSSEWrappersUseFinalMethodNames verifies that the testing plugin +// uses the distinct Go method names chosen by Goa when it writes stream +// wrappers for methods whose design names normalize to the same Go name. +func TestJSONRPCSSEWrappersUseFinalMethodNames(t *testing.T) { + root := goacodegen.RunDSL(t, func() { + dsl.API("stream-collision", func() { + dsl.JSONRPC(func() {}) + }) + event := dsl.Type("Event", func() { + dsl.Field(1, "message", dsl.String) + dsl.Required("message") + }) + dsl.Service("Events", func() { + dsl.JSONRPC(func() { + dsl.POST("/rpc") + }) + for _, name := range []string{"watch-events", "watch_events"} { + dsl.Method(name, func() { + dsl.StreamingResult(event) + dsl.JSONRPC(func() { + dsl.ServerSentEvents() + }) + }) + } + }) + }) + directory, err := os.MkdirTemp(".", "generated_test_") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(directory)) + }) + genpkg := path.Join("goa.design/plugins/v3/testing/codegen", filepath.Base(directory), "gen") + generation, err := goacodegen.NewGeneration(genpkg, []eval.Root{root}) + require.NoError(t, err) + servicePlan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + require.NoError(t, err) + httpPlans, err := httpcodegen.NewJSONRPCPlans(generation, httpcodegen.PlanInput{Root: root, Service: servicePlan}) + require.NoError(t, err) + jsonPlans, err := jsonrpccodegen.NewPlans(generation, jsonrpccodegen.PlanInput{ + Root: root, + Service: servicePlan, + HTTP: httpPlans[0], + }) + require.NoError(t, err) + require.NoError(t, PlanPackage(generation, servicePlan, root, root.Service("Events"))) + require.NoError(t, generation.Freeze()) + require.NoError(t, servicePlan.Link()) + require.NoError(t, httpPlans[0].Link()) + require.NoError(t, jsonPlans[0].Link()) + + files, err := service.Files(servicePlan) + require.NoError(t, err) + files = append(files, jsonPlans[0].ServerFiles()...) + files = append(files, jsonPlans[0].ClientFiles()...) + files = append(files, jsonPlans[0].ServerTypeFiles()...) + files = append(files, jsonPlans[0].ClientTypeFiles()...) + files = append(files, jsonPlans[0].PathFiles()...) + files = append(files, GeneratePlanned( + generation.GenPkg(), + servicePlan.Services().Get("Events"), + root, + root.Service("Events"), + jsonPlans[0], + )...) + + for _, file := range files { + _, err := file.Render(directory) + require.NoError(t, err) + } + command := exec.Command("go", "test", "./...") + command.Dir = directory + output, err := command.CombinedOutput() + require.NoError(t, err, string(output)) +} + +// TestPlanPackageRejectsPublicNameCollisions verifies that testing helpers +// report a fixed API name conflict during planning instead of emitting invalid +// Go source after names have been chosen. +func TestPlanPackageRejectsPublicNameCollisions(t *testing.T) { + root := goacodegen.RunDSL(t, testdata.WithArrayResultDSL) + generation, err := goacodegen.NewGeneration("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + servicePlan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + require.NoError(t, err) + testPackage, err := generation.ClaimPackage("generated.local/gen/with_array_result_service/with_array_result_servicetest") + require.NoError(t, err) + require.NoError(t, testPackage.DeclareName(goacodegen.NewExactName(goacodegen.NameType, "Client"))) + + err = PlanPackage(generation, servicePlan, root, root.Service("WithArrayResultService")) + require.ErrorContains(t, err, `cannot declare exact type "Client"`) +} + +// plannedServiceData runs Goa's service planning steps and returns the values +// that production plugins receive after Goa has chosen every generated name. +func plannedServiceData(t *testing.T, root *expr.RootExpr) *service.ServicesData { + t.Helper() + + generation, err := goacodegen.NewGeneration("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + plan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + require.NoError(t, err) + require.NoError(t, generation.Freeze()) + require.NoError(t, plan.Link()) + return plan.Services() +} + +// plannedJSONRPCData runs Goa's service and JSON-RPC planning steps and +// returns the finalized values that the testing plugin reads during generation. +func plannedJSONRPCData(t *testing.T, root *expr.RootExpr) (*service.ServicesData, *jsonrpccodegen.Plan) { + t.Helper() + + generation, err := goacodegen.NewGeneration("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + servicePlan, err := service.NewPlan(root, generation, expr.NewExampleGenerator(root.API.RandomizerFactory)) + require.NoError(t, err) + jsonHTTPPlans, err := httpcodegen.NewJSONRPCPlans(generation, httpcodegen.PlanInput{Root: root, Service: servicePlan}) + require.NoError(t, err) + jsonPlans, err := jsonrpccodegen.NewPlans(generation, jsonrpccodegen.PlanInput{ + Root: root, + Service: servicePlan, + HTTP: jsonHTTPPlans[0], + }) + require.NoError(t, err) + require.NoError(t, generation.Freeze()) + require.NoError(t, servicePlan.Link()) + require.NoError(t, jsonHTTPPlans[0].Link()) + require.NoError(t, jsonPlans[0].Link()) + return servicePlan.Services(), jsonPlans[0] +} diff --git a/testing/codegen/scenarios.go b/testing/codegen/scenarios.go index 6bbf65aea..b5ebfb1f8 100644 --- a/testing/codegen/scenarios.go +++ b/testing/codegen/scenarios.go @@ -1,3 +1,5 @@ +// This file renders scenario runners and example scenario files from finalized +// Goa service data and transport choices. package codegen import ( @@ -14,16 +16,16 @@ import ( ) // generateScenarios generates the scenario runner for a service. -func generateScenarios(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) []*codegen.File { +func generateScenarios(genpkg string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) []*codegen.File { if svcData == nil { return nil } - data := buildScenariosData(svcData, root, svc) + data := buildScenariosData(svcData, root, svc, transports) files := make([]*codegen.File, 0, 2) // Generate main scenarios runner file - path := filepath.Join(testingPath(genpkg, svc), "scenarios.go") + path := filepath.Join(testingPath(svcData), "scenarios.go") specs := []*codegen.ImportSpec{ {Path: "context"}, {Path: "encoding/json"}, @@ -33,12 +35,12 @@ func generateScenarios(genpkg string, svcData *service.Data, root *expr.RootExpr {Path: "testing"}, {Path: "time"}, {Path: "gopkg.in/yaml.v3", Name: "yaml"}, - {Path: filepath.Join(genpkg, codegen.SnakeCase(svc.Name)), Name: svcData.PkgName}, + {Path: filepath.Join(genpkg, svcData.PathName), Name: svcData.PkgName}, } // Add validator package import if specified in YAML // Only add if it's different from the current package - currentPkgPath := filepath.Join(genpkg, codegen.SnakeCase(svc.Name), codegen.SnakeCase(svc.Name)+"test") + currentPkgPath := filepath.Join(genpkg, svcData.PathName, svcData.PathName+"test") validatorInfo := ExtractValidatorsFromYAML() if validatorInfo.Path != "" && validatorInfo.Package != "" && validatorInfo.Path != currentPkgPath { specs = append(specs, &codegen.ImportSpec{ @@ -50,7 +52,7 @@ func generateScenarios(genpkg string, svcData *service.Data, root *expr.RootExpr } sections := []*codegen.SectionTemplate{ - codegen.Header(fmt.Sprintf("Scenario runner for %s service", svc.Name), codegen.SnakeCase(svc.Name)+"test", specs), + codegen.Header(fmt.Sprintf("Scenario runner for %s service", svc.Name), svcData.PathName+"test", specs), { Name: "scenario-types", Source: testingTemplates.Read("scenario_types"), @@ -87,8 +89,8 @@ type ( HasGRPC bool // HasJSONRPC indicates if service has JSON-RPC transport HasJSONRPC bool - // ValidTransports is the list of all valid transports - ValidTransports []string + // ValidTransports lists the transports used by at least one method. + ValidTransports []*transportData // Validators found in scenarios.yaml Validators map[string][]string // method -> validator names // ValidatorPkg is the package name for validators @@ -107,19 +109,29 @@ type ( // as seen from the generated test package (e.g. "*svc.Foo", "[]*svc.Bar"). // This is used in generated type assertions for custom validators. ResultTypeRef string + // IsClientStream reports that the client sends a sequence of payloads. + IsClientStream bool + // IsServerStream reports that the server sends a sequence of results. + IsServerStream bool + // IsBidirectional reports that both client and server send sequences. + IsBidirectional bool + } + + // transportData describes one scenario transport in generated help text. + transportData struct { + Name string + Description string } ) // generateExampleScenarios generates an example scenarios.yaml file for a service. -func generateExampleScenarios(_ string, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { +func generateExampleScenarios(_ string, svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *codegen.File { path := filepath.Join(codegen.Gendir, "..", "scenarios.yaml") - - svcData := service.NewServicesData(root).Get(svc.Name) if svcData == nil { return nil } - data := buildScenariosData(svcData, root, svc) + data := buildScenariosData(svcData, root, svc, transports) // For YAML files, we need to read the template directly since it's not a .go.tpl file tmplContent, err := templateFS.ReadFile("templates/example_scenarios.yaml.tpl") @@ -142,7 +154,7 @@ func generateExampleScenarios(_ string, root *expr.RootExpr, svc *expr.ServiceEx } } -func buildScenariosData(svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr) *scenariosData { +func buildScenariosData(svcData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *scenariosData { // Extract validator info from YAML validatorInfo := ExtractValidatorsFromYAML() @@ -153,41 +165,34 @@ func buildScenariosData(svcData *service.Data, root *expr.RootExpr, svc *expr.Se HasHTTP: hasHTTPTransport(root, svc), HasGRPC: hasGRPCTransport(root, svc), HasJSONRPC: hasJSONRPCTransport(root, svc), - ValidTransports: make([]string, 0), + ValidTransports: make([]*transportData, 0), Validators: validatorInfo.Validators, ValidatorPkg: "", // Will be set below if it's a different package ValidatorPath: validatorInfo.Path, } - // Build list of valid transports - transportSet := make(map[string]bool) - transportSet["auto"] = true - if data.HasHTTP { - transportSet["http"] = true - transportSet["http-sse"] = true - transportSet["http-ws"] = true - } - if data.HasGRPC { - transportSet["grpc"] = true - } - if data.HasJSONRPC { - transportSet["jsonrpc"] = true - transportSet["jsonrpc-sse"] = true - transportSet["jsonrpc-ws"] = true - } - data.ValidTransports = slices.Sorted(maps.Keys(transportSet)) + // Collect only transports that at least one generated method can use. + allTransports := map[string]bool{"auto": true} // Build method data with available transports for i, m := range svc.Methods { methodData := svcData.Methods[i] // Build targets for this method using shared function - targets := buildMethodTargets(root, svc, m, methodData) + targets := buildMethodTargets(root, svc, m, transports) md := &scenarioMethodData{ MethodData: methodData, Transports: make([]string, 0), } + switch m.Stream { + case expr.ClientStreamKind: + md.IsClientStream = true + case expr.ServerStreamKind: + md.IsServerStream = true + case expr.BidirectionalStreamKind: + md.IsBidirectional = true + } // Compute fully qualified result type reference for type assertions. // This properly handles composite types like ArrayOf(...) without producing // invalid Go like "svc.[]T" (see issue #234). @@ -196,35 +201,64 @@ func buildScenariosData(svcData *service.Data, root *expr.RootExpr, svc *expr.Se } // Build list of valid transport strings based on targets - transportSet := make(map[string]bool) + methodTransports := make(map[string]bool) for _, target := range targets { switch { case target.IsGRPC: - transportSet["grpc"] = true + methodTransports["grpc"] = true case target.IsHTTPPlain: - transportSet["http"] = true + methodTransports["http"] = true case target.IsHTTPServerSent: - transportSet["http-sse"] = true + methodTransports["http-sse"] = true case target.IsHTTPWebSocket: - transportSet["http-ws"] = true + methodTransports["http-ws"] = true case target.IsJSONRPCPlain: - transportSet["jsonrpc"] = true + methodTransports["jsonrpc"] = true case target.IsJSONRPCSSE: - transportSet["jsonrpc-sse"] = true - case target.IsJSONRPCWS: - transportSet["jsonrpc-ws"] = true + methodTransports["jsonrpc-sse"] = true } } // Convert set to sorted list - md.Transports = slices.Sorted(maps.Keys(transportSet)) + md.Transports = slices.Sorted(maps.Keys(methodTransports)) + for transport := range methodTransports { + allTransports[transport] = true + } data.Methods = append(data.Methods, md) } + for _, transport := range slices.Sorted(maps.Keys(allTransports)) { + data.ValidTransports = append(data.ValidTransports, &transportData{ + Name: transport, + Description: transportDescription(transport), + }) + } return data } +// transportDescription explains one generated scenario transport. +func transportDescription(transport string) string { + switch transport { + case "auto": + return "Use the first available transport" + case "grpc": + return "gRPC" + case "http": + return "HTTP" + case "http-sse": + return "HTTP server-sent events" + case "http-ws": + return "HTTP WebSocket" + case "jsonrpc": + return "JSON-RPC over HTTP" + case "jsonrpc-sse": + return "JSON-RPC over server-sent events" + default: + panic(fmt.Sprintf("testing plugin has no description for transport %q", transport)) + } +} + // ValidatorInfo holds validator configuration type ValidatorInfo struct { Validators map[string][]string // method -> validator names diff --git a/testing/codegen/scenarios_test.go b/testing/codegen/scenarios_test.go index c8f4d2203..f840f3fad 100644 --- a/testing/codegen/scenarios_test.go +++ b/testing/codegen/scenarios_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/service" "goa.design/plugins/v3/testing/codegen/testdata" ) @@ -45,10 +44,10 @@ func TestGenerateScenarios(t *testing.T) { for name, c := range cases { t.Run(name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) - services := service.NewServicesData(root) + services := plannedServiceData(t, root) svc := root.Services[0] svcData := services.Get(svc.Name) - fs := generateScenarios("", svcData, root, svc) + fs := generateScenarios("", svcData, root, svc, designedMethodTransports(root)) f := fs[0] assert.Equal(t, c.Path, f.Path) for sec, secCode := range c.Code { @@ -60,10 +59,10 @@ func TestGenerateScenarios(t *testing.T) { func TestGenerateScenarios_ArrayResultTypeAssertion(t *testing.T) { root := codegen.RunDSL(t, testdata.WithArrayResultDSL) - services := service.NewServicesData(root) + services := plannedServiceData(t, root) svc := root.Services[0] svcData := services.Get(svc.Name) - fs := generateScenarios("", svcData, root, svc) + fs := generateScenarios("", svcData, root, svc, designedMethodTransports(root)) f := fs[0] sections := f.Section("scenario-runner") @@ -110,7 +109,7 @@ func TestGenerateExampleScenarios(t *testing.T) { t.Run(name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) svc := root.Services[0] - f := generateExampleScenarios("", root, svc) + f := GenerateExampleScenarios("generated.local/gen", root, svc) assert.Equal(t, "scenarios.yaml", f.Path) for sec, secCode := range c.Code { sections := f.Section(sec) diff --git a/testing/codegen/suite.go b/testing/codegen/suite.go index b474204a1..403b5f62c 100644 --- a/testing/codegen/suite.go +++ b/testing/codegen/suite.go @@ -1,3 +1,5 @@ +// This file renders the user-owned top-level suite that exercises each service +// method through the transports selected by the design. package codegen import ( @@ -36,7 +38,6 @@ type ( IsHTTPWebSocket bool IsJSONRPCPlain bool IsJSONRPCSSE bool - IsJSONRPCWS bool // Per-target stream kind flags (to support mixed transports) IsNoStream bool @@ -54,11 +55,11 @@ type ( ) // generateSuiteTopLevel generates an example test suite for a service at the top-level package (outside gen). -func generateSuiteTopLevel(genpkg string, examplePkg string, root *expr.RootExpr, svc *expr.ServiceExpr) *codegen.File { - fname := codegen.SnakeCase(svc.Name) + "_suite_test.go" +func generateSuiteTopLevel(genpkg string, examplePkg string, serviceData *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *codegen.File { + fname := serviceData.PathName + "_suite_test.go" path := filepath.Join(codegen.Gendir, "..", fname) pkg := examplePkg - data := buildSuiteData(genpkg, pkg, root, svc) + data := buildSuiteData(genpkg, pkg, serviceData, root, svc, transports) specs := []*codegen.ImportSpec{ {Path: "context"}, {Path: "io"}, @@ -87,8 +88,7 @@ func generateSuiteTopLevel(genpkg string, examplePkg string, root *expr.RootExpr } // buildSuiteData builds the template data for test suite from DSL only. -func buildSuiteData(genpkg, pkg string, root *expr.RootExpr, svc *expr.ServiceExpr) *suiteData { - sd := service.NewServicesData(root).Get(svc.Name) +func buildSuiteData(genpkg, pkg string, sd *service.Data, root *expr.RootExpr, svc *expr.ServiceExpr, transports *methodTransports) *suiteData { data := &suiteData{ Package: pkg, Service: sd, @@ -105,79 +105,7 @@ func buildSuiteData(genpkg, pkg string, root *expr.RootExpr, svc *expr.ServiceEx } for _, m := range svc.Methods { md := &suiteMethodData{Method: sd.Method(m.Name)} - // Compute targets for all transports the method supports - var targets []*suiteTarget - if hasMethodGRPC(root, svc, m) { - g := &suiteTarget{IsGRPC: true} - switch m.Stream { - case expr.NoStreamKind, 0: // 0 is the zero value, treat as NoStreamKind - g.IsNoStream = true - case expr.ClientStreamKind: - g.IsClientStream = true - case expr.ServerStreamKind: - g.IsServerStream = true - case expr.BidirectionalStreamKind: - g.IsBidirectional = true - } - targets = append(targets, g) - } - if hasMethodHTTP(root, svc, m) { - if m.Stream == expr.NoStreamKind || m.Stream == 0 { // 0 is the zero value, treat as NoStreamKind - // Plain HTTP, non-stream - targets = append(targets, &suiteTarget{IsHTTPPlain: true, IsNoStream: true}) - } else { - // Inspect HTTP endpoints for SSE; default to WebSocket when streaming - isSSE := false - if root.API != nil && root.API.HTTP != nil { - for _, hs := range root.API.HTTP.Services { - if hs.Name() != svc.Name { - continue - } - if he := hs.Endpoint(m.Name); he != nil { - isSSE = he.SSE != nil - break - } - } - } - if isSSE { - // SSE is server-stream - targets = append(targets, &suiteTarget{IsHTTPServerSent: true, IsServerStream: true}) - } else { - ws := &suiteTarget{IsHTTPWebSocket: true} - switch m.Stream { - case expr.ClientStreamKind: - ws.IsClientStream = true - case expr.ServerStreamKind: - ws.IsServerStream = true - case expr.BidirectionalStreamKind: - ws.IsBidirectional = true - } - targets = append(targets, ws) - } - } - } - if hasMethodJSONRPC(root, svc, m) && md.Method != nil { - if md.Method.IsJSONRPCSSE { - // JSON-RPC SSE is modeled as server stream regardless of m.Stream - targets = append(targets, &suiteTarget{IsJSONRPCSSE: true, IsServerStream: true}) - } - if md.Method.IsJSONRPCWebSocket { - ws := &suiteTarget{IsJSONRPCWS: true} - switch m.Stream { - case expr.ClientStreamKind: - ws.IsClientStream = true - case expr.ServerStreamKind: - ws.IsServerStream = true - case expr.BidirectionalStreamKind: - ws.IsBidirectional = true - } - targets = append(targets, ws) - } - if !md.Method.IsJSONRPCSSE && !md.Method.IsJSONRPCWebSocket { - // Plain JSON-RPC non-stream - targets = append(targets, &suiteTarget{IsJSONRPCPlain: true, IsNoStream: true}) - } - } + targets := buildMethodTargets(root, svc, m, transports) md.Targets = targets // Classify into NonStream and Stream based on targets (supports mixed transports) anyNonStream := false diff --git a/testing/codegen/suite_test.go b/testing/codegen/suite_test.go index 49fedb530..d1604d543 100644 --- a/testing/codegen/suite_test.go +++ b/testing/codegen/suite_test.go @@ -40,7 +40,7 @@ func TestGenerateSuiteTopLevel(t *testing.T) { t.Run(name, func(t *testing.T) { root := goacodegen.RunDSL(t, c.DSL) svc := root.Services[0] - f := generateSuiteTopLevel("", "", root, svc) + f := GenerateSuiteTopLevel("generated.local/gen", "", root, svc) assert.Equal(t, c.Path, f.Path) for sec, secCode := range c.Code { testCode(t, f, sec, secCode) diff --git a/testing/codegen/templates/example_scenarios.yaml.tpl b/testing/codegen/templates/example_scenarios.yaml.tpl index a400b2426..842725a7b 100644 --- a/testing/codegen/templates/example_scenarios.yaml.tpl +++ b/testing/codegen/templates/example_scenarios.yaml.tpl @@ -107,20 +107,20 @@ scenarios: {{- end }} {{- range $method := .Methods }} - {{- if or (eq $method.StreamKind 1) (eq $method.StreamKind 2) (eq $method.StreamKind 3) }} + {{- if or $method.IsClientStream $method.IsServerStream $method.IsBidirectional }} # Streaming test example - name: "streaming_{{ $method.Name }}" description: "Tests streaming for {{ $method.Name }}" steps: - method: {{ $method.Name }} - {{- if eq $method.StreamKind 1 }} + {{- if $method.IsClientStream }} # Client streaming send: - # First message - # Second message expect: result: {} - {{- else if eq $method.StreamKind 2 }} + {{- else if $method.IsServerStream }} # Server streaming {{- if $method.Payload }} payload: {} @@ -128,7 +128,7 @@ scenarios: receive: - # Expected first message - # Expected second message - {{- else if eq $method.StreamKind 3 }} + {{- else if $method.IsBidirectional }} # Bidirectional streaming send: - # First client message @@ -156,10 +156,10 @@ scenarios: # Transport values (based on your service configuration): # {{- range $transport := .ValidTransports }} -# - {{ $transport }} +# - {{ $transport.Name }} # {{- end }} # Available methods and their transports: # {{- range $method := .Methods }} # - {{ $method.Name }}: {{ range $i, $t := $method.Transports }}{{- if $i }}, {{ end }}{{ $t }}{{- end }} -# {{- end }} \ No newline at end of file +# {{- end -}} diff --git a/testing/codegen/templates/scenario_runner.go.tpl b/testing/codegen/templates/scenario_runner.go.tpl index 825550de9..e42fa28cc 100644 --- a/testing/codegen/templates/scenario_runner.go.tpl +++ b/testing/codegen/templates/scenario_runner.go.tpl @@ -70,13 +70,12 @@ func (r *ScenarioRunner) RunNamed(t *testing.T, client *Client, name string) { } func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scenario) { - // Apply default transport if specified - if scenario.Transport != "" { - client = r.selectTransport(client, scenario.Transport) - } - for i, step := range scenario.Steps { t.Run(fmt.Sprintf("step_%d_%s", i+1, step.Method), func(t *testing.T) { + // Use the scenario transport when this step does not choose one. + if step.Transport == "" { + step.Transport = scenario.Transport + } // Apply scenario-level timeout if step doesn't override if step.Timeout == "" && scenario.Timeout != "" { step.Timeout = scenario.Timeout @@ -87,27 +86,27 @@ func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scen } func (r *ScenarioRunner) runStep(t *testing.T, client *Client, step Step) { - // Apply per-step transport override - if step.Transport != "" { - client = r.selectTransport(client, step.Transport) + // Check the method before using its generated transport list. + transports, ok := TransportAvailability[step.Method] + if !ok { + t.Fatalf("unknown method %q", step.Method) } - - // Validate transport availability + + // Check the selected transport before choosing its client. if step.Transport != "" && step.Transport != "auto" { - if transports, ok := TransportAvailability[step.Method]; ok { - found := false - for _, t := range transports { - if t == step.Transport { - found = true - break - } - } - if !found { - t.Fatalf("method %q does not support transport %q, available: %v", - step.Method, step.Transport, transports) + found := false + for _, transport := range transports { + if transport == step.Transport { + found = true + break } } + if !found { + t.Fatalf("method %q does not support transport %q, available: %v", + step.Method, step.Transport, transports) + } } + client = r.selectTransport(client, step.Transport) // Process payload payload := step.Payload @@ -312,6 +311,8 @@ func (r *ScenarioRunner) validateStream(t *testing.T, method string, stream any, func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Client { switch transport { + case "", "auto": + return client {{- if .HasHTTP }} case "http", "http-sse", "http-ws": return client.HTTP() @@ -321,10 +322,10 @@ func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Clie return client.GRPC() {{- end }} {{- if .HasJSONRPC }} - case "jsonrpc", "jsonrpc-sse", "jsonrpc-ws": + case "jsonrpc", "jsonrpc-sse": return client.JSONRPC() {{- end }} default: - return client // auto or unknown - use default + panic(fmt.Sprintf("unsupported transport %q", transport)) } } diff --git a/testing/codegen/templates/scenario_types.go.tpl b/testing/codegen/templates/scenario_types.go.tpl index 8c955f8b9..e77a27415 100644 --- a/testing/codegen/templates/scenario_types.go.tpl +++ b/testing/codegen/templates/scenario_types.go.tpl @@ -43,19 +43,8 @@ type Validators struct { {{ printf "Valid transport values for scenarios based on service configuration." | comment }} {{ printf "Methods may support different combinations of these transports." | comment }} var ValidTransports = []string{ - "auto", // Use default/first available - {{- if .HasHTTP }} - "http", // HTTP plain (non-streaming methods only) - "http-sse", // HTTP Server-Sent Events (server streaming) - "http-ws", // HTTP WebSocket (client/server/bidi streaming) - {{- end }} - {{- if .HasGRPC }} - "grpc", // gRPC (all streaming modes) - {{- end }} - {{- if .HasJSONRPC }} - "jsonrpc", // JSON-RPC over HTTP (non-streaming) - "jsonrpc-sse", // JSON-RPC over SSE (server streaming) - "jsonrpc-ws", // JSON-RPC over WebSocket (streaming only) + {{- range .ValidTransports }} + "{{ .Name }}", // {{ .Description }} {{- end }} } @@ -64,4 +53,4 @@ var TransportAvailability = map[string][]string{ {{- range .Methods }} "{{ .Name }}": { {{- range $i, $t := .Transports }}{{- if $i }}, {{ end }}"{{ $t }}"{{- end }} }, {{- end }} -} \ No newline at end of file +} diff --git a/testing/codegen/testdata.go b/testing/codegen/testdata.go index 8c50cabb5..d4109881a 100644 --- a/testing/codegen/testdata.go +++ b/testing/codegen/testdata.go @@ -1,3 +1,5 @@ +// This file renders valid payload builders and validation-edge examples for +// generated service tests. package codegen import ( @@ -23,15 +25,16 @@ func generateTestData(genpkg string, svcData *service.Data, root *expr.RootExpr, return nil } - path := filepath.Join(testingPath(genpkg, svc), "testdata.go") + path := filepath.Join(testingPath(svcData), "testdata.go") specs := []*codegen.ImportSpec{ {Path: "encoding/json"}, - {Path: filepath.Join(genpkg, codegen.SnakeCase(svc.Name)), Name: svcData.PkgName}, + {Path: filepath.Join(genpkg, svcData.PathName), Name: svcData.PkgName}, } // Build per-method test data matching the template expectations methods := make([]*methodTestData, 0, len(svc.Methods)) + examples := expr.NewExampleGenerator(root.API.RandomizerFactory) for _, m := range svc.Methods { mtd := &methodTestData{ Name: m.Name, @@ -40,11 +43,12 @@ func generateTestData(genpkg string, svcData *service.Data, root *expr.RootExpr, // Payload info (non-streaming) if m.Payload != nil && m.Payload.Type != expr.Empty { + payloadExamples := examples.At(expr.MethodPayloadExampleIdentity(m)) ref := svcData.Scope.GoFullTypeRef(m.Payload, svcData.PkgName) mtd.Payload = true mtd.PayloadRef = ref // Generate JSON example init if available - if ex := m.Payload.Example(root.API.ExampleGenerator); ex != nil { + if ex := m.Payload.Example(payloadExamples); ex != nil { mtd.PayloadEx = true if b, err := json.Marshal(ex); err == nil { var buf bytes.Buffer @@ -53,15 +57,16 @@ func generateTestData(genpkg string, svcData *service.Data, root *expr.RootExpr, } } // Generate edge cases based on validation rules - mtd.EdgeCases = generateEdgeCases(m.Name, m.Payload, root.API.ExampleGenerator) + mtd.EdgeCases = generateEdgeCases(m.Name, m.Payload, payloadExamples) } // Streaming payload info if m.StreamingPayload != nil && m.StreamingPayload.Type != expr.Empty { + streamingExamples := examples.At(expr.MethodStreamingPayloadExampleIdentity(m)) ref := svcData.Scope.GoFullTypeRef(m.StreamingPayload, svcData.PkgName) mtd.Payload = true mtd.PayloadRef = ref - if ex := m.StreamingPayload.Example(root.API.ExampleGenerator); ex != nil { + if ex := m.StreamingPayload.Example(streamingExamples); ex != nil { mtd.PayloadEx = true if b, err := json.Marshal(ex); err == nil { var buf bytes.Buffer @@ -71,7 +76,7 @@ func generateTestData(genpkg string, svcData *service.Data, root *expr.RootExpr, } // Generate edge cases for streaming payloads if len(mtd.EdgeCases) == 0 { - mtd.EdgeCases = generateEdgeCases(m.Name, m.StreamingPayload, root.API.ExampleGenerator) + mtd.EdgeCases = generateEdgeCases(m.Name, m.StreamingPayload, streamingExamples) } } @@ -87,7 +92,7 @@ func generateTestData(genpkg string, svcData *service.Data, root *expr.RootExpr, } sections := []*codegen.SectionTemplate{ - codegen.Header(fmt.Sprintf("Test data generators for %s service", svc.Name), codegen.SnakeCase(svc.Name)+"test", specs), + codegen.Header(fmt.Sprintf("Test data generators for %s service", svc.Name), svcData.PathName+"test", specs), { Name: "testdata-generators", Source: testingTemplates.Read(testdataGeneratorsT), diff --git a/testing/codegen/testdata/code.go b/testing/codegen/testdata/code.go index 4e526fa66..cef726501 100644 --- a/testing/codegen/testdata/code.go +++ b/testing/codegen/testdata/code.go @@ -424,14 +424,10 @@ type Validators struct { // Valid transport values for scenarios based on service configuration. // Methods may support different combinations of these transports. var ValidTransports = []string{ - "auto", // Use default/first available - "http", // HTTP plain (non-streaming methods only) - "http-sse", // HTTP Server-Sent Events (server streaming) - "http-ws", // HTTP WebSocket (client/server/bidi streaming) - "grpc", // gRPC (all streaming modes) - "jsonrpc", // JSON-RPC over HTTP (non-streaming) - "jsonrpc-sse", // JSON-RPC over SSE (server streaming) - "jsonrpc-ws", // JSON-RPC over WebSocket (streaming only) + "auto", // Use the first available transport + "grpc", // gRPC + "http", // HTTP + "jsonrpc", // JSON-RPC over HTTP } // TransportAvailability documents which transports each method supports. @@ -512,13 +508,12 @@ func (r *ScenarioRunner) RunNamed(t *testing.T, client *Client, name string) { } func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scenario) { - // Apply default transport if specified - if scenario.Transport != "" { - client = r.selectTransport(client, scenario.Transport) - } - for i, step := range scenario.Steps { t.Run(fmt.Sprintf("step_%d_%s", i+1, step.Method), func(t *testing.T) { + // Use the scenario transport when this step does not choose one. + if step.Transport == "" { + step.Transport = scenario.Transport + } // Apply scenario-level timeout if step doesn't override if step.Timeout == "" && scenario.Timeout != "" { step.Timeout = scenario.Timeout @@ -529,27 +524,27 @@ func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scen } func (r *ScenarioRunner) runStep(t *testing.T, client *Client, step Step) { - // Apply per-step transport override - if step.Transport != "" { - client = r.selectTransport(client, step.Transport) + // Check the method before using its generated transport list. + transports, ok := TransportAvailability[step.Method] + if !ok { + t.Fatalf("unknown method %q", step.Method) } - // Validate transport availability + // Check the selected transport before choosing its client. if step.Transport != "" && step.Transport != "auto" { - if transports, ok := TransportAvailability[step.Method]; ok { - found := false - for _, t := range transports { - if t == step.Transport { - found = true - break - } - } - if !found { - t.Fatalf("method %q does not support transport %q, available: %v", - step.Method, step.Transport, transports) + found := false + for _, transport := range transports { + if transport == step.Transport { + found = true + break } } + if !found { + t.Fatalf("method %q does not support transport %q, available: %v", + step.Method, step.Transport, transports) + } } + client = r.selectTransport(client, step.Transport) // Process payload payload := step.Payload @@ -721,14 +716,16 @@ func (r *ScenarioRunner) validateStream(t *testing.T, method string, stream any, func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Client { switch transport { + case "", "auto": + return client case "http", "http-sse", "http-ws": return client.HTTP() case "grpc": return client.GRPC() - case "jsonrpc", "jsonrpc-sse", "jsonrpc-ws": + case "jsonrpc", "jsonrpc-sse": return client.JSONRPC() default: - return client // auto or unknown - use default + panic(fmt.Sprintf("unsupported transport %q", transport)) } } ` @@ -778,14 +775,10 @@ type Validators struct { // Valid transport values for scenarios based on service configuration. // Methods may support different combinations of these transports. var ValidTransports = []string{ - "auto", // Use default/first available - "http", // HTTP plain (non-streaming methods only) - "http-sse", // HTTP Server-Sent Events (server streaming) - "http-ws", // HTTP WebSocket (client/server/bidi streaming) - "grpc", // gRPC (all streaming modes) - "jsonrpc", // JSON-RPC over HTTP (non-streaming) - "jsonrpc-sse", // JSON-RPC over SSE (server streaming) - "jsonrpc-ws", // JSON-RPC over WebSocket (streaming only) + "auto", // Use the first available transport + "grpc", // gRPC + "http", // HTTP + "jsonrpc", // JSON-RPC over HTTP } // TransportAvailability documents which transports each method supports. @@ -866,13 +859,12 @@ func (r *ScenarioRunner) RunNamed(t *testing.T, client *Client, name string) { } func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scenario) { - // Apply default transport if specified - if scenario.Transport != "" { - client = r.selectTransport(client, scenario.Transport) - } - for i, step := range scenario.Steps { t.Run(fmt.Sprintf("step_%d_%s", i+1, step.Method), func(t *testing.T) { + // Use the scenario transport when this step does not choose one. + if step.Transport == "" { + step.Transport = scenario.Transport + } // Apply scenario-level timeout if step doesn't override if step.Timeout == "" && scenario.Timeout != "" { step.Timeout = scenario.Timeout @@ -883,27 +875,27 @@ func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scen } func (r *ScenarioRunner) runStep(t *testing.T, client *Client, step Step) { - // Apply per-step transport override - if step.Transport != "" { - client = r.selectTransport(client, step.Transport) + // Check the method before using its generated transport list. + transports, ok := TransportAvailability[step.Method] + if !ok { + t.Fatalf("unknown method %q", step.Method) } - // Validate transport availability + // Check the selected transport before choosing its client. if step.Transport != "" && step.Transport != "auto" { - if transports, ok := TransportAvailability[step.Method]; ok { - found := false - for _, t := range transports { - if t == step.Transport { - found = true - break - } - } - if !found { - t.Fatalf("method %q does not support transport %q, available: %v", - step.Method, step.Transport, transports) + found := false + for _, transport := range transports { + if transport == step.Transport { + found = true + break } } + if !found { + t.Fatalf("method %q does not support transport %q, available: %v", + step.Method, step.Transport, transports) + } } + client = r.selectTransport(client, step.Transport) // Process payload payload := step.Payload @@ -1072,14 +1064,16 @@ func (r *ScenarioRunner) validateStream(t *testing.T, method string, stream any, func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Client { switch transport { + case "", "auto": + return client case "http", "http-sse", "http-ws": return client.HTTP() case "grpc": return client.GRPC() - case "jsonrpc", "jsonrpc-sse", "jsonrpc-ws": + case "jsonrpc", "jsonrpc-sse": return client.JSONRPC() default: - return client // auto or unknown - use default + panic(fmt.Sprintf("unsupported transport %q", transport)) } } ` @@ -1129,14 +1123,10 @@ type Validators struct { // Valid transport values for scenarios based on service configuration. // Methods may support different combinations of these transports. var ValidTransports = []string{ - "auto", // Use default/first available - "http", // HTTP plain (non-streaming methods only) - "http-sse", // HTTP Server-Sent Events (server streaming) - "http-ws", // HTTP WebSocket (client/server/bidi streaming) - "grpc", // gRPC (all streaming modes) - "jsonrpc", // JSON-RPC over HTTP (non-streaming) - "jsonrpc-sse", // JSON-RPC over SSE (server streaming) - "jsonrpc-ws", // JSON-RPC over WebSocket (streaming only) + "auto", // Use the first available transport + "grpc", // gRPC + "http", // HTTP + "jsonrpc", // JSON-RPC over HTTP } // TransportAvailability documents which transports each method supports. @@ -1217,13 +1207,12 @@ func (r *ScenarioRunner) RunNamed(t *testing.T, client *Client, name string) { } func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scenario) { - // Apply default transport if specified - if scenario.Transport != "" { - client = r.selectTransport(client, scenario.Transport) - } - for i, step := range scenario.Steps { t.Run(fmt.Sprintf("step_%d_%s", i+1, step.Method), func(t *testing.T) { + // Use the scenario transport when this step does not choose one. + if step.Transport == "" { + step.Transport = scenario.Transport + } // Apply scenario-level timeout if step doesn't override if step.Timeout == "" && scenario.Timeout != "" { step.Timeout = scenario.Timeout @@ -1234,27 +1223,27 @@ func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scen } func (r *ScenarioRunner) runStep(t *testing.T, client *Client, step Step) { - // Apply per-step transport override - if step.Transport != "" { - client = r.selectTransport(client, step.Transport) + // Check the method before using its generated transport list. + transports, ok := TransportAvailability[step.Method] + if !ok { + t.Fatalf("unknown method %q", step.Method) } - // Validate transport availability + // Check the selected transport before choosing its client. if step.Transport != "" && step.Transport != "auto" { - if transports, ok := TransportAvailability[step.Method]; ok { - found := false - for _, t := range transports { - if t == step.Transport { - found = true - break - } - } - if !found { - t.Fatalf("method %q does not support transport %q, available: %v", - step.Method, step.Transport, transports) + found := false + for _, transport := range transports { + if transport == step.Transport { + found = true + break } } + if !found { + t.Fatalf("method %q does not support transport %q, available: %v", + step.Method, step.Transport, transports) + } } + client = r.selectTransport(client, step.Transport) // Process payload payload := step.Payload @@ -1421,14 +1410,16 @@ func (r *ScenarioRunner) validateStream(t *testing.T, method string, stream any, func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Client { switch transport { + case "", "auto": + return client case "http", "http-sse", "http-ws": return client.HTTP() case "grpc": return client.GRPC() - case "jsonrpc", "jsonrpc-sse", "jsonrpc-ws": + case "jsonrpc", "jsonrpc-sse": return client.JSONRPC() default: - return client // auto or unknown - use default + panic(fmt.Sprintf("unsupported transport %q", transport)) } } ` @@ -1484,16 +1475,8 @@ scenarios: # # - http # -# - http-sse -# -# - http-ws -# # - jsonrpc # -# - jsonrpc-sse -# -# - jsonrpc-ws -# # Available methods and their transports: # @@ -1557,16 +1540,8 @@ scenarios: # # - http # -# - http-sse -# -# - http-ws -# # - jsonrpc # -# - jsonrpc-sse -# -# - jsonrpc-ws -# # Available methods and their transports: # @@ -1619,16 +1594,8 @@ scenarios: # # - http # -# - http-sse -# -# - http-ws -# # - jsonrpc # -# - jsonrpc-sse -# -# - jsonrpc-ws -# # Available methods and their transports: # diff --git a/testing/codegen/testdata/dsls.go b/testing/codegen/testdata/dsls.go index edd6fe2f2..9715fd1ac 100644 --- a/testing/codegen/testdata/dsls.go +++ b/testing/codegen/testdata/dsls.go @@ -1,3 +1,4 @@ +// This file defines small Goa services used to verify generated testing code. package testdata import ( @@ -81,3 +82,23 @@ var WithoutStreamDSL = func() { }) }) } + +var JSONRPCTransportsDSL = func() { + API("testing-jsonrpc", func() { + JSONRPC(func() {}) + }) + Service("JSONRPCService", func() { + JSONRPC(func() { + POST("/rpc") + }) + Method("Plain", func() { + JSONRPC(func() {}) + }) + Method("Events", func() { + StreamingResult(String) + JSONRPC(func() { + ServerSentEvents() + }) + }) + }) +} diff --git a/testing/codegen/transports.go b/testing/codegen/transports.go new file mode 100644 index 000000000..f62ef5e98 --- /dev/null +++ b/testing/codegen/transports.go @@ -0,0 +1,62 @@ +// This file converts Goa's finalized JSON-RPC plan into the small set of +// transport choices used by testing templates. The templates receive only the +// branches that the design can use, so generated tests contain no runtime +// transport guessing. +package codegen + +import ( + "fmt" + + "goa.design/goa/v3/expr" + jsonrpccodegen "goa.design/goa/v3/jsonrpc/codegen" +) + +type ( + // methodTransports records the JSON-RPC transport selected for each method. + // HTTP and gRPC choices remain available directly from their design entries. + methodTransports struct { + jsonRPC map[*expr.MethodExpr]jsonRPCMethodTransport + } + + // jsonRPCMethodTransport records whether one JSON-RPC method sends a + // server-sent event stream. A false value means an ordinary JSON-RPC call. + jsonRPCMethodTransport struct { + serverSentEvents bool + } +) + +// plannedMethodTransports reads each finalized JSON-RPC endpoint and returns +// the transport choice used to specialize the generated testing helpers. +func plannedMethodTransports(root *expr.RootExpr, plan *jsonrpccodegen.Plan) *methodTransports { + transports := &methodTransports{jsonRPC: make(map[*expr.MethodExpr]jsonRPCMethodTransport)} + if root.API.JSONRPC == nil { + return transports + } + for _, transportService := range root.API.JSONRPC.Services { + service, ok := plan.Service(transportService) + if !ok { + panic(fmt.Sprintf("JSON-RPC plan is missing service %q", transportService.Name())) + } + serviceExpr := root.Service(transportService.Name()) + for _, endpoint := range service.Endpoints { + method := serviceExpr.Method(endpoint.Method.Name) + transports.jsonRPC[method] = jsonRPCMethodTransport{serverSentEvents: endpoint.SSE != nil} + } + } + return transports +} + +// designedMethodTransports reads the prepared JSON-RPC design for callers of +// the released callback API, which does not receive Goa's generation plan. +func designedMethodTransports(root *expr.RootExpr) *methodTransports { + transports := &methodTransports{jsonRPC: make(map[*expr.MethodExpr]jsonRPCMethodTransport)} + if root.API.JSONRPC == nil { + return transports + } + for _, service := range root.API.JSONRPC.Services { + for _, endpoint := range service.HTTPEndpoints { + transports.jsonRPC[endpoint.MethodExpr] = jsonRPCMethodTransport{serverSentEvents: endpoint.SSE != nil} + } + } + return transports +} diff --git a/testing/examples/calculator/gen/calculator/calculatortest/scenarios.go b/testing/examples/calculator/gen/calculator/calculatortest/scenarios.go index 0aae713c9..85e718af6 100644 --- a/testing/examples/calculator/gen/calculator/calculatortest/scenarios.go +++ b/testing/examples/calculator/gen/calculator/calculatortest/scenarios.go @@ -67,11 +67,10 @@ type Validators struct { // Valid transport values for scenarios based on service configuration. // Methods may support different combinations of these transports. var ValidTransports = []string{ - "auto", // Use default/first available - "http", // HTTP plain (non-streaming methods only) - "http-sse", // HTTP Server-Sent Events (server streaming) - "http-ws", // HTTP WebSocket (client/server/bidi streaming) - "grpc", // gRPC (all streaming modes) + "auto", // Use the first available transport + "grpc", // gRPC + "http", // HTTP + "http-ws", // HTTP WebSocket } // TransportAvailability documents which transports each method supports. @@ -81,7 +80,9 @@ var TransportAvailability = map[string][]string{ "factorial": {"grpc", "http"}, "statistics": {"grpc", "http"}, "batch_add": {"grpc", "http-ws"}, -} // ScenarioRunner executes test scenarios. +} + +// ScenarioRunner executes test scenarios. type ScenarioRunner struct { scenarios []Scenario validators Validators // Global validator configuration @@ -153,13 +154,12 @@ func (r *ScenarioRunner) RunNamed(t *testing.T, client *Client, name string) { } func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scenario) { - // Apply default transport if specified - if scenario.Transport != "" { - client = r.selectTransport(client, scenario.Transport) - } - for i, step := range scenario.Steps { t.Run(fmt.Sprintf("step_%d_%s", i+1, step.Method), func(t *testing.T) { + // Use the scenario transport when this step does not choose one. + if step.Transport == "" { + step.Transport = scenario.Transport + } // Apply scenario-level timeout if step doesn't override if step.Timeout == "" && scenario.Timeout != "" { step.Timeout = scenario.Timeout @@ -170,27 +170,27 @@ func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scen } func (r *ScenarioRunner) runStep(t *testing.T, client *Client, step Step) { - // Apply per-step transport override - if step.Transport != "" { - client = r.selectTransport(client, step.Transport) + // Check the method before using its generated transport list. + transports, ok := TransportAvailability[step.Method] + if !ok { + t.Fatalf("unknown method %q", step.Method) } - // Validate transport availability + // Check the selected transport before choosing its client. if step.Transport != "" && step.Transport != "auto" { - if transports, ok := TransportAvailability[step.Method]; ok { - found := false - for _, t := range transports { - if t == step.Transport { - found = true - break - } - } - if !found { - t.Fatalf("method %q does not support transport %q, available: %v", - step.Method, step.Transport, transports) + found := false + for _, transport := range transports { + if transport == step.Transport { + found = true + break } } + if !found { + t.Fatalf("method %q does not support transport %q, available: %v", + step.Method, step.Transport, transports) + } } + client = r.selectTransport(client, step.Transport) // Process payload payload := step.Payload @@ -439,11 +439,13 @@ func (r *ScenarioRunner) validateStream(t *testing.T, method string, stream any, func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Client { switch transport { + case "", "auto": + return client case "http", "http-sse", "http-ws": return client.HTTP() case "grpc": return client.GRPC() default: - return client // auto or unknown - use default + panic(fmt.Sprintf("unsupported transport %q", transport)) } } diff --git a/testing/examples/calculator/gen/calculator/calculatortest/testdata.go b/testing/examples/calculator/gen/calculator/calculatortest/testdata.go index c8a9378b0..62cdc1727 100644 --- a/testing/examples/calculator/gen/calculator/calculatortest/testdata.go +++ b/testing/examples/calculator/gen/calculator/calculatortest/testdata.go @@ -25,14 +25,14 @@ func NewTestData() *TestData { // ValidAddPayload generates a valid add payload. func (td *TestData) ValidAddPayload() *calculator.AddPayload { var payload *calculator.AddPayload - json.Unmarshal([]byte(`{"a":0.8046048822894467,"b":0.4725920460831924}`), &payload) + json.Unmarshal([]byte(`{"a":0.44563671576515784,"b":0.9943820459541153}`), &payload) return payload } // AddPayloadWithAllFields generates a add payload with all fields populated. func (td *TestData) AddPayloadWithAllFields() *calculator.AddPayload { var payload *calculator.AddPayload - json.Unmarshal([]byte(`{"a":0.8046048822894467,"b":0.4725920460831924}`), &payload) + json.Unmarshal([]byte(`{"a":0.44563671576515784,"b":0.9943820459541153}`), &payload) return payload } @@ -55,7 +55,7 @@ func (b *AddPayloadBuilder) Build() *calculator.AddPayload { // ValidDividePayload generates a valid divide payload. func (td *TestData) ValidDividePayload() *calculator.DividePayload { var payload *calculator.DividePayload - json.Unmarshal([]byte(`{"dividend":0.15290751445500889,"divisor":0.49929202566829306}`), &payload) + json.Unmarshal([]byte(`{"dividend":0.4490181783753573,"divisor":0.22489100792813552}`), &payload) return payload } @@ -63,7 +63,7 @@ func (td *TestData) ValidDividePayload() *calculator.DividePayload { // populated. func (td *TestData) DividePayloadWithAllFields() *calculator.DividePayload { var payload *calculator.DividePayload - json.Unmarshal([]byte(`{"dividend":0.15290751445500889,"divisor":0.49929202566829306}`), &payload) + json.Unmarshal([]byte(`{"dividend":0.4490181783753573,"divisor":0.22489100792813552}`), &payload) return payload } @@ -86,7 +86,7 @@ func (b *DividePayloadBuilder) Build() *calculator.DividePayload { // ValidFactorialPayload generates a valid factorial payload. func (td *TestData) ValidFactorialPayload() *calculator.FactorialPayload { var payload *calculator.FactorialPayload - json.Unmarshal([]byte(`{"n":14}`), &payload) + json.Unmarshal([]byte(`{"n":2}`), &payload) return payload } @@ -110,7 +110,7 @@ func (td *TestData) FactorialPayloadWithMaxValues() *calculator.FactorialPayload // populated. func (td *TestData) FactorialPayloadWithAllFields() *calculator.FactorialPayload { var payload *calculator.FactorialPayload - json.Unmarshal([]byte(`{"n":14}`), &payload) + json.Unmarshal([]byte(`{"n":2}`), &payload) return payload } @@ -133,7 +133,7 @@ func (b *FactorialPayloadBuilder) Build() *calculator.FactorialPayload { // ValidStatisticsPayload generates a valid statistics payload. func (td *TestData) ValidStatisticsPayload() *calculator.StatisticsPayload { var payload *calculator.StatisticsPayload - json.Unmarshal([]byte(`{"numbers":[0.3381990535444184,0.5267699441186475,0.5182299611503365]}`), &payload) + json.Unmarshal([]byte(`{"numbers":[0.7811727221500537]}`), &payload) return payload } @@ -141,7 +141,7 @@ func (td *TestData) ValidStatisticsPayload() *calculator.StatisticsPayload { // length strings/arrays. func (td *TestData) StatisticsPayloadWithMinLength() *calculator.StatisticsPayload { var payload *calculator.StatisticsPayload - json.Unmarshal([]byte(`{"numbers":[0.37830484879441273]}`), &payload) + json.Unmarshal([]byte(`{"numbers":[0.6514540004476669]}`), &payload) return payload } @@ -149,7 +149,7 @@ func (td *TestData) StatisticsPayloadWithMinLength() *calculator.StatisticsPaylo // fields populated. func (td *TestData) StatisticsPayloadWithAllFields() *calculator.StatisticsPayload { var payload *calculator.StatisticsPayload - json.Unmarshal([]byte(`{"numbers":[0.3381990535444184,0.5267699441186475,0.5182299611503365]}`), &payload) + json.Unmarshal([]byte(`{"numbers":[0.7811727221500537]}`), &payload) return payload } @@ -172,7 +172,7 @@ func (b *StatisticsPayloadBuilder) Build() *calculator.StatisticsPayload { // ValidBatchAddPayload generates a valid batch_add payload. func (td *TestData) ValidBatchAddPayload() *calculator.BatchAddStreamingPayload { var payload *calculator.BatchAddStreamingPayload - json.Unmarshal([]byte(`{"a":0.03867010456428904,"b":0.5620394395461085}`), &payload) + json.Unmarshal([]byte(`{"a":0.46890788570126785,"b":0.8347329767479303}`), &payload) return payload } @@ -180,7 +180,7 @@ func (td *TestData) ValidBatchAddPayload() *calculator.BatchAddStreamingPayload // populated. func (td *TestData) BatchAddPayloadWithAllFields() *calculator.BatchAddStreamingPayload { var payload *calculator.BatchAddStreamingPayload - json.Unmarshal([]byte(`{"a":0.03867010456428904,"b":0.5620394395461085}`), &payload) + json.Unmarshal([]byte(`{"a":0.46890788570126785,"b":0.8347329767479303}`), &payload) return payload } diff --git a/testing/examples/calculator/gen/calculator/service.go b/testing/examples/calculator/gen/calculator/service.go index 034c28cd8..b0f7cae4a 100644 --- a/testing/examples/calculator/gen/calculator/service.go +++ b/testing/examples/calculator/gen/calculator/service.go @@ -8,9 +8,7 @@ package calculator -import ( - "context" -) +import "context" // A simple calculator service to demonstrate testing plugin features type Service interface { diff --git a/testing/examples/calculator/gen/grpc/calculator/client/cli.go b/testing/examples/calculator/gen/grpc/calculator/client/cli.go index 785f78987..bdbcc5a8c 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/cli.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/cli.go @@ -9,11 +9,11 @@ package client import ( - "encoding/json" "fmt" calculator "goa.design/plugins/v3/testing/examples/calculator/gen/calculator" calculatorpb "goa.design/plugins/v3/testing/examples/calculator/gen/grpc/calculator/pb" + "google.golang.org/protobuf/encoding/protojson" ) // BuildAddPayload builds the payload for the calculator add endpoint from CLI @@ -23,15 +23,19 @@ func BuildAddPayload(calculatorAddMessage string) (*calculator.AddPayload, error var message calculatorpb.AddRequest { if calculatorAddMessage != "" { - err = json.Unmarshal([]byte(calculatorAddMessage), &message) + err = protojson.Unmarshal([]byte(calculatorAddMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"a\": 0.7564502808463504,\n \"b\": 0.22116150789969893\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"a\": 0.22091916525730942,\n \"b\": 0.26780525886889645\n }'") } } } + if err := ValidateAddRequest(&message); err != nil { + var zero *calculator.AddPayload + return zero, err + } v := &calculator.AddPayload{ - A: message.A, - B: message.B, + A: *message.A, + B: *message.B, } return v, nil @@ -44,15 +48,19 @@ func BuildDividePayload(calculatorDivideMessage string) (*calculator.DividePaylo var message calculatorpb.DivideRequest { if calculatorDivideMessage != "" { - err = json.Unmarshal([]byte(calculatorDivideMessage), &message) + err = protojson.Unmarshal([]byte(calculatorDivideMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 0.9492693158321226,\n \"divisor\": 0.7671133810336571\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 0.7331112436485956,\n \"divisor\": 0.7550692456980171\n }'") } } } + if err := ValidateDivideRequest(&message); err != nil { + var zero *calculator.DividePayload + return zero, err + } v := &calculator.DividePayload{ - Dividend: message.Dividend, - Divisor: message.Divisor, + Dividend: *message.Dividend, + Divisor: *message.Divisor, } return v, nil @@ -65,14 +73,18 @@ func BuildFactorialPayload(calculatorFactorialMessage string) (*calculator.Facto var message calculatorpb.FactorialRequest { if calculatorFactorialMessage != "" { - err = json.Unmarshal([]byte(calculatorFactorialMessage), &message) + err = protojson.Unmarshal([]byte(calculatorFactorialMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"n\": 11\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"n\": 0\n }'") } } } + if err := ValidateFactorialRequest(&message); err != nil { + var zero *calculator.FactorialPayload + return zero, err + } v := &calculator.FactorialPayload{ - N: int(message.N), + N: int(*message.N), } return v, nil @@ -85,12 +97,16 @@ func BuildStatisticsPayload(calculatorStatisticsMessage string) (*calculator.Sta var message calculatorpb.StatisticsRequest { if calculatorStatisticsMessage != "" { - err = json.Unmarshal([]byte(calculatorStatisticsMessage), &message) + err = protojson.Unmarshal([]byte(calculatorStatisticsMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"numbers\": [\n 0.9839407167429688,\n 0.3969464125201744,\n 0.8182138429566963\n ]\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"numbers\": [\n 0.3811399958331969,\n 0.21022254932918344,\n 0.7555995055194686\n ]\n }'") } } } + if err := ValidateStatisticsRequest(&message); err != nil { + var zero *calculator.StatisticsPayload + return zero, err + } v := &calculator.StatisticsPayload{} if message.Numbers != nil { v.Numbers = make([]float64, len(message.Numbers)) diff --git a/testing/examples/calculator/gen/grpc/calculator/client/client.go b/testing/examples/calculator/gen/grpc/calculator/client/client.go index cfb1002f1..01ddd6ce4 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/client.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/client.go @@ -151,6 +151,9 @@ func (s *BatchAddClientStream) Recv() (*calculator.BatchAddResult, error) { if err != nil { return res, err } + if err = ValidateBatchAddResponse(v); err != nil { + return res, err + } return NewBatchAddResponseBatchAddResult(v), nil } diff --git a/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go b/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go index 240fa76b1..e91d7c05f 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go @@ -47,6 +47,9 @@ func DecodeAddResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, if !ok { return nil, goagrpc.ErrInvalidType("calculator", "add", "*calculatorpb.AddResponse", v) } + if err := ValidateAddResponse(message); err != nil { + return nil, err + } res := NewAddResult(message) return res, nil } @@ -80,6 +83,9 @@ func DecodeDivideResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (an if !ok { return nil, goagrpc.ErrInvalidType("calculator", "divide", "*calculatorpb.DivideResponse", v) } + if err := ValidateDivideResponse(message); err != nil { + return nil, err + } res := NewDivideResult(message) return res, nil } @@ -115,6 +121,9 @@ func DecodeFactorialResponse(ctx context.Context, v any, hdr, trlr metadata.MD) if !ok { return nil, goagrpc.ErrInvalidType("calculator", "factorial", "*calculatorpb.FactorialResponse", v) } + if err := ValidateFactorialResponse(message); err != nil { + return nil, err + } res := NewFactorialResult(message) return res, nil } @@ -150,6 +159,9 @@ func DecodeStatisticsResponse(ctx context.Context, v any, hdr, trlr metadata.MD) if !ok { return nil, goagrpc.ErrInvalidType("calculator", "statistics", "*calculatorpb.StatisticsResponse", v) } + if err := ValidateStatisticsResponse(message); err != nil { + return nil, err + } res := NewStatisticsResult(message) return res, nil } diff --git a/testing/examples/calculator/gen/grpc/calculator/client/types.go b/testing/examples/calculator/gen/grpc/calculator/client/types.go index e695ab87b..dddd58262 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/types.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/types.go @@ -9,72 +9,72 @@ package client import ( + goa "goa.design/goa/v3/pkg" calculator "goa.design/plugins/v3/testing/examples/calculator/gen/calculator" calculatorpb "goa.design/plugins/v3/testing/examples/calculator/gen/grpc/calculator/pb" ) -// NewProtoAddRequest builds the gRPC request type from the payload of the -// "add" endpoint of the "calculator" service. +// NewProtoAddRequest builds *calculatorpb.AddRequest from +// *calculator.AddPayload. func NewProtoAddRequest(payload *calculator.AddPayload) *calculatorpb.AddRequest { message := &calculatorpb.AddRequest{ - A: payload.A, - B: payload.B, + A: &payload.A, + B: &payload.B, } return message } -// NewAddResult builds the result type of the "add" endpoint of the -// "calculator" service from the gRPC response type. +// NewAddResult builds *calculator.AddResult from *calculatorpb.AddResponse. func NewAddResult(message *calculatorpb.AddResponse) *calculator.AddResult { result := &calculator.AddResult{ - Result: message.Result, - Operation: message.Operation, + Result: *message.Result, + Operation: *message.Operation, } return result } -// NewProtoDivideRequest builds the gRPC request type from the payload of the -// "divide" endpoint of the "calculator" service. +// NewProtoDivideRequest builds *calculatorpb.DivideRequest from +// *calculator.DividePayload. func NewProtoDivideRequest(payload *calculator.DividePayload) *calculatorpb.DivideRequest { message := &calculatorpb.DivideRequest{ - Dividend: payload.Dividend, - Divisor: payload.Divisor, + Dividend: &payload.Dividend, + Divisor: &payload.Divisor, } return message } -// NewDivideResult builds the result type of the "divide" endpoint of the -// "calculator" service from the gRPC response type. +// NewDivideResult builds *calculator.DivideResult from +// *calculatorpb.DivideResponse. func NewDivideResult(message *calculatorpb.DivideResponse) *calculator.DivideResult { result := &calculator.DivideResult{ - Result: message.Result, - Operation: message.Operation, + Result: *message.Result, + Operation: *message.Operation, } return result } -// NewProtoFactorialRequest builds the gRPC request type from the payload of -// the "factorial" endpoint of the "calculator" service. +// NewProtoFactorialRequest builds *calculatorpb.FactorialRequest from +// *calculator.FactorialPayload. func NewProtoFactorialRequest(payload *calculator.FactorialPayload) *calculatorpb.FactorialRequest { - message := &calculatorpb.FactorialRequest{ - N: int32(payload.N), - } + message := &calculatorpb.FactorialRequest{} + n := int32(payload.N) + message.N = &n return message } -// NewFactorialResult builds the result type of the "factorial" endpoint of the -// "calculator" service from the gRPC response type. +// NewFactorialResult builds *calculator.FactorialResult from +// *calculatorpb.FactorialResponse. func NewFactorialResult(message *calculatorpb.FactorialResponse) *calculator.FactorialResult { result := &calculator.FactorialResult{ - Result: message.Result, - Operation: message.Operation, - ComputationTimeMs: int(message.ComputationTimeMs), + Result: *message.Result, + Operation: *message.Operation, + ComputationTimeMs: int(*message.ComputationTimeMs), } return result } -// NewProtoStatisticsRequest builds the gRPC request type from the payload of -// the "statistics" endpoint of the "calculator" service. +// NewProtoStatisticsRequest builds *calculatorpb.StatisticsRequest from +// *calculator.StatisticsPayload. func NewProtoStatisticsRequest(payload *calculator.StatisticsPayload) *calculatorpb.StatisticsRequest { message := &calculatorpb.StatisticsRequest{} if payload.Numbers != nil { @@ -86,32 +86,157 @@ func NewProtoStatisticsRequest(payload *calculator.StatisticsPayload) *calculato return message } -// NewStatisticsResult builds the result type of the "statistics" endpoint of -// the "calculator" service from the gRPC response type. +// NewStatisticsResult builds *calculator.StatisticsResult from +// *calculatorpb.StatisticsResponse. func NewStatisticsResult(message *calculatorpb.StatisticsResponse) *calculator.StatisticsResult { result := &calculator.StatisticsResult{ - Mean: message.Mean, - Median: message.Median, - Min: message.Min, - Max: message.Max, - Count: int(message.Count), - Sum: message.Sum, + Mean: *message.Mean, + Median: *message.Median, + Min: *message.Min, + Max: *message.Max, + Count: int(*message.Count), + Sum: *message.Sum, } return result } +// NewBatchAddResponseBatchAddResult builds *calculator.BatchAddResult from +// *calculatorpb.BatchAddResponse. func NewBatchAddResponseBatchAddResult(v *calculatorpb.BatchAddResponse) *calculator.BatchAddResult { result := &calculator.BatchAddResult{ - Result: v.Result, - Index: int(v.Index), + Result: *v.Result, + Index: int(*v.Index), } return result } +// NewProtoBatchAddStreamingPayloadBatchAddStreamingRequest builds +// *calculatorpb.BatchAddStreamingRequest from +// *calculator.BatchAddStreamingPayload. func NewProtoBatchAddStreamingPayloadBatchAddStreamingRequest(spayload *calculator.BatchAddStreamingPayload) *calculatorpb.BatchAddStreamingRequest { v := &calculatorpb.BatchAddStreamingRequest{ - A: spayload.A, - B: spayload.B, + A: &spayload.A, + B: &spayload.B, } return v } + +// ValidateAddRequest runs the validations defined on AddRequest. +func ValidateAddRequest(message *calculatorpb.AddRequest) (err error) { + if message.A == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("a", "message")) + } + if message.B == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("b", "message")) + } + return +} + +// ValidateAddResponse runs the validations defined on AddResponse. +func ValidateAddResponse(message *calculatorpb.AddResponse) (err error) { + if message.Result == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("result", "message")) + } + if message.Operation == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("operation", "message")) + } + return +} + +// ValidateDivideRequest runs the validations defined on DivideRequest. +func ValidateDivideRequest(message *calculatorpb.DivideRequest) (err error) { + if message.Dividend == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("dividend", "message")) + } + if message.Divisor == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("divisor", "message")) + } + return +} + +// ValidateDivideResponse runs the validations defined on DivideResponse. +func ValidateDivideResponse(message *calculatorpb.DivideResponse) (err error) { + if message.Result == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("result", "message")) + } + if message.Operation == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("operation", "message")) + } + return +} + +// ValidateFactorialRequest runs the validations defined on FactorialRequest. +func ValidateFactorialRequest(message *calculatorpb.FactorialRequest) (err error) { + if message.N == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("n", "message")) + } + if message.N != nil { + if *message.N < 0 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.n", *message.N, 0, true)) + } + if *message.N > 20 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.n", *message.N, 20, false)) + } + } + return +} + +// ValidateFactorialResponse runs the validations defined on FactorialResponse. +func ValidateFactorialResponse(message *calculatorpb.FactorialResponse) (err error) { + if message.Result == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("result", "message")) + } + if message.Operation == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("operation", "message")) + } + if message.ComputationTimeMs == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("computation_time_ms", "message")) + } + return +} + +// ValidateStatisticsRequest runs the validations defined on StatisticsRequest. +func ValidateStatisticsRequest(message *calculatorpb.StatisticsRequest) (err error) { + if message.Numbers == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("numbers", "message")) + } + if len(message.Numbers) < 1 { + err = goa.MergeErrors(err, goa.InvalidLengthError("message.numbers", message.Numbers, len(message.Numbers), 1, true)) + } + return +} + +// ValidateStatisticsResponse runs the validations defined on +// StatisticsResponse. +func ValidateStatisticsResponse(message *calculatorpb.StatisticsResponse) (err error) { + if message.Mean == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("mean", "message")) + } + if message.Median == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("median", "message")) + } + if message.Min == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("min", "message")) + } + if message.Max == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("max", "message")) + } + if message.Count == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("count", "message")) + } + if message.Sum == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("sum", "message")) + } + return +} + +// ValidateBatchAddResponse runs the validations defined on BatchAddResponse. +func ValidateBatchAddResponse(message *calculatorpb.BatchAddResponse) (err error) { + if message.Result == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("result", "message")) + } + if message.Index == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("index", "message")) + } + return +} diff --git a/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.pb.go b/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.pb.go index faa706ef0..3f572411b 100644 --- a/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.pb.go +++ b/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.pb.go @@ -8,8 +8,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc-gen-go v1.36.12 +// protoc v7.36.0 // source: goagen_calculator_calculator.proto package calculatorpb @@ -32,9 +32,9 @@ const ( type AddRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // First number - A float64 `protobuf:"fixed64,1,opt,name=a,proto3" json:"a,omitempty"` + A *float64 `protobuf:"fixed64,1,opt,name=a,proto3,oneof" json:"a,omitempty"` // Second number - B float64 `protobuf:"fixed64,2,opt,name=b,proto3" json:"b,omitempty"` + B *float64 `protobuf:"fixed64,2,opt,name=b,proto3,oneof" json:"b,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -70,15 +70,15 @@ func (*AddRequest) Descriptor() ([]byte, []int) { } func (x *AddRequest) GetA() float64 { - if x != nil { - return x.A + if x != nil && x.A != nil { + return *x.A } return 0 } func (x *AddRequest) GetB() float64 { - if x != nil { - return x.B + if x != nil && x.B != nil { + return *x.B } return 0 } @@ -86,9 +86,9 @@ func (x *AddRequest) GetB() float64 { type AddResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Sum of a and b - Result float64 `protobuf:"fixed64,1,opt,name=result,proto3" json:"result,omitempty"` + Result *float64 `protobuf:"fixed64,1,opt,name=result,proto3,oneof" json:"result,omitempty"` // Operation performed - Operation string `protobuf:"bytes,2,opt,name=operation,proto3" json:"operation,omitempty"` + Operation *string `protobuf:"bytes,2,opt,name=operation,proto3,oneof" json:"operation,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -124,15 +124,15 @@ func (*AddResponse) Descriptor() ([]byte, []int) { } func (x *AddResponse) GetResult() float64 { - if x != nil { - return x.Result + if x != nil && x.Result != nil { + return *x.Result } return 0 } func (x *AddResponse) GetOperation() string { - if x != nil { - return x.Operation + if x != nil && x.Operation != nil { + return *x.Operation } return "" } @@ -140,9 +140,9 @@ func (x *AddResponse) GetOperation() string { type DivideRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Number to divide - Dividend float64 `protobuf:"fixed64,1,opt,name=dividend,proto3" json:"dividend,omitempty"` + Dividend *float64 `protobuf:"fixed64,1,opt,name=dividend,proto3,oneof" json:"dividend,omitempty"` // Number to divide by - Divisor float64 `protobuf:"fixed64,2,opt,name=divisor,proto3" json:"divisor,omitempty"` + Divisor *float64 `protobuf:"fixed64,2,opt,name=divisor,proto3,oneof" json:"divisor,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -178,15 +178,15 @@ func (*DivideRequest) Descriptor() ([]byte, []int) { } func (x *DivideRequest) GetDividend() float64 { - if x != nil { - return x.Dividend + if x != nil && x.Dividend != nil { + return *x.Dividend } return 0 } func (x *DivideRequest) GetDivisor() float64 { - if x != nil { - return x.Divisor + if x != nil && x.Divisor != nil { + return *x.Divisor } return 0 } @@ -194,9 +194,9 @@ func (x *DivideRequest) GetDivisor() float64 { type DivideResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Division result - Result float64 `protobuf:"fixed64,1,opt,name=result,proto3" json:"result,omitempty"` + Result *float64 `protobuf:"fixed64,1,opt,name=result,proto3,oneof" json:"result,omitempty"` // Operation performed - Operation string `protobuf:"bytes,2,opt,name=operation,proto3" json:"operation,omitempty"` + Operation *string `protobuf:"bytes,2,opt,name=operation,proto3,oneof" json:"operation,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -232,15 +232,15 @@ func (*DivideResponse) Descriptor() ([]byte, []int) { } func (x *DivideResponse) GetResult() float64 { - if x != nil { - return x.Result + if x != nil && x.Result != nil { + return *x.Result } return 0 } func (x *DivideResponse) GetOperation() string { - if x != nil { - return x.Operation + if x != nil && x.Operation != nil { + return *x.Operation } return "" } @@ -248,7 +248,7 @@ func (x *DivideResponse) GetOperation() string { type FactorialRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Number to calculate factorial of - N int32 `protobuf:"zigzag32,1,opt,name=n,proto3" json:"n,omitempty"` + N *int32 `protobuf:"zigzag32,1,opt,name=n,proto3,oneof" json:"n,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -284,8 +284,8 @@ func (*FactorialRequest) Descriptor() ([]byte, []int) { } func (x *FactorialRequest) GetN() int32 { - if x != nil { - return x.N + if x != nil && x.N != nil { + return *x.N } return 0 } @@ -293,11 +293,11 @@ func (x *FactorialRequest) GetN() int32 { type FactorialResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Factorial result - Result int64 `protobuf:"zigzag64,1,opt,name=result,proto3" json:"result,omitempty"` + Result *int64 `protobuf:"zigzag64,1,opt,name=result,proto3,oneof" json:"result,omitempty"` // Operation performed - Operation string `protobuf:"bytes,2,opt,name=operation,proto3" json:"operation,omitempty"` + Operation *string `protobuf:"bytes,2,opt,name=operation,proto3,oneof" json:"operation,omitempty"` // Time taken in milliseconds - ComputationTimeMs int32 `protobuf:"zigzag32,3,opt,name=computation_time_ms,json=computationTimeMs,proto3" json:"computation_time_ms,omitempty"` + ComputationTimeMs *int32 `protobuf:"zigzag32,3,opt,name=computation_time_ms,json=computationTimeMs,proto3,oneof" json:"computation_time_ms,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -333,22 +333,22 @@ func (*FactorialResponse) Descriptor() ([]byte, []int) { } func (x *FactorialResponse) GetResult() int64 { - if x != nil { - return x.Result + if x != nil && x.Result != nil { + return *x.Result } return 0 } func (x *FactorialResponse) GetOperation() string { - if x != nil { - return x.Operation + if x != nil && x.Operation != nil { + return *x.Operation } return "" } func (x *FactorialResponse) GetComputationTimeMs() int32 { - if x != nil { - return x.ComputationTimeMs + if x != nil && x.ComputationTimeMs != nil { + return *x.ComputationTimeMs } return 0 } @@ -401,17 +401,17 @@ func (x *StatisticsRequest) GetNumbers() []float64 { type StatisticsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Average of numbers - Mean float64 `protobuf:"fixed64,1,opt,name=mean,proto3" json:"mean,omitempty"` + Mean *float64 `protobuf:"fixed64,1,opt,name=mean,proto3,oneof" json:"mean,omitempty"` // Median value - Median float64 `protobuf:"fixed64,2,opt,name=median,proto3" json:"median,omitempty"` + Median *float64 `protobuf:"fixed64,2,opt,name=median,proto3,oneof" json:"median,omitempty"` // Minimum value - Min float64 `protobuf:"fixed64,3,opt,name=min,proto3" json:"min,omitempty"` + Min *float64 `protobuf:"fixed64,3,opt,name=min,proto3,oneof" json:"min,omitempty"` // Maximum value - Max float64 `protobuf:"fixed64,4,opt,name=max,proto3" json:"max,omitempty"` + Max *float64 `protobuf:"fixed64,4,opt,name=max,proto3,oneof" json:"max,omitempty"` // Number of values - Count int32 `protobuf:"zigzag32,5,opt,name=count,proto3" json:"count,omitempty"` + Count *int32 `protobuf:"zigzag32,5,opt,name=count,proto3,oneof" json:"count,omitempty"` // Sum of all values - Sum float64 `protobuf:"fixed64,6,opt,name=sum,proto3" json:"sum,omitempty"` + Sum *float64 `protobuf:"fixed64,6,opt,name=sum,proto3,oneof" json:"sum,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -447,43 +447,43 @@ func (*StatisticsResponse) Descriptor() ([]byte, []int) { } func (x *StatisticsResponse) GetMean() float64 { - if x != nil { - return x.Mean + if x != nil && x.Mean != nil { + return *x.Mean } return 0 } func (x *StatisticsResponse) GetMedian() float64 { - if x != nil { - return x.Median + if x != nil && x.Median != nil { + return *x.Median } return 0 } func (x *StatisticsResponse) GetMin() float64 { - if x != nil { - return x.Min + if x != nil && x.Min != nil { + return *x.Min } return 0 } func (x *StatisticsResponse) GetMax() float64 { - if x != nil { - return x.Max + if x != nil && x.Max != nil { + return *x.Max } return 0 } func (x *StatisticsResponse) GetCount() int32 { - if x != nil { - return x.Count + if x != nil && x.Count != nil { + return *x.Count } return 0 } func (x *StatisticsResponse) GetSum() float64 { - if x != nil { - return x.Sum + if x != nil && x.Sum != nil { + return *x.Sum } return 0 } @@ -491,9 +491,9 @@ func (x *StatisticsResponse) GetSum() float64 { type BatchAddStreamingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // First number - A float64 `protobuf:"fixed64,1,opt,name=a,proto3" json:"a,omitempty"` + A *float64 `protobuf:"fixed64,1,opt,name=a,proto3,oneof" json:"a,omitempty"` // Second number - B float64 `protobuf:"fixed64,2,opt,name=b,proto3" json:"b,omitempty"` + B *float64 `protobuf:"fixed64,2,opt,name=b,proto3,oneof" json:"b,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -529,15 +529,15 @@ func (*BatchAddStreamingRequest) Descriptor() ([]byte, []int) { } func (x *BatchAddStreamingRequest) GetA() float64 { - if x != nil { - return x.A + if x != nil && x.A != nil { + return *x.A } return 0 } func (x *BatchAddStreamingRequest) GetB() float64 { - if x != nil { - return x.B + if x != nil && x.B != nil { + return *x.B } return 0 } @@ -545,9 +545,9 @@ func (x *BatchAddStreamingRequest) GetB() float64 { type BatchAddResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Sum - Result float64 `protobuf:"fixed64,1,opt,name=result,proto3" json:"result,omitempty"` + Result *float64 `protobuf:"fixed64,1,opt,name=result,proto3,oneof" json:"result,omitempty"` // Operation index - Index int32 `protobuf:"zigzag32,2,opt,name=index,proto3" json:"index,omitempty"` + Index *int32 `protobuf:"zigzag32,2,opt,name=index,proto3,oneof" json:"index,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -583,15 +583,15 @@ func (*BatchAddResponse) Descriptor() ([]byte, []int) { } func (x *BatchAddResponse) GetResult() float64 { - if x != nil { - return x.Result + if x != nil && x.Result != nil { + return *x.Result } return 0 } func (x *BatchAddResponse) GetIndex() int32 { - if x != nil { - return x.Index + if x != nil && x.Index != nil { + return *x.Index } return 0 } @@ -601,41 +601,67 @@ var File_goagen_calculator_calculator_proto protoreflect.FileDescriptor const file_goagen_calculator_calculator_proto_rawDesc = "" + "\n" + "\"goagen_calculator_calculator.proto\x12\n" + - "calculator\"(\n" + + "calculator\">\n" + + "\n" + + "AddRequest\x12\x11\n" + + "\x01a\x18\x01 \x01(\x01H\x00R\x01a\x88\x01\x01\x12\x11\n" + + "\x01b\x18\x02 \x01(\x01H\x01R\x01b\x88\x01\x01B\x04\n" + + "\x02_aB\x04\n" + + "\x02_b\"f\n" + + "\vAddResponse\x12\x1b\n" + + "\x06result\x18\x01 \x01(\x01H\x00R\x06result\x88\x01\x01\x12!\n" + + "\toperation\x18\x02 \x01(\tH\x01R\toperation\x88\x01\x01B\t\n" + + "\a_resultB\f\n" + + "\n" + + "_operation\"h\n" + + "\rDivideRequest\x12\x1f\n" + + "\bdividend\x18\x01 \x01(\x01H\x00R\bdividend\x88\x01\x01\x12\x1d\n" + + "\adivisor\x18\x02 \x01(\x01H\x01R\adivisor\x88\x01\x01B\v\n" + + "\t_dividendB\n" + + "\n" + + "\b_divisor\"i\n" + + "\x0eDivideResponse\x12\x1b\n" + + "\x06result\x18\x01 \x01(\x01H\x00R\x06result\x88\x01\x01\x12!\n" + + "\toperation\x18\x02 \x01(\tH\x01R\toperation\x88\x01\x01B\t\n" + + "\a_resultB\f\n" + + "\n" + + "_operation\"+\n" + + "\x10FactorialRequest\x12\x11\n" + + "\x01n\x18\x01 \x01(\x11H\x00R\x01n\x88\x01\x01B\x04\n" + + "\x02_n\"\xb9\x01\n" + + "\x11FactorialResponse\x12\x1b\n" + + "\x06result\x18\x01 \x01(\x12H\x00R\x06result\x88\x01\x01\x12!\n" + + "\toperation\x18\x02 \x01(\tH\x01R\toperation\x88\x01\x01\x123\n" + + "\x13computation_time_ms\x18\x03 \x01(\x11H\x02R\x11computationTimeMs\x88\x01\x01B\t\n" + + "\a_resultB\f\n" + "\n" + - "AddRequest\x12\f\n" + - "\x01a\x18\x01 \x01(\x01R\x01a\x12\f\n" + - "\x01b\x18\x02 \x01(\x01R\x01b\"C\n" + - "\vAddResponse\x12\x16\n" + - "\x06result\x18\x01 \x01(\x01R\x06result\x12\x1c\n" + - "\toperation\x18\x02 \x01(\tR\toperation\"E\n" + - "\rDivideRequest\x12\x1a\n" + - "\bdividend\x18\x01 \x01(\x01R\bdividend\x12\x18\n" + - "\adivisor\x18\x02 \x01(\x01R\adivisor\"F\n" + - "\x0eDivideResponse\x12\x16\n" + - "\x06result\x18\x01 \x01(\x01R\x06result\x12\x1c\n" + - "\toperation\x18\x02 \x01(\tR\toperation\" \n" + - "\x10FactorialRequest\x12\f\n" + - "\x01n\x18\x01 \x01(\x11R\x01n\"y\n" + - "\x11FactorialResponse\x12\x16\n" + - "\x06result\x18\x01 \x01(\x12R\x06result\x12\x1c\n" + - "\toperation\x18\x02 \x01(\tR\toperation\x12.\n" + - "\x13computation_time_ms\x18\x03 \x01(\x11R\x11computationTimeMs\"-\n" + + "_operationB\x16\n" + + "\x14_computation_time_ms\"-\n" + "\x11StatisticsRequest\x12\x18\n" + - "\anumbers\x18\x01 \x03(\x01R\anumbers\"\x8c\x01\n" + - "\x12StatisticsResponse\x12\x12\n" + - "\x04mean\x18\x01 \x01(\x01R\x04mean\x12\x16\n" + - "\x06median\x18\x02 \x01(\x01R\x06median\x12\x10\n" + - "\x03min\x18\x03 \x01(\x01R\x03min\x12\x10\n" + - "\x03max\x18\x04 \x01(\x01R\x03max\x12\x14\n" + - "\x05count\x18\x05 \x01(\x11R\x05count\x12\x10\n" + - "\x03sum\x18\x06 \x01(\x01R\x03sum\"6\n" + - "\x18BatchAddStreamingRequest\x12\f\n" + - "\x01a\x18\x01 \x01(\x01R\x01a\x12\f\n" + - "\x01b\x18\x02 \x01(\x01R\x01b\"@\n" + - "\x10BatchAddResponse\x12\x16\n" + - "\x06result\x18\x01 \x01(\x01R\x06result\x12\x14\n" + - "\x05index\x18\x02 \x01(\x11R\x05index2\xf0\x02\n" + + "\anumbers\x18\x01 \x03(\x01R\anumbers\"\xe0\x01\n" + + "\x12StatisticsResponse\x12\x17\n" + + "\x04mean\x18\x01 \x01(\x01H\x00R\x04mean\x88\x01\x01\x12\x1b\n" + + "\x06median\x18\x02 \x01(\x01H\x01R\x06median\x88\x01\x01\x12\x15\n" + + "\x03min\x18\x03 \x01(\x01H\x02R\x03min\x88\x01\x01\x12\x15\n" + + "\x03max\x18\x04 \x01(\x01H\x03R\x03max\x88\x01\x01\x12\x19\n" + + "\x05count\x18\x05 \x01(\x11H\x04R\x05count\x88\x01\x01\x12\x15\n" + + "\x03sum\x18\x06 \x01(\x01H\x05R\x03sum\x88\x01\x01B\a\n" + + "\x05_meanB\t\n" + + "\a_medianB\x06\n" + + "\x04_minB\x06\n" + + "\x04_maxB\b\n" + + "\x06_countB\x06\n" + + "\x04_sum\"L\n" + + "\x18BatchAddStreamingRequest\x12\x11\n" + + "\x01a\x18\x01 \x01(\x01H\x00R\x01a\x88\x01\x01\x12\x11\n" + + "\x01b\x18\x02 \x01(\x01H\x01R\x01b\x88\x01\x01B\x04\n" + + "\x02_aB\x04\n" + + "\x02_b\"_\n" + + "\x10BatchAddResponse\x12\x1b\n" + + "\x06result\x18\x01 \x01(\x01H\x00R\x06result\x88\x01\x01\x12\x19\n" + + "\x05index\x18\x02 \x01(\x11H\x01R\x05index\x88\x01\x01B\t\n" + + "\a_resultB\b\n" + + "\x06_index2\xf0\x02\n" + "\n" + "Calculator\x126\n" + "\x03Add\x12\x16.calculator.AddRequest\x1a\x17.calculator.AddResponse\x12?\n" + @@ -693,6 +719,15 @@ func file_goagen_calculator_calculator_proto_init() { if File_goagen_calculator_calculator_proto != nil { return } + file_goagen_calculator_calculator_proto_msgTypes[0].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[1].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[2].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[3].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[4].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[5].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[7].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[8].OneofWrappers = []any{} + file_goagen_calculator_calculator_proto_msgTypes[9].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.proto b/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.proto index 258c6e4f5..34e17d754 100644 --- a/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.proto +++ b/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator.proto @@ -28,44 +28,44 @@ service Calculator { message AddRequest { // First number - double a = 1; + optional double a = 1; // Second number - double b = 2; + optional double b = 2; } message AddResponse { // Sum of a and b - double result = 1; + optional double result = 1; // Operation performed - string operation = 2; + optional string operation = 2; } message DivideRequest { // Number to divide - double dividend = 1; + optional double dividend = 1; // Number to divide by - double divisor = 2; + optional double divisor = 2; } message DivideResponse { // Division result - double result = 1; + optional double result = 1; // Operation performed - string operation = 2; + optional string operation = 2; } message FactorialRequest { // Number to calculate factorial of - sint32 n = 1; + optional sint32 n = 1; } message FactorialResponse { // Factorial result - sint64 result = 1; + optional sint64 result = 1; // Operation performed - string operation = 2; + optional string operation = 2; // Time taken in milliseconds - sint32 computation_time_ms = 3; + optional sint32 computation_time_ms = 3; } message StatisticsRequest { @@ -75,29 +75,29 @@ message StatisticsRequest { message StatisticsResponse { // Average of numbers - double mean = 1; + optional double mean = 1; // Median value - double median = 2; + optional double median = 2; // Minimum value - double min = 3; + optional double min = 3; // Maximum value - double max = 4; + optional double max = 4; // Number of values - sint32 count = 5; + optional sint32 count = 5; // Sum of all values - double sum = 6; + optional double sum = 6; } message BatchAddStreamingRequest { // First number - double a = 1; + optional double a = 1; // Second number - double b = 2; + optional double b = 2; } message BatchAddResponse { // Sum - double result = 1; + optional double result = 1; // Operation index - sint32 index = 2; + optional sint32 index = 2; } diff --git a/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator_grpc.pb.go b/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator_grpc.pb.go index 11d9198da..2d45aeaf7 100644 --- a/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator_grpc.pb.go +++ b/testing/examples/calculator/gen/grpc/calculator/pb/goagen_calculator_calculator_grpc.pb.go @@ -9,7 +9,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.2 -// - protoc v7.34.1 +// - protoc v7.36.0 // source: goagen_calculator_calculator.proto package calculatorpb diff --git a/testing/examples/calculator/gen/grpc/calculator/server/encode_decode.go b/testing/examples/calculator/gen/grpc/calculator/server/encode_decode.go index 6dd55c473..344af6591 100644 --- a/testing/examples/calculator/gen/grpc/calculator/server/encode_decode.go +++ b/testing/examples/calculator/gen/grpc/calculator/server/encode_decode.go @@ -39,6 +39,9 @@ func DecodeAddRequest(ctx context.Context, v any, md metadata.MD) (any, error) { if message, ok = v.(*calculatorpb.AddRequest); !ok { return nil, goagrpc.ErrInvalidType("calculator", "add", "*calculatorpb.AddRequest", v) } + if err := ValidateAddRequest(message); err != nil { + return nil, err + } } var payload *calculator.AddPayload { @@ -69,6 +72,9 @@ func DecodeDivideRequest(ctx context.Context, v any, md metadata.MD) (any, error if message, ok = v.(*calculatorpb.DivideRequest); !ok { return nil, goagrpc.ErrInvalidType("calculator", "divide", "*calculatorpb.DivideRequest", v) } + if err := ValidateDivideRequest(message); err != nil { + return nil, err + } } var payload *calculator.DividePayload { diff --git a/testing/examples/calculator/gen/grpc/calculator/server/server.go b/testing/examples/calculator/gen/grpc/calculator/server/server.go index 06fa230b2..dad76a91f 100644 --- a/testing/examples/calculator/gen/grpc/calculator/server/server.go +++ b/testing/examples/calculator/gen/grpc/calculator/server/server.go @@ -159,7 +159,7 @@ func (s *Server) BatchAdd(stream calculatorpb.Calculator_BatchAddServer) error { // Send streams instances of "calculatorpb.BatchAddResponse" to the "batch_add" // endpoint gRPC stream. func (s *BatchAddServerStream) Send(res *calculator.BatchAddResult) error { - v := NewProtoBatchAddResultBatchAddResponse(res) + v := NewProtoBatchAddResponse(res) return s.stream.Send(v) } @@ -177,6 +177,9 @@ func (s *BatchAddServerStream) Recv() (*calculator.BatchAddStreamingPayload, err if err != nil { return res, err } + if err = ValidateBatchAddStreamingRequest(v); err != nil { + return res, err + } return NewBatchAddStreamingRequestBatchAddStreamingPayload(v), nil } diff --git a/testing/examples/calculator/gen/grpc/calculator/server/types.go b/testing/examples/calculator/gen/grpc/calculator/server/types.go index e8021caca..dee4b3b24 100644 --- a/testing/examples/calculator/gen/grpc/calculator/server/types.go +++ b/testing/examples/calculator/gen/grpc/calculator/server/types.go @@ -14,68 +14,68 @@ import ( calculatorpb "goa.design/plugins/v3/testing/examples/calculator/gen/grpc/calculator/pb" ) -// NewAddPayload builds the payload of the "add" endpoint of the "calculator" -// service from the gRPC request type. +// NewAddPayload builds *calculator.AddPayload from *calculatorpb.AddRequest. func NewAddPayload(message *calculatorpb.AddRequest) *calculator.AddPayload { v := &calculator.AddPayload{ - A: message.A, - B: message.B, + A: *message.A, + B: *message.B, } return v } -// NewProtoAddResponse builds the gRPC response type from the result of the -// "add" endpoint of the "calculator" service. +// NewProtoAddResponse builds *calculatorpb.AddResponse from +// *calculator.AddResult. func NewProtoAddResponse(result *calculator.AddResult) *calculatorpb.AddResponse { message := &calculatorpb.AddResponse{ - Result: result.Result, - Operation: result.Operation, + Result: &result.Result, + Operation: &result.Operation, } return message } -// NewDividePayload builds the payload of the "divide" endpoint of the -// "calculator" service from the gRPC request type. +// NewDividePayload builds *calculator.DividePayload from +// *calculatorpb.DivideRequest. func NewDividePayload(message *calculatorpb.DivideRequest) *calculator.DividePayload { v := &calculator.DividePayload{ - Dividend: message.Dividend, - Divisor: message.Divisor, + Dividend: *message.Dividend, + Divisor: *message.Divisor, } return v } -// NewProtoDivideResponse builds the gRPC response type from the result of the -// "divide" endpoint of the "calculator" service. +// NewProtoDivideResponse builds *calculatorpb.DivideResponse from +// *calculator.DivideResult. func NewProtoDivideResponse(result *calculator.DivideResult) *calculatorpb.DivideResponse { message := &calculatorpb.DivideResponse{ - Result: result.Result, - Operation: result.Operation, + Result: &result.Result, + Operation: &result.Operation, } return message } -// NewFactorialPayload builds the payload of the "factorial" endpoint of the -// "calculator" service from the gRPC request type. +// NewFactorialPayload builds *calculator.FactorialPayload from +// *calculatorpb.FactorialRequest. func NewFactorialPayload(message *calculatorpb.FactorialRequest) *calculator.FactorialPayload { v := &calculator.FactorialPayload{ - N: int(message.N), + N: int(*message.N), } return v } -// NewProtoFactorialResponse builds the gRPC response type from the result of -// the "factorial" endpoint of the "calculator" service. +// NewProtoFactorialResponse builds *calculatorpb.FactorialResponse from +// *calculator.FactorialResult. func NewProtoFactorialResponse(result *calculator.FactorialResult) *calculatorpb.FactorialResponse { message := &calculatorpb.FactorialResponse{ - Result: result.Result, - Operation: result.Operation, - ComputationTimeMs: int32(result.ComputationTimeMs), + Result: &result.Result, + Operation: &result.Operation, } + computationTimeMs := int32(result.ComputationTimeMs) + message.ComputationTimeMs = &computationTimeMs return message } -// NewStatisticsPayload builds the payload of the "statistics" endpoint of the -// "calculator" service from the gRPC request type. +// NewStatisticsPayload builds *calculator.StatisticsPayload from +// *calculatorpb.StatisticsRequest. func NewStatisticsPayload(message *calculatorpb.StatisticsRequest) *calculator.StatisticsPayload { v := &calculator.StatisticsPayload{} if message.Numbers != nil { @@ -87,53 +87,77 @@ func NewStatisticsPayload(message *calculatorpb.StatisticsRequest) *calculator.S return v } -// NewProtoStatisticsResponse builds the gRPC response type from the result of -// the "statistics" endpoint of the "calculator" service. +// NewProtoStatisticsResponse builds *calculatorpb.StatisticsResponse from +// *calculator.StatisticsResult. func NewProtoStatisticsResponse(result *calculator.StatisticsResult) *calculatorpb.StatisticsResponse { message := &calculatorpb.StatisticsResponse{ - Mean: result.Mean, - Median: result.Median, - Min: result.Min, - Max: result.Max, - Count: int32(result.Count), - Sum: result.Sum, + Mean: &result.Mean, + Median: &result.Median, + Min: &result.Min, + Max: &result.Max, + Sum: &result.Sum, } + count := int32(result.Count) + message.Count = &count return message } -// NewProtoBatchAddResponse builds the gRPC response type from the result of -// the "batch_add" endpoint of the "calculator" service. +// NewProtoBatchAddResponse builds *calculatorpb.BatchAddResponse from +// *calculator.BatchAddResult. func NewProtoBatchAddResponse(result *calculator.BatchAddResult) *calculatorpb.BatchAddResponse { message := &calculatorpb.BatchAddResponse{ - Result: result.Result, - Index: int32(result.Index), + Result: &result.Result, } + index := int32(result.Index) + message.Index = &index return message } -func NewProtoBatchAddResultBatchAddResponse(result *calculator.BatchAddResult) *calculatorpb.BatchAddResponse { - v := &calculatorpb.BatchAddResponse{ - Result: result.Result, - Index: int32(result.Index), - } - return v -} - +// NewBatchAddStreamingRequestBatchAddStreamingPayload builds +// *calculator.BatchAddStreamingPayload from +// *calculatorpb.BatchAddStreamingRequest. func NewBatchAddStreamingRequestBatchAddStreamingPayload(v *calculatorpb.BatchAddStreamingRequest) *calculator.BatchAddStreamingPayload { spayload := &calculator.BatchAddStreamingPayload{ - A: v.A, - B: v.B, + A: *v.A, + B: *v.B, } return spayload } +// ValidateAddRequest runs the validations defined on AddRequest. +func ValidateAddRequest(message *calculatorpb.AddRequest) (err error) { + if message.A == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("a", "message")) + } + if message.B == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("b", "message")) + } + return +} + +// ValidateDivideRequest runs the validations defined on DivideRequest. +func ValidateDivideRequest(message *calculatorpb.DivideRequest) (err error) { + if message.Dividend == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("dividend", "message")) + } + if message.Divisor == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("divisor", "message")) + } + return +} + // ValidateFactorialRequest runs the validations defined on FactorialRequest. func ValidateFactorialRequest(message *calculatorpb.FactorialRequest) (err error) { - if message.N < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("message.n", message.N, 0, true)) + if message.N == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("n", "message")) } - if message.N > 20 { - err = goa.MergeErrors(err, goa.InvalidRangeError("message.n", message.N, 20, false)) + if message.N != nil { + if *message.N < 0 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.n", *message.N, 0, true)) + } + if *message.N > 20 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.n", *message.N, 20, false)) + } } return } @@ -148,3 +172,15 @@ func ValidateStatisticsRequest(message *calculatorpb.StatisticsRequest) (err err } return } + +// ValidateBatchAddStreamingRequest runs the validations defined on +// BatchAddStreamingRequest. +func ValidateBatchAddStreamingRequest(stream *calculatorpb.BatchAddStreamingRequest) (err error) { + if stream.A == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("a", "stream")) + } + if stream.B == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("b", "stream")) + } + return +} diff --git a/testing/examples/calculator/gen/grpc/cli/calculator/cli.go b/testing/examples/calculator/gen/grpc/cli/calculator/cli.go index f4d7cccee..5bd39e50b 100644 --- a/testing/examples/calculator/gen/grpc/cli/calculator/cli.go +++ b/testing/examples/calculator/gen/grpc/cli/calculator/cli.go @@ -29,7 +29,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "calculator add --message '{\n \"a\": 0.7564502808463504,\n \"b\": 0.22116150789969893\n }'" + "\n" + + return os.Args[0] + " " + "calculator add --message '{\n \"a\": 0.22091916525730942,\n \"b\": 0.26780525886889645\n }'" + "\n" + "" } @@ -191,7 +191,7 @@ func calculatorAddUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator add --message '{\n \"a\": 0.7564502808463504,\n \"b\": 0.22116150789969893\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator add --message '{\n \"a\": 0.22091916525730942,\n \"b\": 0.26780525886889645\n }'") } func calculatorDivideUsage() { @@ -209,7 +209,7 @@ func calculatorDivideUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator divide --message '{\n \"dividend\": 0.9492693158321226,\n \"divisor\": 0.7671133810336571\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator divide --message '{\n \"dividend\": 0.7331112436485956,\n \"divisor\": 0.7550692456980171\n }'") } func calculatorFactorialUsage() { @@ -227,7 +227,7 @@ func calculatorFactorialUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator factorial --message '{\n \"n\": 11\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator factorial --message '{\n \"n\": 0\n }'") } func calculatorStatisticsUsage() { @@ -245,7 +245,7 @@ func calculatorStatisticsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator statistics --message '{\n \"numbers\": [\n 0.9839407167429688,\n 0.3969464125201744,\n 0.8182138429566963\n ]\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator statistics --message '{\n \"numbers\": [\n 0.3811399958331969,\n 0.21022254932918344,\n 0.7555995055194686\n ]\n }'") } func calculatorBatchAddUsage() { diff --git a/testing/examples/calculator/gen/http/calculator/client/cli.go b/testing/examples/calculator/gen/http/calculator/client/cli.go index 962def5d9..8f4dd6cf3 100644 --- a/testing/examples/calculator/gen/http/calculator/client/cli.go +++ b/testing/examples/calculator/gen/http/calculator/client/cli.go @@ -24,7 +24,7 @@ func BuildAddPayload(calculatorAddBody string) (*calculator.AddPayload, error) { { err = json.Unmarshal([]byte(calculatorAddBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"a\": 0.7936796293863132,\n \"b\": 0.05212966980398518\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"a\": 0.490314453132986,\n \"b\": 0.6743292888632025\n }'") } } v := &calculator.AddPayload{ @@ -43,7 +43,7 @@ func BuildDividePayload(calculatorDivideBody string) (*calculator.DividePayload, { err = json.Unmarshal([]byte(calculatorDivideBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 0.5184906879153129,\n \"divisor\": 0.5616730288110641\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 0.7333985315892829,\n \"divisor\": 0.2998442358280339\n }'") } } v := &calculator.DividePayload{ @@ -62,7 +62,7 @@ func BuildFactorialPayload(calculatorFactorialBody string) (*calculator.Factoria { err = json.Unmarshal([]byte(calculatorFactorialBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"n\": 10\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"n\": 12\n }'") } if body.N < 0 { err = goa.MergeErrors(err, goa.InvalidRangeError("body.n", body.N, 0, true)) @@ -89,7 +89,7 @@ func BuildStatisticsPayload(calculatorStatisticsBody string) (*calculator.Statis { err = json.Unmarshal([]byte(calculatorStatisticsBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"numbers\": [\n 0.5842830148544397,\n 0.6757557090818312,\n 0.3604039578340141\n ]\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"numbers\": [\n 0.1819497783049658,\n 0.6452245232691431,\n 0.01204206152248697\n ]\n }'") } if body.Numbers == nil { err = goa.MergeErrors(err, goa.MissingFieldError("numbers", "body")) diff --git a/testing/examples/calculator/gen/http/calculator/client/encode_decode.go b/testing/examples/calculator/gen/http/calculator/client/encode_decode.go index b61ecf70e..9f74e2388 100644 --- a/testing/examples/calculator/gen/http/calculator/client/encode_decode.go +++ b/testing/examples/calculator/gen/http/calculator/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -54,18 +55,24 @@ func EncodeAddRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Re // add endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calculator", "add", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calculator", "add", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -84,7 +91,10 @@ func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody res := NewAddResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calculator", "add", err) + } return nil, goahttp.ErrInvalidResponse("calculator", "add", resp.StatusCode, string(body)) } } @@ -128,18 +138,24 @@ func EncodeDivideRequest(encoder func(*http.Request) goahttp.Encoder) func(*http // - "division_by_zero" (type *calculator.CalculatorError): http.StatusBadRequest // - error: internal error func DecodeDivideResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calculator", "divide", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calculator", "divide", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -172,7 +188,10 @@ func DecodeDivideResponse(decoder func(*http.Response) goahttp.Decoder, restoreB } return nil, NewDivideDivisionByZero(&body) default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calculator", "divide", err) + } return nil, goahttp.ErrInvalidResponse("calculator", "divide", resp.StatusCode, string(body)) } } @@ -216,18 +235,24 @@ func EncodeFactorialRequest(encoder func(*http.Request) goahttp.Encoder) func(*h // - "invalid_input" (type *calculator.CalculatorError): http.StatusBadRequest // - error: internal error func DecodeFactorialResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calculator", "factorial", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calculator", "factorial", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -260,7 +285,10 @@ func DecodeFactorialResponse(decoder func(*http.Response) goahttp.Decoder, resto } return nil, NewFactorialInvalidInput(&body) default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calculator", "factorial", err) + } return nil, goahttp.ErrInvalidResponse("calculator", "factorial", resp.StatusCode, string(body)) } } @@ -304,18 +332,24 @@ func EncodeStatisticsRequest(encoder func(*http.Request) goahttp.Encoder) func(* // - "empty_list" (type *calculator.CalculatorError): http.StatusBadRequest // - error: internal error func DecodeStatisticsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calculator", "statistics", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calculator", "statistics", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -348,7 +382,10 @@ func DecodeStatisticsResponse(decoder func(*http.Response) goahttp.Decoder, rest } return nil, NewStatisticsEmptyList(&body) default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calculator", "statistics", err) + } return nil, goahttp.ErrInvalidResponse("calculator", "statistics", resp.StatusCode, string(body)) } } @@ -380,18 +417,24 @@ func (c *Client) BuildBatchAddRequest(ctx context.Context, v any) (*http.Request // calculator batch_add endpoint. restoreBody controls whether the response // body should be restored after having been read. func DecodeBatchAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calculator", "batch_add", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calculator", "batch_add", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -410,7 +453,10 @@ func DecodeBatchAddResponse(decoder func(*http.Response) goahttp.Decoder, restor res := NewBatchAddResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calculator", "batch_add", err) + } return nil, goahttp.ErrInvalidResponse("calculator", "batch_add", resp.StatusCode, string(body)) } } diff --git a/testing/examples/calculator/gen/http/calculator/client/types.go b/testing/examples/calculator/gen/http/calculator/client/types.go index 710e7ad6c..7911c84cd 100644 --- a/testing/examples/calculator/gen/http/calculator/client/types.go +++ b/testing/examples/calculator/gen/http/calculator/client/types.go @@ -345,7 +345,7 @@ func ValidateStatisticsResponseBody(body *StatisticsResponseBody) (err error) { } // ValidateBatchAddResponseBody runs the validations defined on -// batch_add_response_body +// BatchAddResponseBody func ValidateBatchAddResponseBody(body *BatchAddResponseBody) (err error) { if body.Result == nil { err = goa.MergeErrors(err, goa.MissingFieldError("result", "body")) @@ -357,7 +357,7 @@ func ValidateBatchAddResponseBody(body *BatchAddResponseBody) (err error) { } // ValidateDivideDivisionByZeroResponseBody runs the validations defined on -// divide_division_by_zero_response_body +// DivideDivisionByZeroResponseBody func ValidateDivideDivisionByZeroResponseBody(body *DivideDivisionByZeroResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -369,7 +369,7 @@ func ValidateDivideDivisionByZeroResponseBody(body *DivideDivisionByZeroResponse } // ValidateFactorialInvalidInputResponseBody runs the validations defined on -// factorial_invalid_input_response_body +// FactorialInvalidInputResponseBody func ValidateFactorialInvalidInputResponseBody(body *FactorialInvalidInputResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -381,7 +381,7 @@ func ValidateFactorialInvalidInputResponseBody(body *FactorialInvalidInputRespon } // ValidateStatisticsEmptyListResponseBody runs the validations defined on -// statistics_empty_list_response_body +// StatisticsEmptyListResponseBody func ValidateStatisticsEmptyListResponseBody(body *StatisticsEmptyListResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) diff --git a/testing/examples/calculator/gen/http/calculator/server/types.go b/testing/examples/calculator/gen/http/calculator/server/types.go index a3fc806ab..930247fe4 100644 --- a/testing/examples/calculator/gen/http/calculator/server/types.go +++ b/testing/examples/calculator/gen/http/calculator/server/types.go @@ -304,8 +304,6 @@ func ValidateFactorialRequestBody(body *FactorialRequestBody) (err error) { if *body.N < 0 { err = goa.MergeErrors(err, goa.InvalidRangeError("body.n", *body.N, 0, true)) } - } - if body.N != nil { if *body.N > 20 { err = goa.MergeErrors(err, goa.InvalidRangeError("body.n", *body.N, 20, false)) } @@ -326,7 +324,7 @@ func ValidateStatisticsRequestBody(body *StatisticsRequestBody) (err error) { } // ValidateBatchAddStreamingBody runs the validations defined on -// batch_add_streaming_body +// BatchAddStreamingBody func ValidateBatchAddStreamingBody(body *BatchAddStreamingBody) (err error) { if body.A == nil { err = goa.MergeErrors(err, goa.MissingFieldError("a", "body")) diff --git a/testing/examples/calculator/gen/http/calculator/server/websocket.go b/testing/examples/calculator/gen/http/calculator/server/websocket.go index 6356fa9bf..98e657569 100644 --- a/testing/examples/calculator/gen/http/calculator/server/websocket.go +++ b/testing/examples/calculator/gen/http/calculator/server/websocket.go @@ -32,6 +32,11 @@ type BatchAddServerStream struct { once sync.Once // upgradeErr is the error returned by the websocket upgrade attempt. upgradeErr error + // closeOnce makes repeated Close calls return the first close result without + // writing again. + closeOnce sync.Once + // closeErr is the result of the first Close call. + closeErr error // upgrader is the websocket connection upgrader. upgrader goahttp.Upgrader // configurer is the websocket connection configurer. @@ -136,9 +141,33 @@ func (s *BatchAddServerStream) RecvWithContext(ctx context.Context) (*calculator // Close closes the "batch_add" endpoint websocket connection. func (s *BatchAddServerStream) Close() error { + s.closeOnce.Do(func() { + s.closeErr = s.close() + }) + return s.closeErr +} + +// close opens the websocket connection when needed, sends its normal close +// message, and closes it. +func (s *BatchAddServerStream) close() error { var err error - if s.conn == nil { - return nil + // Upgrade the HTTP connection to a websocket connection only once. Connection + // upgrade is done here so that authorization logic in the endpoint is executed + // before calling the actual service method which may call Close(). + s.once.Do(func() { + var conn *websocket.Conn + conn, err = s.upgrader.Upgrade(s.w, s.r, nil) + if err != nil { + s.upgradeErr = err + return + } + if s.configurer != nil { + conn = s.configurer(conn, s.cancel) + } + s.conn = conn + }) + if s.upgradeErr != nil { + return s.upgradeErr } if err = s.conn.WriteControl( websocket.CloseMessage, diff --git a/testing/examples/calculator/gen/http/cli/calculator/cli.go b/testing/examples/calculator/gen/http/cli/calculator/cli.go index b534ea93b..9d5918dfd 100644 --- a/testing/examples/calculator/gen/http/cli/calculator/cli.go +++ b/testing/examples/calculator/gen/http/cli/calculator/cli.go @@ -30,7 +30,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "calculator add --body '{\n \"a\": 0.7936796293863132,\n \"b\": 0.05212966980398518\n }'" + "\n" + + return os.Args[0] + " " + "calculator add --body '{\n \"a\": 0.490314453132986,\n \"b\": 0.6743292888632025\n }'" + "\n" + "" } @@ -197,7 +197,7 @@ func calculatorAddUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator add --body '{\n \"a\": 0.7936796293863132,\n \"b\": 0.05212966980398518\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator add --body '{\n \"a\": 0.490314453132986,\n \"b\": 0.6743292888632025\n }'") } func calculatorDivideUsage() { @@ -215,7 +215,7 @@ func calculatorDivideUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator divide --body '{\n \"dividend\": 0.5184906879153129,\n \"divisor\": 0.5616730288110641\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator divide --body '{\n \"dividend\": 0.7333985315892829,\n \"divisor\": 0.2998442358280339\n }'") } func calculatorFactorialUsage() { @@ -233,7 +233,7 @@ func calculatorFactorialUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator factorial --body '{\n \"n\": 10\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator factorial --body '{\n \"n\": 12\n }'") } func calculatorStatisticsUsage() { @@ -251,7 +251,7 @@ func calculatorStatisticsUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator statistics --body '{\n \"numbers\": [\n 0.5842830148544397,\n 0.6757557090818312,\n 0.3604039578340141\n ]\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calculator statistics --body '{\n \"numbers\": [\n 0.1819497783049658,\n 0.6452245232691431,\n 0.01204206152248697\n ]\n }'") } func calculatorBatchAddUsage() { diff --git a/testing/examples/calculator/gen/http/openapi.json b/testing/examples/calculator/gen/http/openapi.json index ce7be722a..33e72ba48 100644 --- a/testing/examples/calculator/gen/http/openapi.json +++ b/testing/examples/calculator/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/add":{"post":{"tags":["calculator"],"summary":"add calculator","description":"Add two numbers","operationId":"calculator#add","parameters":[{"name":"AddRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorAddRequestBody","required":["a","b"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorAddResponseBody","required":["result","operation"]}}},"schemes":["http"]}},"/batch/add":{"get":{"tags":["calculator"],"summary":"batch_add calculator","description":"Add multiple pairs of numbers using streaming","operationId":"calculator#batch_add","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/CalculatorBatchAddResponseBody","required":["result","index"]}}},"schemes":["ws"]}},"/divide":{"post":{"tags":["calculator"],"summary":"divide calculator","description":"Divide two numbers","operationId":"calculator#divide","parameters":[{"name":"DivideRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorDivideRequestBody","required":["dividend","divisor"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorDivideResponseBody","required":["result","operation"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/CalculatorError","required":["message","code"]}}},"schemes":["http"]}},"/factorial":{"post":{"tags":["calculator"],"summary":"factorial calculator","description":"Calculate factorial of a number","operationId":"calculator#factorial","parameters":[{"name":"FactorialRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorFactorialRequestBody","required":["n"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorFactorialResponseBody","required":["result","operation","computation_time_ms"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/CalculatorError","required":["message","code"]}}},"schemes":["http"]}},"/statistics":{"post":{"tags":["calculator"],"summary":"statistics calculator","description":"Calculate statistics for a list of numbers","operationId":"calculator#statistics","parameters":[{"name":"StatisticsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorStatisticsRequestBody","required":["numbers"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorStatisticsResponseBody","required":["mean","median","min","max","count","sum"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/CalculatorError","required":["message","code"]}}},"schemes":["http"]}}},"definitions":{"CalculatorAddRequestBody":{"title":"CalculatorAddRequestBody","type":"object","properties":{"a":{"type":"number","description":"First number","example":0.7936796293863132,"format":"double"},"b":{"type":"number","description":"Second number","example":0.05212966980398518,"format":"double"}},"example":{"a":0.7936796293863132,"b":0.05212966980398518},"required":["a","b"]},"CalculatorAddResponseBody":{"title":"CalculatorAddResponseBody","type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"In deleniti sint."},"result":{"type":"number","description":"Sum of a and b","example":0.04198102650322011,"format":"double"}},"example":{"operation":"In deleniti sint.","result":0.04198102650322011},"required":["result","operation"]},"CalculatorBatchAddResponseBody":{"title":"CalculatorBatchAddResponseBody","type":"object","properties":{"index":{"type":"integer","description":"Operation index","example":312737943510911382,"format":"int64"},"result":{"type":"number","description":"Sum","example":0.8965135098234011,"format":"double"}},"example":{"index":312737943510911382,"result":0.8965135098234011},"required":["result","index"]},"CalculatorDivideRequestBody":{"title":"CalculatorDivideRequestBody","type":"object","properties":{"dividend":{"type":"number","description":"Number to divide","example":0.5184906879153129,"format":"double"},"divisor":{"type":"number","description":"Number to divide by","example":0.5616730288110641,"format":"double"}},"example":{"dividend":0.5184906879153129,"divisor":0.5616730288110641},"required":["dividend","divisor"]},"CalculatorDivideResponseBody":{"title":"CalculatorDivideResponseBody","type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"Nesciunt consequatur consectetur cupiditate sit."},"result":{"type":"number","description":"Division result","example":0.08459169404214541,"format":"double"}},"example":{"operation":"Nesciunt consequatur consectetur cupiditate sit.","result":0.08459169404214541},"required":["result","operation"]},"CalculatorError":{"title":"CalculatorError","type":"object","properties":{"code":{"type":"string","description":"Error code","example":"Dignissimos ut voluptate fuga minus vero omnis."},"message":{"type":"string","description":"Error message","example":"Repellendus quidem eligendi voluptas quae eius."}},"description":"Cannot divide by zero","example":{"code":"Dignissimos ut voluptate fuga minus vero omnis.","message":"Repellendus quidem eligendi voluptas quae eius."},"required":["message","code"]},"CalculatorFactorialRequestBody":{"title":"CalculatorFactorialRequestBody","type":"object","properties":{"n":{"type":"integer","description":"Number to calculate factorial of","example":10,"format":"int64","minimum":0,"maximum":20}},"example":{"n":10},"required":["n"]},"CalculatorFactorialResponseBody":{"title":"CalculatorFactorialResponseBody","type":"object","properties":{"computation_time_ms":{"type":"integer","description":"Time taken in milliseconds","example":7430406659417736888,"format":"int64"},"operation":{"type":"string","description":"Operation performed","example":"In quo."},"result":{"type":"integer","description":"Factorial result","example":2802676607681044054,"format":"int64"}},"example":{"computation_time_ms":7430406659417736888,"operation":"In quo.","result":2802676607681044054},"required":["result","operation","computation_time_ms"]},"CalculatorStatisticsRequestBody":{"title":"CalculatorStatisticsRequestBody","type":"object","properties":{"numbers":{"type":"array","items":{"type":"number","example":0.858415483142481,"format":"double"},"description":"List of numbers","example":[0.5842830148544397,0.6757557090818312,0.3604039578340141],"minItems":1}},"example":{"numbers":[0.5842830148544397,0.6757557090818312,0.3604039578340141]},"required":["numbers"]},"CalculatorStatisticsResponseBody":{"title":"CalculatorStatisticsResponseBody","type":"object","properties":{"count":{"type":"integer","description":"Number of values","example":7525631923450415662,"format":"int64"},"max":{"type":"number","description":"Maximum value","example":0.9702023620235123,"format":"double"},"mean":{"type":"number","description":"Average of numbers","example":0.3176589236328283,"format":"double"},"median":{"type":"number","description":"Median value","example":0.5259699852027079,"format":"double"},"min":{"type":"number","description":"Minimum value","example":0.4948460127995459,"format":"double"},"sum":{"type":"number","description":"Sum of all values","example":0.7469671830093337,"format":"double"}},"example":{"count":7525631923450415662,"max":0.9702023620235123,"mean":0.3176589236328283,"median":0.5259699852027079,"min":0.4948460127995459,"sum":0.7469671830093337},"required":["mean","median","min","max","count","sum"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/add":{"post":{"tags":["calculator"],"summary":"add calculator","description":"Add two numbers","operationId":"calculator#add","parameters":[{"name":"AddRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorAddRequestBody","required":["a","b"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorAddResponseBody","required":["result","operation"]}}},"schemes":["http"]}},"/batch/add":{"get":{"tags":["calculator"],"summary":"batch_add calculator","description":"Add multiple pairs of numbers using streaming","operationId":"calculator#batch_add","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/CalculatorBatchAddResponseBody","required":["result","index"]}}},"schemes":["ws"]}},"/divide":{"post":{"tags":["calculator"],"summary":"divide calculator","description":"Divide two numbers","operationId":"calculator#divide","parameters":[{"name":"DivideRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorDivideRequestBody","required":["dividend","divisor"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorDivideResponseBody","required":["result","operation"]}},"400":{"description":"division_by_zero: Cannot divide by zero","schema":{"$ref":"#/definitions/CalculatorError","required":["message","code"]}}},"schemes":["http"]}},"/factorial":{"post":{"tags":["calculator"],"summary":"factorial calculator","description":"Calculate factorial of a number","operationId":"calculator#factorial","parameters":[{"name":"FactorialRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorFactorialRequestBody","required":["n"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorFactorialResponseBody","required":["result","operation","computation_time_ms"]}},"400":{"description":"invalid_input: Input must be between 0 and 20","schema":{"$ref":"#/definitions/CalculatorError","required":["message","code"]}}},"schemes":["http"]}},"/statistics":{"post":{"tags":["calculator"],"summary":"statistics calculator","description":"Calculate statistics for a list of numbers","operationId":"calculator#statistics","parameters":[{"name":"StatisticsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/CalculatorStatisticsRequestBody","required":["numbers"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/CalculatorStatisticsResponseBody","required":["mean","median","min","max","count","sum"]}},"400":{"description":"empty_list: List cannot be empty","schema":{"$ref":"#/definitions/CalculatorError","required":["message","code"]}}},"schemes":["http"]}}},"definitions":{"CalculatorAddRequestBody":{"title":"CalculatorAddRequestBody","type":"object","properties":{"a":{"type":"number","description":"First number","example":0.490314453132986,"format":"double"},"b":{"type":"number","description":"Second number","example":0.6743292888632025,"format":"double"}},"example":{"a":0.490314453132986,"b":0.6743292888632025},"required":["a","b"]},"CalculatorAddResponseBody":{"title":"CalculatorAddResponseBody","type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"Accusamus autem molestiae."},"result":{"type":"number","description":"Sum of a and b","example":0.6697833948471263,"format":"double"}},"example":{"operation":"Accusamus autem molestiae.","result":0.6697833948471263},"required":["result","operation"]},"CalculatorBatchAddResponseBody":{"title":"CalculatorBatchAddResponseBody","type":"object","properties":{"index":{"type":"integer","description":"Operation index","example":4346106390186751403,"format":"int64"},"result":{"type":"number","description":"Sum","example":0.9017860010086814,"format":"double"}},"example":{"index":4346106390186751403,"result":0.9017860010086814},"required":["result","index"]},"CalculatorDivideRequestBody":{"title":"CalculatorDivideRequestBody","type":"object","properties":{"dividend":{"type":"number","description":"Number to divide","example":0.7333985315892829,"format":"double"},"divisor":{"type":"number","description":"Number to divide by","example":0.2998442358280339,"format":"double"}},"example":{"dividend":0.7333985315892829,"divisor":0.2998442358280339},"required":["dividend","divisor"]},"CalculatorDivideResponseBody":{"title":"CalculatorDivideResponseBody","type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"Occaecati dolorem accusamus quia praesentium ut accusamus."},"result":{"type":"number","description":"Division result","example":0.24146955311479495,"format":"double"}},"example":{"operation":"Occaecati dolorem accusamus quia praesentium ut accusamus.","result":0.24146955311479495},"required":["result","operation"]},"CalculatorError":{"title":"CalculatorError","type":"object","properties":{"code":{"type":"string","description":"Error code","example":"Veniam assumenda similique molestias."},"message":{"type":"string","description":"Error message","example":"Labore qui in dolores et soluta quia."}},"description":"Calculator operation error","example":{"code":"Veniam assumenda similique molestias.","message":"Labore qui in dolores et soluta quia."},"required":["message","code"]},"CalculatorFactorialRequestBody":{"title":"CalculatorFactorialRequestBody","type":"object","properties":{"n":{"type":"integer","description":"Number to calculate factorial of","example":12,"format":"int64","minimum":0,"maximum":20}},"example":{"n":12},"required":["n"]},"CalculatorFactorialResponseBody":{"title":"CalculatorFactorialResponseBody","type":"object","properties":{"computation_time_ms":{"type":"integer","description":"Time taken in milliseconds","example":8677282720135103333,"format":"int64"},"operation":{"type":"string","description":"Operation performed","example":"Maiores voluptas in laudantium."},"result":{"type":"integer","description":"Factorial result","example":4772021109354887343,"format":"int64"}},"example":{"computation_time_ms":8677282720135103333,"operation":"Maiores voluptas in laudantium.","result":4772021109354887343},"required":["result","operation","computation_time_ms"]},"CalculatorStatisticsRequestBody":{"title":"CalculatorStatisticsRequestBody","type":"object","properties":{"numbers":{"type":"array","items":{"type":"number","example":0.1819497783049658,"format":"double"},"description":"List of numbers","example":[0.1819497783049658,0.6452245232691431,0.01204206152248697],"minItems":1}},"example":{"numbers":[0.1819497783049658,0.6452245232691431,0.01204206152248697]},"required":["numbers"]},"CalculatorStatisticsResponseBody":{"title":"CalculatorStatisticsResponseBody","type":"object","properties":{"count":{"type":"integer","description":"Number of values","example":2250719573348189391,"format":"int64"},"max":{"type":"number","description":"Maximum value","example":0.7274177146624164,"format":"double"},"mean":{"type":"number","description":"Average of numbers","example":0.9548358232183869,"format":"double"},"median":{"type":"number","description":"Median value","example":0.5054842136268939,"format":"double"},"min":{"type":"number","description":"Minimum value","example":0.8647384391803151,"format":"double"},"sum":{"type":"number","description":"Sum of all values","example":0.08114586604607756,"format":"double"}},"example":{"count":2250719573348189391,"max":0.7274177146624164,"mean":0.9548358232183869,"median":0.5054842136268939,"min":0.8647384391803151,"sum":0.08114586604607756},"required":["mean","median","min","max","count","sum"]}}} \ No newline at end of file diff --git a/testing/examples/calculator/gen/http/openapi.yaml b/testing/examples/calculator/gen/http/openapi.yaml index 48db6dc16..035527d1d 100644 --- a/testing/examples/calculator/gen/http/openapi.yaml +++ b/testing/examples/calculator/gen/http/openapi.yaml @@ -80,7 +80,7 @@ paths: - result - operation "400": - description: Bad Request response. + description: 'division_by_zero: Cannot divide by zero' schema: $ref: '#/definitions/CalculatorError' required: @@ -113,7 +113,7 @@ paths: - operation - computation_time_ms "400": - description: Bad Request response. + description: 'invalid_input: Input must be between 0 and 20' schema: $ref: '#/definitions/CalculatorError' required: @@ -149,7 +149,7 @@ paths: - count - sum "400": - description: Bad Request response. + description: 'empty_list: List cannot be empty' schema: $ref: '#/definitions/CalculatorError' required: @@ -165,16 +165,16 @@ definitions: a: type: number description: First number - example: 0.7936796293863132 + example: 0.490314453132986 format: double b: type: number description: Second number - example: 0.05212966980398518 + example: 0.6743292888632025 format: double example: - a: 0.7936796293863132 - b: 0.05212966980398518 + a: 0.490314453132986 + b: 0.6743292888632025 required: - a - b @@ -185,15 +185,15 @@ definitions: operation: type: string description: Operation performed - example: In deleniti sint. + example: Accusamus autem molestiae. result: type: number description: Sum of a and b - example: 0.04198102650322011 + example: 0.6697833948471263 format: double example: - operation: In deleniti sint. - result: 0.04198102650322011 + operation: Accusamus autem molestiae. + result: 0.6697833948471263 required: - result - operation @@ -204,16 +204,16 @@ definitions: index: type: integer description: Operation index - example: 312737943510911382 + example: 4346106390186751403 format: int64 result: type: number description: Sum - example: 0.8965135098234011 + example: 0.9017860010086814 format: double example: - index: 312737943510911382 - result: 0.8965135098234011 + index: 4346106390186751403 + result: 0.9017860010086814 required: - result - index @@ -224,16 +224,16 @@ definitions: dividend: type: number description: Number to divide - example: 0.5184906879153129 + example: 0.7333985315892829 format: double divisor: type: number description: Number to divide by - example: 0.5616730288110641 + example: 0.2998442358280339 format: double example: - dividend: 0.5184906879153129 - divisor: 0.5616730288110641 + dividend: 0.7333985315892829 + divisor: 0.2998442358280339 required: - dividend - divisor @@ -244,15 +244,15 @@ definitions: operation: type: string description: Operation performed - example: Nesciunt consequatur consectetur cupiditate sit. + example: Occaecati dolorem accusamus quia praesentium ut accusamus. result: type: number description: Division result - example: 0.08459169404214541 + example: 0.24146955311479495 format: double example: - operation: Nesciunt consequatur consectetur cupiditate sit. - result: 0.08459169404214541 + operation: Occaecati dolorem accusamus quia praesentium ut accusamus. + result: 0.24146955311479495 required: - result - operation @@ -263,15 +263,15 @@ definitions: code: type: string description: Error code - example: Dignissimos ut voluptate fuga minus vero omnis. + example: Veniam assumenda similique molestias. message: type: string description: Error message - example: Repellendus quidem eligendi voluptas quae eius. - description: Cannot divide by zero + example: Labore qui in dolores et soluta quia. + description: Calculator operation error example: - code: Dignissimos ut voluptate fuga minus vero omnis. - message: Repellendus quidem eligendi voluptas quae eius. + code: Veniam assumenda similique molestias. + message: Labore qui in dolores et soluta quia. required: - message - code @@ -282,12 +282,12 @@ definitions: "n": type: integer description: Number to calculate factorial of - example: 10 + example: 12 format: int64 minimum: 0 maximum: 20 example: - "n": 10 + "n": 12 required: - "n" CalculatorFactorialResponseBody: @@ -297,21 +297,21 @@ definitions: computation_time_ms: type: integer description: Time taken in milliseconds - example: 7430406659417736888 + example: 8677282720135103333 format: int64 operation: type: string description: Operation performed - example: In quo. + example: Maiores voluptas in laudantium. result: type: integer description: Factorial result - example: 2802676607681044054 + example: 4772021109354887343 format: int64 example: - computation_time_ms: 7430406659417736888 - operation: In quo. - result: 2802676607681044054 + computation_time_ms: 8677282720135103333 + operation: Maiores voluptas in laudantium. + result: 4772021109354887343 required: - result - operation @@ -324,19 +324,19 @@ definitions: type: array items: type: number - example: 0.858415483142481 + example: 0.1819497783049658 format: double description: List of numbers example: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 minItems: 1 example: numbers: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 required: - numbers CalculatorStatisticsResponseBody: @@ -346,40 +346,40 @@ definitions: count: type: integer description: Number of values - example: 7525631923450415662 + example: 2250719573348189391 format: int64 max: type: number description: Maximum value - example: 0.9702023620235123 + example: 0.7274177146624164 format: double mean: type: number description: Average of numbers - example: 0.3176589236328283 + example: 0.9548358232183869 format: double median: type: number description: Median value - example: 0.5259699852027079 + example: 0.5054842136268939 format: double min: type: number description: Minimum value - example: 0.4948460127995459 + example: 0.8647384391803151 format: double sum: type: number description: Sum of all values - example: 0.7469671830093337 + example: 0.08114586604607756 format: double example: - count: 7525631923450415662 - max: 0.9702023620235123 - mean: 0.3176589236328283 - median: 0.5259699852027079 - min: 0.4948460127995459 - sum: 0.7469671830093337 + count: 2250719573348189391 + max: 0.7274177146624164 + mean: 0.9548358232183869 + median: 0.5054842136268939 + min: 0.8647384391803151 + sum: 0.08114586604607756 required: - mean - median diff --git a/testing/examples/calculator/gen/http/openapi3.2.json b/testing/examples/calculator/gen/http/openapi3.2.json index 0951b141e..aa79ac50f 100644 --- a/testing/examples/calculator/gen/http/openapi3.2.json +++ b/testing/examples/calculator/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"calculator","description":"Default server for calculator"}],"paths":{"/add":{"post":{"tags":["calculator"],"summary":"add calculator","description":"Add two numbers","operationId":"calculator#add","requestBody":{"description":"Request body for add.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRequestBody"},"example":{"a":0.7936796293863132,"b":0.05212966980398518}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"In deleniti sint.","result":0.04198102650322011}}}}}}},"/batch/add":{"get":{"tags":["calculator"],"summary":"batch_add calculator","description":"Add multiple pairs of numbers using streaming","operationId":"calculator#batch_add","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchAddResponseBody"},"example":{"index":312737943510911382,"result":0.8965135098234011}}}}}}},"/divide":{"post":{"tags":["calculator"],"summary":"divide calculator","description":"Divide two numbers","operationId":"calculator#divide","requestBody":{"description":"Request body for divide.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivideRequestBody"},"example":{"dividend":0.5184906879153129,"divisor":0.5616730288110641}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"Nesciunt consequatur consectetur cupiditate sit.","result":0.08459169404214541}}}},"400":{"description":"division_by_zero: Cannot divide by zero","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Dignissimos ut voluptate fuga minus vero omnis.","message":"Repellendus quidem eligendi voluptas quae eius."}}}}}}},"/factorial":{"post":{"tags":["calculator"],"summary":"factorial calculator","description":"Calculate factorial of a number","operationId":"calculator#factorial","requestBody":{"description":"Request body for factorial.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialRequestBody"},"example":{"n":10}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialResponseBody"},"example":{"computation_time_ms":7430406659417736888,"operation":"In quo.","result":2802676607681044054}}}},"400":{"description":"invalid_input: Input must be between 0 and 20","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Laborum laudantium delectus illo explicabo.","message":"Nobis ea."}}}}}}},"/statistics":{"post":{"tags":["calculator"],"summary":"statistics calculator","description":"Calculate statistics for a list of numbers","operationId":"calculator#statistics","requestBody":{"description":"Request body for statistics.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsRequestBody"},"example":{"numbers":[0.5842830148544397,0.6757557090818312,0.3604039578340141]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsResponseBody"},"example":{"count":7525631923450415662,"max":0.9702023620235123,"mean":0.3176589236328283,"median":0.5259699852027079,"min":0.4948460127995459,"sum":0.7469671830093337}}}},"400":{"description":"empty_list: List cannot be empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Non aut expedita soluta ratione maiores.","message":"Velit doloribus."}}}}}}}},"components":{"schemas":{"AddRequestBody":{"type":"object","properties":{"a":{"type":"number","description":"First number","example":0.7936796293863132,"format":"double"},"b":{"type":"number","description":"Second number","example":0.05212966980398518,"format":"double"}},"description":"Request body for add.","example":{"a":0.7936796293863132,"b":0.05212966980398518},"required":["a","b"]},"AddResponseBody":{"type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"In deleniti sint."},"result":{"type":"number","description":"Sum of a and b","example":0.04198102650322011,"format":"double"}},"example":{"operation":"In deleniti sint.","result":0.04198102650322011},"required":["result","operation"]},"BatchAddResponseBody":{"type":"object","properties":{"index":{"type":"integer","description":"Operation index","example":312737943510911382,"format":"int64"},"result":{"type":"number","description":"Sum","example":0.8965135098234011,"format":"double"}},"example":{"index":312737943510911382,"result":0.8965135098234011},"required":["result","index"]},"CalculatorError":{"type":"object","properties":{"code":{"type":"string","description":"Error code","example":"Dignissimos ut voluptate fuga minus vero omnis."},"message":{"type":"string","description":"Error message","example":"Repellendus quidem eligendi voluptas quae eius."}},"description":"Cannot divide by zero","example":{"code":"Dignissimos ut voluptate fuga minus vero omnis.","message":"Repellendus quidem eligendi voluptas quae eius."},"required":["message","code"]},"DivideRequestBody":{"type":"object","properties":{"dividend":{"type":"number","description":"Number to divide","example":0.5184906879153129,"format":"double"},"divisor":{"type":"number","description":"Number to divide by","example":0.5616730288110641,"format":"double"}},"description":"Request body for divide.","example":{"dividend":0.5184906879153129,"divisor":0.5616730288110641},"required":["dividend","divisor"]},"FactorialRequestBody":{"type":"object","properties":{"n":{"type":"integer","description":"Number to calculate factorial of","example":10,"format":"int64","minimum":0,"maximum":20}},"description":"Request body for factorial.","example":{"n":10},"required":["n"]},"FactorialResponseBody":{"type":"object","properties":{"computation_time_ms":{"type":"integer","description":"Time taken in milliseconds","example":7430406659417736888,"format":"int64"},"operation":{"type":"string","description":"Operation performed","example":"In quo."},"result":{"type":"integer","description":"Factorial result","example":2802676607681044054,"format":"int64"}},"example":{"computation_time_ms":7430406659417736888,"operation":"In quo.","result":2802676607681044054},"required":["result","operation","computation_time_ms"]},"StatisticsRequestBody":{"type":"object","properties":{"numbers":{"type":"array","items":{"type":"number","example":0.858415483142481,"format":"double"},"description":"List of numbers","example":[0.5842830148544397,0.6757557090818312,0.3604039578340141],"minItems":1}},"description":"Request body for statistics.","example":{"numbers":[0.5842830148544397,0.6757557090818312,0.3604039578340141]},"required":["numbers"]},"StatisticsResponseBody":{"type":"object","properties":{"count":{"type":"integer","description":"Number of values","example":7525631923450415662,"format":"int64"},"max":{"type":"number","description":"Maximum value","example":0.9702023620235123,"format":"double"},"mean":{"type":"number","description":"Average of numbers","example":0.3176589236328283,"format":"double"},"median":{"type":"number","description":"Median value","example":0.5259699852027079,"format":"double"},"min":{"type":"number","description":"Minimum value","example":0.4948460127995459,"format":"double"},"sum":{"type":"number","description":"Sum of all values","example":0.7469671830093337,"format":"double"}},"example":{"count":7525631923450415662,"max":0.9702023620235123,"mean":0.3176589236328283,"median":0.5259699852027079,"min":0.4948460127995459,"sum":0.7469671830093337},"required":["mean","median","min","max","count","sum"]}}},"tags":[{"name":"calculator","description":"A simple calculator service to demonstrate testing plugin features"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"calculator","description":"Default server for calculator"}],"paths":{"/add":{"post":{"tags":["calculator"],"summary":"add calculator","description":"Add two numbers","operationId":"calculator#add","requestBody":{"description":"Request body for add.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRequestBody"},"example":{"a":0.490314453132986,"b":0.6743292888632025}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"Accusamus autem molestiae.","result":0.6697833948471263}}}}}}},"/batch/add":{"get":{"tags":["calculator"],"summary":"batch_add calculator","description":"Add multiple pairs of numbers using streaming","operationId":"calculator#batch_add","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchAddResponseBody"},"example":{"index":4346106390186751403,"result":0.9017860010086814}}}}}}},"/divide":{"post":{"tags":["calculator"],"summary":"divide calculator","description":"Divide two numbers","operationId":"calculator#divide","requestBody":{"description":"Request body for divide.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivideRequestBody"},"example":{"dividend":0.7333985315892829,"divisor":0.2998442358280339}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"Occaecati dolorem accusamus quia praesentium ut accusamus.","result":0.24146955311479495}}}},"400":{"description":"division_by_zero: Cannot divide by zero","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Veniam assumenda similique molestias.","message":"Labore qui in dolores et soluta quia."}}}}}}},"/factorial":{"post":{"tags":["calculator"],"summary":"factorial calculator","description":"Calculate factorial of a number","operationId":"calculator#factorial","requestBody":{"description":"Request body for factorial.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialRequestBody"},"example":{"n":12}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialResponseBody"},"example":{"computation_time_ms":8677282720135103333,"operation":"Maiores voluptas in laudantium.","result":4772021109354887343}}}},"400":{"description":"invalid_input: Input must be between 0 and 20","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Distinctio deleniti qui velit odit nihil qui.","message":"Reprehenderit quo possimus cum quas magni."}}}}}}},"/statistics":{"post":{"tags":["calculator"],"summary":"statistics calculator","description":"Calculate statistics for a list of numbers","operationId":"calculator#statistics","requestBody":{"description":"Request body for statistics.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsRequestBody"},"example":{"numbers":[0.1819497783049658,0.6452245232691431,0.01204206152248697]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsResponseBody"},"example":{"count":2250719573348189391,"max":0.7274177146624164,"mean":0.9548358232183869,"median":0.5054842136268939,"min":0.8647384391803151,"sum":0.08114586604607756}}}},"400":{"description":"empty_list: List cannot be empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Nihil qui amet sunt est deleniti quas.","message":"Molestiae sed ut aut."}}}}}}}},"components":{"schemas":{"AddRequestBody":{"type":"object","properties":{"a":{"type":"number","description":"First number","example":0.490314453132986,"format":"double"},"b":{"type":"number","description":"Second number","example":0.6743292888632025,"format":"double"}},"description":"Request body for add.","example":{"a":0.490314453132986,"b":0.6743292888632025},"required":["a","b"]},"AddResponseBody":{"type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"Accusamus autem molestiae."},"result":{"type":"number","description":"Sum of a and b","example":0.6697833948471263,"format":"double"}},"example":{"operation":"Accusamus autem molestiae.","result":0.6697833948471263},"required":["result","operation"]},"BatchAddResponseBody":{"type":"object","properties":{"index":{"type":"integer","description":"Operation index","example":4346106390186751403,"format":"int64"},"result":{"type":"number","description":"Sum","example":0.9017860010086814,"format":"double"}},"example":{"index":4346106390186751403,"result":0.9017860010086814},"required":["result","index"]},"CalculatorError":{"type":"object","properties":{"code":{"type":"string","description":"Error code","example":"Veniam assumenda similique molestias."},"message":{"type":"string","description":"Error message","example":"Labore qui in dolores et soluta quia."}},"description":"Calculator operation error","example":{"code":"Veniam assumenda similique molestias.","message":"Labore qui in dolores et soluta quia."},"required":["message","code"]},"DivideRequestBody":{"type":"object","properties":{"dividend":{"type":"number","description":"Number to divide","example":0.7333985315892829,"format":"double"},"divisor":{"type":"number","description":"Number to divide by","example":0.2998442358280339,"format":"double"}},"description":"Request body for divide.","example":{"dividend":0.7333985315892829,"divisor":0.2998442358280339},"required":["dividend","divisor"]},"FactorialRequestBody":{"type":"object","properties":{"n":{"type":"integer","description":"Number to calculate factorial of","example":12,"format":"int64","minimum":0,"maximum":20}},"description":"Request body for factorial.","example":{"n":12},"required":["n"]},"FactorialResponseBody":{"type":"object","properties":{"computation_time_ms":{"type":"integer","description":"Time taken in milliseconds","example":8677282720135103333,"format":"int64"},"operation":{"type":"string","description":"Operation performed","example":"Maiores voluptas in laudantium."},"result":{"type":"integer","description":"Factorial result","example":4772021109354887343,"format":"int64"}},"example":{"computation_time_ms":8677282720135103333,"operation":"Maiores voluptas in laudantium.","result":4772021109354887343},"required":["result","operation","computation_time_ms"]},"StatisticsRequestBody":{"type":"object","properties":{"numbers":{"type":"array","items":{"type":"number","example":0.1819497783049658,"format":"double"},"description":"List of numbers","example":[0.1819497783049658,0.6452245232691431,0.01204206152248697],"minItems":1}},"description":"Request body for statistics.","example":{"numbers":[0.1819497783049658,0.6452245232691431,0.01204206152248697]},"required":["numbers"]},"StatisticsResponseBody":{"type":"object","properties":{"count":{"type":"integer","description":"Number of values","example":2250719573348189391,"format":"int64"},"max":{"type":"number","description":"Maximum value","example":0.7274177146624164,"format":"double"},"mean":{"type":"number","description":"Average of numbers","example":0.9548358232183869,"format":"double"},"median":{"type":"number","description":"Median value","example":0.5054842136268939,"format":"double"},"min":{"type":"number","description":"Minimum value","example":0.8647384391803151,"format":"double"},"sum":{"type":"number","description":"Sum of all values","example":0.08114586604607756,"format":"double"}},"example":{"count":2250719573348189391,"max":0.7274177146624164,"mean":0.9548358232183869,"median":0.5054842136268939,"min":0.8647384391803151,"sum":0.08114586604607756},"required":["mean","median","min","max","count","sum"]}}},"tags":[{"name":"calculator","description":"A simple calculator service to demonstrate testing plugin features"}]} \ No newline at end of file diff --git a/testing/examples/calculator/gen/http/openapi3.2.yaml b/testing/examples/calculator/gen/http/openapi3.2.yaml index 40ae313ea..610dd7821 100644 --- a/testing/examples/calculator/gen/http/openapi3.2.yaml +++ b/testing/examples/calculator/gen/http/openapi3.2.yaml @@ -22,8 +22,8 @@ paths: schema: $ref: '#/components/schemas/AddRequestBody' example: - a: 0.7936796293863132 - b: 0.05212966980398518 + a: 0.490314453132986 + b: 0.6743292888632025 responses: "200": description: OK response. @@ -32,8 +32,8 @@ paths: schema: $ref: '#/components/schemas/AddResponseBody' example: - operation: In deleniti sint. - result: 0.04198102650322011 + operation: Accusamus autem molestiae. + result: 0.6697833948471263 /batch/add: get: tags: @@ -49,8 +49,8 @@ paths: schema: $ref: '#/components/schemas/BatchAddResponseBody' example: - index: 312737943510911382 - result: 0.8965135098234011 + index: 4346106390186751403 + result: 0.9017860010086814 /divide: post: tags: @@ -66,8 +66,8 @@ paths: schema: $ref: '#/components/schemas/DivideRequestBody' example: - dividend: 0.5184906879153129 - divisor: 0.5616730288110641 + dividend: 0.7333985315892829 + divisor: 0.2998442358280339 responses: "200": description: OK response. @@ -76,8 +76,8 @@ paths: schema: $ref: '#/components/schemas/AddResponseBody' example: - operation: Nesciunt consequatur consectetur cupiditate sit. - result: 0.08459169404214541 + operation: Occaecati dolorem accusamus quia praesentium ut accusamus. + result: 0.24146955311479495 "400": description: 'division_by_zero: Cannot divide by zero' content: @@ -85,8 +85,8 @@ paths: schema: $ref: '#/components/schemas/CalculatorError' example: - code: Dignissimos ut voluptate fuga minus vero omnis. - message: Repellendus quidem eligendi voluptas quae eius. + code: Veniam assumenda similique molestias. + message: Labore qui in dolores et soluta quia. /factorial: post: tags: @@ -102,7 +102,7 @@ paths: schema: $ref: '#/components/schemas/FactorialRequestBody' example: - "n": 10 + "n": 12 responses: "200": description: OK response. @@ -111,9 +111,9 @@ paths: schema: $ref: '#/components/schemas/FactorialResponseBody' example: - computation_time_ms: 7430406659417736888 - operation: In quo. - result: 2802676607681044054 + computation_time_ms: 8677282720135103333 + operation: Maiores voluptas in laudantium. + result: 4772021109354887343 "400": description: 'invalid_input: Input must be between 0 and 20' content: @@ -121,8 +121,8 @@ paths: schema: $ref: '#/components/schemas/CalculatorError' example: - code: Laborum laudantium delectus illo explicabo. - message: Nobis ea. + code: Distinctio deleniti qui velit odit nihil qui. + message: Reprehenderit quo possimus cum quas magni. /statistics: post: tags: @@ -139,9 +139,9 @@ paths: $ref: '#/components/schemas/StatisticsRequestBody' example: numbers: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 responses: "200": description: OK response. @@ -150,12 +150,12 @@ paths: schema: $ref: '#/components/schemas/StatisticsResponseBody' example: - count: 7525631923450415662 - max: 0.9702023620235123 - mean: 0.3176589236328283 - median: 0.5259699852027079 - min: 0.4948460127995459 - sum: 0.7469671830093337 + count: 2250719573348189391 + max: 0.7274177146624164 + mean: 0.9548358232183869 + median: 0.5054842136268939 + min: 0.8647384391803151 + sum: 0.08114586604607756 "400": description: 'empty_list: List cannot be empty' content: @@ -163,8 +163,8 @@ paths: schema: $ref: '#/components/schemas/CalculatorError' example: - code: Non aut expedita soluta ratione maiores. - message: Velit doloribus. + code: Nihil qui amet sunt est deleniti quas. + message: Molestiae sed ut aut. components: schemas: AddRequestBody: @@ -173,17 +173,17 @@ components: a: type: number description: First number - example: 0.7936796293863132 + example: 0.490314453132986 format: double b: type: number description: Second number - example: 0.05212966980398518 + example: 0.6743292888632025 format: double description: Request body for add. example: - a: 0.7936796293863132 - b: 0.05212966980398518 + a: 0.490314453132986 + b: 0.6743292888632025 required: - a - b @@ -193,15 +193,15 @@ components: operation: type: string description: Operation performed - example: In deleniti sint. + example: Accusamus autem molestiae. result: type: number description: Sum of a and b - example: 0.04198102650322011 + example: 0.6697833948471263 format: double example: - operation: In deleniti sint. - result: 0.04198102650322011 + operation: Accusamus autem molestiae. + result: 0.6697833948471263 required: - result - operation @@ -211,16 +211,16 @@ components: index: type: integer description: Operation index - example: 312737943510911382 + example: 4346106390186751403 format: int64 result: type: number description: Sum - example: 0.8965135098234011 + example: 0.9017860010086814 format: double example: - index: 312737943510911382 - result: 0.8965135098234011 + index: 4346106390186751403 + result: 0.9017860010086814 required: - result - index @@ -230,15 +230,15 @@ components: code: type: string description: Error code - example: Dignissimos ut voluptate fuga minus vero omnis. + example: Veniam assumenda similique molestias. message: type: string description: Error message - example: Repellendus quidem eligendi voluptas quae eius. - description: Cannot divide by zero + example: Labore qui in dolores et soluta quia. + description: Calculator operation error example: - code: Dignissimos ut voluptate fuga minus vero omnis. - message: Repellendus quidem eligendi voluptas quae eius. + code: Veniam assumenda similique molestias. + message: Labore qui in dolores et soluta quia. required: - message - code @@ -248,17 +248,17 @@ components: dividend: type: number description: Number to divide - example: 0.5184906879153129 + example: 0.7333985315892829 format: double divisor: type: number description: Number to divide by - example: 0.5616730288110641 + example: 0.2998442358280339 format: double description: Request body for divide. example: - dividend: 0.5184906879153129 - divisor: 0.5616730288110641 + dividend: 0.7333985315892829 + divisor: 0.2998442358280339 required: - dividend - divisor @@ -268,13 +268,13 @@ components: "n": type: integer description: Number to calculate factorial of - example: 10 + example: 12 format: int64 minimum: 0 maximum: 20 description: Request body for factorial. example: - "n": 10 + "n": 12 required: - "n" FactorialResponseBody: @@ -283,21 +283,21 @@ components: computation_time_ms: type: integer description: Time taken in milliseconds - example: 7430406659417736888 + example: 8677282720135103333 format: int64 operation: type: string description: Operation performed - example: In quo. + example: Maiores voluptas in laudantium. result: type: integer description: Factorial result - example: 2802676607681044054 + example: 4772021109354887343 format: int64 example: - computation_time_ms: 7430406659417736888 - operation: In quo. - result: 2802676607681044054 + computation_time_ms: 8677282720135103333 + operation: Maiores voluptas in laudantium. + result: 4772021109354887343 required: - result - operation @@ -309,20 +309,20 @@ components: type: array items: type: number - example: 0.858415483142481 + example: 0.1819497783049658 format: double description: List of numbers example: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 minItems: 1 description: Request body for statistics. example: numbers: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 required: - numbers StatisticsResponseBody: @@ -331,40 +331,40 @@ components: count: type: integer description: Number of values - example: 7525631923450415662 + example: 2250719573348189391 format: int64 max: type: number description: Maximum value - example: 0.9702023620235123 + example: 0.7274177146624164 format: double mean: type: number description: Average of numbers - example: 0.3176589236328283 + example: 0.9548358232183869 format: double median: type: number description: Median value - example: 0.5259699852027079 + example: 0.5054842136268939 format: double min: type: number description: Minimum value - example: 0.4948460127995459 + example: 0.8647384391803151 format: double sum: type: number description: Sum of all values - example: 0.7469671830093337 + example: 0.08114586604607756 format: double example: - count: 7525631923450415662 - max: 0.9702023620235123 - mean: 0.3176589236328283 - median: 0.5259699852027079 - min: 0.4948460127995459 - sum: 0.7469671830093337 + count: 2250719573348189391 + max: 0.7274177146624164 + mean: 0.9548358232183869 + median: 0.5054842136268939 + min: 0.8647384391803151 + sum: 0.08114586604607756 required: - mean - median diff --git a/testing/examples/calculator/gen/http/openapi3.json b/testing/examples/calculator/gen/http/openapi3.json index 9632342c2..ffbe81eba 100644 --- a/testing/examples/calculator/gen/http/openapi3.json +++ b/testing/examples/calculator/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for calculator"}],"paths":{"/add":{"post":{"tags":["calculator"],"summary":"add calculator","description":"Add two numbers","operationId":"calculator#add","requestBody":{"description":"Request body for add.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRequestBody"},"example":{"a":0.7936796293863132,"b":0.05212966980398518}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"In deleniti sint.","result":0.04198102650322011}}}}}}},"/batch/add":{"get":{"tags":["calculator"],"summary":"batch_add calculator","description":"Add multiple pairs of numbers using streaming","operationId":"calculator#batch_add","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchAddResponseBody"},"example":{"index":312737943510911382,"result":0.8965135098234011}}}}}}},"/divide":{"post":{"tags":["calculator"],"summary":"divide calculator","description":"Divide two numbers","operationId":"calculator#divide","requestBody":{"description":"Request body for divide.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivideRequestBody"},"example":{"dividend":0.5184906879153129,"divisor":0.5616730288110641}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"Nesciunt consequatur consectetur cupiditate sit.","result":0.08459169404214541}}}},"400":{"description":"division_by_zero: Cannot divide by zero","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Dignissimos ut voluptate fuga minus vero omnis.","message":"Repellendus quidem eligendi voluptas quae eius."}}}}}}},"/factorial":{"post":{"tags":["calculator"],"summary":"factorial calculator","description":"Calculate factorial of a number","operationId":"calculator#factorial","requestBody":{"description":"Request body for factorial.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialRequestBody"},"example":{"n":10}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialResponseBody"},"example":{"computation_time_ms":7430406659417736888,"operation":"In quo.","result":2802676607681044054}}}},"400":{"description":"invalid_input: Input must be between 0 and 20","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Laborum laudantium delectus illo explicabo.","message":"Nobis ea."}}}}}}},"/statistics":{"post":{"tags":["calculator"],"summary":"statistics calculator","description":"Calculate statistics for a list of numbers","operationId":"calculator#statistics","requestBody":{"description":"Request body for statistics.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsRequestBody"},"example":{"numbers":[0.5842830148544397,0.6757557090818312,0.3604039578340141]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsResponseBody"},"example":{"count":7525631923450415662,"max":0.9702023620235123,"mean":0.3176589236328283,"median":0.5259699852027079,"min":0.4948460127995459,"sum":0.7469671830093337}}}},"400":{"description":"empty_list: List cannot be empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Non aut expedita soluta ratione maiores.","message":"Velit doloribus."}}}}}}}},"components":{"schemas":{"AddRequestBody":{"type":"object","properties":{"a":{"type":"number","description":"First number","example":0.7936796293863132,"format":"double"},"b":{"type":"number","description":"Second number","example":0.05212966980398518,"format":"double"}},"description":"Request body for add.","example":{"a":0.7936796293863132,"b":0.05212966980398518},"required":["a","b"]},"AddResponseBody":{"type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"In deleniti sint."},"result":{"type":"number","description":"Sum of a and b","example":0.04198102650322011,"format":"double"}},"example":{"operation":"In deleniti sint.","result":0.04198102650322011},"required":["result","operation"]},"BatchAddResponseBody":{"type":"object","properties":{"index":{"type":"integer","description":"Operation index","example":312737943510911382,"format":"int64"},"result":{"type":"number","description":"Sum","example":0.8965135098234011,"format":"double"}},"example":{"index":312737943510911382,"result":0.8965135098234011},"required":["result","index"]},"CalculatorError":{"type":"object","properties":{"code":{"type":"string","description":"Error code","example":"Dignissimos ut voluptate fuga minus vero omnis."},"message":{"type":"string","description":"Error message","example":"Repellendus quidem eligendi voluptas quae eius."}},"description":"Cannot divide by zero","example":{"code":"Dignissimos ut voluptate fuga minus vero omnis.","message":"Repellendus quidem eligendi voluptas quae eius."},"required":["message","code"]},"DivideRequestBody":{"type":"object","properties":{"dividend":{"type":"number","description":"Number to divide","example":0.5184906879153129,"format":"double"},"divisor":{"type":"number","description":"Number to divide by","example":0.5616730288110641,"format":"double"}},"description":"Request body for divide.","example":{"dividend":0.5184906879153129,"divisor":0.5616730288110641},"required":["dividend","divisor"]},"FactorialRequestBody":{"type":"object","properties":{"n":{"type":"integer","description":"Number to calculate factorial of","example":10,"format":"int64","minimum":0,"maximum":20}},"description":"Request body for factorial.","example":{"n":10},"required":["n"]},"FactorialResponseBody":{"type":"object","properties":{"computation_time_ms":{"type":"integer","description":"Time taken in milliseconds","example":7430406659417736888,"format":"int64"},"operation":{"type":"string","description":"Operation performed","example":"In quo."},"result":{"type":"integer","description":"Factorial result","example":2802676607681044054,"format":"int64"}},"example":{"computation_time_ms":7430406659417736888,"operation":"In quo.","result":2802676607681044054},"required":["result","operation","computation_time_ms"]},"StatisticsRequestBody":{"type":"object","properties":{"numbers":{"type":"array","items":{"type":"number","example":0.858415483142481,"format":"double"},"description":"List of numbers","example":[0.5842830148544397,0.6757557090818312,0.3604039578340141],"minItems":1}},"description":"Request body for statistics.","example":{"numbers":[0.5842830148544397,0.6757557090818312,0.3604039578340141]},"required":["numbers"]},"StatisticsResponseBody":{"type":"object","properties":{"count":{"type":"integer","description":"Number of values","example":7525631923450415662,"format":"int64"},"max":{"type":"number","description":"Maximum value","example":0.9702023620235123,"format":"double"},"mean":{"type":"number","description":"Average of numbers","example":0.3176589236328283,"format":"double"},"median":{"type":"number","description":"Median value","example":0.5259699852027079,"format":"double"},"min":{"type":"number","description":"Minimum value","example":0.4948460127995459,"format":"double"},"sum":{"type":"number","description":"Sum of all values","example":0.7469671830093337,"format":"double"}},"example":{"count":7525631923450415662,"max":0.9702023620235123,"mean":0.3176589236328283,"median":0.5259699852027079,"min":0.4948460127995459,"sum":0.7469671830093337},"required":["mean","median","min","max","count","sum"]}}},"tags":[{"name":"calculator","description":"A simple calculator service to demonstrate testing plugin features"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for calculator"}],"paths":{"/add":{"post":{"tags":["calculator"],"summary":"add calculator","description":"Add two numbers","operationId":"calculator#add","requestBody":{"description":"Request body for add.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRequestBody"},"example":{"a":0.490314453132986,"b":0.6743292888632025}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"Accusamus autem molestiae.","result":0.6697833948471263}}}}}}},"/batch/add":{"get":{"tags":["calculator"],"summary":"batch_add calculator","description":"Add multiple pairs of numbers using streaming","operationId":"calculator#batch_add","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchAddResponseBody"},"example":{"index":4346106390186751403,"result":0.9017860010086814}}}}}}},"/divide":{"post":{"tags":["calculator"],"summary":"divide calculator","description":"Divide two numbers","operationId":"calculator#divide","requestBody":{"description":"Request body for divide.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivideRequestBody"},"example":{"dividend":0.7333985315892829,"divisor":0.2998442358280339}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddResponseBody"},"example":{"operation":"Occaecati dolorem accusamus quia praesentium ut accusamus.","result":0.24146955311479495}}}},"400":{"description":"division_by_zero: Cannot divide by zero","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Veniam assumenda similique molestias.","message":"Labore qui in dolores et soluta quia."}}}}}}},"/factorial":{"post":{"tags":["calculator"],"summary":"factorial calculator","description":"Calculate factorial of a number","operationId":"calculator#factorial","requestBody":{"description":"Request body for factorial.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialRequestBody"},"example":{"n":12}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactorialResponseBody"},"example":{"computation_time_ms":8677282720135103333,"operation":"Maiores voluptas in laudantium.","result":4772021109354887343}}}},"400":{"description":"invalid_input: Input must be between 0 and 20","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Distinctio deleniti qui velit odit nihil qui.","message":"Reprehenderit quo possimus cum quas magni."}}}}}}},"/statistics":{"post":{"tags":["calculator"],"summary":"statistics calculator","description":"Calculate statistics for a list of numbers","operationId":"calculator#statistics","requestBody":{"description":"Request body for statistics.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsRequestBody"},"example":{"numbers":[0.1819497783049658,0.6452245232691431,0.01204206152248697]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsResponseBody"},"example":{"count":2250719573348189391,"max":0.7274177146624164,"mean":0.9548358232183869,"median":0.5054842136268939,"min":0.8647384391803151,"sum":0.08114586604607756}}}},"400":{"description":"empty_list: List cannot be empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalculatorError"},"example":{"code":"Nihil qui amet sunt est deleniti quas.","message":"Molestiae sed ut aut."}}}}}}}},"components":{"schemas":{"AddRequestBody":{"type":"object","properties":{"a":{"type":"number","description":"First number","example":0.490314453132986,"format":"double"},"b":{"type":"number","description":"Second number","example":0.6743292888632025,"format":"double"}},"description":"Request body for add.","example":{"a":0.490314453132986,"b":0.6743292888632025},"required":["a","b"]},"AddResponseBody":{"type":"object","properties":{"operation":{"type":"string","description":"Operation performed","example":"Accusamus autem molestiae."},"result":{"type":"number","description":"Sum of a and b","example":0.6697833948471263,"format":"double"}},"example":{"operation":"Accusamus autem molestiae.","result":0.6697833948471263},"required":["result","operation"]},"BatchAddResponseBody":{"type":"object","properties":{"index":{"type":"integer","description":"Operation index","example":4346106390186751403,"format":"int64"},"result":{"type":"number","description":"Sum","example":0.9017860010086814,"format":"double"}},"example":{"index":4346106390186751403,"result":0.9017860010086814},"required":["result","index"]},"CalculatorError":{"type":"object","properties":{"code":{"type":"string","description":"Error code","example":"Veniam assumenda similique molestias."},"message":{"type":"string","description":"Error message","example":"Labore qui in dolores et soluta quia."}},"description":"Calculator operation error","example":{"code":"Veniam assumenda similique molestias.","message":"Labore qui in dolores et soluta quia."},"required":["message","code"]},"DivideRequestBody":{"type":"object","properties":{"dividend":{"type":"number","description":"Number to divide","example":0.7333985315892829,"format":"double"},"divisor":{"type":"number","description":"Number to divide by","example":0.2998442358280339,"format":"double"}},"description":"Request body for divide.","example":{"dividend":0.7333985315892829,"divisor":0.2998442358280339},"required":["dividend","divisor"]},"FactorialRequestBody":{"type":"object","properties":{"n":{"type":"integer","description":"Number to calculate factorial of","example":12,"format":"int64","minimum":0,"maximum":20}},"description":"Request body for factorial.","example":{"n":12},"required":["n"]},"FactorialResponseBody":{"type":"object","properties":{"computation_time_ms":{"type":"integer","description":"Time taken in milliseconds","example":8677282720135103333,"format":"int64"},"operation":{"type":"string","description":"Operation performed","example":"Maiores voluptas in laudantium."},"result":{"type":"integer","description":"Factorial result","example":4772021109354887343,"format":"int64"}},"example":{"computation_time_ms":8677282720135103333,"operation":"Maiores voluptas in laudantium.","result":4772021109354887343},"required":["result","operation","computation_time_ms"]},"StatisticsRequestBody":{"type":"object","properties":{"numbers":{"type":"array","items":{"type":"number","example":0.1819497783049658,"format":"double"},"description":"List of numbers","example":[0.1819497783049658,0.6452245232691431,0.01204206152248697],"minItems":1}},"description":"Request body for statistics.","example":{"numbers":[0.1819497783049658,0.6452245232691431,0.01204206152248697]},"required":["numbers"]},"StatisticsResponseBody":{"type":"object","properties":{"count":{"type":"integer","description":"Number of values","example":2250719573348189391,"format":"int64"},"max":{"type":"number","description":"Maximum value","example":0.7274177146624164,"format":"double"},"mean":{"type":"number","description":"Average of numbers","example":0.9548358232183869,"format":"double"},"median":{"type":"number","description":"Median value","example":0.5054842136268939,"format":"double"},"min":{"type":"number","description":"Minimum value","example":0.8647384391803151,"format":"double"},"sum":{"type":"number","description":"Sum of all values","example":0.08114586604607756,"format":"double"}},"example":{"count":2250719573348189391,"max":0.7274177146624164,"mean":0.9548358232183869,"median":0.5054842136268939,"min":0.8647384391803151,"sum":0.08114586604607756},"required":["mean","median","min","max","count","sum"]}}},"tags":[{"name":"calculator","description":"A simple calculator service to demonstrate testing plugin features"}]} \ No newline at end of file diff --git a/testing/examples/calculator/gen/http/openapi3.yaml b/testing/examples/calculator/gen/http/openapi3.yaml index 1fbbb6f17..e675392a0 100644 --- a/testing/examples/calculator/gen/http/openapi3.yaml +++ b/testing/examples/calculator/gen/http/openapi3.yaml @@ -21,8 +21,8 @@ paths: schema: $ref: '#/components/schemas/AddRequestBody' example: - a: 0.7936796293863132 - b: 0.05212966980398518 + a: 0.490314453132986 + b: 0.6743292888632025 responses: "200": description: OK response. @@ -31,8 +31,8 @@ paths: schema: $ref: '#/components/schemas/AddResponseBody' example: - operation: In deleniti sint. - result: 0.04198102650322011 + operation: Accusamus autem molestiae. + result: 0.6697833948471263 /batch/add: get: tags: @@ -48,8 +48,8 @@ paths: schema: $ref: '#/components/schemas/BatchAddResponseBody' example: - index: 312737943510911382 - result: 0.8965135098234011 + index: 4346106390186751403 + result: 0.9017860010086814 /divide: post: tags: @@ -65,8 +65,8 @@ paths: schema: $ref: '#/components/schemas/DivideRequestBody' example: - dividend: 0.5184906879153129 - divisor: 0.5616730288110641 + dividend: 0.7333985315892829 + divisor: 0.2998442358280339 responses: "200": description: OK response. @@ -75,8 +75,8 @@ paths: schema: $ref: '#/components/schemas/AddResponseBody' example: - operation: Nesciunt consequatur consectetur cupiditate sit. - result: 0.08459169404214541 + operation: Occaecati dolorem accusamus quia praesentium ut accusamus. + result: 0.24146955311479495 "400": description: 'division_by_zero: Cannot divide by zero' content: @@ -84,8 +84,8 @@ paths: schema: $ref: '#/components/schemas/CalculatorError' example: - code: Dignissimos ut voluptate fuga minus vero omnis. - message: Repellendus quidem eligendi voluptas quae eius. + code: Veniam assumenda similique molestias. + message: Labore qui in dolores et soluta quia. /factorial: post: tags: @@ -101,7 +101,7 @@ paths: schema: $ref: '#/components/schemas/FactorialRequestBody' example: - "n": 10 + "n": 12 responses: "200": description: OK response. @@ -110,9 +110,9 @@ paths: schema: $ref: '#/components/schemas/FactorialResponseBody' example: - computation_time_ms: 7430406659417736888 - operation: In quo. - result: 2802676607681044054 + computation_time_ms: 8677282720135103333 + operation: Maiores voluptas in laudantium. + result: 4772021109354887343 "400": description: 'invalid_input: Input must be between 0 and 20' content: @@ -120,8 +120,8 @@ paths: schema: $ref: '#/components/schemas/CalculatorError' example: - code: Laborum laudantium delectus illo explicabo. - message: Nobis ea. + code: Distinctio deleniti qui velit odit nihil qui. + message: Reprehenderit quo possimus cum quas magni. /statistics: post: tags: @@ -138,9 +138,9 @@ paths: $ref: '#/components/schemas/StatisticsRequestBody' example: numbers: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 responses: "200": description: OK response. @@ -149,12 +149,12 @@ paths: schema: $ref: '#/components/schemas/StatisticsResponseBody' example: - count: 7525631923450415662 - max: 0.9702023620235123 - mean: 0.3176589236328283 - median: 0.5259699852027079 - min: 0.4948460127995459 - sum: 0.7469671830093337 + count: 2250719573348189391 + max: 0.7274177146624164 + mean: 0.9548358232183869 + median: 0.5054842136268939 + min: 0.8647384391803151 + sum: 0.08114586604607756 "400": description: 'empty_list: List cannot be empty' content: @@ -162,8 +162,8 @@ paths: schema: $ref: '#/components/schemas/CalculatorError' example: - code: Non aut expedita soluta ratione maiores. - message: Velit doloribus. + code: Nihil qui amet sunt est deleniti quas. + message: Molestiae sed ut aut. components: schemas: AddRequestBody: @@ -172,17 +172,17 @@ components: a: type: number description: First number - example: 0.7936796293863132 + example: 0.490314453132986 format: double b: type: number description: Second number - example: 0.05212966980398518 + example: 0.6743292888632025 format: double description: Request body for add. example: - a: 0.7936796293863132 - b: 0.05212966980398518 + a: 0.490314453132986 + b: 0.6743292888632025 required: - a - b @@ -192,15 +192,15 @@ components: operation: type: string description: Operation performed - example: In deleniti sint. + example: Accusamus autem molestiae. result: type: number description: Sum of a and b - example: 0.04198102650322011 + example: 0.6697833948471263 format: double example: - operation: In deleniti sint. - result: 0.04198102650322011 + operation: Accusamus autem molestiae. + result: 0.6697833948471263 required: - result - operation @@ -210,16 +210,16 @@ components: index: type: integer description: Operation index - example: 312737943510911382 + example: 4346106390186751403 format: int64 result: type: number description: Sum - example: 0.8965135098234011 + example: 0.9017860010086814 format: double example: - index: 312737943510911382 - result: 0.8965135098234011 + index: 4346106390186751403 + result: 0.9017860010086814 required: - result - index @@ -229,15 +229,15 @@ components: code: type: string description: Error code - example: Dignissimos ut voluptate fuga minus vero omnis. + example: Veniam assumenda similique molestias. message: type: string description: Error message - example: Repellendus quidem eligendi voluptas quae eius. - description: Cannot divide by zero + example: Labore qui in dolores et soluta quia. + description: Calculator operation error example: - code: Dignissimos ut voluptate fuga minus vero omnis. - message: Repellendus quidem eligendi voluptas quae eius. + code: Veniam assumenda similique molestias. + message: Labore qui in dolores et soluta quia. required: - message - code @@ -247,17 +247,17 @@ components: dividend: type: number description: Number to divide - example: 0.5184906879153129 + example: 0.7333985315892829 format: double divisor: type: number description: Number to divide by - example: 0.5616730288110641 + example: 0.2998442358280339 format: double description: Request body for divide. example: - dividend: 0.5184906879153129 - divisor: 0.5616730288110641 + dividend: 0.7333985315892829 + divisor: 0.2998442358280339 required: - dividend - divisor @@ -267,13 +267,13 @@ components: "n": type: integer description: Number to calculate factorial of - example: 10 + example: 12 format: int64 minimum: 0 maximum: 20 description: Request body for factorial. example: - "n": 10 + "n": 12 required: - "n" FactorialResponseBody: @@ -282,21 +282,21 @@ components: computation_time_ms: type: integer description: Time taken in milliseconds - example: 7430406659417736888 + example: 8677282720135103333 format: int64 operation: type: string description: Operation performed - example: In quo. + example: Maiores voluptas in laudantium. result: type: integer description: Factorial result - example: 2802676607681044054 + example: 4772021109354887343 format: int64 example: - computation_time_ms: 7430406659417736888 - operation: In quo. - result: 2802676607681044054 + computation_time_ms: 8677282720135103333 + operation: Maiores voluptas in laudantium. + result: 4772021109354887343 required: - result - operation @@ -308,20 +308,20 @@ components: type: array items: type: number - example: 0.858415483142481 + example: 0.1819497783049658 format: double description: List of numbers example: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 minItems: 1 description: Request body for statistics. example: numbers: - - 0.5842830148544397 - - 0.6757557090818312 - - 0.3604039578340141 + - 0.1819497783049658 + - 0.6452245232691431 + - 0.01204206152248697 required: - numbers StatisticsResponseBody: @@ -330,40 +330,40 @@ components: count: type: integer description: Number of values - example: 7525631923450415662 + example: 2250719573348189391 format: int64 max: type: number description: Maximum value - example: 0.9702023620235123 + example: 0.7274177146624164 format: double mean: type: number description: Average of numbers - example: 0.3176589236328283 + example: 0.9548358232183869 format: double median: type: number description: Median value - example: 0.5259699852027079 + example: 0.5054842136268939 format: double min: type: number description: Minimum value - example: 0.4948460127995459 + example: 0.8647384391803151 format: double sum: type: number description: Sum of all values - example: 0.7469671830093337 + example: 0.08114586604607756 format: double example: - count: 7525631923450415662 - max: 0.9702023620235123 - mean: 0.3176589236328283 - median: 0.5259699852027079 - min: 0.4948460127995459 - sum: 0.7469671830093337 + count: 2250719573348189391 + max: 0.7274177146624164 + mean: 0.9548358232183869 + median: 0.5054842136268939 + min: 0.8647384391803151 + sum: 0.08114586604607756 required: - mean - median diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index ced4c2b21..1a11bd2fa 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.0 + goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 @@ -15,14 +15,12 @@ require ( require ( github.com/aws/smithy-go v1.27.8 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/go-chi/chi/v5 v5.3.1 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/gohugoio/hashstructure v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.12.1 // indirect go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 7a6b8acc1..ea0400bda 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -1,23 +1,23 @@ -github.com/aws/smithy-go v1.27.0 h1:ZoFioDKJxkSIW2otF9T0aPtNlUwhdVCcuZh/rzH9Hus= -github.com/aws/smithy-go v1.27.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/aws/smithy-go v1.27.7/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/smithy-go v1.27.8 h1:FR0dxZfIlV7Z8eh2iHfIofdunw382XsDV3Mxt9nUvRY= github.com/aws/smithy-go v1.27.8/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= -github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= -github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/getkin/kin-openapi v0.144.0 h1:hIRcTH+KjLfkLpYU6bSSfdFpi0fZi1fp+hSPi4aQu9Y= +github.com/getkin/kin-openapi v0.144.0/go.mod h1:3BH9M9XDe/y9M5DSvEocVYAYq1w0qrhJHjC/vZi0AaY= +github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg= -github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= github.com/gohugoio/hashstructure v1.0.0 h1:vWYuyzs1n0LdI0F54TJQeYAiB44fHX7H9hCp9X6gHKg= github.com/gohugoio/hashstructure v1.0.0/go.mod h1:FSbTK4QwxucJ2bC4Lvrs9a6x0DbQDXNoyBO+h4nlCgE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -36,88 +36,57 @@ github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqC github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b/go.mod h1:Bj8LjjP0ReT1eKt5QlKjwgi5AFm5mI6O1A2G4ChI0Ag= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY= +github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU= +github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw= +github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.28.1-0.20260613055708-11527bf14be7 h1:gg3ZcAGKckMgNSDA7eOjg98eVC8rgyIzNzpKKEUpR2E= -goa.design/goa/v3 v3.28.1-0.20260613055708-11527bf14be7/go.mod h1:EliUsJT3ObuebAPvYZsZtsl2wzEqf0N3HJRw6MrfDxQ= -goa.design/goa/v3 v3.29.0 h1:s8ic5VzONqm4unMM7qlGnCUCJ4cOfsZXBtqActDXDqU= -goa.design/goa/v3 v3.29.0/go.mod h1:FpTADA0Z2onOoZZD2YLWrA2IeXmaBEbs/FeFYag6hxg= -goa.design/goa/v3 v3.29.1 h1:O/FtqeWRI73e6EU6fuODelhLuSI40eSD7Cxn5fmoU5g= -goa.design/goa/v3 v3.29.1/go.mod h1:FpTADA0Z2onOoZZD2YLWrA2IeXmaBEbs/FeFYag6hxg= -goa.design/goa/v3 v3.29.2 h1:nabAmgYTUsE+NfG4KkMyRl6ddHN2uTAflOHC/FuaTfI= -goa.design/goa/v3 v3.29.2/go.mod h1:CTCdFy0UYeiYuoXmFkUJXji1xGPG7K6XFsO2NqU/QaQ= -goa.design/goa/v3 v3.30.0 h1:KXfAR5qlUCJLVPlHTZ6pHdC5b013BB78PLbDf2fFhfY= -goa.design/goa/v3 v3.30.0/go.mod h1:Is2byRdJddS20jhGT3Ql80N5hQyAc4HgAl7H/A0ZmJ4= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= -golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= -google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go b/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go index 41341878e..269bd06f5 100644 --- a/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go +++ b/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go @@ -29,7 +29,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "test-http-grpc grpc-no-stream --message '{\n \"msg\": \"Nisi unde.\"\n }'" + "\n" + + return os.Args[0] + " " + "test-http-grpc grpc-no-stream --message '{\n \"msg\": \"Ratione earum et ut temporibus tempora praesentium.\"\n }'" + "\n" + "" } @@ -227,7 +227,7 @@ func testHTTPGrpcGrpcNoStreamUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc grpc-no-stream --message '{\n \"msg\": \"Nisi unde.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc grpc-no-stream --message '{\n \"msg\": \"Ratione earum et ut temporibus tempora praesentium.\"\n }'") } func testHTTPGrpcGrpcNoStreamErrorDivByZeroUsage() { @@ -245,7 +245,7 @@ func testHTTPGrpcGrpcNoStreamErrorDivByZeroUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc grpc-no-stream-error-div-by-zero --message '{\n \"dividend\": 512.7504790865268,\n \"divisor\": -75.50495230788519\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc grpc-no-stream-error-div-by-zero --message '{\n \"dividend\": 238.4063147460167,\n \"divisor\": -73.4004594816786\n }'") } func testHTTPGrpcGrpcServerStreamUsage() { @@ -263,7 +263,7 @@ func testHTTPGrpcGrpcServerStreamUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc grpc-server-stream --message '{\n \"from\": 417059585657762780\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc grpc-server-stream --message '{\n \"from\": 436641374502740749\n }'") } func testHTTPGrpcGrpcClientStreamUsage() { @@ -313,7 +313,7 @@ func testHTTPGrpcMixedNoStreamUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc mixed-no-stream --message '{\n \"msg\": \"Commodi optio.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc mixed-no-stream --message '{\n \"msg\": \"Ut non id provident tempora suscipit quo.\"\n }'") } func testHTTPGrpcMixedServerStreamUsage() { diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go index 3157d5931..019d4e90f 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go @@ -9,11 +9,11 @@ package client import ( - "encoding/json" "fmt" test_http_grpcpb "goa.design/plugins/v3/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb" testhttpgrpc "goa.design/plugins/v3/testing/examples/httpgrpc/gen/test_http_grpc" + "google.golang.org/protobuf/encoding/protojson" ) // BuildGrpcNoStreamPayload builds the payload for the test-http-grpc @@ -23,14 +23,18 @@ func BuildGrpcNoStreamPayload(testHTTPGrpcGrpcNoStreamMessage string) (*testhttp var message test_http_grpcpb.GrpcNoStreamRequest { if testHTTPGrpcGrpcNoStreamMessage != "" { - err = json.Unmarshal([]byte(testHTTPGrpcGrpcNoStreamMessage), &message) + err = protojson.Unmarshal([]byte(testHTTPGrpcGrpcNoStreamMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Nisi unde.\"\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Ratione earum et ut temporibus tempora praesentium.\"\n }'") } } } + if err := ValidateGrpcNoStreamRequest(&message); err != nil { + var zero *testhttpgrpc.GrpcNoStreamPayload + return zero, err + } v := &testhttpgrpc.GrpcNoStreamPayload{ - Msg: message.Msg, + Msg: *message.Msg, } return v, nil @@ -43,15 +47,19 @@ func BuildGrpcNoStreamErrorDivByZeroPayload(testHTTPGrpcGrpcNoStreamErrorDivByZe var message test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest { if testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage != "" { - err = json.Unmarshal([]byte(testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage), &message) + err = protojson.Unmarshal([]byte(testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 512.7504790865268,\n \"divisor\": -75.50495230788519\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 238.4063147460167,\n \"divisor\": -73.4004594816786\n }'") } } } + if err := ValidateGrpcNoStreamErrorDivByZeroRequest(&message); err != nil { + var zero *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload + return zero, err + } v := &testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload{ - Dividend: message.Dividend, - Divisor: message.Divisor, + Dividend: *message.Dividend, + Divisor: *message.Divisor, } return v, nil @@ -64,14 +72,18 @@ func BuildGrpcServerStreamPayload(testHTTPGrpcGrpcServerStreamMessage string) (* var message test_http_grpcpb.GrpcServerStreamRequest { if testHTTPGrpcGrpcServerStreamMessage != "" { - err = json.Unmarshal([]byte(testHTTPGrpcGrpcServerStreamMessage), &message) + err = protojson.Unmarshal([]byte(testHTTPGrpcGrpcServerStreamMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"from\": 417059585657762780\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"from\": 436641374502740749\n }'") } } } + if err := ValidateGrpcServerStreamRequest(&message); err != nil { + var zero *testhttpgrpc.GrpcServerStreamPayload + return zero, err + } v := &testhttpgrpc.GrpcServerStreamPayload{ - From: int(message.From), + From: int(*message.From), } return v, nil @@ -84,14 +96,18 @@ func BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamMessage string) (*testht var message test_http_grpcpb.MixedNoStreamRequest { if testHTTPGrpcMixedNoStreamMessage != "" { - err = json.Unmarshal([]byte(testHTTPGrpcMixedNoStreamMessage), &message) + err = protojson.Unmarshal([]byte(testHTTPGrpcMixedNoStreamMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Commodi optio.\"\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Ut non id provident tempora suscipit quo.\"\n }'") } } } + if err := ValidateMixedNoStreamRequest(&message); err != nil { + var zero *testhttpgrpc.MixedNoStreamPayload + return zero, err + } v := &testhttpgrpc.MixedNoStreamPayload{ - Msg: message.Msg, + Msg: *message.Msg, } return v, nil diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go index 0ca172b1b..964e045cb 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go @@ -104,6 +104,9 @@ func (c *Client) GrpcNoStreamErrorDivByZero() goa.Endpoint { resp := goagrpc.DecodeError(err) switch message := resp.(type) { case *test_http_grpcpb.GrpcNoStreamErrorDivByZeroDivisionByZeroError: + if err := ValidateGrpcNoStreamErrorDivByZeroDivisionByZeroError(message); err != nil { + return nil, err + } return nil, NewGrpcNoStreamErrorDivByZeroDivisionByZeroError(message) case *goapb.ErrorResponse: return nil, goagrpc.NewServiceError(message) @@ -270,6 +273,9 @@ func (s *GrpcServerStreamClientStream) Recv() (*testhttpgrpc.GrpcServerStreamRes if err != nil { return res, err } + if err = ValidateGrpcServerStreamResponse(v); err != nil { + return res, err + } return NewGrpcServerStreamResponseGrpcServerStreamResult(v), nil } @@ -288,6 +294,9 @@ func (s *GrpcClientStreamClientStream) CloseAndRecv() (*testhttpgrpc.GrpcClientS if err != nil { return res, err } + if err = ValidateGrpcClientStreamResponse(v); err != nil { + return res, err + } return NewGrpcClientStreamResponseGrpcClientStreamResult(v), nil } @@ -321,6 +330,9 @@ func (s *GrpcBidiStreamClientStream) Recv() (*testhttpgrpc.GrpcBidiStreamResult, if err != nil { return res, err } + if err = ValidateGrpcBidiStreamResponse(v); err != nil { + return res, err + } return NewGrpcBidiStreamResponseGrpcBidiStreamResult(v), nil } @@ -357,6 +369,9 @@ func (s *MixedServerStreamClientStream) Recv() (*testhttpgrpc.MixedServerStreamR if err != nil { return res, err } + if err = ValidateMixedServerStreamResponse(v); err != nil { + return res, err + } return NewMixedServerStreamResponseMixedServerStreamResult(v), nil } @@ -376,6 +391,9 @@ func (s *MixedClientStreamWsGrpcClientStream) CloseAndRecv() (*testhttpgrpc.Mixe if err != nil { return res, err } + if err = ValidateMixedClientStreamWsGrpcResponse(v); err != nil { + return res, err + } return NewMixedClientStreamWsGrpcResponseMixedClientStreamWsGrpcResult(v), nil } @@ -409,6 +427,9 @@ func (s *MixedBidiStreamWsGrpcClientStream) Recv() (*testhttpgrpc.MixedBidiStrea if err != nil { return res, err } + if err = ValidateMixedBidiStreamWsGrpcResponse(v); err != nil { + return res, err + } return NewMixedBidiStreamWsGrpcResponseMixedBidiStreamWsGrpcResult(v), nil } diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go index 6b4aaceb2..1d9a6d033 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go @@ -49,6 +49,9 @@ func DecodeGrpcNoStreamResponse(ctx context.Context, v any, hdr, trlr metadata.M if !ok { return nil, goagrpc.ErrInvalidType("test-http-grpc", "grpc_no_stream", "*test_http_grpcpb.GrpcNoStreamResponse", v) } + if err := ValidateGrpcNoStreamResponse(message); err != nil { + return nil, err + } res := NewGrpcNoStreamResult(message) return res, nil } @@ -84,6 +87,9 @@ func DecodeGrpcNoStreamErrorDivByZeroResponse(ctx context.Context, v any, hdr, t if !ok { return nil, goagrpc.ErrInvalidType("test-http-grpc", "grpc_no_stream_error_div_by_zero", "*test_http_grpcpb.GrpcNoStreamErrorDivByZeroResponse", v) } + if err := ValidateGrpcNoStreamErrorDivByZeroResponse(message); err != nil { + return nil, err + } res := NewGrpcNoStreamErrorDivByZeroResult(message) return res, nil } @@ -195,6 +201,9 @@ func DecodeMixedNoStreamResponse(ctx context.Context, v any, hdr, trlr metadata. if !ok { return nil, goagrpc.ErrInvalidType("test-http-grpc", "mixed_no_stream", "*test_http_grpcpb.MixedNoStreamResponse", v) } + if err := ValidateMixedNoStreamResponse(message); err != nil { + return nil, err + } res := NewMixedNoStreamResult(message) return res, nil } diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/types.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/types.go index 6836d704f..e808e72c3 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/types.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/types.go @@ -9,160 +9,336 @@ package client import ( + goa "goa.design/goa/v3/pkg" test_http_grpcpb "goa.design/plugins/v3/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb" testhttpgrpc "goa.design/plugins/v3/testing/examples/httpgrpc/gen/test_http_grpc" ) -// NewProtoGrpcNoStreamRequest builds the gRPC request type from the payload of -// the "grpc_no_stream" endpoint of the "test-http-grpc" service. +// NewProtoGrpcNoStreamRequest builds *test_http_grpcpb.GrpcNoStreamRequest +// from *testhttpgrpc.GrpcNoStreamPayload. func NewProtoGrpcNoStreamRequest(payload *testhttpgrpc.GrpcNoStreamPayload) *test_http_grpcpb.GrpcNoStreamRequest { message := &test_http_grpcpb.GrpcNoStreamRequest{ - Msg: payload.Msg, + Msg: &payload.Msg, } return message } -// NewGrpcNoStreamResult builds the result type of the "grpc_no_stream" -// endpoint of the "test-http-grpc" service from the gRPC response type. +// NewGrpcNoStreamResult builds *testhttpgrpc.GrpcNoStreamResult from +// *test_http_grpcpb.GrpcNoStreamResponse. func NewGrpcNoStreamResult(message *test_http_grpcpb.GrpcNoStreamResponse) *testhttpgrpc.GrpcNoStreamResult { result := &testhttpgrpc.GrpcNoStreamResult{ - Out: message.Out, + Out: *message.Out, } return result } -// NewProtoGrpcNoStreamErrorDivByZeroRequest builds the gRPC request type from -// the payload of the "grpc_no_stream_error_div_by_zero" endpoint of the -// "test-http-grpc" service. +// NewProtoGrpcNoStreamErrorDivByZeroRequest builds +// *test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest from +// *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload. func NewProtoGrpcNoStreamErrorDivByZeroRequest(payload *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload) *test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest { message := &test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest{ - Dividend: payload.Dividend, - Divisor: payload.Divisor, + Dividend: &payload.Dividend, + Divisor: &payload.Divisor, } return message } -// NewGrpcNoStreamErrorDivByZeroResult builds the result type of the -// "grpc_no_stream_error_div_by_zero" endpoint of the "test-http-grpc" service -// from the gRPC response type. +// NewGrpcNoStreamErrorDivByZeroResult builds +// *testhttpgrpc.GrpcNoStreamErrorDivByZeroResult from +// *test_http_grpcpb.GrpcNoStreamErrorDivByZeroResponse. func NewGrpcNoStreamErrorDivByZeroResult(message *test_http_grpcpb.GrpcNoStreamErrorDivByZeroResponse) *testhttpgrpc.GrpcNoStreamErrorDivByZeroResult { result := &testhttpgrpc.GrpcNoStreamErrorDivByZeroResult{ - Quotient: message.Quotient, + Quotient: *message.Quotient, } return result } -// NewGrpcNoStreamErrorDivByZeroDivisionByZeroError builds the error type of -// the "grpc_no_stream_error_div_by_zero" endpoint of the "test-http-grpc" -// service from the gRPC error response type. +// NewGrpcNoStreamErrorDivByZeroDivisionByZeroError builds +// *testhttpgrpc.DivisionByZeroError from +// *test_http_grpcpb.GrpcNoStreamErrorDivByZeroDivisionByZeroError. func NewGrpcNoStreamErrorDivByZeroDivisionByZeroError(message *test_http_grpcpb.GrpcNoStreamErrorDivByZeroDivisionByZeroError) *testhttpgrpc.DivisionByZeroError { er := &testhttpgrpc.DivisionByZeroError{ - Message: message.Message_, + Message: *message.Message_, } return er } -// NewProtoGrpcServerStreamRequest builds the gRPC request type from the -// payload of the "grpc_server_stream" endpoint of the "test-http-grpc" service. +// NewProtoGrpcServerStreamRequest builds +// *test_http_grpcpb.GrpcServerStreamRequest from +// *testhttpgrpc.GrpcServerStreamPayload. func NewProtoGrpcServerStreamRequest(payload *testhttpgrpc.GrpcServerStreamPayload) *test_http_grpcpb.GrpcServerStreamRequest { - message := &test_http_grpcpb.GrpcServerStreamRequest{ - From: int32(payload.From), - } + message := &test_http_grpcpb.GrpcServerStreamRequest{} + from := int32(payload.From) + message.From = &from return message } +// NewGrpcServerStreamResponseGrpcServerStreamResult builds +// *testhttpgrpc.GrpcServerStreamResult from +// *test_http_grpcpb.GrpcServerStreamResponse. func NewGrpcServerStreamResponseGrpcServerStreamResult(v *test_http_grpcpb.GrpcServerStreamResponse) *testhttpgrpc.GrpcServerStreamResult { result := &testhttpgrpc.GrpcServerStreamResult{ - Value: int(v.Value), + Value: int(*v.Value), } return result } +// NewGrpcClientStreamResponseGrpcClientStreamResult builds +// *testhttpgrpc.GrpcClientStreamResult from +// *test_http_grpcpb.GrpcClientStreamResponse. func NewGrpcClientStreamResponseGrpcClientStreamResult(v *test_http_grpcpb.GrpcClientStreamResponse) *testhttpgrpc.GrpcClientStreamResult { result := &testhttpgrpc.GrpcClientStreamResult{ - Sum: int(v.Sum), + Sum: int(*v.Sum), } return result } +// NewProtoGrpcClientStreamStreamingPayloadGrpcClientStreamStreamingRequest +// builds *test_http_grpcpb.GrpcClientStreamStreamingRequest from +// *testhttpgrpc.GrpcClientStreamStreamingPayload. func NewProtoGrpcClientStreamStreamingPayloadGrpcClientStreamStreamingRequest(spayload *testhttpgrpc.GrpcClientStreamStreamingPayload) *test_http_grpcpb.GrpcClientStreamStreamingRequest { - v := &test_http_grpcpb.GrpcClientStreamStreamingRequest{ - Value: int32(spayload.Value), - } + v := &test_http_grpcpb.GrpcClientStreamStreamingRequest{} + value := int32(spayload.Value) + v.Value = &value return v } +// NewGrpcBidiStreamResponseGrpcBidiStreamResult builds +// *testhttpgrpc.GrpcBidiStreamResult from +// *test_http_grpcpb.GrpcBidiStreamResponse. func NewGrpcBidiStreamResponseGrpcBidiStreamResult(v *test_http_grpcpb.GrpcBidiStreamResponse) *testhttpgrpc.GrpcBidiStreamResult { result := &testhttpgrpc.GrpcBidiStreamResult{ - Out: v.Out, + Out: *v.Out, } return result } +// NewProtoGrpcBidiStreamStreamingPayloadGrpcBidiStreamStreamingRequest builds +// *test_http_grpcpb.GrpcBidiStreamStreamingRequest from +// *testhttpgrpc.GrpcBidiStreamStreamingPayload. func NewProtoGrpcBidiStreamStreamingPayloadGrpcBidiStreamStreamingRequest(spayload *testhttpgrpc.GrpcBidiStreamStreamingPayload) *test_http_grpcpb.GrpcBidiStreamStreamingRequest { v := &test_http_grpcpb.GrpcBidiStreamStreamingRequest{ - In: spayload.In, + In: &spayload.In, } return v } -// NewProtoMixedNoStreamRequest builds the gRPC request type from the payload -// of the "mixed_no_stream" endpoint of the "test-http-grpc" service. +// NewProtoMixedNoStreamRequest builds *test_http_grpcpb.MixedNoStreamRequest +// from *testhttpgrpc.MixedNoStreamPayload. func NewProtoMixedNoStreamRequest(payload *testhttpgrpc.MixedNoStreamPayload) *test_http_grpcpb.MixedNoStreamRequest { message := &test_http_grpcpb.MixedNoStreamRequest{ - Msg: payload.Msg, + Msg: &payload.Msg, } return message } -// NewMixedNoStreamResult builds the result type of the "mixed_no_stream" -// endpoint of the "test-http-grpc" service from the gRPC response type. +// NewMixedNoStreamResult builds *testhttpgrpc.MixedNoStreamResult from +// *test_http_grpcpb.MixedNoStreamResponse. func NewMixedNoStreamResult(message *test_http_grpcpb.MixedNoStreamResponse) *testhttpgrpc.MixedNoStreamResult { result := &testhttpgrpc.MixedNoStreamResult{ - Out: message.Out, + Out: *message.Out, } return result } -// NewProtoMixedServerStreamRequest builds the gRPC request type from the -// payload of the "mixed_server_stream" endpoint of the "test-http-grpc" -// service. +// NewProtoMixedServerStreamRequest builds +// *test_http_grpcpb.MixedServerStreamRequest from metadata values. func NewProtoMixedServerStreamRequest() *test_http_grpcpb.MixedServerStreamRequest { message := &test_http_grpcpb.MixedServerStreamRequest{} return message } +// NewMixedServerStreamResponseMixedServerStreamResult builds +// *testhttpgrpc.MixedServerStreamResult from +// *test_http_grpcpb.MixedServerStreamResponse. func NewMixedServerStreamResponseMixedServerStreamResult(v *test_http_grpcpb.MixedServerStreamResponse) *testhttpgrpc.MixedServerStreamResult { result := &testhttpgrpc.MixedServerStreamResult{ - Event: v.Event, + Event: *v.Event, } return result } +// NewMixedClientStreamWsGrpcResponseMixedClientStreamWsGrpcResult builds +// *testhttpgrpc.MixedClientStreamWsGrpcResult from +// *test_http_grpcpb.MixedClientStreamWsGrpcResponse. func NewMixedClientStreamWsGrpcResponseMixedClientStreamWsGrpcResult(v *test_http_grpcpb.MixedClientStreamWsGrpcResponse) *testhttpgrpc.MixedClientStreamWsGrpcResult { result := &testhttpgrpc.MixedClientStreamWsGrpcResult{ - Out: v.Out, + Out: *v.Out, } return result } +// NewProtoMixedClientStreamWsGrpcStreamingPayloadMixedClientStreamWsGrpcStreamingRequest +// builds *test_http_grpcpb.MixedClientStreamWsGrpcStreamingRequest from +// *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload. func NewProtoMixedClientStreamWsGrpcStreamingPayloadMixedClientStreamWsGrpcStreamingRequest(spayload *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload) *test_http_grpcpb.MixedClientStreamWsGrpcStreamingRequest { v := &test_http_grpcpb.MixedClientStreamWsGrpcStreamingRequest{ - Message_: spayload.Message, + Message_: &spayload.Message, } return v } +// NewMixedBidiStreamWsGrpcResponseMixedBidiStreamWsGrpcResult builds +// *testhttpgrpc.MixedBidiStreamWsGrpcResult from +// *test_http_grpcpb.MixedBidiStreamWsGrpcResponse. func NewMixedBidiStreamWsGrpcResponseMixedBidiStreamWsGrpcResult(v *test_http_grpcpb.MixedBidiStreamWsGrpcResponse) *testhttpgrpc.MixedBidiStreamWsGrpcResult { result := &testhttpgrpc.MixedBidiStreamWsGrpcResult{ - Message: v.Message_, + Message: *v.Message_, } return result } +// NewProtoMixedBidiStreamWsGrpcStreamingPayloadMixedBidiStreamWsGrpcStreamingRequest +// builds *test_http_grpcpb.MixedBidiStreamWsGrpcStreamingRequest from +// *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload. func NewProtoMixedBidiStreamWsGrpcStreamingPayloadMixedBidiStreamWsGrpcStreamingRequest(spayload *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload) *test_http_grpcpb.MixedBidiStreamWsGrpcStreamingRequest { v := &test_http_grpcpb.MixedBidiStreamWsGrpcStreamingRequest{ - Message_: spayload.Message, + Message_: &spayload.Message, } return v } + +// ValidateGrpcNoStreamRequest runs the validations defined on +// GrpcNoStreamRequest. +func ValidateGrpcNoStreamRequest(message *test_http_grpcpb.GrpcNoStreamRequest) (err error) { + if message.Msg == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("msg", "message")) + } + return +} + +// ValidateGrpcNoStreamResponse runs the validations defined on +// GrpcNoStreamResponse. +func ValidateGrpcNoStreamResponse(message *test_http_grpcpb.GrpcNoStreamResponse) (err error) { + if message.Out == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("out", "message")) + } + return +} + +// ValidateGrpcNoStreamErrorDivByZeroRequest runs the validations defined on +// GrpcNoStreamErrorDivByZeroRequest. +func ValidateGrpcNoStreamErrorDivByZeroRequest(message *test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest) (err error) { + if message.Dividend == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("dividend", "message")) + } + if message.Divisor == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("divisor", "message")) + } + if message.Dividend != nil { + if *message.Dividend < -1000 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.dividend", *message.Dividend, -1000, true)) + } + if *message.Dividend > 1000 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.dividend", *message.Dividend, 1000, false)) + } + } + if message.Divisor != nil { + if *message.Divisor < -100 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.divisor", *message.Divisor, -100, true)) + } + if *message.Divisor > 100 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.divisor", *message.Divisor, 100, false)) + } + } + return +} + +// ValidateGrpcNoStreamErrorDivByZeroResponse runs the validations defined on +// GrpcNoStreamErrorDivByZeroResponse. +func ValidateGrpcNoStreamErrorDivByZeroResponse(message *test_http_grpcpb.GrpcNoStreamErrorDivByZeroResponse) (err error) { + if message.Quotient == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("quotient", "message")) + } + return +} + +// ValidateGrpcNoStreamErrorDivByZeroDivisionByZeroError runs the validations +// defined on GrpcNoStreamErrorDivByZeroDivisionByZeroError. +func ValidateGrpcNoStreamErrorDivByZeroDivisionByZeroError(errmsg *test_http_grpcpb.GrpcNoStreamErrorDivByZeroDivisionByZeroError) (err error) { + if errmsg.Message_ == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "errmsg")) + } + return +} + +// ValidateGrpcServerStreamRequest runs the validations defined on +// GrpcServerStreamRequest. +func ValidateGrpcServerStreamRequest(message *test_http_grpcpb.GrpcServerStreamRequest) (err error) { + if message.From == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("from", "message")) + } + return +} + +// ValidateGrpcServerStreamResponse runs the validations defined on +// GrpcServerStreamResponse. +func ValidateGrpcServerStreamResponse(message *test_http_grpcpb.GrpcServerStreamResponse) (err error) { + if message.Value == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("value", "message")) + } + return +} + +// ValidateGrpcClientStreamResponse runs the validations defined on +// GrpcClientStreamResponse. +func ValidateGrpcClientStreamResponse(message *test_http_grpcpb.GrpcClientStreamResponse) (err error) { + if message.Sum == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("sum", "message")) + } + return +} + +// ValidateGrpcBidiStreamResponse runs the validations defined on +// GrpcBidiStreamResponse. +func ValidateGrpcBidiStreamResponse(message *test_http_grpcpb.GrpcBidiStreamResponse) (err error) { + if message.Out == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("out", "message")) + } + return +} + +// ValidateMixedNoStreamRequest runs the validations defined on +// MixedNoStreamRequest. +func ValidateMixedNoStreamRequest(message *test_http_grpcpb.MixedNoStreamRequest) (err error) { + if message.Msg == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("msg", "message")) + } + return +} + +// ValidateMixedNoStreamResponse runs the validations defined on +// MixedNoStreamResponse. +func ValidateMixedNoStreamResponse(message *test_http_grpcpb.MixedNoStreamResponse) (err error) { + if message.Out == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("out", "message")) + } + return +} + +// ValidateMixedServerStreamResponse runs the validations defined on +// MixedServerStreamResponse. +func ValidateMixedServerStreamResponse(message *test_http_grpcpb.MixedServerStreamResponse) (err error) { + if message.Event == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("event", "message")) + } + return +} + +// ValidateMixedClientStreamWsGrpcResponse runs the validations defined on +// MixedClientStreamWsGrpcResponse. +func ValidateMixedClientStreamWsGrpcResponse(message *test_http_grpcpb.MixedClientStreamWsGrpcResponse) (err error) { + if message.Out == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("out", "message")) + } + return +} + +// ValidateMixedBidiStreamWsGrpcResponse runs the validations defined on +// MixedBidiStreamWsGrpcResponse. +func ValidateMixedBidiStreamWsGrpcResponse(message *test_http_grpcpb.MixedBidiStreamWsGrpcResponse) (err error) { + if message.Message_ == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "message")) + } + return +} diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.pb.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.pb.go index ec607b9e8..7b9cce64d 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.pb.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.pb.go @@ -8,8 +8,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc-gen-go v1.36.12 +// protoc v7.36.0 // source: goagen_httpgrpc_test_http_grpc.proto package test_http_grpcpb @@ -31,7 +31,7 @@ const ( type GrpcNoStreamRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` + Msg *string `protobuf:"bytes,1,opt,name=msg,proto3,oneof" json:"msg,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -67,15 +67,15 @@ func (*GrpcNoStreamRequest) Descriptor() ([]byte, []int) { } func (x *GrpcNoStreamRequest) GetMsg() string { - if x != nil { - return x.Msg + if x != nil && x.Msg != nil { + return *x.Msg } return "" } type GrpcNoStreamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Out string `protobuf:"bytes,1,opt,name=out,proto3" json:"out,omitempty"` + Out *string `protobuf:"bytes,1,opt,name=out,proto3,oneof" json:"out,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -111,8 +111,8 @@ func (*GrpcNoStreamResponse) Descriptor() ([]byte, []int) { } func (x *GrpcNoStreamResponse) GetOut() string { - if x != nil { - return x.Out + if x != nil && x.Out != nil { + return *x.Out } return "" } @@ -120,7 +120,7 @@ func (x *GrpcNoStreamResponse) GetOut() string { type GrpcNoStreamErrorDivByZeroDivisionByZeroError struct { state protoimpl.MessageState `protogen:"open.v1"` // division by zero error message - Message_ string `protobuf:"bytes,1,opt,name=message_,json=message,proto3" json:"message_,omitempty"` + Message_ *string `protobuf:"bytes,1,opt,name=message_,json=message,proto3,oneof" json:"message_,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -156,16 +156,16 @@ func (*GrpcNoStreamErrorDivByZeroDivisionByZeroError) Descriptor() ([]byte, []in } func (x *GrpcNoStreamErrorDivByZeroDivisionByZeroError) GetMessage_() string { - if x != nil { - return x.Message_ + if x != nil && x.Message_ != nil { + return *x.Message_ } return "" } type GrpcNoStreamErrorDivByZeroRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Dividend float64 `protobuf:"fixed64,1,opt,name=dividend,proto3" json:"dividend,omitempty"` - Divisor float64 `protobuf:"fixed64,2,opt,name=divisor,proto3" json:"divisor,omitempty"` + Dividend *float64 `protobuf:"fixed64,1,opt,name=dividend,proto3,oneof" json:"dividend,omitempty"` + Divisor *float64 `protobuf:"fixed64,2,opt,name=divisor,proto3,oneof" json:"divisor,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -201,22 +201,22 @@ func (*GrpcNoStreamErrorDivByZeroRequest) Descriptor() ([]byte, []int) { } func (x *GrpcNoStreamErrorDivByZeroRequest) GetDividend() float64 { - if x != nil { - return x.Dividend + if x != nil && x.Dividend != nil { + return *x.Dividend } return 0 } func (x *GrpcNoStreamErrorDivByZeroRequest) GetDivisor() float64 { - if x != nil { - return x.Divisor + if x != nil && x.Divisor != nil { + return *x.Divisor } return 0 } type GrpcNoStreamErrorDivByZeroResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Quotient float64 `protobuf:"fixed64,1,opt,name=quotient,proto3" json:"quotient,omitempty"` + Quotient *float64 `protobuf:"fixed64,1,opt,name=quotient,proto3,oneof" json:"quotient,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -252,15 +252,15 @@ func (*GrpcNoStreamErrorDivByZeroResponse) Descriptor() ([]byte, []int) { } func (x *GrpcNoStreamErrorDivByZeroResponse) GetQuotient() float64 { - if x != nil { - return x.Quotient + if x != nil && x.Quotient != nil { + return *x.Quotient } return 0 } type GrpcServerStreamRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - From int32 `protobuf:"zigzag32,1,opt,name=from,proto3" json:"from,omitempty"` + From *int32 `protobuf:"zigzag32,1,opt,name=from,proto3,oneof" json:"from,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -296,15 +296,15 @@ func (*GrpcServerStreamRequest) Descriptor() ([]byte, []int) { } func (x *GrpcServerStreamRequest) GetFrom() int32 { - if x != nil { - return x.From + if x != nil && x.From != nil { + return *x.From } return 0 } type GrpcServerStreamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Value int32 `protobuf:"zigzag32,1,opt,name=value,proto3" json:"value,omitempty"` + Value *int32 `protobuf:"zigzag32,1,opt,name=value,proto3,oneof" json:"value,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -340,15 +340,15 @@ func (*GrpcServerStreamResponse) Descriptor() ([]byte, []int) { } func (x *GrpcServerStreamResponse) GetValue() int32 { - if x != nil { - return x.Value + if x != nil && x.Value != nil { + return *x.Value } return 0 } type GrpcClientStreamStreamingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Value int32 `protobuf:"zigzag32,1,opt,name=value,proto3" json:"value,omitempty"` + Value *int32 `protobuf:"zigzag32,1,opt,name=value,proto3,oneof" json:"value,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -384,15 +384,15 @@ func (*GrpcClientStreamStreamingRequest) Descriptor() ([]byte, []int) { } func (x *GrpcClientStreamStreamingRequest) GetValue() int32 { - if x != nil { - return x.Value + if x != nil && x.Value != nil { + return *x.Value } return 0 } type GrpcClientStreamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Sum int32 `protobuf:"zigzag32,1,opt,name=sum,proto3" json:"sum,omitempty"` + Sum *int32 `protobuf:"zigzag32,1,opt,name=sum,proto3,oneof" json:"sum,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -428,15 +428,15 @@ func (*GrpcClientStreamResponse) Descriptor() ([]byte, []int) { } func (x *GrpcClientStreamResponse) GetSum() int32 { - if x != nil { - return x.Sum + if x != nil && x.Sum != nil { + return *x.Sum } return 0 } type GrpcBidiStreamStreamingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - In string `protobuf:"bytes,1,opt,name=in,proto3" json:"in,omitempty"` + In *string `protobuf:"bytes,1,opt,name=in,proto3,oneof" json:"in,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -472,15 +472,15 @@ func (*GrpcBidiStreamStreamingRequest) Descriptor() ([]byte, []int) { } func (x *GrpcBidiStreamStreamingRequest) GetIn() string { - if x != nil { - return x.In + if x != nil && x.In != nil { + return *x.In } return "" } type GrpcBidiStreamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Out string `protobuf:"bytes,1,opt,name=out,proto3" json:"out,omitempty"` + Out *string `protobuf:"bytes,1,opt,name=out,proto3,oneof" json:"out,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -516,15 +516,15 @@ func (*GrpcBidiStreamResponse) Descriptor() ([]byte, []int) { } func (x *GrpcBidiStreamResponse) GetOut() string { - if x != nil { - return x.Out + if x != nil && x.Out != nil { + return *x.Out } return "" } type MixedNoStreamRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` + Msg *string `protobuf:"bytes,1,opt,name=msg,proto3,oneof" json:"msg,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -560,15 +560,15 @@ func (*MixedNoStreamRequest) Descriptor() ([]byte, []int) { } func (x *MixedNoStreamRequest) GetMsg() string { - if x != nil { - return x.Msg + if x != nil && x.Msg != nil { + return *x.Msg } return "" } type MixedNoStreamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Out string `protobuf:"bytes,1,opt,name=out,proto3" json:"out,omitempty"` + Out *string `protobuf:"bytes,1,opt,name=out,proto3,oneof" json:"out,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -604,8 +604,8 @@ func (*MixedNoStreamResponse) Descriptor() ([]byte, []int) { } func (x *MixedNoStreamResponse) GetOut() string { - if x != nil { - return x.Out + if x != nil && x.Out != nil { + return *x.Out } return "" } @@ -648,7 +648,7 @@ func (*MixedServerStreamRequest) Descriptor() ([]byte, []int) { type MixedServerStreamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Event string `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"` + Event *string `protobuf:"bytes,1,opt,name=event,proto3,oneof" json:"event,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -684,15 +684,15 @@ func (*MixedServerStreamResponse) Descriptor() ([]byte, []int) { } func (x *MixedServerStreamResponse) GetEvent() string { - if x != nil { - return x.Event + if x != nil && x.Event != nil { + return *x.Event } return "" } type MixedClientStreamWsGrpcStreamingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Message_ string `protobuf:"bytes,1,opt,name=message_,json=message,proto3" json:"message_,omitempty"` + Message_ *string `protobuf:"bytes,1,opt,name=message_,json=message,proto3,oneof" json:"message_,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -728,15 +728,15 @@ func (*MixedClientStreamWsGrpcStreamingRequest) Descriptor() ([]byte, []int) { } func (x *MixedClientStreamWsGrpcStreamingRequest) GetMessage_() string { - if x != nil { - return x.Message_ + if x != nil && x.Message_ != nil { + return *x.Message_ } return "" } type MixedClientStreamWsGrpcResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Out string `protobuf:"bytes,1,opt,name=out,proto3" json:"out,omitempty"` + Out *string `protobuf:"bytes,1,opt,name=out,proto3,oneof" json:"out,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -772,15 +772,15 @@ func (*MixedClientStreamWsGrpcResponse) Descriptor() ([]byte, []int) { } func (x *MixedClientStreamWsGrpcResponse) GetOut() string { - if x != nil { - return x.Out + if x != nil && x.Out != nil { + return *x.Out } return "" } type MixedBidiStreamWsGrpcStreamingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Message_ string `protobuf:"bytes,1,opt,name=message_,json=message,proto3" json:"message_,omitempty"` + Message_ *string `protobuf:"bytes,1,opt,name=message_,json=message,proto3,oneof" json:"message_,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -816,15 +816,15 @@ func (*MixedBidiStreamWsGrpcStreamingRequest) Descriptor() ([]byte, []int) { } func (x *MixedBidiStreamWsGrpcStreamingRequest) GetMessage_() string { - if x != nil { - return x.Message_ + if x != nil && x.Message_ != nil { + return *x.Message_ } return "" } type MixedBidiStreamWsGrpcResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Message_ string `protobuf:"bytes,1,opt,name=message_,json=message,proto3" json:"message_,omitempty"` + Message_ *string `protobuf:"bytes,1,opt,name=message_,json=message,proto3,oneof" json:"message_,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -860,8 +860,8 @@ func (*MixedBidiStreamWsGrpcResponse) Descriptor() ([]byte, []int) { } func (x *MixedBidiStreamWsGrpcResponse) GetMessage_() string { - if x != nil { - return x.Message_ + if x != nil && x.Message_ != nil { + return *x.Message_ } return "" } @@ -870,45 +870,65 @@ var File_goagen_httpgrpc_test_http_grpc_proto protoreflect.FileDescriptor const file_goagen_httpgrpc_test_http_grpc_proto_rawDesc = "" + "\n" + - "$goagen_httpgrpc_test_http_grpc.proto\x12\x0etest_http_grpc\"'\n" + - "\x13GrpcNoStreamRequest\x12\x10\n" + - "\x03msg\x18\x01 \x01(\tR\x03msg\"(\n" + - "\x14GrpcNoStreamResponse\x12\x10\n" + - "\x03out\x18\x01 \x01(\tR\x03out\"J\n" + - "-GrpcNoStreamErrorDivByZeroDivisionByZeroError\x12\x19\n" + - "\bmessage_\x18\x01 \x01(\tR\amessage\"Y\n" + - "!GrpcNoStreamErrorDivByZeroRequest\x12\x1a\n" + - "\bdividend\x18\x01 \x01(\x01R\bdividend\x12\x18\n" + - "\adivisor\x18\x02 \x01(\x01R\adivisor\"@\n" + - "\"GrpcNoStreamErrorDivByZeroResponse\x12\x1a\n" + - "\bquotient\x18\x01 \x01(\x01R\bquotient\"-\n" + - "\x17GrpcServerStreamRequest\x12\x12\n" + - "\x04from\x18\x01 \x01(\x11R\x04from\"0\n" + - "\x18GrpcServerStreamResponse\x12\x14\n" + - "\x05value\x18\x01 \x01(\x11R\x05value\"8\n" + - " GrpcClientStreamStreamingRequest\x12\x14\n" + - "\x05value\x18\x01 \x01(\x11R\x05value\",\n" + - "\x18GrpcClientStreamResponse\x12\x10\n" + - "\x03sum\x18\x01 \x01(\x11R\x03sum\"0\n" + - "\x1eGrpcBidiStreamStreamingRequest\x12\x0e\n" + - "\x02in\x18\x01 \x01(\tR\x02in\"*\n" + - "\x16GrpcBidiStreamResponse\x12\x10\n" + - "\x03out\x18\x01 \x01(\tR\x03out\"(\n" + - "\x14MixedNoStreamRequest\x12\x10\n" + - "\x03msg\x18\x01 \x01(\tR\x03msg\")\n" + - "\x15MixedNoStreamResponse\x12\x10\n" + - "\x03out\x18\x01 \x01(\tR\x03out\"\x1a\n" + - "\x18MixedServerStreamRequest\"1\n" + - "\x19MixedServerStreamResponse\x12\x14\n" + - "\x05event\x18\x01 \x01(\tR\x05event\"D\n" + - "'MixedClientStreamWsGrpcStreamingRequest\x12\x19\n" + - "\bmessage_\x18\x01 \x01(\tR\amessage\"3\n" + - "\x1fMixedClientStreamWsGrpcResponse\x12\x10\n" + - "\x03out\x18\x01 \x01(\tR\x03out\"B\n" + - "%MixedBidiStreamWsGrpcStreamingRequest\x12\x19\n" + - "\bmessage_\x18\x01 \x01(\tR\amessage\":\n" + - "\x1dMixedBidiStreamWsGrpcResponse\x12\x19\n" + - "\bmessage_\x18\x01 \x01(\tR\amessage2\x8e\b\n" + + "$goagen_httpgrpc_test_http_grpc.proto\x12\x0etest_http_grpc\"4\n" + + "\x13GrpcNoStreamRequest\x12\x15\n" + + "\x03msg\x18\x01 \x01(\tH\x00R\x03msg\x88\x01\x01B\x06\n" + + "\x04_msg\"5\n" + + "\x14GrpcNoStreamResponse\x12\x15\n" + + "\x03out\x18\x01 \x01(\tH\x00R\x03out\x88\x01\x01B\x06\n" + + "\x04_out\"\\\n" + + "-GrpcNoStreamErrorDivByZeroDivisionByZeroError\x12\x1e\n" + + "\bmessage_\x18\x01 \x01(\tH\x00R\amessage\x88\x01\x01B\v\n" + + "\t_message_\"|\n" + + "!GrpcNoStreamErrorDivByZeroRequest\x12\x1f\n" + + "\bdividend\x18\x01 \x01(\x01H\x00R\bdividend\x88\x01\x01\x12\x1d\n" + + "\adivisor\x18\x02 \x01(\x01H\x01R\adivisor\x88\x01\x01B\v\n" + + "\t_dividendB\n" + + "\n" + + "\b_divisor\"R\n" + + "\"GrpcNoStreamErrorDivByZeroResponse\x12\x1f\n" + + "\bquotient\x18\x01 \x01(\x01H\x00R\bquotient\x88\x01\x01B\v\n" + + "\t_quotient\";\n" + + "\x17GrpcServerStreamRequest\x12\x17\n" + + "\x04from\x18\x01 \x01(\x11H\x00R\x04from\x88\x01\x01B\a\n" + + "\x05_from\"?\n" + + "\x18GrpcServerStreamResponse\x12\x19\n" + + "\x05value\x18\x01 \x01(\x11H\x00R\x05value\x88\x01\x01B\b\n" + + "\x06_value\"G\n" + + " GrpcClientStreamStreamingRequest\x12\x19\n" + + "\x05value\x18\x01 \x01(\x11H\x00R\x05value\x88\x01\x01B\b\n" + + "\x06_value\"9\n" + + "\x18GrpcClientStreamResponse\x12\x15\n" + + "\x03sum\x18\x01 \x01(\x11H\x00R\x03sum\x88\x01\x01B\x06\n" + + "\x04_sum\"<\n" + + "\x1eGrpcBidiStreamStreamingRequest\x12\x13\n" + + "\x02in\x18\x01 \x01(\tH\x00R\x02in\x88\x01\x01B\x05\n" + + "\x03_in\"7\n" + + "\x16GrpcBidiStreamResponse\x12\x15\n" + + "\x03out\x18\x01 \x01(\tH\x00R\x03out\x88\x01\x01B\x06\n" + + "\x04_out\"5\n" + + "\x14MixedNoStreamRequest\x12\x15\n" + + "\x03msg\x18\x01 \x01(\tH\x00R\x03msg\x88\x01\x01B\x06\n" + + "\x04_msg\"6\n" + + "\x15MixedNoStreamResponse\x12\x15\n" + + "\x03out\x18\x01 \x01(\tH\x00R\x03out\x88\x01\x01B\x06\n" + + "\x04_out\"\x1a\n" + + "\x18MixedServerStreamRequest\"@\n" + + "\x19MixedServerStreamResponse\x12\x19\n" + + "\x05event\x18\x01 \x01(\tH\x00R\x05event\x88\x01\x01B\b\n" + + "\x06_event\"V\n" + + "'MixedClientStreamWsGrpcStreamingRequest\x12\x1e\n" + + "\bmessage_\x18\x01 \x01(\tH\x00R\amessage\x88\x01\x01B\v\n" + + "\t_message_\"@\n" + + "\x1fMixedClientStreamWsGrpcResponse\x12\x15\n" + + "\x03out\x18\x01 \x01(\tH\x00R\x03out\x88\x01\x01B\x06\n" + + "\x04_out\"T\n" + + "%MixedBidiStreamWsGrpcStreamingRequest\x12\x1e\n" + + "\bmessage_\x18\x01 \x01(\tH\x00R\amessage\x88\x01\x01B\v\n" + + "\t_message_\"L\n" + + "\x1dMixedBidiStreamWsGrpcResponse\x12\x1e\n" + + "\bmessage_\x18\x01 \x01(\tH\x00R\amessage\x88\x01\x01B\v\n" + + "\t_message_2\x8e\b\n" + "\fTestHTTPGrpc\x12Y\n" + "\fGrpcNoStream\x12#.test_http_grpc.GrpcNoStreamRequest\x1a$.test_http_grpc.GrpcNoStreamResponse\x12\x83\x01\n" + "\x1aGrpcNoStreamErrorDivByZero\x121.test_http_grpc.GrpcNoStreamErrorDivByZeroRequest\x1a2.test_http_grpc.GrpcNoStreamErrorDivByZeroResponse\x12g\n" + @@ -985,6 +1005,24 @@ func file_goagen_httpgrpc_test_http_grpc_proto_init() { if File_goagen_httpgrpc_test_http_grpc_proto != nil { return } + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[0].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[1].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[2].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[3].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[4].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[5].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[6].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[7].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[8].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[9].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[10].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[11].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[12].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[14].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[15].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[16].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[17].OneofWrappers = []any{} + file_goagen_httpgrpc_test_http_grpc_proto_msgTypes[18].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.proto b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.proto index b04808246..52c055016 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.proto +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc.proto @@ -35,78 +35,78 @@ service TestHTTPGrpc { } message GrpcNoStreamRequest { - string msg = 1; + optional string msg = 1; } message GrpcNoStreamResponse { - string out = 1; + optional string out = 1; } message GrpcNoStreamErrorDivByZeroDivisionByZeroError { // division by zero error message - string message_ = 1; + optional string message_ = 1; } message GrpcNoStreamErrorDivByZeroRequest { - double dividend = 1; - double divisor = 2; + optional double dividend = 1; + optional double divisor = 2; } message GrpcNoStreamErrorDivByZeroResponse { - double quotient = 1; + optional double quotient = 1; } message GrpcServerStreamRequest { - sint32 from = 1; + optional sint32 from = 1; } message GrpcServerStreamResponse { - sint32 value = 1; + optional sint32 value = 1; } message GrpcClientStreamStreamingRequest { - sint32 value = 1; + optional sint32 value = 1; } message GrpcClientStreamResponse { - sint32 sum = 1; + optional sint32 sum = 1; } message GrpcBidiStreamStreamingRequest { - string in = 1; + optional string in = 1; } message GrpcBidiStreamResponse { - string out = 1; + optional string out = 1; } message MixedNoStreamRequest { - string msg = 1; + optional string msg = 1; } message MixedNoStreamResponse { - string out = 1; + optional string out = 1; } message MixedServerStreamRequest { } message MixedServerStreamResponse { - string event = 1; + optional string event = 1; } message MixedClientStreamWsGrpcStreamingRequest { - string message_ = 1; + optional string message_ = 1; } message MixedClientStreamWsGrpcResponse { - string out = 1; + optional string out = 1; } message MixedBidiStreamWsGrpcStreamingRequest { - string message_ = 1; + optional string message_ = 1; } message MixedBidiStreamWsGrpcResponse { - string message_ = 1; + optional string message_ = 1; } diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc_grpc.pb.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc_grpc.pb.go index f9befe76f..be5e23f57 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc_grpc.pb.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/pb/goagen_httpgrpc_test_http_grpc_grpc.pb.go @@ -9,7 +9,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.2 -// - protoc v7.34.1 +// - protoc v7.36.0 // source: goagen_httpgrpc_test_http_grpc.proto package test_http_grpcpb diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/encode_decode.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/encode_decode.go index f5bef98b2..ef9a5a88d 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/encode_decode.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/encode_decode.go @@ -39,6 +39,9 @@ func DecodeGrpcNoStreamRequest(ctx context.Context, v any, md metadata.MD) (any, if message, ok = v.(*test_http_grpcpb.GrpcNoStreamRequest); !ok { return nil, goagrpc.ErrInvalidType("test-http-grpc", "grpc_no_stream", "*test_http_grpcpb.GrpcNoStreamRequest", v) } + if err := ValidateGrpcNoStreamRequest(message); err != nil { + return nil, err + } } var payload *testhttpgrpc.GrpcNoStreamPayload { @@ -102,6 +105,9 @@ func DecodeGrpcServerStreamRequest(ctx context.Context, v any, md metadata.MD) ( if message, ok = v.(*test_http_grpcpb.GrpcServerStreamRequest); !ok { return nil, goagrpc.ErrInvalidType("test-http-grpc", "grpc_server_stream", "*test_http_grpcpb.GrpcServerStreamRequest", v) } + if err := ValidateGrpcServerStreamRequest(message); err != nil { + return nil, err + } } var payload *testhttpgrpc.GrpcServerStreamPayload { @@ -154,6 +160,9 @@ func DecodeMixedNoStreamRequest(ctx context.Context, v any, md metadata.MD) (any if message, ok = v.(*test_http_grpcpb.MixedNoStreamRequest); !ok { return nil, goagrpc.ErrInvalidType("test-http-grpc", "mixed_no_stream", "*test_http_grpcpb.MixedNoStreamRequest", v) } + if err := ValidateMixedNoStreamRequest(message); err != nil { + return nil, err + } } var payload *testhttpgrpc.MixedNoStreamPayload { diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/server.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/server.go index ac130e433..c94a951c6 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/server.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/server.go @@ -334,7 +334,7 @@ func (s *Server) MixedBidiStreamWsGrpc(stream test_http_grpcpb.TestHTTPGrpc_Mixe // Send streams instances of "test_http_grpcpb.GrpcServerStreamResponse" to the // "grpc_server_stream" endpoint gRPC stream. func (s *GrpcServerStreamServerStream) Send(res *testhttpgrpc.GrpcServerStreamResult) error { - v := NewProtoGrpcServerStreamResultGrpcServerStreamResponse(res) + v := NewProtoGrpcServerStreamResponse(res) return s.stream.Send(v) } @@ -354,7 +354,7 @@ func (s *GrpcServerStreamServerStream) Close() error { // "test_http_grpcpb.GrpcClientStreamResponse" to the "grpc_client_stream" // endpoint gRPC stream. func (s *GrpcClientStreamServerStream) SendAndClose(res *testhttpgrpc.GrpcClientStreamResult) error { - v := NewProtoGrpcClientStreamResultGrpcClientStreamResponse(res) + v := NewProtoGrpcClientStreamResponse(res) return s.stream.SendAndClose(v) } @@ -373,6 +373,9 @@ func (s *GrpcClientStreamServerStream) Recv() (*testhttpgrpc.GrpcClientStreamStr if err != nil { return res, err } + if err = ValidateGrpcClientStreamStreamingRequest(v); err != nil { + return res, err + } return NewGrpcClientStreamStreamingRequestGrpcClientStreamStreamingPayload(v), nil } @@ -386,7 +389,7 @@ func (s *GrpcClientStreamServerStream) RecvWithContext(ctx context.Context) (*te // Send streams instances of "test_http_grpcpb.GrpcBidiStreamResponse" to the // "grpc_bidi_stream" endpoint gRPC stream. func (s *GrpcBidiStreamServerStream) Send(res *testhttpgrpc.GrpcBidiStreamResult) error { - v := NewProtoGrpcBidiStreamResultGrpcBidiStreamResponse(res) + v := NewProtoGrpcBidiStreamResponse(res) return s.stream.Send(v) } @@ -405,6 +408,9 @@ func (s *GrpcBidiStreamServerStream) Recv() (*testhttpgrpc.GrpcBidiStreamStreami if err != nil { return res, err } + if err = ValidateGrpcBidiStreamStreamingRequest(v); err != nil { + return res, err + } return NewGrpcBidiStreamStreamingRequestGrpcBidiStreamStreamingPayload(v), nil } @@ -423,7 +429,7 @@ func (s *GrpcBidiStreamServerStream) Close() error { // Send streams instances of "test_http_grpcpb.MixedServerStreamResponse" to // the "mixed_server_stream" endpoint gRPC stream. func (s *MixedServerStreamServerStream) Send(res *testhttpgrpc.MixedServerStreamResult) error { - v := NewProtoMixedServerStreamResultMixedServerStreamResponse(res) + v := NewProtoMixedServerStreamResponse(res) return s.stream.Send(v) } @@ -443,7 +449,7 @@ func (s *MixedServerStreamServerStream) Close() error { // "test_http_grpcpb.MixedClientStreamWsGrpcResponse" to the // "mixed_client_stream_ws_grpc" endpoint gRPC stream. func (s *MixedClientStreamWsGrpcServerStream) SendAndClose(res *testhttpgrpc.MixedClientStreamWsGrpcResult) error { - v := NewProtoMixedClientStreamWsGrpcResultMixedClientStreamWsGrpcResponse(res) + v := NewProtoMixedClientStreamWsGrpcResponse(res) return s.stream.SendAndClose(v) } @@ -463,6 +469,9 @@ func (s *MixedClientStreamWsGrpcServerStream) Recv() (*testhttpgrpc.MixedClientS if err != nil { return res, err } + if err = ValidateMixedClientStreamWsGrpcStreamingRequest(v); err != nil { + return res, err + } return NewMixedClientStreamWsGrpcStreamingRequestMixedClientStreamWsGrpcStreamingPayload(v), nil } @@ -476,7 +485,7 @@ func (s *MixedClientStreamWsGrpcServerStream) RecvWithContext(ctx context.Contex // Send streams instances of "test_http_grpcpb.MixedBidiStreamWsGrpcResponse" // to the "mixed_bidi_stream_ws_grpc" endpoint gRPC stream. func (s *MixedBidiStreamWsGrpcServerStream) Send(res *testhttpgrpc.MixedBidiStreamWsGrpcResult) error { - v := NewProtoMixedBidiStreamWsGrpcResultMixedBidiStreamWsGrpcResponse(res) + v := NewProtoMixedBidiStreamWsGrpcResponse(res) return s.stream.Send(v) } @@ -496,6 +505,9 @@ func (s *MixedBidiStreamWsGrpcServerStream) Recv() (*testhttpgrpc.MixedBidiStrea if err != nil { return res, err } + if err = ValidateMixedBidiStreamWsGrpcStreamingRequest(v); err != nil { + return res, err + } return NewMixedBidiStreamWsGrpcStreamingRequestMixedBidiStreamWsGrpcStreamingPayload(v), nil } diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/types.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/types.go index 2a8572ea8..cee6bfee4 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/types.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/server/types.go @@ -14,222 +14,269 @@ import ( testhttpgrpc "goa.design/plugins/v3/testing/examples/httpgrpc/gen/test_http_grpc" ) -// NewGrpcNoStreamPayload builds the payload of the "grpc_no_stream" endpoint -// of the "test-http-grpc" service from the gRPC request type. +// NewGrpcNoStreamPayload builds *testhttpgrpc.GrpcNoStreamPayload from +// *test_http_grpcpb.GrpcNoStreamRequest. func NewGrpcNoStreamPayload(message *test_http_grpcpb.GrpcNoStreamRequest) *testhttpgrpc.GrpcNoStreamPayload { v := &testhttpgrpc.GrpcNoStreamPayload{ - Msg: message.Msg, + Msg: *message.Msg, } return v } -// NewProtoGrpcNoStreamResponse builds the gRPC response type from the result -// of the "grpc_no_stream" endpoint of the "test-http-grpc" service. +// NewProtoGrpcNoStreamResponse builds *test_http_grpcpb.GrpcNoStreamResponse +// from *testhttpgrpc.GrpcNoStreamResult. func NewProtoGrpcNoStreamResponse(result *testhttpgrpc.GrpcNoStreamResult) *test_http_grpcpb.GrpcNoStreamResponse { message := &test_http_grpcpb.GrpcNoStreamResponse{ - Out: result.Out, + Out: &result.Out, } return message } -// NewGrpcNoStreamErrorDivByZeroPayload builds the payload of the -// "grpc_no_stream_error_div_by_zero" endpoint of the "test-http-grpc" service -// from the gRPC request type. +// NewGrpcNoStreamErrorDivByZeroPayload builds +// *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload from +// *test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest. func NewGrpcNoStreamErrorDivByZeroPayload(message *test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest) *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload { v := &testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload{ - Dividend: message.Dividend, - Divisor: message.Divisor, + Dividend: *message.Dividend, + Divisor: *message.Divisor, } return v } -// NewProtoGrpcNoStreamErrorDivByZeroResponse builds the gRPC response type -// from the result of the "grpc_no_stream_error_div_by_zero" endpoint of the -// "test-http-grpc" service. +// NewProtoGrpcNoStreamErrorDivByZeroResponse builds +// *test_http_grpcpb.GrpcNoStreamErrorDivByZeroResponse from +// *testhttpgrpc.GrpcNoStreamErrorDivByZeroResult. func NewProtoGrpcNoStreamErrorDivByZeroResponse(result *testhttpgrpc.GrpcNoStreamErrorDivByZeroResult) *test_http_grpcpb.GrpcNoStreamErrorDivByZeroResponse { message := &test_http_grpcpb.GrpcNoStreamErrorDivByZeroResponse{ - Quotient: result.Quotient, + Quotient: &result.Quotient, } return message } -// NewGrpcNoStreamErrorDivByZeroDivisionByZeroError builds the gRPC error -// response type from the error of the "grpc_no_stream_error_div_by_zero" -// endpoint of the "test-http-grpc" service. +// NewGrpcNoStreamErrorDivByZeroDivisionByZeroError builds +// *test_http_grpcpb.GrpcNoStreamErrorDivByZeroDivisionByZeroError from +// *testhttpgrpc.DivisionByZeroError. func NewGrpcNoStreamErrorDivByZeroDivisionByZeroError(er *testhttpgrpc.DivisionByZeroError) *test_http_grpcpb.GrpcNoStreamErrorDivByZeroDivisionByZeroError { message := &test_http_grpcpb.GrpcNoStreamErrorDivByZeroDivisionByZeroError{ - Message_: er.Message, + Message_: &er.Message, } return message } -// NewGrpcServerStreamPayload builds the payload of the "grpc_server_stream" -// endpoint of the "test-http-grpc" service from the gRPC request type. +// NewGrpcServerStreamPayload builds *testhttpgrpc.GrpcServerStreamPayload from +// *test_http_grpcpb.GrpcServerStreamRequest. func NewGrpcServerStreamPayload(message *test_http_grpcpb.GrpcServerStreamRequest) *testhttpgrpc.GrpcServerStreamPayload { v := &testhttpgrpc.GrpcServerStreamPayload{ - From: int(message.From), + From: int(*message.From), } return v } -// NewProtoGrpcServerStreamResponse builds the gRPC response type from the -// result of the "grpc_server_stream" endpoint of the "test-http-grpc" service. +// NewProtoGrpcServerStreamResponse builds +// *test_http_grpcpb.GrpcServerStreamResponse from +// *testhttpgrpc.GrpcServerStreamResult. func NewProtoGrpcServerStreamResponse(result *testhttpgrpc.GrpcServerStreamResult) *test_http_grpcpb.GrpcServerStreamResponse { - message := &test_http_grpcpb.GrpcServerStreamResponse{ - Value: int32(result.Value), - } + message := &test_http_grpcpb.GrpcServerStreamResponse{} + value := int32(result.Value) + message.Value = &value return message } -func NewProtoGrpcServerStreamResultGrpcServerStreamResponse(result *testhttpgrpc.GrpcServerStreamResult) *test_http_grpcpb.GrpcServerStreamResponse { - v := &test_http_grpcpb.GrpcServerStreamResponse{ - Value: int32(result.Value), - } - return v -} - -// NewProtoGrpcClientStreamResponse builds the gRPC response type from the -// result of the "grpc_client_stream" endpoint of the "test-http-grpc" service. +// NewProtoGrpcClientStreamResponse builds +// *test_http_grpcpb.GrpcClientStreamResponse from +// *testhttpgrpc.GrpcClientStreamResult. func NewProtoGrpcClientStreamResponse(result *testhttpgrpc.GrpcClientStreamResult) *test_http_grpcpb.GrpcClientStreamResponse { - message := &test_http_grpcpb.GrpcClientStreamResponse{ - Sum: int32(result.Sum), - } + message := &test_http_grpcpb.GrpcClientStreamResponse{} + sum := int32(result.Sum) + message.Sum = &sum return message } -func NewProtoGrpcClientStreamResultGrpcClientStreamResponse(result *testhttpgrpc.GrpcClientStreamResult) *test_http_grpcpb.GrpcClientStreamResponse { - v := &test_http_grpcpb.GrpcClientStreamResponse{ - Sum: int32(result.Sum), - } - return v -} - +// NewGrpcClientStreamStreamingRequestGrpcClientStreamStreamingPayload builds +// *testhttpgrpc.GrpcClientStreamStreamingPayload from +// *test_http_grpcpb.GrpcClientStreamStreamingRequest. func NewGrpcClientStreamStreamingRequestGrpcClientStreamStreamingPayload(v *test_http_grpcpb.GrpcClientStreamStreamingRequest) *testhttpgrpc.GrpcClientStreamStreamingPayload { spayload := &testhttpgrpc.GrpcClientStreamStreamingPayload{ - Value: int(v.Value), + Value: int(*v.Value), } return spayload } -// NewProtoGrpcBidiStreamResponse builds the gRPC response type from the result -// of the "grpc_bidi_stream" endpoint of the "test-http-grpc" service. +// NewProtoGrpcBidiStreamResponse builds +// *test_http_grpcpb.GrpcBidiStreamResponse from +// *testhttpgrpc.GrpcBidiStreamResult. func NewProtoGrpcBidiStreamResponse(result *testhttpgrpc.GrpcBidiStreamResult) *test_http_grpcpb.GrpcBidiStreamResponse { message := &test_http_grpcpb.GrpcBidiStreamResponse{ - Out: result.Out, + Out: &result.Out, } return message } -func NewProtoGrpcBidiStreamResultGrpcBidiStreamResponse(result *testhttpgrpc.GrpcBidiStreamResult) *test_http_grpcpb.GrpcBidiStreamResponse { - v := &test_http_grpcpb.GrpcBidiStreamResponse{ - Out: result.Out, - } - return v -} - +// NewGrpcBidiStreamStreamingRequestGrpcBidiStreamStreamingPayload builds +// *testhttpgrpc.GrpcBidiStreamStreamingPayload from +// *test_http_grpcpb.GrpcBidiStreamStreamingRequest. func NewGrpcBidiStreamStreamingRequestGrpcBidiStreamStreamingPayload(v *test_http_grpcpb.GrpcBidiStreamStreamingRequest) *testhttpgrpc.GrpcBidiStreamStreamingPayload { spayload := &testhttpgrpc.GrpcBidiStreamStreamingPayload{ - In: v.In, + In: *v.In, } return spayload } -// NewMixedNoStreamPayload builds the payload of the "mixed_no_stream" endpoint -// of the "test-http-grpc" service from the gRPC request type. +// NewMixedNoStreamPayload builds *testhttpgrpc.MixedNoStreamPayload from +// *test_http_grpcpb.MixedNoStreamRequest. func NewMixedNoStreamPayload(message *test_http_grpcpb.MixedNoStreamRequest) *testhttpgrpc.MixedNoStreamPayload { v := &testhttpgrpc.MixedNoStreamPayload{ - Msg: message.Msg, + Msg: *message.Msg, } return v } -// NewProtoMixedNoStreamResponse builds the gRPC response type from the result -// of the "mixed_no_stream" endpoint of the "test-http-grpc" service. +// NewProtoMixedNoStreamResponse builds *test_http_grpcpb.MixedNoStreamResponse +// from *testhttpgrpc.MixedNoStreamResult. func NewProtoMixedNoStreamResponse(result *testhttpgrpc.MixedNoStreamResult) *test_http_grpcpb.MixedNoStreamResponse { message := &test_http_grpcpb.MixedNoStreamResponse{ - Out: result.Out, + Out: &result.Out, } return message } -// NewProtoMixedServerStreamResponse builds the gRPC response type from the -// result of the "mixed_server_stream" endpoint of the "test-http-grpc" service. +// NewProtoMixedServerStreamResponse builds +// *test_http_grpcpb.MixedServerStreamResponse from +// *testhttpgrpc.MixedServerStreamResult. func NewProtoMixedServerStreamResponse(result *testhttpgrpc.MixedServerStreamResult) *test_http_grpcpb.MixedServerStreamResponse { message := &test_http_grpcpb.MixedServerStreamResponse{ - Event: result.Event, + Event: &result.Event, } return message } -func NewProtoMixedServerStreamResultMixedServerStreamResponse(result *testhttpgrpc.MixedServerStreamResult) *test_http_grpcpb.MixedServerStreamResponse { - v := &test_http_grpcpb.MixedServerStreamResponse{ - Event: result.Event, - } - return v -} - -// NewProtoMixedClientStreamWsGrpcResponse builds the gRPC response type from -// the result of the "mixed_client_stream_ws_grpc" endpoint of the -// "test-http-grpc" service. +// NewProtoMixedClientStreamWsGrpcResponse builds +// *test_http_grpcpb.MixedClientStreamWsGrpcResponse from +// *testhttpgrpc.MixedClientStreamWsGrpcResult. func NewProtoMixedClientStreamWsGrpcResponse(result *testhttpgrpc.MixedClientStreamWsGrpcResult) *test_http_grpcpb.MixedClientStreamWsGrpcResponse { message := &test_http_grpcpb.MixedClientStreamWsGrpcResponse{ - Out: result.Out, + Out: &result.Out, } return message } -func NewProtoMixedClientStreamWsGrpcResultMixedClientStreamWsGrpcResponse(result *testhttpgrpc.MixedClientStreamWsGrpcResult) *test_http_grpcpb.MixedClientStreamWsGrpcResponse { - v := &test_http_grpcpb.MixedClientStreamWsGrpcResponse{ - Out: result.Out, - } - return v -} - +// NewMixedClientStreamWsGrpcStreamingRequestMixedClientStreamWsGrpcStreamingPayload +// builds *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload from +// *test_http_grpcpb.MixedClientStreamWsGrpcStreamingRequest. func NewMixedClientStreamWsGrpcStreamingRequestMixedClientStreamWsGrpcStreamingPayload(v *test_http_grpcpb.MixedClientStreamWsGrpcStreamingRequest) *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload { spayload := &testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload{ - Message: v.Message_, + Message: *v.Message_, } return spayload } -// NewProtoMixedBidiStreamWsGrpcResponse builds the gRPC response type from the -// result of the "mixed_bidi_stream_ws_grpc" endpoint of the "test-http-grpc" -// service. +// NewProtoMixedBidiStreamWsGrpcResponse builds +// *test_http_grpcpb.MixedBidiStreamWsGrpcResponse from +// *testhttpgrpc.MixedBidiStreamWsGrpcResult. func NewProtoMixedBidiStreamWsGrpcResponse(result *testhttpgrpc.MixedBidiStreamWsGrpcResult) *test_http_grpcpb.MixedBidiStreamWsGrpcResponse { message := &test_http_grpcpb.MixedBidiStreamWsGrpcResponse{ - Message_: result.Message, + Message_: &result.Message, } return message } -func NewProtoMixedBidiStreamWsGrpcResultMixedBidiStreamWsGrpcResponse(result *testhttpgrpc.MixedBidiStreamWsGrpcResult) *test_http_grpcpb.MixedBidiStreamWsGrpcResponse { - v := &test_http_grpcpb.MixedBidiStreamWsGrpcResponse{ - Message_: result.Message, - } - return v -} - +// NewMixedBidiStreamWsGrpcStreamingRequestMixedBidiStreamWsGrpcStreamingPayload +// builds *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload from +// *test_http_grpcpb.MixedBidiStreamWsGrpcStreamingRequest. func NewMixedBidiStreamWsGrpcStreamingRequestMixedBidiStreamWsGrpcStreamingPayload(v *test_http_grpcpb.MixedBidiStreamWsGrpcStreamingRequest) *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload { spayload := &testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload{ - Message: v.Message_, + Message: *v.Message_, } return spayload } +// ValidateGrpcNoStreamRequest runs the validations defined on +// GrpcNoStreamRequest. +func ValidateGrpcNoStreamRequest(message *test_http_grpcpb.GrpcNoStreamRequest) (err error) { + if message.Msg == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("msg", "message")) + } + return +} + // ValidateGrpcNoStreamErrorDivByZeroRequest runs the validations defined on // GrpcNoStreamErrorDivByZeroRequest. func ValidateGrpcNoStreamErrorDivByZeroRequest(message *test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest) (err error) { - if message.Dividend < -1000 { - err = goa.MergeErrors(err, goa.InvalidRangeError("message.dividend", message.Dividend, -1000, true)) + if message.Dividend == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("dividend", "message")) + } + if message.Divisor == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("divisor", "message")) } - if message.Dividend > 1000 { - err = goa.MergeErrors(err, goa.InvalidRangeError("message.dividend", message.Dividend, 1000, false)) + if message.Dividend != nil { + if *message.Dividend < -1000 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.dividend", *message.Dividend, -1000, true)) + } + if *message.Dividend > 1000 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.dividend", *message.Dividend, 1000, false)) + } + } + if message.Divisor != nil { + if *message.Divisor < -100 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.divisor", *message.Divisor, -100, true)) + } + if *message.Divisor > 100 { + err = goa.MergeErrors(err, goa.InvalidRangeError("message.divisor", *message.Divisor, 100, false)) + } + } + return +} + +// ValidateGrpcServerStreamRequest runs the validations defined on +// GrpcServerStreamRequest. +func ValidateGrpcServerStreamRequest(message *test_http_grpcpb.GrpcServerStreamRequest) (err error) { + if message.From == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("from", "message")) } - if message.Divisor < -100 { - err = goa.MergeErrors(err, goa.InvalidRangeError("message.divisor", message.Divisor, -100, true)) + return +} + +// ValidateGrpcClientStreamStreamingRequest runs the validations defined on +// GrpcClientStreamStreamingRequest. +func ValidateGrpcClientStreamStreamingRequest(stream *test_http_grpcpb.GrpcClientStreamStreamingRequest) (err error) { + if stream.Value == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("value", "stream")) } - if message.Divisor > 100 { - err = goa.MergeErrors(err, goa.InvalidRangeError("message.divisor", message.Divisor, 100, false)) + return +} + +// ValidateGrpcBidiStreamStreamingRequest runs the validations defined on +// GrpcBidiStreamStreamingRequest. +func ValidateGrpcBidiStreamStreamingRequest(stream *test_http_grpcpb.GrpcBidiStreamStreamingRequest) (err error) { + if stream.In == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("in", "stream")) + } + return +} + +// ValidateMixedNoStreamRequest runs the validations defined on +// MixedNoStreamRequest. +func ValidateMixedNoStreamRequest(message *test_http_grpcpb.MixedNoStreamRequest) (err error) { + if message.Msg == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("msg", "message")) + } + return +} + +// ValidateMixedClientStreamWsGrpcStreamingRequest runs the validations defined +// on MixedClientStreamWsGrpcStreamingRequest. +func ValidateMixedClientStreamWsGrpcStreamingRequest(stream *test_http_grpcpb.MixedClientStreamWsGrpcStreamingRequest) (err error) { + if stream.Message_ == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "stream")) + } + return +} + +// ValidateMixedBidiStreamWsGrpcStreamingRequest runs the validations defined +// on MixedBidiStreamWsGrpcStreamingRequest. +func ValidateMixedBidiStreamWsGrpcStreamingRequest(stream *test_http_grpcpb.MixedBidiStreamWsGrpcStreamingRequest) (err error) { + if stream.Message_ == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "stream")) } return } diff --git a/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go b/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go index 789448a55..e17054102 100644 --- a/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go +++ b/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go @@ -30,7 +30,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "test-http-grpc http-no-stream --body '{\n \"msg\": \"Omnis animi.\"\n }'" + "\n" + + return os.Args[0] + " " + "test-http-grpc http-no-stream --body '{\n \"msg\": \"Expedita eum recusandae non.\"\n }'" + "\n" + "" } @@ -240,7 +240,7 @@ func testHTTPGrpcHTTPNoStreamUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc http-no-stream --body '{\n \"msg\": \"Omnis animi.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc http-no-stream --body '{\n \"msg\": \"Expedita eum recusandae non.\"\n }'") } func testHTTPGrpcHTTPNoStreamErrorUsage() { @@ -258,7 +258,7 @@ func testHTTPGrpcHTTPNoStreamErrorUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc http-no-stream-error --body '{\n \"msg\": \"Reiciendis consequatur.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc http-no-stream-error --body '{\n \"msg\": \"Ipsum asperiores omnis culpa.\"\n }'") } func testHTTPGrpcHTTPServerStreamSseUsage() { @@ -340,7 +340,7 @@ func testHTTPGrpcMixedNoStreamUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc mixed-no-stream --body '{\n \"msg\": \"Sequi aut totam eum.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-http-grpc mixed-no-stream --body '{\n \"msg\": \"Repellendus fuga quisquam sunt eligendi ad praesentium.\"\n }'") } func testHTTPGrpcMixedServerStreamUsage() { diff --git a/testing/examples/httpgrpc/gen/http/openapi.json b/testing/examples/httpgrpc/gen/http/openapi.json index 37d3c5cc2..0fb1e1f5a 100644 --- a/testing/examples/httpgrpc/gen/http/openapi.json +++ b/testing/examples/httpgrpc/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/http/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream test-http-grpc","operationId":"test-http-grpc#http_no_stream","parameters":[{"name":"http_no_stream_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamRequestBody","required":["msg"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamResponseBody","required":["out"]}}},"schemes":["http"]}},"/http/no-stream-error":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream_error test-http-grpc","operationId":"test-http-grpc#http_no_stream_error","parameters":[{"name":"http_no_stream_error_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamErrorRequestBody","required":["msg"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamErrorResponseBody","required":["out"]}},"400":{"description":"Division by zero error","schema":{"$ref":"#/definitions/DivisionByZeroError","required":["message"]}}},"schemes":["http"]}},"/http/sse":{"post":{"tags":["test-http-grpc"],"summary":"http_server_stream_sse test-http-grpc","operationId":"test-http-grpc#http_server_stream_sse","produces":["text/event-stream"],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPServerStreamSseResponseBody","required":["event"]}}},"schemes":["http"]}},"/http/ws/bidi":{"get":{"tags":["test-http-grpc"],"summary":"http_bidi_stream_ws test-http-grpc","operationId":"test-http-grpc#http_bidi_stream_ws","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPBidiStreamWsResponseBody","required":["message"]}}},"schemes":["ws"]}},"/http/ws/client":{"get":{"tags":["test-http-grpc"],"summary":"http_client_stream_ws test-http-grpc","operationId":"test-http-grpc#http_client_stream_ws","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPClientStreamWsResponseBody","required":["out"]}}},"schemes":["ws"]}},"/http/ws/server":{"get":{"tags":["test-http-grpc"],"summary":"http_server_stream_ws test-http-grpc","operationId":"test-http-grpc#http_server_stream_ws","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPServerStreamWsResponseBody","required":["message"]}}},"schemes":["ws"]}},"/mixed/bidi-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_bidi_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_bidi_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedBidiStreamWsGrpcResponseBody","required":["message"]}}},"schemes":["ws"]}},"/mixed/client-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_client_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_client_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedClientStreamWsGrpcResponseBody","required":["out"]}}},"schemes":["ws"]}},"/mixed/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_no_stream test-http-grpc","operationId":"test-http-grpc#mixed_no_stream","parameters":[{"name":"mixed_no_stream_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/TestHTTPGrpcMixedNoStreamRequestBody","required":["msg"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedNoStreamResponseBody","required":["out"]}}},"schemes":["http"]}},"/mixed/server-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_server_stream test-http-grpc","operationId":"test-http-grpc#mixed_server_stream","produces":["text/event-stream"],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedServerStreamResponseBody","required":["event"]}}},"schemes":["http"]}}},"definitions":{"DivisionByZeroError":{"title":"DivisionByZeroError","type":"object","properties":{"message":{"type":"string","description":"division by zero error message","example":"Ducimus quae sed et consequatur autem."}},"description":"Division by zero error","example":{"message":"Ducimus quae sed et consequatur autem."},"required":["message"]},"TestHTTPGrpcHTTPBidiStreamWsResponseBody":{"title":"TestHTTPGrpcHTTPBidiStreamWsResponseBody","type":"object","properties":{"message":{"type":"string","example":"Quos ipsam rerum."}},"example":{"message":"Quos ipsam rerum."},"required":["message"]},"TestHTTPGrpcHTTPClientStreamWsResponseBody":{"title":"TestHTTPGrpcHTTPClientStreamWsResponseBody","type":"object","properties":{"out":{"type":"string","example":"Id vel."}},"example":{"out":"Id vel."},"required":["out"]},"TestHTTPGrpcHTTPNoStreamErrorRequestBody":{"title":"TestHTTPGrpcHTTPNoStreamErrorRequestBody","type":"object","properties":{"msg":{"type":"string","example":"Reiciendis consequatur."}},"example":{"msg":"Reiciendis consequatur."},"required":["msg"]},"TestHTTPGrpcHTTPNoStreamErrorResponseBody":{"title":"TestHTTPGrpcHTTPNoStreamErrorResponseBody","type":"object","properties":{"out":{"type":"string","example":"Aut unde laboriosam modi incidunt occaecati sed."}},"example":{"out":"Aut unde laboriosam modi incidunt occaecati sed."},"required":["out"]},"TestHTTPGrpcHTTPNoStreamRequestBody":{"title":"TestHTTPGrpcHTTPNoStreamRequestBody","type":"object","properties":{"msg":{"type":"string","example":"Omnis animi."}},"example":{"msg":"Omnis animi."},"required":["msg"]},"TestHTTPGrpcHTTPNoStreamResponseBody":{"title":"TestHTTPGrpcHTTPNoStreamResponseBody","type":"object","properties":{"out":{"type":"string","example":"Inventore libero laudantium veniam sunt aut."}},"example":{"out":"Inventore libero laudantium veniam sunt aut."},"required":["out"]},"TestHTTPGrpcHTTPServerStreamSseResponseBody":{"title":"TestHTTPGrpcHTTPServerStreamSseResponseBody","type":"object","properties":{"event":{"type":"string","example":"Vitae ipsam eum aliquam corporis sed architecto."}},"example":{"event":"Vitae ipsam eum aliquam corporis sed architecto."},"required":["event"]},"TestHTTPGrpcHTTPServerStreamWsResponseBody":{"title":"TestHTTPGrpcHTTPServerStreamWsResponseBody","type":"object","properties":{"message":{"type":"string","example":"Modi quaerat."}},"example":{"message":"Modi quaerat."},"required":["message"]},"TestHTTPGrpcMixedBidiStreamWsGrpcResponseBody":{"title":"TestHTTPGrpcMixedBidiStreamWsGrpcResponseBody","type":"object","properties":{"message":{"type":"string","example":"Maiores magni voluptatem consequatur voluptatem sed."}},"example":{"message":"Maiores magni voluptatem consequatur voluptatem sed."},"required":["message"]},"TestHTTPGrpcMixedClientStreamWsGrpcResponseBody":{"title":"TestHTTPGrpcMixedClientStreamWsGrpcResponseBody","type":"object","properties":{"out":{"type":"string","example":"Ipsum et doloremque."}},"example":{"out":"Ipsum et doloremque."},"required":["out"]},"TestHTTPGrpcMixedNoStreamRequestBody":{"title":"TestHTTPGrpcMixedNoStreamRequestBody","type":"object","properties":{"msg":{"type":"string","example":"Sequi aut totam eum."}},"example":{"msg":"Sequi aut totam eum."},"required":["msg"]},"TestHTTPGrpcMixedNoStreamResponseBody":{"title":"TestHTTPGrpcMixedNoStreamResponseBody","type":"object","properties":{"out":{"type":"string","example":"Sunt voluptatum consequatur vitae facere est."}},"example":{"out":"Sunt voluptatum consequatur vitae facere est."},"required":["out"]},"TestHTTPGrpcMixedServerStreamResponseBody":{"title":"TestHTTPGrpcMixedServerStreamResponseBody","type":"object","properties":{"event":{"type":"string","example":"Ab est voluptas in et."}},"example":{"event":"Ab est voluptas in et."},"required":["event"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/http/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream test-http-grpc","operationId":"test-http-grpc#http_no_stream","parameters":[{"name":"http_no_stream_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamRequestBody","required":["msg"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamResponseBody","required":["out"]}}},"schemes":["http"]}},"/http/no-stream-error":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream_error test-http-grpc","operationId":"test-http-grpc#http_no_stream_error","parameters":[{"name":"http_no_stream_error_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamErrorRequestBody","required":["msg"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPNoStreamErrorResponseBody","required":["out"]}},"400":{"description":"division_by_zero: Division by zero error","schema":{"$ref":"#/definitions/DivisionByZeroError","required":["message"]}}},"schemes":["http"]}},"/http/sse":{"post":{"tags":["test-http-grpc"],"summary":"http_server_stream_sse test-http-grpc","operationId":"test-http-grpc#http_server_stream_sse","produces":["text/event-stream"],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPServerStreamSseResponseBody","required":["event"]}}},"schemes":["http"]}},"/http/ws/bidi":{"get":{"tags":["test-http-grpc"],"summary":"http_bidi_stream_ws test-http-grpc","operationId":"test-http-grpc#http_bidi_stream_ws","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPBidiStreamWsResponseBody","required":["message"]}}},"schemes":["ws"]}},"/http/ws/client":{"get":{"tags":["test-http-grpc"],"summary":"http_client_stream_ws test-http-grpc","operationId":"test-http-grpc#http_client_stream_ws","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPClientStreamWsResponseBody","required":["out"]}}},"schemes":["ws"]}},"/http/ws/server":{"get":{"tags":["test-http-grpc"],"summary":"http_server_stream_ws test-http-grpc","operationId":"test-http-grpc#http_server_stream_ws","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcHTTPServerStreamWsResponseBody","required":["message"]}}},"schemes":["ws"]}},"/mixed/bidi-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_bidi_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_bidi_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedBidiStreamWsGrpcResponseBody","required":["message"]}}},"schemes":["ws"]}},"/mixed/client-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_client_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_client_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedClientStreamWsGrpcResponseBody","required":["out"]}}},"schemes":["ws"]}},"/mixed/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_no_stream test-http-grpc","operationId":"test-http-grpc#mixed_no_stream","parameters":[{"name":"mixed_no_stream_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/TestHTTPGrpcMixedNoStreamRequestBody","required":["msg"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedNoStreamResponseBody","required":["out"]}}},"schemes":["http"]}},"/mixed/server-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_server_stream test-http-grpc","operationId":"test-http-grpc#mixed_server_stream","produces":["text/event-stream"],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TestHTTPGrpcMixedServerStreamResponseBody","required":["event"]}}},"schemes":["http"]}}},"definitions":{"DivisionByZeroError":{"title":"DivisionByZeroError","type":"object","properties":{"message":{"type":"string","description":"division by zero error message","example":"Dolore eum provident autem iste."}},"example":{"message":"Dolore eum provident autem iste."},"required":["message"]},"TestHTTPGrpcHTTPBidiStreamWsResponseBody":{"title":"TestHTTPGrpcHTTPBidiStreamWsResponseBody","type":"object","properties":{"message":{"type":"string","example":"Quaerat suscipit minus."}},"example":{"message":"Quaerat suscipit minus."},"required":["message"]},"TestHTTPGrpcHTTPClientStreamWsResponseBody":{"title":"TestHTTPGrpcHTTPClientStreamWsResponseBody","type":"object","properties":{"out":{"type":"string","example":"Nihil eos."}},"example":{"out":"Nihil eos."},"required":["out"]},"TestHTTPGrpcHTTPNoStreamErrorRequestBody":{"title":"TestHTTPGrpcHTTPNoStreamErrorRequestBody","type":"object","properties":{"msg":{"type":"string","example":"Ipsum asperiores omnis culpa."}},"example":{"msg":"Ipsum asperiores omnis culpa."},"required":["msg"]},"TestHTTPGrpcHTTPNoStreamErrorResponseBody":{"title":"TestHTTPGrpcHTTPNoStreamErrorResponseBody","type":"object","properties":{"out":{"type":"string","example":"Voluptatum et et incidunt."}},"example":{"out":"Voluptatum et et incidunt."},"required":["out"]},"TestHTTPGrpcHTTPNoStreamRequestBody":{"title":"TestHTTPGrpcHTTPNoStreamRequestBody","type":"object","properties":{"msg":{"type":"string","example":"Expedita eum recusandae non."}},"example":{"msg":"Expedita eum recusandae non."},"required":["msg"]},"TestHTTPGrpcHTTPNoStreamResponseBody":{"title":"TestHTTPGrpcHTTPNoStreamResponseBody","type":"object","properties":{"out":{"type":"string","example":"Et quam quia et."}},"example":{"out":"Et quam quia et."},"required":["out"]},"TestHTTPGrpcHTTPServerStreamSseResponseBody":{"title":"TestHTTPGrpcHTTPServerStreamSseResponseBody","type":"object","properties":{"event":{"type":"string","example":"Dolores cupiditate voluptatem voluptatibus accusantium sit."}},"example":{"event":"Dolores cupiditate voluptatem voluptatibus accusantium sit."},"required":["event"]},"TestHTTPGrpcHTTPServerStreamWsResponseBody":{"title":"TestHTTPGrpcHTTPServerStreamWsResponseBody","type":"object","properties":{"message":{"type":"string","example":"Et sunt."}},"example":{"message":"Et sunt."},"required":["message"]},"TestHTTPGrpcMixedBidiStreamWsGrpcResponseBody":{"title":"TestHTTPGrpcMixedBidiStreamWsGrpcResponseBody","type":"object","properties":{"message":{"type":"string","example":"Eum qui nam rerum eveniet ducimus dolores."}},"example":{"message":"Eum qui nam rerum eveniet ducimus dolores."},"required":["message"]},"TestHTTPGrpcMixedClientStreamWsGrpcResponseBody":{"title":"TestHTTPGrpcMixedClientStreamWsGrpcResponseBody","type":"object","properties":{"out":{"type":"string","example":"Porro soluta."}},"example":{"out":"Porro soluta."},"required":["out"]},"TestHTTPGrpcMixedNoStreamRequestBody":{"title":"TestHTTPGrpcMixedNoStreamRequestBody","type":"object","properties":{"msg":{"type":"string","example":"Repellendus fuga quisquam sunt eligendi ad praesentium."}},"example":{"msg":"Repellendus fuga quisquam sunt eligendi ad praesentium."},"required":["msg"]},"TestHTTPGrpcMixedNoStreamResponseBody":{"title":"TestHTTPGrpcMixedNoStreamResponseBody","type":"object","properties":{"out":{"type":"string","example":"Alias consequuntur culpa suscipit quasi."}},"example":{"out":"Alias consequuntur culpa suscipit quasi."},"required":["out"]},"TestHTTPGrpcMixedServerStreamResponseBody":{"title":"TestHTTPGrpcMixedServerStreamResponseBody","type":"object","properties":{"event":{"type":"string","example":"Autem aliquid quis praesentium odit."}},"example":{"event":"Autem aliquid quis praesentium odit."},"required":["event"]}}} \ No newline at end of file diff --git a/testing/examples/httpgrpc/gen/http/openapi.yaml b/testing/examples/httpgrpc/gen/http/openapi.yaml index 3c0af379b..49a794d84 100644 --- a/testing/examples/httpgrpc/gen/http/openapi.yaml +++ b/testing/examples/httpgrpc/gen/http/openapi.yaml @@ -57,7 +57,7 @@ paths: required: - out "400": - description: Division by zero error + description: 'division_by_zero: Division by zero error' schema: $ref: '#/definitions/DivisionByZeroError' required: @@ -204,10 +204,9 @@ definitions: message: type: string description: division by zero error message - example: Ducimus quae sed et consequatur autem. - description: Division by zero error + example: Dolore eum provident autem iste. example: - message: Ducimus quae sed et consequatur autem. + message: Dolore eum provident autem iste. required: - message TestHTTPGrpcHTTPBidiStreamWsResponseBody: @@ -216,9 +215,9 @@ definitions: properties: message: type: string - example: Quos ipsam rerum. + example: Quaerat suscipit minus. example: - message: Quos ipsam rerum. + message: Quaerat suscipit minus. required: - message TestHTTPGrpcHTTPClientStreamWsResponseBody: @@ -227,9 +226,9 @@ definitions: properties: out: type: string - example: Id vel. + example: Nihil eos. example: - out: Id vel. + out: Nihil eos. required: - out TestHTTPGrpcHTTPNoStreamErrorRequestBody: @@ -238,9 +237,9 @@ definitions: properties: msg: type: string - example: Reiciendis consequatur. + example: Ipsum asperiores omnis culpa. example: - msg: Reiciendis consequatur. + msg: Ipsum asperiores omnis culpa. required: - msg TestHTTPGrpcHTTPNoStreamErrorResponseBody: @@ -249,9 +248,9 @@ definitions: properties: out: type: string - example: Aut unde laboriosam modi incidunt occaecati sed. + example: Voluptatum et et incidunt. example: - out: Aut unde laboriosam modi incidunt occaecati sed. + out: Voluptatum et et incidunt. required: - out TestHTTPGrpcHTTPNoStreamRequestBody: @@ -260,9 +259,9 @@ definitions: properties: msg: type: string - example: Omnis animi. + example: Expedita eum recusandae non. example: - msg: Omnis animi. + msg: Expedita eum recusandae non. required: - msg TestHTTPGrpcHTTPNoStreamResponseBody: @@ -271,9 +270,9 @@ definitions: properties: out: type: string - example: Inventore libero laudantium veniam sunt aut. + example: Et quam quia et. example: - out: Inventore libero laudantium veniam sunt aut. + out: Et quam quia et. required: - out TestHTTPGrpcHTTPServerStreamSseResponseBody: @@ -282,9 +281,9 @@ definitions: properties: event: type: string - example: Vitae ipsam eum aliquam corporis sed architecto. + example: Dolores cupiditate voluptatem voluptatibus accusantium sit. example: - event: Vitae ipsam eum aliquam corporis sed architecto. + event: Dolores cupiditate voluptatem voluptatibus accusantium sit. required: - event TestHTTPGrpcHTTPServerStreamWsResponseBody: @@ -293,9 +292,9 @@ definitions: properties: message: type: string - example: Modi quaerat. + example: Et sunt. example: - message: Modi quaerat. + message: Et sunt. required: - message TestHTTPGrpcMixedBidiStreamWsGrpcResponseBody: @@ -304,9 +303,9 @@ definitions: properties: message: type: string - example: Maiores magni voluptatem consequatur voluptatem sed. + example: Eum qui nam rerum eveniet ducimus dolores. example: - message: Maiores magni voluptatem consequatur voluptatem sed. + message: Eum qui nam rerum eveniet ducimus dolores. required: - message TestHTTPGrpcMixedClientStreamWsGrpcResponseBody: @@ -315,9 +314,9 @@ definitions: properties: out: type: string - example: Ipsum et doloremque. + example: Porro soluta. example: - out: Ipsum et doloremque. + out: Porro soluta. required: - out TestHTTPGrpcMixedNoStreamRequestBody: @@ -326,9 +325,9 @@ definitions: properties: msg: type: string - example: Sequi aut totam eum. + example: Repellendus fuga quisquam sunt eligendi ad praesentium. example: - msg: Sequi aut totam eum. + msg: Repellendus fuga quisquam sunt eligendi ad praesentium. required: - msg TestHTTPGrpcMixedNoStreamResponseBody: @@ -337,9 +336,9 @@ definitions: properties: out: type: string - example: Sunt voluptatum consequatur vitae facere est. + example: Alias consequuntur culpa suscipit quasi. example: - out: Sunt voluptatum consequatur vitae facere est. + out: Alias consequuntur culpa suscipit quasi. required: - out TestHTTPGrpcMixedServerStreamResponseBody: @@ -348,8 +347,8 @@ definitions: properties: event: type: string - example: Ab est voluptas in et. + example: Autem aliquid quis praesentium odit. example: - event: Ab est voluptas in et. + event: Autem aliquid quis praesentium odit. required: - event diff --git a/testing/examples/httpgrpc/gen/http/openapi3.2.json b/testing/examples/httpgrpc/gen/http/openapi3.2.json index f687f16fe..3003d946c 100644 --- a/testing/examples/httpgrpc/gen/http/openapi3.2.json +++ b/testing/examples/httpgrpc/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"test-http-grpc","description":"Default server for test-http-grpc"}],"paths":{"/http/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream test-http-grpc","operationId":"test-http-grpc#http_no_stream","requestBody":{"description":"Request body for http_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Omnis animi."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Inventore libero laudantium veniam sunt aut."}}}}}}},"/http/no-stream-error":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream_error test-http-grpc","operationId":"test-http-grpc#http_no_stream_error","requestBody":{"description":"Request body for http_no_stream_error.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Reiciendis consequatur."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Aut unde laboriosam modi incidunt occaecati sed."}}}},"400":{"description":"division_by_zero: Division by zero error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Ducimus quae sed et consequatur autem."}}}}}}},"/http/sse":{"post":{"tags":["test-http-grpc"],"summary":"http_server_stream_sse test-http-grpc","operationId":"test-http-grpc#http_server_stream_sse","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"itemSchema":{"type":"object","properties":{"data":{"type":"string","contentMediaType":"application/json","contentSchema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"}}},"required":["data"]}}}}}}},"/http/ws/bidi":{"get":{"tags":["test-http-grpc"],"summary":"http_bidi_stream_ws test-http-grpc","operationId":"test-http-grpc#http_bidi_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Quos ipsam rerum."}}}}}}},"/http/ws/client":{"get":{"tags":["test-http-grpc"],"summary":"http_client_stream_ws test-http-grpc","operationId":"test-http-grpc#http_client_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Id vel."}}}}}}},"/http/ws/server":{"get":{"tags":["test-http-grpc"],"summary":"http_server_stream_ws test-http-grpc","operationId":"test-http-grpc#http_server_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Modi quaerat."}}}}}}},"/mixed/bidi-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_bidi_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_bidi_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Maiores magni voluptatem consequatur voluptatem sed."}}}}}}},"/mixed/client-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_client_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_client_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Ipsum et doloremque."}}}}}}},"/mixed/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_no_stream test-http-grpc","operationId":"test-http-grpc#mixed_no_stream","requestBody":{"description":"Request body for mixed_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Sequi aut totam eum."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Sunt voluptatum consequatur vitae facere est."}}}}}}},"/mixed/server-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_server_stream test-http-grpc","operationId":"test-http-grpc#mixed_server_stream","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"itemSchema":{"type":"object","properties":{"data":{"type":"string","contentMediaType":"application/json","contentSchema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"}}},"required":["data"]}}}}}}}},"components":{"schemas":{"DivisionByZeroError":{"type":"object","properties":{"message":{"type":"string","description":"division by zero error message","example":"Ducimus quae sed et consequatur autem."}},"description":"Division by zero error","example":{"message":"Ducimus quae sed et consequatur autem."},"required":["message"]},"HTTPNoStreamRequestBody":{"type":"object","properties":{"msg":{"type":"string","example":"Omnis animi."}},"description":"Request body for http_no_stream.","example":{"msg":"Omnis animi."},"required":["msg"]},"HTTPNoStreamResponseBody":{"type":"object","properties":{"out":{"type":"string","example":"Inventore libero laudantium veniam sunt aut."}},"example":{"out":"Inventore libero laudantium veniam sunt aut."},"required":["out"]},"HTTPServerStreamSseResponseBody":{"type":"object","properties":{"event":{"type":"string","example":"Vitae ipsam eum aliquam corporis sed architecto."}},"example":{"event":"Vitae ipsam eum aliquam corporis sed architecto."},"required":["event"]}}},"tags":[{"name":"test-http-grpc","description":"Testing plugin matrix across HTTP and gRPC transports"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"test-http-grpc","description":"Default server for test-http-grpc"}],"paths":{"/http/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream test-http-grpc","operationId":"test-http-grpc#http_no_stream","requestBody":{"description":"Request body for http_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Expedita eum recusandae non."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Et quam quia et."}}}}}}},"/http/no-stream-error":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream_error test-http-grpc","operationId":"test-http-grpc#http_no_stream_error","requestBody":{"description":"Request body for http_no_stream_error.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Ipsum asperiores omnis culpa."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Voluptatum et et incidunt."}}}},"400":{"description":"division_by_zero: Division by zero error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Dolore eum provident autem iste."}}}}}}},"/http/sse":{"post":{"tags":["test-http-grpc"],"summary":"http_server_stream_sse test-http-grpc","operationId":"test-http-grpc#http_server_stream_sse","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"itemSchema":{"type":"object","properties":{"data":{"type":"string","contentMediaType":"application/json","contentSchema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"}}},"required":["data"]}}}}}}},"/http/ws/bidi":{"get":{"tags":["test-http-grpc"],"summary":"http_bidi_stream_ws test-http-grpc","operationId":"test-http-grpc#http_bidi_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Quaerat suscipit minus."}}}}}}},"/http/ws/client":{"get":{"tags":["test-http-grpc"],"summary":"http_client_stream_ws test-http-grpc","operationId":"test-http-grpc#http_client_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Nihil eos."}}}}}}},"/http/ws/server":{"get":{"tags":["test-http-grpc"],"summary":"http_server_stream_ws test-http-grpc","operationId":"test-http-grpc#http_server_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Et sunt."}}}}}}},"/mixed/bidi-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_bidi_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_bidi_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Eum qui nam rerum eveniet ducimus dolores."}}}}}}},"/mixed/client-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_client_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_client_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Porro soluta."}}}}}}},"/mixed/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_no_stream test-http-grpc","operationId":"test-http-grpc#mixed_no_stream","requestBody":{"description":"Request body for mixed_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Repellendus fuga quisquam sunt eligendi ad praesentium."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Alias consequuntur culpa suscipit quasi."}}}}}}},"/mixed/server-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_server_stream test-http-grpc","operationId":"test-http-grpc#mixed_server_stream","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"itemSchema":{"type":"object","properties":{"data":{"type":"string","contentMediaType":"application/json","contentSchema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"}}},"required":["data"]}}}}}}}},"components":{"schemas":{"DivisionByZeroError":{"type":"object","properties":{"message":{"type":"string","description":"division by zero error message","example":"Dolore eum provident autem iste."}},"example":{"message":"Dolore eum provident autem iste."},"required":["message"]},"HTTPNoStreamRequestBody":{"type":"object","properties":{"msg":{"type":"string","example":"Expedita eum recusandae non."}},"description":"Request body for http_no_stream.","example":{"msg":"Expedita eum recusandae non."},"required":["msg"]},"HTTPNoStreamResponseBody":{"type":"object","properties":{"out":{"type":"string","example":"Et quam quia et."}},"example":{"out":"Et quam quia et."},"required":["out"]},"HTTPServerStreamSseResponseBody":{"type":"object","properties":{"event":{"type":"string","example":"Dolores cupiditate voluptatem voluptatibus accusantium sit."}},"example":{"event":"Dolores cupiditate voluptatem voluptatibus accusantium sit."},"required":["event"]}}},"tags":[{"name":"test-http-grpc","description":"Testing plugin matrix across HTTP and gRPC transports"}]} \ No newline at end of file diff --git a/testing/examples/httpgrpc/gen/http/openapi3.2.yaml b/testing/examples/httpgrpc/gen/http/openapi3.2.yaml index 681bcad66..bb18fb2ba 100644 --- a/testing/examples/httpgrpc/gen/http/openapi3.2.yaml +++ b/testing/examples/httpgrpc/gen/http/openapi3.2.yaml @@ -21,7 +21,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamRequestBody' example: - msg: Omnis animi. + msg: Expedita eum recusandae non. responses: "200": description: OK response. @@ -30,7 +30,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Inventore libero laudantium veniam sunt aut. + out: Et quam quia et. /http/no-stream-error: post: tags: @@ -45,7 +45,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamRequestBody' example: - msg: Reiciendis consequatur. + msg: Ipsum asperiores omnis culpa. responses: "200": description: OK response. @@ -54,7 +54,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Aut unde laboriosam modi incidunt occaecati sed. + out: Voluptatum et et incidunt. "400": description: 'division_by_zero: Division by zero error' content: @@ -62,7 +62,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Ducimus quae sed et consequatur autem. + message: Dolore eum provident autem iste. /http/sse: post: tags: @@ -98,7 +98,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Quos ipsam rerum. + message: Quaerat suscipit minus. /http/ws/client: get: tags: @@ -113,7 +113,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Id vel. + out: Nihil eos. /http/ws/server: get: tags: @@ -128,7 +128,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Modi quaerat. + message: Et sunt. /mixed/bidi-stream/ws: get: tags: @@ -143,7 +143,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Maiores magni voluptatem consequatur voluptatem sed. + message: Eum qui nam rerum eveniet ducimus dolores. /mixed/client-stream/ws: get: tags: @@ -158,7 +158,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Ipsum et doloremque. + out: Porro soluta. /mixed/no-stream: post: tags: @@ -173,7 +173,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamRequestBody' example: - msg: Sequi aut totam eum. + msg: Repellendus fuga quisquam sunt eligendi ad praesentium. responses: "200": description: OK response. @@ -182,7 +182,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Sunt voluptatum consequatur vitae facere est. + out: Alias consequuntur culpa suscipit quasi. /mixed/server-stream: post: tags: @@ -212,10 +212,9 @@ components: message: type: string description: division by zero error message - example: Ducimus quae sed et consequatur autem. - description: Division by zero error + example: Dolore eum provident autem iste. example: - message: Ducimus quae sed et consequatur autem. + message: Dolore eum provident autem iste. required: - message HTTPNoStreamRequestBody: @@ -223,10 +222,10 @@ components: properties: msg: type: string - example: Omnis animi. + example: Expedita eum recusandae non. description: Request body for http_no_stream. example: - msg: Omnis animi. + msg: Expedita eum recusandae non. required: - msg HTTPNoStreamResponseBody: @@ -234,9 +233,9 @@ components: properties: out: type: string - example: Inventore libero laudantium veniam sunt aut. + example: Et quam quia et. example: - out: Inventore libero laudantium veniam sunt aut. + out: Et quam quia et. required: - out HTTPServerStreamSseResponseBody: @@ -244,9 +243,9 @@ components: properties: event: type: string - example: Vitae ipsam eum aliquam corporis sed architecto. + example: Dolores cupiditate voluptatem voluptatibus accusantium sit. example: - event: Vitae ipsam eum aliquam corporis sed architecto. + event: Dolores cupiditate voluptatem voluptatibus accusantium sit. required: - event tags: diff --git a/testing/examples/httpgrpc/gen/http/openapi3.json b/testing/examples/httpgrpc/gen/http/openapi3.json index 6ea9689f1..c9cf41c25 100644 --- a/testing/examples/httpgrpc/gen/http/openapi3.json +++ b/testing/examples/httpgrpc/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test-http-grpc"}],"paths":{"/http/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream test-http-grpc","operationId":"test-http-grpc#http_no_stream","requestBody":{"description":"Request body for http_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Omnis animi."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Inventore libero laudantium veniam sunt aut."}}}}}}},"/http/no-stream-error":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream_error test-http-grpc","operationId":"test-http-grpc#http_no_stream_error","requestBody":{"description":"Request body for http_no_stream_error.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Reiciendis consequatur."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Aut unde laboriosam modi incidunt occaecati sed."}}}},"400":{"description":"division_by_zero: Division by zero error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Ducimus quae sed et consequatur autem."}}}}}}},"/http/sse":{"post":{"tags":["test-http-grpc"],"summary":"http_server_stream_sse test-http-grpc","operationId":"test-http-grpc#http_server_stream_sse","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"},"example":{"event":"Vitae ipsam eum aliquam corporis sed architecto."}}}}}}},"/http/ws/bidi":{"get":{"tags":["test-http-grpc"],"summary":"http_bidi_stream_ws test-http-grpc","operationId":"test-http-grpc#http_bidi_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Quos ipsam rerum."}}}}}}},"/http/ws/client":{"get":{"tags":["test-http-grpc"],"summary":"http_client_stream_ws test-http-grpc","operationId":"test-http-grpc#http_client_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Id vel."}}}}}}},"/http/ws/server":{"get":{"tags":["test-http-grpc"],"summary":"http_server_stream_ws test-http-grpc","operationId":"test-http-grpc#http_server_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Modi quaerat."}}}}}}},"/mixed/bidi-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_bidi_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_bidi_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Maiores magni voluptatem consequatur voluptatem sed."}}}}}}},"/mixed/client-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_client_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_client_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Ipsum et doloremque."}}}}}}},"/mixed/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_no_stream test-http-grpc","operationId":"test-http-grpc#mixed_no_stream","requestBody":{"description":"Request body for mixed_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Sequi aut totam eum."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Sunt voluptatum consequatur vitae facere est."}}}}}}},"/mixed/server-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_server_stream test-http-grpc","operationId":"test-http-grpc#mixed_server_stream","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"},"example":{"event":"Ab est voluptas in et."}}}}}}}},"components":{"schemas":{"DivisionByZeroError":{"type":"object","properties":{"message":{"type":"string","description":"division by zero error message","example":"Ducimus quae sed et consequatur autem."}},"description":"Division by zero error","example":{"message":"Ducimus quae sed et consequatur autem."},"required":["message"]},"HTTPNoStreamRequestBody":{"type":"object","properties":{"msg":{"type":"string","example":"Omnis animi."}},"description":"Request body for http_no_stream.","example":{"msg":"Omnis animi."},"required":["msg"]},"HTTPNoStreamResponseBody":{"type":"object","properties":{"out":{"type":"string","example":"Inventore libero laudantium veniam sunt aut."}},"example":{"out":"Inventore libero laudantium veniam sunt aut."},"required":["out"]},"HTTPServerStreamSseResponseBody":{"type":"object","properties":{"event":{"type":"string","example":"Vitae ipsam eum aliquam corporis sed architecto."}},"example":{"event":"Vitae ipsam eum aliquam corporis sed architecto."},"required":["event"]}}},"tags":[{"name":"test-http-grpc","description":"Testing plugin matrix across HTTP and gRPC transports"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test-http-grpc"}],"paths":{"/http/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream test-http-grpc","operationId":"test-http-grpc#http_no_stream","requestBody":{"description":"Request body for http_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Expedita eum recusandae non."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Et quam quia et."}}}}}}},"/http/no-stream-error":{"post":{"tags":["test-http-grpc"],"summary":"http_no_stream_error test-http-grpc","operationId":"test-http-grpc#http_no_stream_error","requestBody":{"description":"Request body for http_no_stream_error.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Ipsum asperiores omnis culpa."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Voluptatum et et incidunt."}}}},"400":{"description":"division_by_zero: Division by zero error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Dolore eum provident autem iste."}}}}}}},"/http/sse":{"post":{"tags":["test-http-grpc"],"summary":"http_server_stream_sse test-http-grpc","operationId":"test-http-grpc#http_server_stream_sse","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"},"example":{"event":"Dolores cupiditate voluptatem voluptatibus accusantium sit."}}}}}}},"/http/ws/bidi":{"get":{"tags":["test-http-grpc"],"summary":"http_bidi_stream_ws test-http-grpc","operationId":"test-http-grpc#http_bidi_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Quaerat suscipit minus."}}}}}}},"/http/ws/client":{"get":{"tags":["test-http-grpc"],"summary":"http_client_stream_ws test-http-grpc","operationId":"test-http-grpc#http_client_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Nihil eos."}}}}}}},"/http/ws/server":{"get":{"tags":["test-http-grpc"],"summary":"http_server_stream_ws test-http-grpc","operationId":"test-http-grpc#http_server_stream_ws","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Et sunt."}}}}}}},"/mixed/bidi-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_bidi_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_bidi_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DivisionByZeroError"},"example":{"message":"Eum qui nam rerum eveniet ducimus dolores."}}}}}}},"/mixed/client-stream/ws":{"get":{"tags":["test-http-grpc"],"summary":"mixed_client_stream_ws_grpc test-http-grpc","operationId":"test-http-grpc#mixed_client_stream_ws_grpc","responses":{"101":{"description":"Switching Protocols response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Porro soluta."}}}}}}},"/mixed/no-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_no_stream test-http-grpc","operationId":"test-http-grpc#mixed_no_stream","requestBody":{"description":"Request body for mixed_no_stream.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamRequestBody"},"example":{"msg":"Repellendus fuga quisquam sunt eligendi ad praesentium."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPNoStreamResponseBody"},"example":{"out":"Alias consequuntur culpa suscipit quasi."}}}}}}},"/mixed/server-stream":{"post":{"tags":["test-http-grpc"],"summary":"mixed_server_stream test-http-grpc","operationId":"test-http-grpc#mixed_server_stream","responses":{"200":{"description":"OK response.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/HTTPServerStreamSseResponseBody"},"example":{"event":"Autem aliquid quis praesentium odit."}}}}}}}},"components":{"schemas":{"DivisionByZeroError":{"type":"object","properties":{"message":{"type":"string","description":"division by zero error message","example":"Dolore eum provident autem iste."}},"example":{"message":"Dolore eum provident autem iste."},"required":["message"]},"HTTPNoStreamRequestBody":{"type":"object","properties":{"msg":{"type":"string","example":"Expedita eum recusandae non."}},"description":"Request body for http_no_stream.","example":{"msg":"Expedita eum recusandae non."},"required":["msg"]},"HTTPNoStreamResponseBody":{"type":"object","properties":{"out":{"type":"string","example":"Et quam quia et."}},"example":{"out":"Et quam quia et."},"required":["out"]},"HTTPServerStreamSseResponseBody":{"type":"object","properties":{"event":{"type":"string","example":"Dolores cupiditate voluptatem voluptatibus accusantium sit."}},"example":{"event":"Dolores cupiditate voluptatem voluptatibus accusantium sit."},"required":["event"]}}},"tags":[{"name":"test-http-grpc","description":"Testing plugin matrix across HTTP and gRPC transports"}]} \ No newline at end of file diff --git a/testing/examples/httpgrpc/gen/http/openapi3.yaml b/testing/examples/httpgrpc/gen/http/openapi3.yaml index db4d5cfe1..4e7bfcc0f 100644 --- a/testing/examples/httpgrpc/gen/http/openapi3.yaml +++ b/testing/examples/httpgrpc/gen/http/openapi3.yaml @@ -20,7 +20,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamRequestBody' example: - msg: Omnis animi. + msg: Expedita eum recusandae non. responses: "200": description: OK response. @@ -29,7 +29,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Inventore libero laudantium veniam sunt aut. + out: Et quam quia et. /http/no-stream-error: post: tags: @@ -44,7 +44,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamRequestBody' example: - msg: Reiciendis consequatur. + msg: Ipsum asperiores omnis culpa. responses: "200": description: OK response. @@ -53,7 +53,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Aut unde laboriosam modi incidunt occaecati sed. + out: Voluptatum et et incidunt. "400": description: 'division_by_zero: Division by zero error' content: @@ -61,7 +61,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Ducimus quae sed et consequatur autem. + message: Dolore eum provident autem iste. /http/sse: post: tags: @@ -76,7 +76,7 @@ paths: schema: $ref: '#/components/schemas/HTTPServerStreamSseResponseBody' example: - event: Vitae ipsam eum aliquam corporis sed architecto. + event: Dolores cupiditate voluptatem voluptatibus accusantium sit. /http/ws/bidi: get: tags: @@ -91,7 +91,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Quos ipsam rerum. + message: Quaerat suscipit minus. /http/ws/client: get: tags: @@ -106,7 +106,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Id vel. + out: Nihil eos. /http/ws/server: get: tags: @@ -121,7 +121,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Modi quaerat. + message: Et sunt. /mixed/bidi-stream/ws: get: tags: @@ -136,7 +136,7 @@ paths: schema: $ref: '#/components/schemas/DivisionByZeroError' example: - message: Maiores magni voluptatem consequatur voluptatem sed. + message: Eum qui nam rerum eveniet ducimus dolores. /mixed/client-stream/ws: get: tags: @@ -151,7 +151,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Ipsum et doloremque. + out: Porro soluta. /mixed/no-stream: post: tags: @@ -166,7 +166,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamRequestBody' example: - msg: Sequi aut totam eum. + msg: Repellendus fuga quisquam sunt eligendi ad praesentium. responses: "200": description: OK response. @@ -175,7 +175,7 @@ paths: schema: $ref: '#/components/schemas/HTTPNoStreamResponseBody' example: - out: Sunt voluptatum consequatur vitae facere est. + out: Alias consequuntur culpa suscipit quasi. /mixed/server-stream: post: tags: @@ -190,7 +190,7 @@ paths: schema: $ref: '#/components/schemas/HTTPServerStreamSseResponseBody' example: - event: Ab est voluptas in et. + event: Autem aliquid quis praesentium odit. components: schemas: DivisionByZeroError: @@ -199,10 +199,9 @@ components: message: type: string description: division by zero error message - example: Ducimus quae sed et consequatur autem. - description: Division by zero error + example: Dolore eum provident autem iste. example: - message: Ducimus quae sed et consequatur autem. + message: Dolore eum provident autem iste. required: - message HTTPNoStreamRequestBody: @@ -210,10 +209,10 @@ components: properties: msg: type: string - example: Omnis animi. + example: Expedita eum recusandae non. description: Request body for http_no_stream. example: - msg: Omnis animi. + msg: Expedita eum recusandae non. required: - msg HTTPNoStreamResponseBody: @@ -221,9 +220,9 @@ components: properties: out: type: string - example: Inventore libero laudantium veniam sunt aut. + example: Et quam quia et. example: - out: Inventore libero laudantium veniam sunt aut. + out: Et quam quia et. required: - out HTTPServerStreamSseResponseBody: @@ -231,9 +230,9 @@ components: properties: event: type: string - example: Vitae ipsam eum aliquam corporis sed architecto. + example: Dolores cupiditate voluptatem voluptatibus accusantium sit. example: - event: Vitae ipsam eum aliquam corporis sed architecto. + event: Dolores cupiditate voluptatem voluptatibus accusantium sit. required: - event tags: diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go index 0f5f84633..9654f5fd2 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go @@ -23,7 +23,7 @@ func BuildHTTPNoStreamPayload(testHTTPGrpcHTTPNoStreamBody string) (*testhttpgrp { err = json.Unmarshal([]byte(testHTTPGrpcHTTPNoStreamBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Omnis animi.\"\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Expedita eum recusandae non.\"\n }'") } } v := &testhttpgrpc.HTTPNoStreamPayload{ @@ -41,7 +41,7 @@ func BuildHTTPNoStreamErrorPayload(testHTTPGrpcHTTPNoStreamErrorBody string) (*t { err = json.Unmarshal([]byte(testHTTPGrpcHTTPNoStreamErrorBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Reiciendis consequatur.\"\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Ipsum asperiores omnis culpa.\"\n }'") } } v := &testhttpgrpc.HTTPNoStreamErrorPayload{ @@ -59,7 +59,7 @@ func BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamBody string) (*testhttpg { err = json.Unmarshal([]byte(testHTTPGrpcMixedNoStreamBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Sequi aut totam eum.\"\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Repellendus fuga quisquam sunt eligendi ad praesentium.\"\n }'") } } v := &testhttpgrpc.MixedNoStreamPayload{ diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go index ab12588a2..2805c4df0 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go @@ -10,6 +10,7 @@ package client import ( "context" + "errors" "fmt" "net/http" "strings" @@ -182,8 +183,11 @@ func (c *Client) HTTPServerStreamSse() goa.Endpoint { contentType := resp.Header.Get("Content-Type") if contentType != "" && !strings.HasPrefix(contentType, "text/event-stream") { - resp.Body.Close() - return nil, fmt.Errorf("unexpected content type: %s (expected text/event-stream)", contentType) + contentTypeErr := fmt.Errorf("unexpected content type: %s (expected text/event-stream)", contentType) + if err := resp.Body.Close(); err != nil { + return nil, errors.Join(contentTypeErr, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_sse", err)) + } + return nil, contentTypeErr } return NewHTTPServerStreamSseStream(resp, c.decoder), nil @@ -327,8 +331,11 @@ func (c *Client) MixedServerStream() goa.Endpoint { contentType := resp.Header.Get("Content-Type") if contentType != "" && !strings.HasPrefix(contentType, "text/event-stream") { - resp.Body.Close() - return nil, fmt.Errorf("unexpected content type: %s (expected text/event-stream)", contentType) + contentTypeErr := fmt.Errorf("unexpected content type: %s (expected text/event-stream)", contentType) + if err := resp.Body.Close(); err != nil { + return nil, errors.Join(contentTypeErr, goahttp.ErrDecodingError("test-http-grpc", "mixed_server_stream", err)) + } + return nil, contentTypeErr } return NewMixedServerStreamStream(resp, c.decoder), nil diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/encode_decode.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/encode_decode.go index cd3bb807d..4640ccbbf 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/encode_decode.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -54,18 +55,24 @@ func EncodeHTTPNoStreamRequest(encoder func(*http.Request) goahttp.Encoder) func // test-http-grpc http_no_stream endpoint. restoreBody controls whether the // response body should be restored after having been read. func DecodeHTTPNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_no_stream", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "http_no_stream", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -84,7 +91,10 @@ func DecodeHTTPNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, re res := NewHTTPNoStreamResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_no_stream", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "http_no_stream", resp.StatusCode, string(body)) } } @@ -129,18 +139,24 @@ func EncodeHTTPNoStreamErrorRequest(encoder func(*http.Request) goahttp.Encoder) // - "division_by_zero" (type *testhttpgrpc.DivisionByZeroError): http.StatusBadRequest // - error: internal error func DecodeHTTPNoStreamErrorResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_no_stream_error", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "http_no_stream_error", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -173,7 +189,10 @@ func DecodeHTTPNoStreamErrorResponse(decoder func(*http.Response) goahttp.Decode } return nil, NewHTTPNoStreamErrorDivisionByZero(&body) default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_no_stream_error", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "http_no_stream_error", resp.StatusCode, string(body)) } } @@ -199,18 +218,24 @@ func (c *Client) BuildHTTPServerStreamSseRequest(ctx context.Context, v any) (*h // by the test-http-grpc http_server_stream_sse endpoint. restoreBody controls // whether the response body should be restored after having been read. func DecodeHTTPServerStreamSseResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_sse", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_sse", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -229,7 +254,10 @@ func DecodeHTTPServerStreamSseResponse(decoder func(*http.Response) goahttp.Deco res := NewHTTPServerStreamSseResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_sse", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "http_server_stream_sse", resp.StatusCode, string(body)) } } @@ -262,18 +290,24 @@ func (c *Client) BuildHTTPServerStreamWsRequest(ctx context.Context, v any) (*ht // the test-http-grpc http_server_stream_ws endpoint. restoreBody controls // whether the response body should be restored after having been read. func DecodeHTTPServerStreamWsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_ws", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_ws", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -292,7 +326,10 @@ func DecodeHTTPServerStreamWsResponse(decoder func(*http.Response) goahttp.Decod res := NewHTTPServerStreamWsResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_ws", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "http_server_stream_ws", resp.StatusCode, string(body)) } } @@ -325,18 +362,24 @@ func (c *Client) BuildHTTPClientStreamWsRequest(ctx context.Context, v any) (*ht // the test-http-grpc http_client_stream_ws endpoint. restoreBody controls // whether the response body should be restored after having been read. func DecodeHTTPClientStreamWsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_client_stream_ws", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "http_client_stream_ws", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -355,7 +398,10 @@ func DecodeHTTPClientStreamWsResponse(decoder func(*http.Response) goahttp.Decod res := NewHTTPClientStreamWsResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_client_stream_ws", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "http_client_stream_ws", resp.StatusCode, string(body)) } } @@ -388,18 +434,24 @@ func (c *Client) BuildHTTPBidiStreamWsRequest(ctx context.Context, v any) (*http // the test-http-grpc http_bidi_stream_ws endpoint. restoreBody controls // whether the response body should be restored after having been read. func DecodeHTTPBidiStreamWsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_bidi_stream_ws", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "http_bidi_stream_ws", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -418,7 +470,10 @@ func DecodeHTTPBidiStreamWsResponse(decoder func(*http.Response) goahttp.Decoder res := NewHTTPBidiStreamWsResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "http_bidi_stream_ws", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "http_bidi_stream_ws", resp.StatusCode, string(body)) } } @@ -459,18 +514,24 @@ func EncodeMixedNoStreamRequest(encoder func(*http.Request) goahttp.Encoder) fun // test-http-grpc mixed_no_stream endpoint. restoreBody controls whether the // response body should be restored after having been read. func DecodeMixedNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_no_stream", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "mixed_no_stream", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -489,7 +550,10 @@ func DecodeMixedNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, r res := NewMixedNoStreamResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_no_stream", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "mixed_no_stream", resp.StatusCode, string(body)) } } @@ -515,18 +579,24 @@ func (c *Client) BuildMixedServerStreamRequest(ctx context.Context, v any) (*htt // the test-http-grpc mixed_server_stream endpoint. restoreBody controls // whether the response body should be restored after having been read. func DecodeMixedServerStreamResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_server_stream", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "mixed_server_stream", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -545,7 +615,10 @@ func DecodeMixedServerStreamResponse(decoder func(*http.Response) goahttp.Decode res := NewMixedServerStreamResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_server_stream", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "mixed_server_stream", resp.StatusCode, string(body)) } } @@ -579,18 +652,24 @@ func (c *Client) BuildMixedClientStreamWsGrpcRequest(ctx context.Context, v any) // restoreBody controls whether the response body should be restored after // having been read. func DecodeMixedClientStreamWsGrpcResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_client_stream_ws_grpc", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "mixed_client_stream_ws_grpc", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -609,7 +688,10 @@ func DecodeMixedClientStreamWsGrpcResponse(decoder func(*http.Response) goahttp. res := NewMixedClientStreamWsGrpcResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_client_stream_ws_grpc", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "mixed_client_stream_ws_grpc", resp.StatusCode, string(body)) } } @@ -642,18 +724,24 @@ func (c *Client) BuildMixedBidiStreamWsGrpcRequest(ctx context.Context, v any) ( // by the test-http-grpc mixed_bidi_stream_ws_grpc endpoint. restoreBody // controls whether the response body should be restored after having been read. func DecodeMixedBidiStreamWsGrpcResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_bidi_stream_ws_grpc", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-http-grpc", "mixed_bidi_stream_ws_grpc", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -672,7 +760,10 @@ func DecodeMixedBidiStreamWsGrpcResponse(decoder func(*http.Response) goahttp.De res := NewMixedBidiStreamWsGrpcResultOK(&body) return res, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-http-grpc", "mixed_bidi_stream_ws_grpc", err) + } return nil, goahttp.ErrInvalidResponse("test-http-grpc", "mixed_bidi_stream_ws_grpc", resp.StatusCode, string(body)) } } diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go index a70556896..5d2a5658c 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go @@ -221,13 +221,13 @@ func (s *HTTPServerStreamSseStreamImpl) processEvent(eventData []byte) (event *t continue } if bytes.HasPrefix(line, []byte("data:")) { - dataLines = append(dataLines, s.trimHeader(len("data:"), line)) + dataLines = append(dataLines, s.trimHeader(line[len("data:"):])) continue } } if len(dataLines) > 0 { dataContent := strings.Join(dataLines, "\n") - // Decode JSON into the struct pointer directly + // Decode the event data into the result value returned by Recv. respBody := &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader([]byte(dataContent))), @@ -240,12 +240,8 @@ func (s *HTTPServerStreamSseStreamImpl) processEvent(eventData []byte) (event *t return } -// trimHeader removes the header prefix and optional leading space -func (s *HTTPServerStreamSseStreamImpl) trimHeader(size int, data []byte) string { - if len(data) < size { - return string(data) - } - data = data[size:] +// trimHeader removes the optional space after an SSE field name. +func (s *HTTPServerStreamSseStreamImpl) trimHeader(data []byte) string { if len(data) > 0 && data[0] == ' ' { data = data[1:] } @@ -452,13 +448,13 @@ func (s *MixedServerStreamStreamImpl) processEvent(eventData []byte) (event *tes continue } if bytes.HasPrefix(line, []byte("data:")) { - dataLines = append(dataLines, s.trimHeader(len("data:"), line)) + dataLines = append(dataLines, s.trimHeader(line[len("data:"):])) continue } } if len(dataLines) > 0 { dataContent := strings.Join(dataLines, "\n") - // Decode JSON into the struct pointer directly + // Decode the event data into the result value returned by Recv. respBody := &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader([]byte(dataContent))), @@ -471,12 +467,8 @@ func (s *MixedServerStreamStreamImpl) processEvent(eventData []byte) (event *tes return } -// trimHeader removes the header prefix and optional leading space -func (s *MixedServerStreamStreamImpl) trimHeader(size int, data []byte) string { - if len(data) < size { - return string(data) - } - data = data[size:] +// trimHeader removes the optional space after an SSE field name. +func (s *MixedServerStreamStreamImpl) trimHeader(data []byte) string { if len(data) > 0 && data[0] == ' ' { data = data[1:] } diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/types.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/types.go index 302d4b8d4..03c0757e5 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/types.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/types.go @@ -302,7 +302,7 @@ func NewMixedBidiStreamWsGrpcResultOK(body *MixedBidiStreamWsGrpcResponseBody) * } // ValidateHTTPNoStreamResponseBody runs the validations defined on -// http_no_stream_response_body +// HTTPNoStreamResponseBody func ValidateHTTPNoStreamResponseBody(body *HTTPNoStreamResponseBody) (err error) { if body.Out == nil { err = goa.MergeErrors(err, goa.MissingFieldError("out", "body")) @@ -311,7 +311,7 @@ func ValidateHTTPNoStreamResponseBody(body *HTTPNoStreamResponseBody) (err error } // ValidateHTTPNoStreamErrorResponseBody runs the validations defined on -// http_no_stream_error_response_body +// HTTPNoStreamErrorResponseBody func ValidateHTTPNoStreamErrorResponseBody(body *HTTPNoStreamErrorResponseBody) (err error) { if body.Out == nil { err = goa.MergeErrors(err, goa.MissingFieldError("out", "body")) @@ -320,7 +320,7 @@ func ValidateHTTPNoStreamErrorResponseBody(body *HTTPNoStreamErrorResponseBody) } // ValidateHTTPServerStreamSseResponseBody runs the validations defined on -// http_server_stream_sse_response_body +// HTTPServerStreamSseResponseBody func ValidateHTTPServerStreamSseResponseBody(body *HTTPServerStreamSseResponseBody) (err error) { if body.Event == nil { err = goa.MergeErrors(err, goa.MissingFieldError("event", "body")) @@ -329,7 +329,7 @@ func ValidateHTTPServerStreamSseResponseBody(body *HTTPServerStreamSseResponseBo } // ValidateHTTPServerStreamWsResponseBody runs the validations defined on -// http_server_stream_ws_response_body +// HTTPServerStreamWsResponseBody func ValidateHTTPServerStreamWsResponseBody(body *HTTPServerStreamWsResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -338,7 +338,7 @@ func ValidateHTTPServerStreamWsResponseBody(body *HTTPServerStreamWsResponseBody } // ValidateHTTPClientStreamWsResponseBody runs the validations defined on -// http_client_stream_ws_response_body +// HTTPClientStreamWsResponseBody func ValidateHTTPClientStreamWsResponseBody(body *HTTPClientStreamWsResponseBody) (err error) { if body.Out == nil { err = goa.MergeErrors(err, goa.MissingFieldError("out", "body")) @@ -347,7 +347,7 @@ func ValidateHTTPClientStreamWsResponseBody(body *HTTPClientStreamWsResponseBody } // ValidateHTTPBidiStreamWsResponseBody runs the validations defined on -// http_bidi_stream_ws_response_body +// HTTPBidiStreamWsResponseBody func ValidateHTTPBidiStreamWsResponseBody(body *HTTPBidiStreamWsResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -356,7 +356,7 @@ func ValidateHTTPBidiStreamWsResponseBody(body *HTTPBidiStreamWsResponseBody) (e } // ValidateMixedNoStreamResponseBody runs the validations defined on -// mixed_no_stream_response_body +// MixedNoStreamResponseBody func ValidateMixedNoStreamResponseBody(body *MixedNoStreamResponseBody) (err error) { if body.Out == nil { err = goa.MergeErrors(err, goa.MissingFieldError("out", "body")) @@ -365,7 +365,7 @@ func ValidateMixedNoStreamResponseBody(body *MixedNoStreamResponseBody) (err err } // ValidateMixedServerStreamResponseBody runs the validations defined on -// mixed_server_stream_response_body +// MixedServerStreamResponseBody func ValidateMixedServerStreamResponseBody(body *MixedServerStreamResponseBody) (err error) { if body.Event == nil { err = goa.MergeErrors(err, goa.MissingFieldError("event", "body")) @@ -374,7 +374,7 @@ func ValidateMixedServerStreamResponseBody(body *MixedServerStreamResponseBody) } // ValidateMixedClientStreamWsGrpcResponseBody runs the validations defined on -// mixed_client_stream_ws_grpc_response_body +// MixedClientStreamWsGrpcResponseBody func ValidateMixedClientStreamWsGrpcResponseBody(body *MixedClientStreamWsGrpcResponseBody) (err error) { if body.Out == nil { err = goa.MergeErrors(err, goa.MissingFieldError("out", "body")) @@ -383,7 +383,7 @@ func ValidateMixedClientStreamWsGrpcResponseBody(body *MixedClientStreamWsGrpcRe } // ValidateMixedBidiStreamWsGrpcResponseBody runs the validations defined on -// mixed_bidi_stream_ws_grpc_response_body +// MixedBidiStreamWsGrpcResponseBody func ValidateMixedBidiStreamWsGrpcResponseBody(body *MixedBidiStreamWsGrpcResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -392,7 +392,7 @@ func ValidateMixedBidiStreamWsGrpcResponseBody(body *MixedBidiStreamWsGrpcRespon } // ValidateHTTPNoStreamErrorDivisionByZeroResponseBody runs the validations -// defined on http_no_stream_error_division_by_zero_response_body +// defined on HTTPNoStreamErrorDivisionByZeroResponseBody func ValidateHTTPNoStreamErrorDivisionByZeroResponseBody(body *HTTPNoStreamErrorDivisionByZeroResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go index 307e2cc84..682878e58 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go @@ -273,6 +273,13 @@ func NewHTTPServerStreamSseHandler( } _, err = endpoint(ctx, v) if err != nil { + stream := v.Stream.(*HTTPServerStreamSseServerStream) + if stream.attempted { + if errhandler != nil { + errhandler(ctx, w, err) + } + return + } if err := encodeError(ctx, w, err); err != nil && errhandler != nil { errhandler(ctx, w, err) } @@ -574,6 +581,13 @@ func NewMixedServerStreamHandler( } _, err = endpoint(ctx, v) if err != nil { + stream := v.Stream.(*MixedServerStreamServerStream) + if stream.attempted { + if errhandler != nil { + errhandler(ctx, w, err) + } + return + } if err := encodeError(ctx, w, err); err != nil && errhandler != nil { errhandler(ctx, w, err) } diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go index 643974991..2dd668c3f 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go @@ -28,6 +28,8 @@ type HTTPServerStreamSseServerStream struct { w http.ResponseWriter // r is the HTTP request. r *http.Request + // attempted is true after this stream writes the HTTP success status. + attempted bool } // Send Send streams instances of "testhttpgrpc.HTTPServerStreamSseResult" to @@ -40,6 +42,16 @@ func (s *HTTPServerStreamSseServerStream) Send(v *testhttpgrpc.HTTPServerStreamS // "testhttpgrpc.HTTPServerStreamSseResult" to the "http_server_stream_sse" // endpoint SSE connection with context. func (s *HTTPServerStreamSseServerStream) SendWithContext(ctx context.Context, v *testhttpgrpc.HTTPServerStreamSseResult) error { + res := v + + var data string + body := NewHTTPServerStreamSseResponseBody(res) + + byts, err := json.Marshal(body) + if err != nil { + return err + } + data = string(byts) s.once.Do(func() { header := s.w.Header() if header.Get("Content-Type") == "" { @@ -52,65 +64,21 @@ func (s *HTTPServerStreamSseServerStream) SendWithContext(ctx context.Context, v header.Set("Connection", "keep-alive") } s.w.WriteHeader(http.StatusOK) + s.attempted = true }) - res := v - var data string - var payload any - body := NewHTTPServerStreamSseResponseBody(res) - payload = body - switch v := payload.(type) { - case nil: - data = "null" - case string: - data = v - case []byte: - data = string(v) - case bool: - if v { - data = "true" - } else { - data = "false" - } - case int: - data = fmt.Sprintf("%d", v) - case int8: - data = fmt.Sprintf("%d", v) - case int16: - data = fmt.Sprintf("%d", v) - case int32: - data = fmt.Sprintf("%d", v) - case int64: - data = fmt.Sprintf("%d", v) - case uint: - data = fmt.Sprintf("%d", v) - case uint8: - data = fmt.Sprintf("%d", v) - case uint16: - data = fmt.Sprintf("%d", v) - case uint32: - data = fmt.Sprintf("%d", v) - case uint64: - data = fmt.Sprintf("%d", v) - case float32: - data = fmt.Sprintf("%g", v) - case float64: - data = fmt.Sprintf("%g", v) - default: - byts, err := json.Marshal(payload) - if err != nil { - return err - } - data = string(byts) + if _, err := fmt.Fprintf(s.w, "data: %s\n\n", data); err != nil { + return err } - fmt.Fprintf(s.w, "data: %s\n\n", data) - http.NewResponseController(s.w).Flush() + if err := http.NewResponseController(s.w).Flush(); err != nil { + return err + } return nil } -// Close is a no-op for SSE. We keep the method for compatibility with other -// stream types. +// Close does nothing because an SSE stream closes with its HTTP response. The +// common stream interface still requires this method. func (s *HTTPServerStreamSseServerStream) Close() error { return nil } @@ -125,6 +93,8 @@ type MixedServerStreamServerStream struct { w http.ResponseWriter // r is the HTTP request. r *http.Request + // attempted is true after this stream writes the HTTP success status. + attempted bool } // Send Send streams instances of "testhttpgrpc.MixedServerStreamResult" to the @@ -137,6 +107,16 @@ func (s *MixedServerStreamServerStream) Send(v *testhttpgrpc.MixedServerStreamRe // "testhttpgrpc.MixedServerStreamResult" to the "mixed_server_stream" endpoint // SSE connection with context. func (s *MixedServerStreamServerStream) SendWithContext(ctx context.Context, v *testhttpgrpc.MixedServerStreamResult) error { + res := v + + var data string + body := NewMixedServerStreamResponseBody(res) + + byts, err := json.Marshal(body) + if err != nil { + return err + } + data = string(byts) s.once.Do(func() { header := s.w.Header() if header.Get("Content-Type") == "" { @@ -149,65 +129,21 @@ func (s *MixedServerStreamServerStream) SendWithContext(ctx context.Context, v * header.Set("Connection", "keep-alive") } s.w.WriteHeader(http.StatusOK) + s.attempted = true }) - res := v - var data string - var payload any - body := NewMixedServerStreamResponseBody(res) - payload = body - switch v := payload.(type) { - case nil: - data = "null" - case string: - data = v - case []byte: - data = string(v) - case bool: - if v { - data = "true" - } else { - data = "false" - } - case int: - data = fmt.Sprintf("%d", v) - case int8: - data = fmt.Sprintf("%d", v) - case int16: - data = fmt.Sprintf("%d", v) - case int32: - data = fmt.Sprintf("%d", v) - case int64: - data = fmt.Sprintf("%d", v) - case uint: - data = fmt.Sprintf("%d", v) - case uint8: - data = fmt.Sprintf("%d", v) - case uint16: - data = fmt.Sprintf("%d", v) - case uint32: - data = fmt.Sprintf("%d", v) - case uint64: - data = fmt.Sprintf("%d", v) - case float32: - data = fmt.Sprintf("%g", v) - case float64: - data = fmt.Sprintf("%g", v) - default: - byts, err := json.Marshal(payload) - if err != nil { - return err - } - data = string(byts) + if _, err := fmt.Fprintf(s.w, "data: %s\n\n", data); err != nil { + return err } - fmt.Fprintf(s.w, "data: %s\n\n", data) - http.NewResponseController(s.w).Flush() + if err := http.NewResponseController(s.w).Flush(); err != nil { + return err + } return nil } -// Close is a no-op for SSE. We keep the method for compatibility with other -// stream types. +// Close does nothing because an SSE stream closes with its HTTP response. The +// common stream interface still requires this method. func (s *MixedServerStreamServerStream) Close() error { return nil } diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/types.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/types.go index 5278601fa..356cd16f3 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/types.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/types.go @@ -300,7 +300,7 @@ func NewMixedBidiStreamWsGrpcStreamingBody(body *MixedBidiStreamWsGrpcStreamingB } // ValidateHTTPNoStreamRequestBody runs the validations defined on -// http_no_stream_request_body +// HTTPNoStreamRequestBody func ValidateHTTPNoStreamRequestBody(body *HTTPNoStreamRequestBody) (err error) { if body.Msg == nil { err = goa.MergeErrors(err, goa.MissingFieldError("msg", "body")) @@ -309,7 +309,7 @@ func ValidateHTTPNoStreamRequestBody(body *HTTPNoStreamRequestBody) (err error) } // ValidateHTTPNoStreamErrorRequestBody runs the validations defined on -// http_no_stream_error_request_body +// HTTPNoStreamErrorRequestBody func ValidateHTTPNoStreamErrorRequestBody(body *HTTPNoStreamErrorRequestBody) (err error) { if body.Msg == nil { err = goa.MergeErrors(err, goa.MissingFieldError("msg", "body")) @@ -318,7 +318,7 @@ func ValidateHTTPNoStreamErrorRequestBody(body *HTTPNoStreamErrorRequestBody) (e } // ValidateHTTPClientStreamWsStreamingBody runs the validations defined on -// http_client_stream_ws_streaming_body +// HTTPClientStreamWsStreamingBody func ValidateHTTPClientStreamWsStreamingBody(body *HTTPClientStreamWsStreamingBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -327,7 +327,7 @@ func ValidateHTTPClientStreamWsStreamingBody(body *HTTPClientStreamWsStreamingBo } // ValidateHTTPBidiStreamWsStreamingBody runs the validations defined on -// http_bidi_stream_ws_streaming_body +// HTTPBidiStreamWsStreamingBody func ValidateHTTPBidiStreamWsStreamingBody(body *HTTPBidiStreamWsStreamingBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -336,7 +336,7 @@ func ValidateHTTPBidiStreamWsStreamingBody(body *HTTPBidiStreamWsStreamingBody) } // ValidateMixedNoStreamRequestBody runs the validations defined on -// mixed_no_stream_request_body +// MixedNoStreamRequestBody func ValidateMixedNoStreamRequestBody(body *MixedNoStreamRequestBody) (err error) { if body.Msg == nil { err = goa.MergeErrors(err, goa.MissingFieldError("msg", "body")) @@ -345,7 +345,7 @@ func ValidateMixedNoStreamRequestBody(body *MixedNoStreamRequestBody) (err error } // ValidateMixedClientStreamWsGrpcStreamingBody runs the validations defined on -// mixed_client_stream_ws_grpc_streaming_body +// MixedClientStreamWsGrpcStreamingBody func ValidateMixedClientStreamWsGrpcStreamingBody(body *MixedClientStreamWsGrpcStreamingBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) @@ -354,7 +354,7 @@ func ValidateMixedClientStreamWsGrpcStreamingBody(body *MixedClientStreamWsGrpcS } // ValidateMixedBidiStreamWsGrpcStreamingBody runs the validations defined on -// mixed_bidi_stream_ws_grpc_streaming_body +// MixedBidiStreamWsGrpcStreamingBody func ValidateMixedBidiStreamWsGrpcStreamingBody(body *MixedBidiStreamWsGrpcStreamingBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/websocket.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/websocket.go index 934391f4f..0ea9746d3 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/websocket.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/websocket.go @@ -36,6 +36,11 @@ type HTTPServerStreamWsServerStream struct { once sync.Once // upgradeErr is the error returned by the websocket upgrade attempt. upgradeErr error + // closeOnce makes repeated Close calls return the first close result without + // writing again. + closeOnce sync.Once + // closeErr is the result of the first Close call. + closeErr error // upgrader is the websocket connection upgrader. upgrader goahttp.Upgrader // configurer is the websocket connection configurer. @@ -78,6 +83,11 @@ type HTTPBidiStreamWsServerStream struct { once sync.Once // upgradeErr is the error returned by the websocket upgrade attempt. upgradeErr error + // closeOnce makes repeated Close calls return the first close result without + // writing again. + closeOnce sync.Once + // closeErr is the result of the first Close call. + closeErr error // upgrader is the websocket connection upgrader. upgrader goahttp.Upgrader // configurer is the websocket connection configurer. @@ -120,6 +130,11 @@ type MixedBidiStreamWsGrpcServerStream struct { once sync.Once // upgradeErr is the error returned by the websocket upgrade attempt. upgradeErr error + // closeOnce makes repeated Close calls return the first close result without + // writing again. + closeOnce sync.Once + // closeErr is the result of the first Close call. + closeErr error // upgrader is the websocket connection upgrader. upgrader goahttp.Upgrader // configurer is the websocket connection configurer. @@ -182,9 +197,33 @@ func (s *HTTPServerStreamWsServerStream) SendWithContext(ctx context.Context, v // Close closes the "http_server_stream_ws" endpoint websocket connection. func (s *HTTPServerStreamWsServerStream) Close() error { + s.closeOnce.Do(func() { + s.closeErr = s.close() + }) + return s.closeErr +} + +// close opens the websocket connection when needed, sends its normal close +// message, and closes it. +func (s *HTTPServerStreamWsServerStream) close() error { var err error - if s.conn == nil { - return nil + // Upgrade the HTTP connection to a websocket connection only once. Connection + // upgrade is done here so that authorization logic in the endpoint is executed + // before calling the actual service method which may call Close(). + s.once.Do(func() { + var conn *websocket.Conn + conn, err = s.upgrader.Upgrade(s.w, s.r, nil) + if err != nil { + s.upgradeErr = err + return + } + if s.configurer != nil { + conn = s.configurer(conn, s.cancel) + } + s.conn = conn + }) + if s.upgradeErr != nil { + return s.upgradeErr } if err = s.conn.WriteControl( websocket.CloseMessage, @@ -342,9 +381,33 @@ func (s *HTTPBidiStreamWsServerStream) RecvWithContext(ctx context.Context) (*te // Close closes the "http_bidi_stream_ws" endpoint websocket connection. func (s *HTTPBidiStreamWsServerStream) Close() error { + s.closeOnce.Do(func() { + s.closeErr = s.close() + }) + return s.closeErr +} + +// close opens the websocket connection when needed, sends its normal close +// message, and closes it. +func (s *HTTPBidiStreamWsServerStream) close() error { var err error - if s.conn == nil { - return nil + // Upgrade the HTTP connection to a websocket connection only once. Connection + // upgrade is done here so that authorization logic in the endpoint is executed + // before calling the actual service method which may call Close(). + s.once.Do(func() { + var conn *websocket.Conn + conn, err = s.upgrader.Upgrade(s.w, s.r, nil) + if err != nil { + s.upgradeErr = err + return + } + if s.configurer != nil { + conn = s.configurer(conn, s.cancel) + } + s.conn = conn + }) + if s.upgradeErr != nil { + return s.upgradeErr } if err = s.conn.WriteControl( websocket.CloseMessage, @@ -506,9 +569,33 @@ func (s *MixedBidiStreamWsGrpcServerStream) RecvWithContext(ctx context.Context) // Close closes the "mixed_bidi_stream_ws_grpc" endpoint websocket connection. func (s *MixedBidiStreamWsGrpcServerStream) Close() error { + s.closeOnce.Do(func() { + s.closeErr = s.close() + }) + return s.closeErr +} + +// close opens the websocket connection when needed, sends its normal close +// message, and closes it. +func (s *MixedBidiStreamWsGrpcServerStream) close() error { var err error - if s.conn == nil { - return nil + // Upgrade the HTTP connection to a websocket connection only once. Connection + // upgrade is done here so that authorization logic in the endpoint is executed + // before calling the actual service method which may call Close(). + s.once.Do(func() { + var conn *websocket.Conn + conn, err = s.upgrader.Upgrade(s.w, s.r, nil) + if err != nil { + s.upgradeErr = err + return + } + if s.configurer != nil { + conn = s.configurer(conn, s.cancel) + } + s.conn = conn + }) + if s.upgradeErr != nil { + return s.upgradeErr } if err = s.conn.WriteControl( websocket.CloseMessage, diff --git a/testing/examples/httpgrpc/gen/test_http_grpc/service.go b/testing/examples/httpgrpc/gen/test_http_grpc/service.go index af0a1e7f7..109f4eba0 100644 --- a/testing/examples/httpgrpc/gen/test_http_grpc/service.go +++ b/testing/examples/httpgrpc/gen/test_http_grpc/service.go @@ -8,9 +8,7 @@ package testhttpgrpc -import ( - "context" -) +import "context" // Testing plugin matrix across HTTP and gRPC transports type Service interface { diff --git a/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/scenarios.go b/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/scenarios.go index 32002ae6d..6c6a135dc 100644 --- a/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/scenarios.go +++ b/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/scenarios.go @@ -67,11 +67,11 @@ type Validators struct { // Valid transport values for scenarios based on service configuration. // Methods may support different combinations of these transports. var ValidTransports = []string{ - "auto", // Use default/first available - "http", // HTTP plain (non-streaming methods only) - "http-sse", // HTTP Server-Sent Events (server streaming) - "http-ws", // HTTP WebSocket (client/server/bidi streaming) - "grpc", // gRPC (all streaming modes) + "auto", // Use the first available transport + "grpc", // gRPC + "http", // HTTP + "http-sse", // HTTP server-sent events + "http-ws", // HTTP WebSocket } // TransportAvailability documents which transports each method supports. @@ -91,7 +91,9 @@ var TransportAvailability = map[string][]string{ "mixed_server_stream": {"grpc", "http-sse"}, "mixed_client_stream_ws_grpc": {"grpc", "http-ws"}, "mixed_bidi_stream_ws_grpc": {"grpc", "http-ws"}, -} // ScenarioRunner executes test scenarios. +} + +// ScenarioRunner executes test scenarios. type ScenarioRunner struct { scenarios []Scenario validators Validators // Global validator configuration @@ -163,13 +165,12 @@ func (r *ScenarioRunner) RunNamed(t *testing.T, client *Client, name string) { } func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scenario) { - // Apply default transport if specified - if scenario.Transport != "" { - client = r.selectTransport(client, scenario.Transport) - } - for i, step := range scenario.Steps { t.Run(fmt.Sprintf("step_%d_%s", i+1, step.Method), func(t *testing.T) { + // Use the scenario transport when this step does not choose one. + if step.Transport == "" { + step.Transport = scenario.Transport + } // Apply scenario-level timeout if step doesn't override if step.Timeout == "" && scenario.Timeout != "" { step.Timeout = scenario.Timeout @@ -180,27 +181,27 @@ func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scen } func (r *ScenarioRunner) runStep(t *testing.T, client *Client, step Step) { - // Apply per-step transport override - if step.Transport != "" { - client = r.selectTransport(client, step.Transport) + // Check the method before using its generated transport list. + transports, ok := TransportAvailability[step.Method] + if !ok { + t.Fatalf("unknown method %q", step.Method) } - // Validate transport availability + // Check the selected transport before choosing its client. if step.Transport != "" && step.Transport != "auto" { - if transports, ok := TransportAvailability[step.Method]; ok { - found := false - for _, t := range transports { - if t == step.Transport { - found = true - break - } - } - if !found { - t.Fatalf("method %q does not support transport %q, available: %v", - step.Method, step.Transport, transports) + found := false + for _, transport := range transports { + if transport == step.Transport { + found = true + break } } + if !found { + t.Fatalf("method %q does not support transport %q, available: %v", + step.Method, step.Transport, transports) + } } + client = r.selectTransport(client, step.Transport) // Process payload payload := step.Payload @@ -499,11 +500,13 @@ func (r *ScenarioRunner) validateStream(t *testing.T, method string, stream any, func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Client { switch transport { + case "", "auto": + return client case "http", "http-sse", "http-ws": return client.HTTP() case "grpc": return client.GRPC() default: - return client // auto or unknown - use default + panic(fmt.Sprintf("unsupported transport %q", transport)) } } diff --git a/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/testdata.go b/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/testdata.go index 1b7642de4..4aa3faf4e 100644 --- a/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/testdata.go +++ b/testing/examples/httpgrpc/gen/test_http_grpc/test_http_grpctest/testdata.go @@ -25,7 +25,7 @@ func NewTestData() *TestData { // ValidHTTPNoStreamPayload generates a valid http_no_stream payload. func (td *TestData) ValidHTTPNoStreamPayload() *testhttpgrpc.HTTPNoStreamPayload { var payload *testhttpgrpc.HTTPNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Id qui qui reprehenderit quae."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Aut assumenda minima voluptas."}`), &payload) return payload } @@ -33,7 +33,7 @@ func (td *TestData) ValidHTTPNoStreamPayload() *testhttpgrpc.HTTPNoStreamPayload // fields populated. func (td *TestData) HTTPNoStreamPayloadWithAllFields() *testhttpgrpc.HTTPNoStreamPayload { var payload *testhttpgrpc.HTTPNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Id qui qui reprehenderit quae."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Aut assumenda minima voluptas."}`), &payload) return payload } @@ -57,7 +57,7 @@ func (b *HTTPNoStreamPayloadBuilder) Build() *testhttpgrpc.HTTPNoStreamPayload { // ValidGrpcNoStreamPayload generates a valid grpc_no_stream payload. func (td *TestData) ValidGrpcNoStreamPayload() *testhttpgrpc.GrpcNoStreamPayload { var payload *testhttpgrpc.GrpcNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Excepturi doloribus vitae nostrum."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Eum ad."}`), &payload) return payload } @@ -65,7 +65,7 @@ func (td *TestData) ValidGrpcNoStreamPayload() *testhttpgrpc.GrpcNoStreamPayload // fields populated. func (td *TestData) GrpcNoStreamPayloadWithAllFields() *testhttpgrpc.GrpcNoStreamPayload { var payload *testhttpgrpc.GrpcNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Excepturi doloribus vitae nostrum."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Eum ad."}`), &payload) return payload } @@ -89,7 +89,7 @@ func (b *GrpcNoStreamPayloadBuilder) Build() *testhttpgrpc.GrpcNoStreamPayload { // ValidHTTPNoStreamErrorPayload generates a valid http_no_stream_error payload. func (td *TestData) ValidHTTPNoStreamErrorPayload() *testhttpgrpc.HTTPNoStreamErrorPayload { var payload *testhttpgrpc.HTTPNoStreamErrorPayload - json.Unmarshal([]byte(`{"msg":"Deleniti aut culpa corrupti aperiam."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Dolorum reiciendis iste aperiam sit iste tempora."}`), &payload) return payload } @@ -97,7 +97,7 @@ func (td *TestData) ValidHTTPNoStreamErrorPayload() *testhttpgrpc.HTTPNoStreamEr // payload with all fields populated. func (td *TestData) HTTPNoStreamErrorPayloadWithAllFields() *testhttpgrpc.HTTPNoStreamErrorPayload { var payload *testhttpgrpc.HTTPNoStreamErrorPayload - json.Unmarshal([]byte(`{"msg":"Deleniti aut culpa corrupti aperiam."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Dolorum reiciendis iste aperiam sit iste tempora."}`), &payload) return payload } @@ -122,7 +122,7 @@ func (b *HTTPNoStreamErrorPayloadBuilder) Build() *testhttpgrpc.HTTPNoStreamErro // grpc_no_stream_error_div_by_zero payload. func (td *TestData) ValidGrpcNoStreamErrorDivByZeroPayload() *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload { var payload *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload - json.Unmarshal([]byte(`{"dividend":318.5228850215451,"divisor":64.0342870343787}`), &payload) + json.Unmarshal([]byte(`{"dividend":397.09948508741564,"divisor":-36.01233702377532}`), &payload) return payload } @@ -146,7 +146,7 @@ func (td *TestData) GrpcNoStreamErrorDivByZeroPayloadWithMaxValues() *testhttpgr // grpc_no_stream_error_div_by_zero payload with all fields populated. func (td *TestData) GrpcNoStreamErrorDivByZeroPayloadWithAllFields() *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload { var payload *testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload - json.Unmarshal([]byte(`{"dividend":318.5228850215451,"divisor":64.0342870343787}`), &payload) + json.Unmarshal([]byte(`{"dividend":397.09948508741564,"divisor":-36.01233702377532}`), &payload) return payload } @@ -170,7 +170,7 @@ func (b *GrpcNoStreamErrorDivByZeroPayloadBuilder) Build() *testhttpgrpc.GrpcNoS // ValidGrpcServerStreamPayload generates a valid grpc_server_stream payload. func (td *TestData) ValidGrpcServerStreamPayload() *testhttpgrpc.GrpcServerStreamPayload { var payload *testhttpgrpc.GrpcServerStreamPayload - json.Unmarshal([]byte(`{"from":2209498185759183982}`), &payload) + json.Unmarshal([]byte(`{"from":4130128606002420225}`), &payload) return payload } @@ -178,7 +178,7 @@ func (td *TestData) ValidGrpcServerStreamPayload() *testhttpgrpc.GrpcServerStrea // with all fields populated. func (td *TestData) GrpcServerStreamPayloadWithAllFields() *testhttpgrpc.GrpcServerStreamPayload { var payload *testhttpgrpc.GrpcServerStreamPayload - json.Unmarshal([]byte(`{"from":2209498185759183982}`), &payload) + json.Unmarshal([]byte(`{"from":4130128606002420225}`), &payload) return payload } @@ -203,7 +203,7 @@ func (b *GrpcServerStreamPayloadBuilder) Build() *testhttpgrpc.GrpcServerStreamP // payload. func (td *TestData) ValidHTTPClientStreamWsPayload() *testhttpgrpc.HTTPClientStreamWsStreamingPayload { var payload *testhttpgrpc.HTTPClientStreamWsStreamingPayload - json.Unmarshal([]byte(`{"message":"Aut dolor illum qui tempore ut sit."}`), &payload) + json.Unmarshal([]byte(`{"message":"Aliquid quibusdam voluptatem ut eveniet porro beatae."}`), &payload) return payload } @@ -211,7 +211,7 @@ func (td *TestData) ValidHTTPClientStreamWsPayload() *testhttpgrpc.HTTPClientStr // payload with all fields populated. func (td *TestData) HTTPClientStreamWsPayloadWithAllFields() *testhttpgrpc.HTTPClientStreamWsStreamingPayload { var payload *testhttpgrpc.HTTPClientStreamWsStreamingPayload - json.Unmarshal([]byte(`{"message":"Aut dolor illum qui tempore ut sit."}`), &payload) + json.Unmarshal([]byte(`{"message":"Aliquid quibusdam voluptatem ut eveniet porro beatae."}`), &payload) return payload } @@ -235,7 +235,7 @@ func (b *HTTPClientStreamWsPayloadBuilder) Build() *testhttpgrpc.HTTPClientStrea // ValidGrpcClientStreamPayload generates a valid grpc_client_stream payload. func (td *TestData) ValidGrpcClientStreamPayload() *testhttpgrpc.GrpcClientStreamStreamingPayload { var payload *testhttpgrpc.GrpcClientStreamStreamingPayload - json.Unmarshal([]byte(`{"value":4501586917888906260}`), &payload) + json.Unmarshal([]byte(`{"value":3747076685653658800}`), &payload) return payload } @@ -243,7 +243,7 @@ func (td *TestData) ValidGrpcClientStreamPayload() *testhttpgrpc.GrpcClientStrea // with all fields populated. func (td *TestData) GrpcClientStreamPayloadWithAllFields() *testhttpgrpc.GrpcClientStreamStreamingPayload { var payload *testhttpgrpc.GrpcClientStreamStreamingPayload - json.Unmarshal([]byte(`{"value":4501586917888906260}`), &payload) + json.Unmarshal([]byte(`{"value":3747076685653658800}`), &payload) return payload } @@ -267,7 +267,7 @@ func (b *GrpcClientStreamPayloadBuilder) Build() *testhttpgrpc.GrpcClientStreamS // ValidHTTPBidiStreamWsPayload generates a valid http_bidi_stream_ws payload. func (td *TestData) ValidHTTPBidiStreamWsPayload() *testhttpgrpc.HTTPBidiStreamWsStreamingPayload { var payload *testhttpgrpc.HTTPBidiStreamWsStreamingPayload - json.Unmarshal([]byte(`{"message":"Libero suscipit voluptatem ut possimus."}`), &payload) + json.Unmarshal([]byte(`{"message":"Aut veniam."}`), &payload) return payload } @@ -275,7 +275,7 @@ func (td *TestData) ValidHTTPBidiStreamWsPayload() *testhttpgrpc.HTTPBidiStreamW // with all fields populated. func (td *TestData) HTTPBidiStreamWsPayloadWithAllFields() *testhttpgrpc.HTTPBidiStreamWsStreamingPayload { var payload *testhttpgrpc.HTTPBidiStreamWsStreamingPayload - json.Unmarshal([]byte(`{"message":"Libero suscipit voluptatem ut possimus."}`), &payload) + json.Unmarshal([]byte(`{"message":"Aut veniam."}`), &payload) return payload } @@ -299,7 +299,7 @@ func (b *HTTPBidiStreamWsPayloadBuilder) Build() *testhttpgrpc.HTTPBidiStreamWsS // ValidGrpcBidiStreamPayload generates a valid grpc_bidi_stream payload. func (td *TestData) ValidGrpcBidiStreamPayload() *testhttpgrpc.GrpcBidiStreamStreamingPayload { var payload *testhttpgrpc.GrpcBidiStreamStreamingPayload - json.Unmarshal([]byte(`{"in":"Est facilis perspiciatis."}`), &payload) + json.Unmarshal([]byte(`{"in":"Est est voluptas tempore."}`), &payload) return payload } @@ -307,7 +307,7 @@ func (td *TestData) ValidGrpcBidiStreamPayload() *testhttpgrpc.GrpcBidiStreamStr // all fields populated. func (td *TestData) GrpcBidiStreamPayloadWithAllFields() *testhttpgrpc.GrpcBidiStreamStreamingPayload { var payload *testhttpgrpc.GrpcBidiStreamStreamingPayload - json.Unmarshal([]byte(`{"in":"Est facilis perspiciatis."}`), &payload) + json.Unmarshal([]byte(`{"in":"Est est voluptas tempore."}`), &payload) return payload } @@ -331,7 +331,7 @@ func (b *GrpcBidiStreamPayloadBuilder) Build() *testhttpgrpc.GrpcBidiStreamStrea // ValidMixedNoStreamPayload generates a valid mixed_no_stream payload. func (td *TestData) ValidMixedNoStreamPayload() *testhttpgrpc.MixedNoStreamPayload { var payload *testhttpgrpc.MixedNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Error odio repellat illum est consequatur sint."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Maiores consequatur impedit consequatur nam in."}`), &payload) return payload } @@ -339,7 +339,7 @@ func (td *TestData) ValidMixedNoStreamPayload() *testhttpgrpc.MixedNoStreamPaylo // all fields populated. func (td *TestData) MixedNoStreamPayloadWithAllFields() *testhttpgrpc.MixedNoStreamPayload { var payload *testhttpgrpc.MixedNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Error odio repellat illum est consequatur sint."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Maiores consequatur impedit consequatur nam in."}`), &payload) return payload } @@ -364,7 +364,7 @@ func (b *MixedNoStreamPayloadBuilder) Build() *testhttpgrpc.MixedNoStreamPayload // mixed_client_stream_ws_grpc payload. func (td *TestData) ValidMixedClientStreamWsGrpcPayload() *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload { var payload *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload - json.Unmarshal([]byte(`{"message":"Itaque temporibus et qui vitae."}`), &payload) + json.Unmarshal([]byte(`{"message":"Velit excepturi."}`), &payload) return payload } @@ -372,7 +372,7 @@ func (td *TestData) ValidMixedClientStreamWsGrpcPayload() *testhttpgrpc.MixedCli // mixed_client_stream_ws_grpc payload with all fields populated. func (td *TestData) MixedClientStreamWsGrpcPayloadWithAllFields() *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload { var payload *testhttpgrpc.MixedClientStreamWsGrpcStreamingPayload - json.Unmarshal([]byte(`{"message":"Itaque temporibus et qui vitae."}`), &payload) + json.Unmarshal([]byte(`{"message":"Velit excepturi."}`), &payload) return payload } @@ -397,7 +397,7 @@ func (b *MixedClientStreamWsGrpcPayloadBuilder) Build() *testhttpgrpc.MixedClien // mixed_bidi_stream_ws_grpc payload. func (td *TestData) ValidMixedBidiStreamWsGrpcPayload() *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload { var payload *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload - json.Unmarshal([]byte(`{"message":"Optio dolorem occaecati est cumque saepe."}`), &payload) + json.Unmarshal([]byte(`{"message":"Exercitationem doloremque porro."}`), &payload) return payload } @@ -405,7 +405,7 @@ func (td *TestData) ValidMixedBidiStreamWsGrpcPayload() *testhttpgrpc.MixedBidiS // mixed_bidi_stream_ws_grpc payload with all fields populated. func (td *TestData) MixedBidiStreamWsGrpcPayloadWithAllFields() *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload { var payload *testhttpgrpc.MixedBidiStreamWsGrpcStreamingPayload - json.Unmarshal([]byte(`{"message":"Optio dolorem occaecati est cumque saepe."}`), &payload) + json.Unmarshal([]byte(`{"message":"Exercitationem doloremque porro."}`), &payload) return payload } diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 725f77390..8e83a81a8 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.0 + goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 @@ -15,14 +15,12 @@ require ( require ( github.com/aws/smithy-go v1.27.8 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/go-chi/chi/v5 v5.3.1 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/gohugoio/hashstructure v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.12.1 // indirect go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 7a6b8acc1..ea0400bda 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -1,23 +1,23 @@ -github.com/aws/smithy-go v1.27.0 h1:ZoFioDKJxkSIW2otF9T0aPtNlUwhdVCcuZh/rzH9Hus= -github.com/aws/smithy-go v1.27.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/aws/smithy-go v1.27.7/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/smithy-go v1.27.8 h1:FR0dxZfIlV7Z8eh2iHfIofdunw382XsDV3Mxt9nUvRY= github.com/aws/smithy-go v1.27.8/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= -github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= -github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/getkin/kin-openapi v0.144.0 h1:hIRcTH+KjLfkLpYU6bSSfdFpi0fZi1fp+hSPi4aQu9Y= +github.com/getkin/kin-openapi v0.144.0/go.mod h1:3BH9M9XDe/y9M5DSvEocVYAYq1w0qrhJHjC/vZi0AaY= +github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg= -github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= github.com/gohugoio/hashstructure v1.0.0 h1:vWYuyzs1n0LdI0F54TJQeYAiB44fHX7H9hCp9X6gHKg= github.com/gohugoio/hashstructure v1.0.0/go.mod h1:FSbTK4QwxucJ2bC4Lvrs9a6x0DbQDXNoyBO+h4nlCgE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -36,88 +36,57 @@ github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqC github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b/go.mod h1:Bj8LjjP0ReT1eKt5QlKjwgi5AFm5mI6O1A2G4ChI0Ag= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY= +github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU= +github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw= +github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.28.1-0.20260613055708-11527bf14be7 h1:gg3ZcAGKckMgNSDA7eOjg98eVC8rgyIzNzpKKEUpR2E= -goa.design/goa/v3 v3.28.1-0.20260613055708-11527bf14be7/go.mod h1:EliUsJT3ObuebAPvYZsZtsl2wzEqf0N3HJRw6MrfDxQ= -goa.design/goa/v3 v3.29.0 h1:s8ic5VzONqm4unMM7qlGnCUCJ4cOfsZXBtqActDXDqU= -goa.design/goa/v3 v3.29.0/go.mod h1:FpTADA0Z2onOoZZD2YLWrA2IeXmaBEbs/FeFYag6hxg= -goa.design/goa/v3 v3.29.1 h1:O/FtqeWRI73e6EU6fuODelhLuSI40eSD7Cxn5fmoU5g= -goa.design/goa/v3 v3.29.1/go.mod h1:FpTADA0Z2onOoZZD2YLWrA2IeXmaBEbs/FeFYag6hxg= -goa.design/goa/v3 v3.29.2 h1:nabAmgYTUsE+NfG4KkMyRl6ddHN2uTAflOHC/FuaTfI= -goa.design/goa/v3 v3.29.2/go.mod h1:CTCdFy0UYeiYuoXmFkUJXji1xGPG7K6XFsO2NqU/QaQ= -goa.design/goa/v3 v3.30.0 h1:KXfAR5qlUCJLVPlHTZ6pHdC5b013BB78PLbDf2fFhfY= -goa.design/goa/v3 v3.30.0/go.mod h1:Is2byRdJddS20jhGT3Ql80N5hQyAc4HgAl7H/A0ZmJ4= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= -golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= -google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go b/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go index 4583a7d04..73ef0a28b 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go @@ -30,7 +30,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "test-jsonrpc jsonrpc-no-stream --body '{\n \"msg\": \"Vel optio magni.\"\n }'" + "\n" + + return os.Args[0] + " " + "test-jsonrpc jsonrpc-no-stream --body '{\n \"msg\": \"Enim eligendi ut.\"\n }'" + "\n" + "" } @@ -164,7 +164,7 @@ func testJsonrpcJsonrpcNoStreamUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-jsonrpc jsonrpc-no-stream --body '{\n \"msg\": \"Vel optio magni.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-jsonrpc jsonrpc-no-stream --body '{\n \"msg\": \"Enim eligendi ut.\"\n }'") } func testJsonrpcJsonrpcNoStreamErrorUsage() { @@ -182,5 +182,5 @@ func testJsonrpcJsonrpcNoStreamErrorUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-jsonrpc jsonrpc-no-stream-error --body '{\n \"msg\": \"Sit qui sint culpa dolorem officia temporibus.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "test-jsonrpc jsonrpc-no-stream-error --body '{\n \"msg\": \"Voluptatem fuga.\"\n }'") } diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go index 920f7931d..292d7249c 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go @@ -23,7 +23,7 @@ func BuildJsonrpcNoStreamPayload(testJsonrpcJsonrpcNoStreamBody string) (*testjs { err = json.Unmarshal([]byte(testJsonrpcJsonrpcNoStreamBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Vel optio magni.\"\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Enim eligendi ut.\"\n }'") } } v := &testjsonrpc.JsonrpcNoStreamPayload{ @@ -41,7 +41,7 @@ func BuildJsonrpcNoStreamErrorPayload(testJsonrpcJsonrpcNoStreamErrorBody string { err = json.Unmarshal([]byte(testJsonrpcJsonrpcNoStreamErrorBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Sit qui sint culpa dolorem officia temporibus.\"\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Voluptatem fuga.\"\n }'") } } v := &testjsonrpc.JsonrpcNoStreamErrorPayload{ diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go index da692190b..1dfa42306 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go @@ -32,12 +32,12 @@ type Client struct { decoder func(*http.Response) goahttp.Decoder } -// bufferPool is a pool of bytes.Buffers for encoding requests. +// bufferPool reuses byte buffers while requests are encoded. var bufferPool = sync.Pool{ New: func() any { return new(bytes.Buffer) }, } -// NewClient instantiates HTTP clients for all the test-jsonrpc service servers. +// NewClient creates HTTP clients for all the test-jsonrpc service servers. func NewClient( scheme string, host string, @@ -46,7 +46,6 @@ func NewClient( dec func(*http.Response) goahttp.Decoder, restoreBody bool, ) *Client { - return &Client{ Doer: doer, RestoreResponseBody: restoreBody, diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go index 1a7ddfcbd..5df410cc6 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -64,21 +65,31 @@ func EncodeJsonrpcNoStreamRequest(encoder func(*http.Request) goahttp.Encoder) f // the test-jsonrpc service jsonrpc_no_stream JSON-RPC method. restoreBody // controls whether the response body should be restored after having been read. func DecodeJsonrpcNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() + } else { + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream", err)) + } + }() } - defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream", err) + } return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream", resp.StatusCode, string(body)) } @@ -90,8 +101,7 @@ func DecodeJsonrpcNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, if jresp.Error != nil { switch jresp.Error.Code { default: - body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream", resp.StatusCode, string(jresp.Error.Data)) } } resp.Body = io.NopCloser(bytes.NewBuffer(jresp.Result)) @@ -157,21 +167,31 @@ func EncodeJsonrpcNoStreamErrorRequest(encoder func(*http.Request) goahttp.Encod // restoreBody controls whether the response body should be restored after // having been read. func DecodeJsonrpcNoStreamErrorResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream_error", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() + } else { + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream_error", err)) + } + }() } - defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream_error", err) + } return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream_error", resp.StatusCode, string(body)) } @@ -183,8 +203,7 @@ func DecodeJsonrpcNoStreamErrorResponse(decoder func(*http.Response) goahttp.Dec if jresp.Error != nil { switch jresp.Error.Code { default: - body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream_error", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream_error", resp.StatusCode, string(jresp.Error.Data)) } } resp.Body = io.NopCloser(bytes.NewBuffer(jresp.Result)) diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/types.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/types.go index f89b29d6e..128c532cd 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/types.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/types.go @@ -77,7 +77,7 @@ func NewJsonrpcNoStreamErrorResultOK(body *JsonrpcNoStreamErrorResponseBody) *te } // ValidateJsonrpcNoStreamResponseBody runs the validations defined on -// jsonrpc_no_stream_response_body +// JsonrpcNoStreamResponseBody func ValidateJsonrpcNoStreamResponseBody(body *JsonrpcNoStreamResponseBody) (err error) { if body.Out == nil { err = goa.MergeErrors(err, goa.MissingFieldError("out", "body")) @@ -86,7 +86,7 @@ func ValidateJsonrpcNoStreamResponseBody(body *JsonrpcNoStreamResponseBody) (err } // ValidateJsonrpcNoStreamErrorResponseBody runs the validations defined on -// jsonrpc_no_stream_error_response_body +// JsonrpcNoStreamErrorResponseBody func ValidateJsonrpcNoStreamErrorResponseBody(body *JsonrpcNoStreamErrorResponseBody) (err error) { if body.Out == nil { err = goa.MergeErrors(err, goa.MissingFieldError("out", "body")) diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go index 334a17c1d..031626713 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go @@ -58,8 +58,8 @@ func New( encoder: encoder, errhandler: errhandler, } - // Default HTTP handler per transport kind - // Plain HTTP JSON-RPC + // Install the request handler required by this service's methods. + // ServeHTTP handles ordinary JSON-RPC request bodies. s.Handler = http.HandlerFunc(s.ServeHTTP) return s } @@ -78,32 +78,44 @@ func (s *Server) MethodNames() []string { return testjsonrpc.MethodNames[:] } // ServeHTTP handles JSON-RPC requests. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.handleHTTP(w, r) -} // handleHTTP handles JSON-RPC requests. +} + +// handleHTTP reads one JSON-RPC request object or one array of requests. func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { - // Peek at the first byte to determine request type - bufReader := bufio.NewReader(r.Body) - peek, err := bufReader.Peek(1) - if err != nil && err != io.EOF { - r.Body.Close() - s.errhandler(r.Context(), w, fmt.Errorf("failed to read request body: %w", err)) - return + originalBody := r.Body + + // Find the first JSON byte so leading whitespace does not change whether the + // body is decoded as one request or an array. + bufReader := bufio.NewReader(originalBody) + var peek []byte + for { + var err error + peek, err = bufReader.Peek(1) + if err != nil && err != io.EOF { + closeErr := originalBody.Close() + s.errhandler(r.Context(), w, fmt.Errorf("failed to read request body: %w", errors.Join(err, closeErr))) + return + } + if len(peek) == 0 || (peek[0] != ' ' && peek[0] != '\t' && peek[0] != '\n' && peek[0] != '\r') { + break + } + if _, err := bufReader.Discard(1); err != nil { + closeErr := originalBody.Close() + s.errhandler(r.Context(), w, fmt.Errorf("failed to read request body: %w", errors.Join(err, closeErr))) + return + } } - // Wrap the buffered reader with the original closer - r.Body = struct { - io.Reader - io.Closer - }{ - Reader: bufReader, - Closer: r.Body, - } - defer func(r *http.Request) { - if err := r.Body.Close(); err != nil { + // The generated handler owns the original body. Decoders receive a wrapper + // whose Close method cannot close it a second time. + r.Body = io.NopCloser(bufReader) + defer func() { + if err := originalBody.Close(); err != nil { s.errhandler(r.Context(), w, fmt.Errorf("failed to close request body: %w", err)) } - }(r) + }() - // Route to appropriate handler + // A leading '[' starts an array of requests. if len(peek) > 0 && peek[0] == '[' { s.handleBatch(w, r) return @@ -111,11 +123,11 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { s.handleSingle(w, r) } -// handleSingle handles a single JSON-RPC request. +// handleSingle decodes and runs one JSON-RPC request. func (s *Server) handleSingle(w http.ResponseWriter, r *http.Request) { var req jsonrpc.RawRequest if err := s.decoder(r).Decode(&req); err != nil { - // JSON-RPC parse error with null id and generic message + // A request that cannot be decoded receives the JSON-RPC parse error. response := jsonrpc.MakeErrorResponse(nil, jsonrpc.ParseError, "Parse error", nil) if encErr := s.encoder(r.Context(), w).Encode(response); encErr != nil { s.errhandler(r.Context(), w, fmt.Errorf("failed to encode parse error response: %w", encErr)) @@ -125,36 +137,46 @@ func (s *Server) handleSingle(w http.ResponseWriter, r *http.Request) { s.processRequest(r.Context(), r, &req, w) } -// handleBatch handles a batch of JSON-RPC requests. +// handleBatch handles an array of JSON-RPC values and writes the required responses. func (s *Server) handleBatch(w http.ResponseWriter, r *http.Request) { var reqs []jsonrpc.RawRequest if err := s.decoder(r).Decode(&reqs); err != nil { - // JSON-RPC parse error for batch with null id and generic message + // An array that cannot be decoded receives the JSON-RPC parse error. response := jsonrpc.MakeErrorResponse(nil, jsonrpc.ParseError, "Parse error", nil) if encErr := s.encoder(r.Context(), w).Encode(response); encErr != nil { s.errhandler(r.Context(), w, fmt.Errorf("failed to encode parse error response: %w", encErr)) } return } + if len(reqs) == 0 { + // JSON-RPC defines an empty request array as one invalid request. + response := jsonrpc.MakeErrorResponse(nil, jsonrpc.InvalidRequest, "Invalid request", nil) + if err := s.encoder(r.Context(), w).Encode(response); err != nil { + s.errhandler(r.Context(), w, fmt.Errorf("failed to encode invalid request response: %w", err)) + } + return + } - // Write responses + // Write every response into one JSON array. w.Header().Set("Content-Type", "application/json") writer := &batchWriter{Writer: w} for _, req := range reqs { - // Process the request with batch writer + // The writer inserts the array separators around each response. s.processRequest(r.Context(), r, &req, writer) } - // Close the batch array + // Write the closing bracket only when at least one request produced a response. if writer.written { - writer.Writer.Write([]byte{']'}) + if _, err := writer.Writer.Write([]byte{']'}); err != nil { + s.errhandler(r.Context(), w, fmt.Errorf("failed to close JSON-RPC batch response: %w", err)) + } } } -// ProcessRequest processes a single JSON-RPC request. +// processRequest validates the JSON-RPC version and method, then calls the matching handler. func (s *Server) processRequest(ctx context.Context, r *http.Request, req *jsonrpc.RawRequest, w http.ResponseWriter) { - if req.JSONRPC != "2.0" { + if req.Invalid || req.JSONRPC != "2.0" { s.encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidRequest, "Invalid request", nil) return } @@ -174,11 +196,14 @@ func (s *Server) processRequest(ctx context.Context, r *http.Request, req *jsonr s.errhandler(ctx, w, fmt.Errorf("handler error for %s: %w", "jsonrpc_no_stream_error", err)) } default: - s.encodeJSONRPCError(ctx, w, req, jsonrpc.MethodNotFound, "Method not found", nil) + if req.HasID { + s.encodeJSONRPCError(ctx, w, req, jsonrpc.MethodNotFound, "Method not found", nil) + } } } -// batchWriter is a helper type that implements http.ResponseWriter for writing multiple JSON-RPC responses +// batchWriter inserts JSON array separators around responses from one request +// array. type batchWriter struct { io.Writer header http.Header @@ -201,18 +226,20 @@ func (rb *batchWriter) WriteHeader(statusCode int) { } func (rb *batchWriter) Write(data []byte) (int, error) { + separator := byte(',') if !rb.written { - rb.written = true - rb.Writer.Write([]byte{'['}) - } else { - rb.Writer.Write([]byte{','}) + separator = '[' + } + if _, err := rb.Writer.Write([]byte{separator}); err != nil { + return 0, err } + rb.written = true return rb.Writer.Write(data) } // Mount configures the mux to serve the JSON-RPC test-jsonrpc service methods. func Mount(mux goahttp.Muxer, h *Server) { - // HTTP only + // This server handles ordinary JSON-RPC request bodies. mux.Handle("POST", "/", h.ServeHTTP) } @@ -236,61 +263,36 @@ func NewJsonrpcNoStreamHandler( ctx = context.WithValue(ctx, goa.ServiceKey, "test-jsonrpc") params, err := decodeParams(r, req) if err != nil { - // Only send error response if request has ID (not nil or empty string) - if req.ID != nil && req.ID != "" { - code := jsonrpc.InternalError - if _, ok := err.(*goa.ServiceError); ok { - code = jsonrpc.InvalidParams - } - encodeJSONRPCError(ctx, w, req, code, err.Error(), nil, encoder, errhandler) + if req.HasID { + encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) } else { - // No ID means notification - just log error + // A notification receives no JSON-RPC response, so pass the decode error to the configured error handler. errhandler(ctx, w, fmt.Errorf("failed to decode parameters: %w", err)) } return nil } res, err := endpoint(ctx, params) if err != nil { - // Only send error response if request has ID (not nil or empty string) - if req.ID != nil && req.ID != "" { - var en goa.GoaErrorNamer - if !errors.As(err, &en) { - encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) - return nil - } - switch en.GoaErrorName() { - case "invalid_params": - encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) - case "method_not_found": - encodeJSONRPCError(ctx, w, req, jsonrpc.MethodNotFound, err.Error(), nil, encoder, errhandler) - default: - code := jsonrpc.InternalError - if _, ok := err.(*goa.ServiceError); ok { - code = jsonrpc.InvalidParams - } - encodeJSONRPCError(ctx, w, req, code, err.Error(), nil, encoder, errhandler) - } + if req.HasID { + encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) } else { - // No ID means notification - just log error + // A notification receives no JSON-RPC response, so pass the service error to the configured error handler. errhandler(ctx, w, fmt.Errorf("endpoint error: %w", err)) } return nil } - - // For methods with no result, check if this is a notification + if !req.HasID { + // A notification has no ID field and receives no response. + return nil + } // For methods with results, determine the ID to use for the response var id any // No ID field in result - use request ID id = req.ID - if id == nil || id == "" { - // Notification - no response - return nil - } - // Send response with the result - // Convert result to response body with proper JSON tags + // Build the response body with the fields and JSON names declared by the service. body := NewJsonrpcNoStreamResponseBody(res.(*testjsonrpc.JsonrpcNoStreamResult)) response := jsonrpc.MakeSuccessResponse(id, body) if err := encoder(ctx, w).Encode(response); err != nil { @@ -315,61 +317,36 @@ func NewJsonrpcNoStreamErrorHandler( ctx = context.WithValue(ctx, goa.ServiceKey, "test-jsonrpc") params, err := decodeParams(r, req) if err != nil { - // Only send error response if request has ID (not nil or empty string) - if req.ID != nil && req.ID != "" { - code := jsonrpc.InternalError - if _, ok := err.(*goa.ServiceError); ok { - code = jsonrpc.InvalidParams - } - encodeJSONRPCError(ctx, w, req, code, err.Error(), nil, encoder, errhandler) + if req.HasID { + encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) } else { - // No ID means notification - just log error + // A notification receives no JSON-RPC response, so pass the decode error to the configured error handler. errhandler(ctx, w, fmt.Errorf("failed to decode parameters: %w", err)) } return nil } res, err := endpoint(ctx, params) if err != nil { - // Only send error response if request has ID (not nil or empty string) - if req.ID != nil && req.ID != "" { - var en goa.GoaErrorNamer - if !errors.As(err, &en) { - encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) - return nil - } - switch en.GoaErrorName() { - case "invalid_params": - encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) - case "method_not_found": - encodeJSONRPCError(ctx, w, req, jsonrpc.MethodNotFound, err.Error(), nil, encoder, errhandler) - default: - code := jsonrpc.InternalError - if _, ok := err.(*goa.ServiceError); ok { - code = jsonrpc.InvalidParams - } - encodeJSONRPCError(ctx, w, req, code, err.Error(), nil, encoder, errhandler) - } + if req.HasID { + encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) } else { - // No ID means notification - just log error + // A notification receives no JSON-RPC response, so pass the service error to the configured error handler. errhandler(ctx, w, fmt.Errorf("endpoint error: %w", err)) } return nil } - - // For methods with no result, check if this is a notification + if !req.HasID { + // A notification has no ID field and receives no response. + return nil + } // For methods with results, determine the ID to use for the response var id any // No ID field in result - use request ID id = req.ID - if id == nil || id == "" { - // Notification - no response - return nil - } - // Send response with the result - // Convert result to response body with proper JSON tags + // Build the response body with the fields and JSON names declared by the service. body := NewJsonrpcNoStreamErrorResponseBody(res.(*testjsonrpc.JsonrpcNoStreamErrorResult)) response := jsonrpc.MakeSuccessResponse(id, body) if err := encoder(ctx, w).Encode(response); err != nil { @@ -379,14 +356,14 @@ func NewJsonrpcNoStreamErrorHandler( } } -// encodeJSONRPCError creates and sends a JSON-RPC error response (handles nil -// ID gracefully) +// encodeJSONRPCError writes one error, copying the request ID or using null +// when none is available. func (s *Server) encodeJSONRPCError(ctx context.Context, w http.ResponseWriter, req *jsonrpc.RawRequest, code jsonrpc.Code, message string, data any) { encodeJSONRPCError(ctx, w, req, code, message, data, s.encoder, s.errhandler) } -// encodeJSONRPCError creates and sends a JSON-RPC error response (handles nil -// ID gracefully) +// encodeJSONRPCError writes one error, copying the request ID or using null +// when none is available. func encodeJSONRPCError( ctx context.Context, w http.ResponseWriter, @@ -397,10 +374,8 @@ func encodeJSONRPCError( encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, errhandler func(context.Context, http.ResponseWriter, error), ) { - if req.ID != nil { - response := jsonrpc.MakeErrorResponse(req.ID, code, message, data) - if err := encoder(ctx, w).Encode(response); err != nil { - errhandler(ctx, w, fmt.Errorf("failed to encode JSON-RPC response: %w", err)) - } + response := jsonrpc.MakeErrorResponse(req.ID, code, message, data) + if err := encoder(ctx, w).Encode(response); err != nil { + errhandler(ctx, w, fmt.Errorf("failed to encode JSON-RPC response: %w", err)) } } diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/types.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/types.go index 1fc02f941..2ea1efb50 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/types.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/types.go @@ -77,7 +77,7 @@ func NewJsonrpcNoStreamErrorPayload(body *JsonrpcNoStreamErrorRequestBody) *test } // ValidateJsonrpcNoStreamRequestBody runs the validations defined on -// jsonrpc_no_stream_request_body +// JsonrpcNoStreamRequestBody func ValidateJsonrpcNoStreamRequestBody(body *JsonrpcNoStreamRequestBody) (err error) { if body.Msg == nil { err = goa.MergeErrors(err, goa.MissingFieldError("msg", "body")) @@ -86,7 +86,7 @@ func ValidateJsonrpcNoStreamRequestBody(body *JsonrpcNoStreamRequestBody) (err e } // ValidateJsonrpcNoStreamErrorRequestBody runs the validations defined on -// jsonrpc_no_stream_error_request_body +// JsonrpcNoStreamErrorRequestBody func ValidateJsonrpcNoStreamErrorRequestBody(body *JsonrpcNoStreamErrorRequestBody) (err error) { if body.Msg == nil { err = goa.MergeErrors(err, goa.MissingFieldError("msg", "body")) diff --git a/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/scenarios.go b/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/scenarios.go index 462a35df6..79de02a19 100644 --- a/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/scenarios.go +++ b/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/scenarios.go @@ -66,17 +66,17 @@ type Validators struct { // Valid transport values for scenarios based on service configuration. // Methods may support different combinations of these transports. var ValidTransports = []string{ - "auto", // Use default/first available - "jsonrpc", // JSON-RPC over HTTP (non-streaming) - "jsonrpc-sse", // JSON-RPC over SSE (server streaming) - "jsonrpc-ws", // JSON-RPC over WebSocket (streaming only) + "auto", // Use the first available transport + "jsonrpc", // JSON-RPC over HTTP } // TransportAvailability documents which transports each method supports. var TransportAvailability = map[string][]string{ "jsonrpc_no_stream": {"jsonrpc"}, "jsonrpc_no_stream_error": {"jsonrpc"}, -} // ScenarioRunner executes test scenarios. +} + +// ScenarioRunner executes test scenarios. type ScenarioRunner struct { scenarios []Scenario validators Validators // Global validator configuration @@ -148,13 +148,12 @@ func (r *ScenarioRunner) RunNamed(t *testing.T, client *Client, name string) { } func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scenario) { - // Apply default transport if specified - if scenario.Transport != "" { - client = r.selectTransport(client, scenario.Transport) - } - for i, step := range scenario.Steps { t.Run(fmt.Sprintf("step_%d_%s", i+1, step.Method), func(t *testing.T) { + // Use the scenario transport when this step does not choose one. + if step.Transport == "" { + step.Transport = scenario.Transport + } // Apply scenario-level timeout if step doesn't override if step.Timeout == "" && scenario.Timeout != "" { step.Timeout = scenario.Timeout @@ -165,27 +164,27 @@ func (r *ScenarioRunner) runScenario(t *testing.T, client *Client, scenario Scen } func (r *ScenarioRunner) runStep(t *testing.T, client *Client, step Step) { - // Apply per-step transport override - if step.Transport != "" { - client = r.selectTransport(client, step.Transport) + // Check the method before using its generated transport list. + transports, ok := TransportAvailability[step.Method] + if !ok { + t.Fatalf("unknown method %q", step.Method) } - // Validate transport availability + // Check the selected transport before choosing its client. if step.Transport != "" && step.Transport != "auto" { - if transports, ok := TransportAvailability[step.Method]; ok { - found := false - for _, t := range transports { - if t == step.Transport { - found = true - break - } - } - if !found { - t.Fatalf("method %q does not support transport %q, available: %v", - step.Method, step.Transport, transports) + found := false + for _, transport := range transports { + if transport == step.Transport { + found = true + break } } + if !found { + t.Fatalf("method %q does not support transport %q, available: %v", + step.Method, step.Transport, transports) + } } + client = r.selectTransport(client, step.Transport) // Process payload payload := step.Payload @@ -370,9 +369,11 @@ func (r *ScenarioRunner) validateStream(t *testing.T, method string, stream any, func (r *ScenarioRunner) selectTransport(client *Client, transport string) *Client { switch transport { - case "jsonrpc", "jsonrpc-sse", "jsonrpc-ws": + case "", "auto": + return client + case "jsonrpc", "jsonrpc-sse": return client.JSONRPC() default: - return client // auto or unknown - use default + panic(fmt.Sprintf("unsupported transport %q", transport)) } } diff --git a/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/testdata.go b/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/testdata.go index d2561c69c..02427ad76 100644 --- a/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/testdata.go +++ b/testing/examples/jsonrpc/gen/test_jsonrpc/test_jsonrpctest/testdata.go @@ -25,7 +25,7 @@ func NewTestData() *TestData { // ValidJsonrpcNoStreamPayload generates a valid jsonrpc_no_stream payload. func (td *TestData) ValidJsonrpcNoStreamPayload() *testjsonrpc.JsonrpcNoStreamPayload { var payload *testjsonrpc.JsonrpcNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Sit dolore omnis."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Iusto totam aut."}`), &payload) return payload } @@ -33,7 +33,7 @@ func (td *TestData) ValidJsonrpcNoStreamPayload() *testjsonrpc.JsonrpcNoStreamPa // with all fields populated. func (td *TestData) JsonrpcNoStreamPayloadWithAllFields() *testjsonrpc.JsonrpcNoStreamPayload { var payload *testjsonrpc.JsonrpcNoStreamPayload - json.Unmarshal([]byte(`{"msg":"Sit dolore omnis."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Iusto totam aut."}`), &payload) return payload } @@ -58,7 +58,7 @@ func (b *JsonrpcNoStreamPayloadBuilder) Build() *testjsonrpc.JsonrpcNoStreamPayl // payload. func (td *TestData) ValidJsonrpcNoStreamErrorPayload() *testjsonrpc.JsonrpcNoStreamErrorPayload { var payload *testjsonrpc.JsonrpcNoStreamErrorPayload - json.Unmarshal([]byte(`{"msg":"Pariatur corporis."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Neque itaque qui voluptate cumque."}`), &payload) return payload } @@ -66,7 +66,7 @@ func (td *TestData) ValidJsonrpcNoStreamErrorPayload() *testjsonrpc.JsonrpcNoStr // payload with all fields populated. func (td *TestData) JsonrpcNoStreamErrorPayloadWithAllFields() *testjsonrpc.JsonrpcNoStreamErrorPayload { var payload *testjsonrpc.JsonrpcNoStreamErrorPayload - json.Unmarshal([]byte(`{"msg":"Pariatur corporis."}`), &payload) + json.Unmarshal([]byte(`{"msg":"Neque itaque qui voluptate cumque."}`), &payload) return payload } diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 6ab81a605..69806bf28 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.0 + goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) @@ -13,14 +13,12 @@ require ( require ( github.com/aws/smithy-go v1.27.8 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/go-chi/chi/v5 v5.3.1 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/gohugoio/hashstructure v1.0.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.12.1 // indirect go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 7a6b8acc1..ea0400bda 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -1,23 +1,23 @@ -github.com/aws/smithy-go v1.27.0 h1:ZoFioDKJxkSIW2otF9T0aPtNlUwhdVCcuZh/rzH9Hus= -github.com/aws/smithy-go v1.27.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/aws/smithy-go v1.27.7/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/smithy-go v1.27.8 h1:FR0dxZfIlV7Z8eh2iHfIofdunw382XsDV3Mxt9nUvRY= github.com/aws/smithy-go v1.27.8/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= -github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= -github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/getkin/kin-openapi v0.144.0 h1:hIRcTH+KjLfkLpYU6bSSfdFpi0fZi1fp+hSPi4aQu9Y= +github.com/getkin/kin-openapi v0.144.0/go.mod h1:3BH9M9XDe/y9M5DSvEocVYAYq1w0qrhJHjC/vZi0AaY= +github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg= -github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= github.com/gohugoio/hashstructure v1.0.0 h1:vWYuyzs1n0LdI0F54TJQeYAiB44fHX7H9hCp9X6gHKg= github.com/gohugoio/hashstructure v1.0.0/go.mod h1:FSbTK4QwxucJ2bC4Lvrs9a6x0DbQDXNoyBO+h4nlCgE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -36,88 +36,57 @@ github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqC github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b/go.mod h1:Bj8LjjP0ReT1eKt5QlKjwgi5AFm5mI6O1A2G4ChI0Ag= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY= +github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU= +github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw= +github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.28.1-0.20260613055708-11527bf14be7 h1:gg3ZcAGKckMgNSDA7eOjg98eVC8rgyIzNzpKKEUpR2E= -goa.design/goa/v3 v3.28.1-0.20260613055708-11527bf14be7/go.mod h1:EliUsJT3ObuebAPvYZsZtsl2wzEqf0N3HJRw6MrfDxQ= -goa.design/goa/v3 v3.29.0 h1:s8ic5VzONqm4unMM7qlGnCUCJ4cOfsZXBtqActDXDqU= -goa.design/goa/v3 v3.29.0/go.mod h1:FpTADA0Z2onOoZZD2YLWrA2IeXmaBEbs/FeFYag6hxg= -goa.design/goa/v3 v3.29.1 h1:O/FtqeWRI73e6EU6fuODelhLuSI40eSD7Cxn5fmoU5g= -goa.design/goa/v3 v3.29.1/go.mod h1:FpTADA0Z2onOoZZD2YLWrA2IeXmaBEbs/FeFYag6hxg= -goa.design/goa/v3 v3.29.2 h1:nabAmgYTUsE+NfG4KkMyRl6ddHN2uTAflOHC/FuaTfI= -goa.design/goa/v3 v3.29.2/go.mod h1:CTCdFy0UYeiYuoXmFkUJXji1xGPG7K6XFsO2NqU/QaQ= -goa.design/goa/v3 v3.30.0 h1:KXfAR5qlUCJLVPlHTZ6pHdC5b013BB78PLbDf2fFhfY= -goa.design/goa/v3 v3.30.0/go.mod h1:Is2byRdJddS20jhGT3Ql80N5hQyAc4HgAl7H/A0ZmJ4= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= -golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= +goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= -google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/testing/examples/jsonrpc/scenarios.yaml b/testing/examples/jsonrpc/scenarios.yaml index 1842181eb..d0a0e9c66 100644 --- a/testing/examples/jsonrpc/scenarios.yaml +++ b/testing/examples/jsonrpc/scenarios.yaml @@ -42,14 +42,10 @@ scenarios: # # - jsonrpc # -# - jsonrpc-sse -# -# - jsonrpc-ws -# # Available methods and their transports: # # - jsonrpc_no_stream: jsonrpc # # - jsonrpc_no_stream_error: jsonrpc -# \ No newline at end of file +# diff --git a/testing/generate.go b/testing/generate.go index 5a4ca09c8..d593002d7 100644 --- a/testing/generate.go +++ b/testing/generate.go @@ -1,31 +1,23 @@ +// This file registers the testing plugin and supports both current per-run Goa +// plans and the released callback API used by existing applications. package testing import ( "strings" "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/generator" "goa.design/goa/v3/codegen/service" "goa.design/goa/v3/eval" "goa.design/goa/v3/expr" testcodegen "goa.design/plugins/v3/testing/codegen" ) -// Register registers the testing plugin with Goa. -func init() { - codegen.RegisterPlugin("testing", "gen", nil, Generate) - codegen.RegisterPlugin("testing", "example", nil, GenerateExample) -} - // Generate produces test harness files for each service in the design (gen phase). func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { - // Collect service.Data from incoming files - svcByName := map[string]*service.Data{} - for _, f := range files { - for _, s := range f.SectionTemplates { - if sd, ok := s.Data.(*service.Data); ok && sd != nil { - svcByName[sd.Name] = sd - } - } + services, err := plannedServices(genpkg, roots) + if err != nil { + return nil, err } for _, root := range roots { @@ -34,7 +26,7 @@ func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codeg continue } for _, svc := range r.Services { - if sd := svcByName[svc.Name]; sd != nil { + if sd := services[r].Get(svc.Name); sd != nil { if fs := testcodegen.Generate(genpkg, sd, r, svc); len(fs) > 0 { files = append(files, fs...) } @@ -46,15 +38,18 @@ func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codeg // GenerateExample produces the top-level user-editable test suite files using only DSL data (example phase). func GenerateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { + services, err := plannedServices(genpkg, roots) + if err != nil { + return nil, err + } for _, root := range roots { r, ok := root.(*expr.RootExpr) if !ok { continue } - services := service.NewServicesData(r) scope := codegen.NewNameScope() for _, svc := range r.Services { - s := services.Get(svc.Name) + s := services[r].Get(svc.Name) if s == nil { continue } @@ -62,11 +57,15 @@ func GenerateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([ } apipkg := scope.Unique(strings.ToLower(codegen.Goify(r.API.Name, false)), "api") for _, svc := range r.Services { - if f := testcodegen.GenerateSuiteTopLevel(genpkg, apipkg, r, svc); f != nil { + svcData := services[r].Get(svc.Name) + if svcData == nil { + continue + } + if f := testcodegen.GenerateSuiteTopLevelFromData(genpkg, apipkg, svcData, r, svc); f != nil { files = append(files, f) } // Generate example scenarios.yaml (only for first service to avoid duplicates) - if f := testcodegen.GenerateExampleScenarios(genpkg, r, svc); f != nil { + if f := testcodegen.GenerateExampleScenariosFromData(genpkg, svcData, r, svc); f != nil { files = append(files, f) break // Only generate one scenarios.yaml for all services } @@ -74,3 +73,123 @@ func GenerateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([ } return files, nil } + +// init registers fresh plugin callbacks for every Goa generation run. +func init() { + generator.RegisterPlugin("testing", "gen", newGeneratePlugin) + generator.RegisterPlugin("testing", "example", newExamplePlugin) +} + +// newGeneratePlugin creates the callback that reads finalized Goa plans for +// one generation run and appends generated testing helpers. +func newGeneratePlugin() generator.Plugin { + return generator.Plugin{ + Plan: planGeneratedPackages, + Generate: generatePlanned, + } +} + +// newExamplePlugin creates the callback that reads finalized Goa plans for +// one example run and appends user-owned testing files when they do not exist. +func newExamplePlugin() generator.Plugin { + return generator.Plugin{Generate: generatePlannedExample} +} + +// planGeneratedPackages records the imports and fixed public names emitted by +// each generated service testing package. +func planGeneratedPackages(plan *generator.Plan) error { + for _, candidate := range plan.Generation().Roots() { + root, ok := candidate.(*expr.RootExpr) + if !ok { + continue + } + servicePlan := plan.Service(root) + for _, svc := range root.Services { + if err := testcodegen.PlanPackage(plan.Generation(), servicePlan, root, svc); err != nil { + return err + } + } + } + return nil +} + +// generatePlanned appends testing helpers using the service names and +// JSON-RPC endpoints finalized in the current Goa generation plan. +func generatePlanned(plan *generator.Plan, files []*codegen.File) ([]*codegen.File, error) { + for _, root := range plan.Generation().Roots() { + r, ok := root.(*expr.RootExpr) + if !ok { + continue + } + services := plan.Service(r).Services() + jsonPlan, _ := plan.JSONRPC(r) + for _, svc := range r.Services { + if generated := testcodegen.GeneratePlanned(plan.Generation().GenPkg(), services.Get(svc.Name), r, svc, jsonPlan); len(generated) > 0 { + files = append(files, generated...) + } + } + } + return files, nil +} + +// generatePlannedExample appends the user-owned suite and scenario files using +// the names and JSON-RPC endpoints finalized in the current Goa plan. +func generatePlannedExample(plan *generator.Plan, files []*codegen.File) ([]*codegen.File, error) { + for _, root := range plan.Generation().Roots() { + r, ok := root.(*expr.RootExpr) + if !ok { + continue + } + services := plan.Service(r).Services() + scope := codegen.NewNameScope() + for _, svc := range r.Services { + scope.Unique(services.Get(svc.Name).PkgName) + } + apiPackage := scope.Unique(strings.ToLower(codegen.Goify(r.API.Name, false)), "api") + jsonPlan, _ := plan.JSONRPC(r) + for _, svc := range r.Services { + svcData := services.Get(svc.Name) + if file := testcodegen.GeneratePlannedSuiteTopLevel(plan.Generation().GenPkg(), apiPackage, svcData, r, svc, jsonPlan); file != nil { + files = append(files, file) + } + if file := testcodegen.GeneratePlannedExampleScenarios(plan.Generation().GenPkg(), svcData, r, svc, jsonPlan); file != nil { + files = append(files, file) + break + } + } + } + return files, nil +} + +// plannedServices runs Goa's current service planner for released callbacks, +// which receive roots and files but not the generation plan used by Goa. +func plannedServices(genpkg string, roots []eval.Root) (map[*expr.RootExpr]*service.ServicesData, error) { + generation, err := codegen.NewGeneration(genpkg, roots) + if err != nil { + return nil, err + } + inputs := make([]service.PlanInput, 0, len(roots)) + for _, root := range roots { + if r, ok := root.(*expr.RootExpr); ok { + inputs = append(inputs, service.PlanInput{ + Root: r, + Examples: expr.NewExampleGenerator(r.API.RandomizerFactory), + }) + } + } + plans, err := service.NewPlans(generation, inputs...) + if err != nil { + return nil, err + } + if err := generation.Freeze(); err != nil { + return nil, err + } + services := make(map[*expr.RootExpr]*service.ServicesData, len(plans)) + for _, plan := range plans { + if err := plan.Link(); err != nil { + return nil, err + } + services[plan.Root()] = plan.Services() + } + return services, nil +} diff --git a/testing/generate_test.go b/testing/generate_test.go new file mode 100644 index 000000000..273cacbdc --- /dev/null +++ b/testing/generate_test.go @@ -0,0 +1,35 @@ +// This file checks compatibility entry points that existing applications call +// without access to Goa's internal generation plan. +package testing + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "goa.design/goa/v3/codegen" + "goa.design/goa/v3/eval" + "goa.design/plugins/v3/testing/codegen/testdata" +) + +func TestReleasedCallbacksPlanServiceDataWithoutInputFiles(t *testing.T) { + root := codegen.RunDSL(t, testdata.WithPayloadDSL) + + generated, err := Generate("generated.local/gen", []eval.Root{root}, nil) + require.NoError(t, err) + assert.Len(t, generated, 4) + + examples, err := GenerateExample("generated.local/gen", []eval.Root{root}, nil) + require.NoError(t, err) + assert.Len(t, examples, 2) +} + +func TestReleasedCallbacksRejectEmptyGeneratedPackage(t *testing.T) { + root := codegen.RunDSL(t, testdata.WithPayloadDSL) + + _, err := Generate("", []eval.Root{root}, nil) + require.Error(t, err) + _, err = GenerateExample("", []eval.Root{root}, nil) + require.Error(t, err) +} diff --git a/tscap/auth/auth.go b/tscap/auth/auth.go index f1f19b06f..e061b9fea 100644 --- a/tscap/auth/auth.go +++ b/tscap/auth/auth.go @@ -26,9 +26,9 @@ type Requirement struct { // Gate stores the authorization requirements for a method. type Gate struct { - MethodName string + MethodName string AllowAnonymous bool - Requirement *Requirement + Requirement *Requirement } // ParseCapabilities extracts capabilities from the Tailscale header. diff --git a/tscap/example/cmd/example/main.go b/tscap/example/cmd/example/main.go index 89d8762cf..7919f7448 100644 --- a/tscap/example/cmd/example/main.go +++ b/tscap/example/cmd/example/main.go @@ -13,8 +13,8 @@ import ( goahttp "goa.design/goa/v3/http" "goa.design/plugins/v3/tscap/example" - tscapsvc "goa.design/plugins/v3/tscap/example/gen/tscap" tscapsvr "goa.design/plugins/v3/tscap/example/gen/http/tscap/server" + tscapsvc "goa.design/plugins/v3/tscap/example/gen/tscap" ) func main() { diff --git a/tscap/example/cmd/example/main_test.go b/tscap/example/cmd/example/main_test.go index 8701ffe13..f77166c39 100644 --- a/tscap/example/cmd/example/main_test.go +++ b/tscap/example/cmd/example/main_test.go @@ -12,8 +12,8 @@ import ( goahttp "goa.design/goa/v3/http" "goa.design/plugins/v3/tscap/example" - tscapsvc "goa.design/plugins/v3/tscap/example/gen/tscap" tscapsvr "goa.design/plugins/v3/tscap/example/gen/http/tscap/server" + tscapsvc "goa.design/plugins/v3/tscap/example/gen/tscap" ) func setupServer() http.Handler { diff --git a/tscap/example/gen/http/cli/tscap_example/cli.go b/tscap/example/gen/http/cli/tscap_example/cli.go index ec141d095..6d94604fd 100644 --- a/tscap/example/gen/http/cli/tscap_example/cli.go +++ b/tscap/example/gen/http/cli/tscap_example/cli.go @@ -197,7 +197,7 @@ func tscapCreateUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "tscap create --body '{\n \"name\": \"Quod libero reprehenderit.\"\n }'") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "tscap create --body '{\n \"name\": \"Quo omnis quia ipsum.\"\n }'") } func tscapAdminUsage() { @@ -215,7 +215,7 @@ func tscapAdminUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "tscap admin --id \"Quidem est repellat omnis voluptatem veniam.\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "tscap admin --id \"Quidem eos voluptas eveniet.\"") } func tscapHealthUsage() { diff --git a/tscap/example/gen/http/openapi.json b/tscap/example/gen/http/openapi.json index b076ed37c..346d7d6d1 100644 --- a/tscap/example/gen/http/openapi.json +++ b/tscap/example/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Tailscale Capabilities Example API","description":"An example API demonstrating Tailscale app capabilities authorization","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/health":{"get":{"tags":["tscap"],"summary":"health tscap","description":"Health check - no capability required","operationId":"tscap#health","responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["http"]}},"/items":{"get":{"tags":["tscap"],"summary":"list tscap","description":"List items - requires read capability","operationId":"tscap#list","responses":{"200":{"description":"OK response.","schema":{"type":"array","items":{"type":"string","example":"Est quia repudiandae eos nam necessitatibus."}}}},"schemes":["http"]},"post":{"tags":["tscap"],"summary":"create tscap","description":"Create an item - requires write capability","operationId":"tscap#create","parameters":[{"name":"CreateRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/TscapCreateRequestBody","required":["name"]}}],"responses":{"201":{"description":"Created response.","schema":{"type":"string"}}},"schemes":["http"]}},"/items/{id}":{"delete":{"tags":["tscap"],"summary":"admin tscap","description":"Admin action - requires admin capability","operationId":"tscap#admin","parameters":[{"name":"id","in":"path","description":"Item ID","required":true,"type":"string"}],"responses":{"204":{"description":"No Content response."}},"schemes":["http"]}},"/openapi.json":{"get":{"tags":["openapi"],"summary":"Download gen/http/openapi3.json","operationId":"openapi#/openapi.json","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}}},"definitions":{"TscapCreateRequestBody":{"title":"TscapCreateRequestBody","type":"object","properties":{"name":{"type":"string","description":"Item name","example":"Quod libero reprehenderit."}},"example":{"name":"Quod libero reprehenderit."},"required":["name"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Tailscale Capabilities Example API","description":"An example API demonstrating Tailscale app capabilities authorization","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/health":{"get":{"tags":["tscap"],"summary":"health tscap","description":"Health check - no capability required","operationId":"tscap#health","responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["http"]}},"/items":{"get":{"tags":["tscap"],"summary":"list tscap","description":"List items - requires read capability","operationId":"tscap#list","responses":{"200":{"description":"OK response.","schema":{"type":"array","items":{"type":"string","example":"Maxime dolor quidem."}}}},"schemes":["http"]},"post":{"tags":["tscap"],"summary":"create tscap","description":"Create an item - requires write capability","operationId":"tscap#create","parameters":[{"name":"CreateRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/TscapCreateRequestBody","required":["name"]}}],"responses":{"201":{"description":"Created response.","schema":{"type":"string"}}},"schemes":["http"]}},"/items/{id}":{"delete":{"tags":["tscap"],"summary":"admin tscap","description":"Admin action - requires admin capability","operationId":"tscap#admin","parameters":[{"name":"id","in":"path","description":"Item ID","required":true,"type":"string"}],"responses":{"204":{"description":"No Content response."}},"schemes":["http"]}},"/openapi.json":{"get":{"tags":["openapi"],"summary":"Download gen/http/openapi3.json","operationId":"openapi#/openapi.json","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}}},"definitions":{"TscapCreateRequestBody":{"title":"TscapCreateRequestBody","type":"object","properties":{"name":{"type":"string","description":"Item name","example":"Quo omnis quia ipsum."}},"example":{"name":"Quo omnis quia ipsum."},"required":["name"]}}} \ No newline at end of file diff --git a/tscap/example/gen/http/openapi.yaml b/tscap/example/gen/http/openapi.yaml index f75631e7a..897695c66 100644 --- a/tscap/example/gen/http/openapi.yaml +++ b/tscap/example/gen/http/openapi.yaml @@ -41,7 +41,7 @@ paths: type: array items: type: string - example: Est quia repudiandae eos nam necessitatibus. + example: Maxime dolor quidem. schemes: - http post: @@ -104,8 +104,8 @@ definitions: name: type: string description: Item name - example: Quod libero reprehenderit. + example: Quo omnis quia ipsum. example: - name: Quod libero reprehenderit. + name: Quo omnis quia ipsum. required: - name diff --git a/tscap/example/gen/http/openapi/server/server.go b/tscap/example/gen/http/openapi/server/server.go index b23e74afb..21124bc85 100644 --- a/tscap/example/gen/http/openapi/server/server.go +++ b/tscap/example/gen/http/openapi/server/server.go @@ -81,15 +81,14 @@ func (s *Server) Mount(mux goahttp.Muxer) { Mount(mux, s) } -// appendFS is a custom implementation of fs.FS that appends a specified prefix -// to the file paths before delegating the Open call to the underlying fs.FS. +// appendFS adds a fixed directory to file paths before opening them. type appendFS struct { prefix string fs http.FileSystem } // Open opens the named file, appending the prefix to the file path before -// passing it to the underlying fs.FS. +// passing it to the underlying file system. func (s appendFS) Open(name string) (http.File, error) { switch name { case "/openapi.json": @@ -98,8 +97,7 @@ func (s appendFS) Open(name string) (http.File, error) { return s.fs.Open(path.Join(s.prefix, name)) } -// appendPrefix returns a new fs.FS that appends the specified prefix to file paths -// before delegating to the provided embed.FS. +// appendPrefix returns a file system that adds prefix before opening each path. func appendPrefix(fsys http.FileSystem, prefix string) http.FileSystem { return appendFS{prefix: prefix, fs: fsys} } diff --git a/tscap/example/gen/http/openapi3.2.json b/tscap/example/gen/http/openapi3.2.json index 389f4a738..6d6ee03fa 100644 --- a/tscap/example/gen/http/openapi3.2.json +++ b/tscap/example/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"Tailscale Capabilities Example API","description":"An example API demonstrating Tailscale app capabilities authorization","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"tscap_example","description":"Default server for tscap_example"}],"paths":{"/health":{"get":{"tags":["tscap"],"summary":"health tscap","description":"Health check - no capability required","operationId":"tscap#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Est exercitationem amet."},"example":"Est exercitationem amet."}}}}}},"/items":{"get":{"tags":["tscap"],"summary":"list tscap","description":"List items - requires read capability","operationId":"tscap#list","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"array","items":{"type":"string","example":"Provident sit."},"example":["Provident sit.","Dolores eveniet.","Sit molestiae deleniti."]},"example":["Provident sit.","Dolores eveniet.","Sit molestiae deleniti."]}}}}},"post":{"tags":["tscap"],"summary":"create tscap","description":"Create an item - requires write capability","operationId":"tscap#create","requestBody":{"description":"Request body for create.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRequestBody"},"example":{"name":"Quod libero reprehenderit."}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"type":"string","example":"Voluptas omnis ea."},"example":"Voluptas omnis ea."}}}}}},"/items/{id}":{"delete":{"tags":["tscap"],"summary":"admin tscap","description":"Admin action - requires admin capability","operationId":"tscap#admin","parameters":[{"name":"id","in":"path","description":"Item ID","required":true,"schema":{"type":"string","description":"Item ID","example":"Quidem est repellat omnis voluptatem veniam."},"example":"Inventore aliquam et."}],"responses":{"204":{"description":"No Content response."}}}},"/openapi.json":{"get":{"tags":["openapi"],"summary":"Download gen/http/openapi3.json","operationId":"openapi#/openapi.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{"schemas":{"CreateRequestBody":{"type":"object","properties":{"name":{"type":"string","description":"Item name","example":"Quod libero reprehenderit."}},"description":"Request body for create.","example":{"name":"Quod libero reprehenderit."},"required":["name"]}}},"tags":[{"name":"openapi"},{"name":"tscap","description":"A service demonstrating Tailscale app capabilities"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"Tailscale Capabilities Example API","description":"An example API demonstrating Tailscale app capabilities authorization","version":"0.0.1"},"servers":[{"url":"http://localhost:80","name":"tscap_example","description":"Default server for tscap_example"}],"paths":{"/health":{"get":{"tags":["tscap"],"summary":"health tscap","description":"Health check - no capability required","operationId":"tscap#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Iure dicta."},"example":"Iure dicta."}}}}}},"/items":{"get":{"tags":["tscap"],"summary":"list tscap","description":"List items - requires read capability","operationId":"tscap#list","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"array","items":{"type":"string","example":"Maxime dolor quidem."},"example":["Maxime dolor quidem.","Dolores ducimus eos."]},"example":["Maxime dolor quidem.","Dolores ducimus eos."]}}}}},"post":{"tags":["tscap"],"summary":"create tscap","description":"Create an item - requires write capability","operationId":"tscap#create","requestBody":{"description":"Request body for create.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRequestBody"},"example":{"name":"Quo omnis quia ipsum."}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"type":"string","example":"Voluptatem enim nesciunt sunt omnis."},"example":"Voluptatem enim nesciunt sunt omnis."}}}}}},"/items/{id}":{"delete":{"tags":["tscap"],"summary":"admin tscap","description":"Admin action - requires admin capability","operationId":"tscap#admin","parameters":[{"name":"id","in":"path","description":"Item ID","required":true,"schema":{"type":"string","description":"Item ID","example":"Quidem eos voluptas eveniet."},"example":"Quidem eos voluptas eveniet."}],"responses":{"204":{"description":"No Content response."}}}},"/openapi.json":{"get":{"tags":["openapi"],"summary":"Download gen/http/openapi3.json","operationId":"openapi#/openapi.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{"schemas":{"CreateRequestBody":{"type":"object","properties":{"name":{"type":"string","description":"Item name","example":"Quo omnis quia ipsum."}},"description":"Request body for create.","example":{"name":"Quo omnis quia ipsum."},"required":["name"]}}},"tags":[{"name":"openapi"},{"name":"tscap","description":"A service demonstrating Tailscale app capabilities"}]} \ No newline at end of file diff --git a/tscap/example/gen/http/openapi3.2.yaml b/tscap/example/gen/http/openapi3.2.yaml index d63c2be95..b1c4c5531 100644 --- a/tscap/example/gen/http/openapi3.2.yaml +++ b/tscap/example/gen/http/openapi3.2.yaml @@ -22,8 +22,8 @@ paths: application/json: schema: type: string - example: Est exercitationem amet. - example: Est exercitationem amet. + example: Iure dicta. + example: Iure dicta. /items: get: tags: @@ -40,15 +40,13 @@ paths: type: array items: type: string - example: Provident sit. + example: Maxime dolor quidem. example: - - Provident sit. - - Dolores eveniet. - - Sit molestiae deleniti. + - Maxime dolor quidem. + - Dolores ducimus eos. example: - - Provident sit. - - Dolores eveniet. - - Sit molestiae deleniti. + - Maxime dolor quidem. + - Dolores ducimus eos. post: tags: - tscap @@ -63,7 +61,7 @@ paths: schema: $ref: '#/components/schemas/CreateRequestBody' example: - name: Quod libero reprehenderit. + name: Quo omnis quia ipsum. responses: "201": description: Created response. @@ -71,8 +69,8 @@ paths: application/json: schema: type: string - example: Voluptas omnis ea. - example: Voluptas omnis ea. + example: Voluptatem enim nesciunt sunt omnis. + example: Voluptatem enim nesciunt sunt omnis. /items/{id}: delete: tags: @@ -88,8 +86,8 @@ paths: schema: type: string description: Item ID - example: Quidem est repellat omnis voluptatem veniam. - example: Inventore aliquam et. + example: Quidem eos voluptas eveniet. + example: Quidem eos voluptas eveniet. responses: "204": description: No Content response. @@ -110,10 +108,10 @@ components: name: type: string description: Item name - example: Quod libero reprehenderit. + example: Quo omnis quia ipsum. description: Request body for create. example: - name: Quod libero reprehenderit. + name: Quo omnis quia ipsum. required: - name tags: diff --git a/tscap/example/gen/http/openapi3.json b/tscap/example/gen/http/openapi3.json index bf76425d0..b36fb4655 100644 --- a/tscap/example/gen/http/openapi3.json +++ b/tscap/example/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Tailscale Capabilities Example API","description":"An example API demonstrating Tailscale app capabilities authorization","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for tscap_example"}],"paths":{"/health":{"get":{"tags":["tscap"],"summary":"health tscap","description":"Health check - no capability required","operationId":"tscap#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Est exercitationem amet."},"example":"Est exercitationem amet."}}}}}},"/items":{"get":{"tags":["tscap"],"summary":"list tscap","description":"List items - requires read capability","operationId":"tscap#list","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"array","items":{"type":"string","example":"Provident sit."},"example":["Provident sit.","Dolores eveniet.","Sit molestiae deleniti."]},"example":["Provident sit.","Dolores eveniet.","Sit molestiae deleniti."]}}}}},"post":{"tags":["tscap"],"summary":"create tscap","description":"Create an item - requires write capability","operationId":"tscap#create","requestBody":{"description":"Request body for create.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRequestBody"},"example":{"name":"Quod libero reprehenderit."}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"type":"string","example":"Voluptas omnis ea."},"example":"Voluptas omnis ea."}}}}}},"/items/{id}":{"delete":{"tags":["tscap"],"summary":"admin tscap","description":"Admin action - requires admin capability","operationId":"tscap#admin","parameters":[{"name":"id","in":"path","description":"Item ID","required":true,"schema":{"type":"string","description":"Item ID","example":"Quidem est repellat omnis voluptatem veniam."},"example":"Inventore aliquam et."}],"responses":{"204":{"description":"No Content response."}}}},"/openapi.json":{"get":{"tags":["openapi"],"summary":"Download gen/http/openapi3.json","operationId":"openapi#/openapi.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{"schemas":{"CreateRequestBody":{"type":"object","properties":{"name":{"type":"string","description":"Item name","example":"Quod libero reprehenderit."}},"description":"Request body for create.","example":{"name":"Quod libero reprehenderit."},"required":["name"]}}},"tags":[{"name":"openapi"},{"name":"tscap","description":"A service demonstrating Tailscale app capabilities"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Tailscale Capabilities Example API","description":"An example API demonstrating Tailscale app capabilities authorization","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for tscap_example"}],"paths":{"/health":{"get":{"tags":["tscap"],"summary":"health tscap","description":"Health check - no capability required","operationId":"tscap#health","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Iure dicta."},"example":"Iure dicta."}}}}}},"/items":{"get":{"tags":["tscap"],"summary":"list tscap","description":"List items - requires read capability","operationId":"tscap#list","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"array","items":{"type":"string","example":"Maxime dolor quidem."},"example":["Maxime dolor quidem.","Dolores ducimus eos."]},"example":["Maxime dolor quidem.","Dolores ducimus eos."]}}}}},"post":{"tags":["tscap"],"summary":"create tscap","description":"Create an item - requires write capability","operationId":"tscap#create","requestBody":{"description":"Request body for create.","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRequestBody"},"example":{"name":"Quo omnis quia ipsum."}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"type":"string","example":"Voluptatem enim nesciunt sunt omnis."},"example":"Voluptatem enim nesciunt sunt omnis."}}}}}},"/items/{id}":{"delete":{"tags":["tscap"],"summary":"admin tscap","description":"Admin action - requires admin capability","operationId":"tscap#admin","parameters":[{"name":"id","in":"path","description":"Item ID","required":true,"schema":{"type":"string","description":"Item ID","example":"Quidem eos voluptas eveniet."},"example":"Quidem eos voluptas eveniet."}],"responses":{"204":{"description":"No Content response."}}}},"/openapi.json":{"get":{"tags":["openapi"],"summary":"Download gen/http/openapi3.json","operationId":"openapi#/openapi.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{"schemas":{"CreateRequestBody":{"type":"object","properties":{"name":{"type":"string","description":"Item name","example":"Quo omnis quia ipsum."}},"description":"Request body for create.","example":{"name":"Quo omnis quia ipsum."},"required":["name"]}}},"tags":[{"name":"openapi"},{"name":"tscap","description":"A service demonstrating Tailscale app capabilities"}]} \ No newline at end of file diff --git a/tscap/example/gen/http/openapi3.yaml b/tscap/example/gen/http/openapi3.yaml index e12322cad..7ce3f497c 100644 --- a/tscap/example/gen/http/openapi3.yaml +++ b/tscap/example/gen/http/openapi3.yaml @@ -21,8 +21,8 @@ paths: application/json: schema: type: string - example: Est exercitationem amet. - example: Est exercitationem amet. + example: Iure dicta. + example: Iure dicta. /items: get: tags: @@ -39,15 +39,13 @@ paths: type: array items: type: string - example: Provident sit. + example: Maxime dolor quidem. example: - - Provident sit. - - Dolores eveniet. - - Sit molestiae deleniti. + - Maxime dolor quidem. + - Dolores ducimus eos. example: - - Provident sit. - - Dolores eveniet. - - Sit molestiae deleniti. + - Maxime dolor quidem. + - Dolores ducimus eos. post: tags: - tscap @@ -62,7 +60,7 @@ paths: schema: $ref: '#/components/schemas/CreateRequestBody' example: - name: Quod libero reprehenderit. + name: Quo omnis quia ipsum. responses: "201": description: Created response. @@ -70,8 +68,8 @@ paths: application/json: schema: type: string - example: Voluptas omnis ea. - example: Voluptas omnis ea. + example: Voluptatem enim nesciunt sunt omnis. + example: Voluptatem enim nesciunt sunt omnis. /items/{id}: delete: tags: @@ -87,8 +85,8 @@ paths: schema: type: string description: Item ID - example: Quidem est repellat omnis voluptatem veniam. - example: Inventore aliquam et. + example: Quidem eos voluptas eveniet. + example: Quidem eos voluptas eveniet. responses: "204": description: No Content response. @@ -109,10 +107,10 @@ components: name: type: string description: Item name - example: Quod libero reprehenderit. + example: Quo omnis quia ipsum. description: Request body for create. example: - name: Quod libero reprehenderit. + name: Quo omnis quia ipsum. required: - name tags: diff --git a/tscap/example/gen/http/tscap/client/cli.go b/tscap/example/gen/http/tscap/client/cli.go index d7146589b..1b3a259e9 100644 --- a/tscap/example/gen/http/tscap/client/cli.go +++ b/tscap/example/gen/http/tscap/client/cli.go @@ -23,7 +23,7 @@ func BuildCreatePayload(tscapCreateBody string) (*tscap.CreatePayload, error) { { err = json.Unmarshal([]byte(tscapCreateBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"name\": \"Quod libero reprehenderit.\"\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"name\": \"Quo omnis quia ipsum.\"\n }'") } } v := &tscap.CreatePayload{ diff --git a/tscap/example/gen/http/tscap/client/encode_decode.go b/tscap/example/gen/http/tscap/client/encode_decode.go index 12cd6d04b..6fb1ee4b1 100644 --- a/tscap/example/gen/http/tscap/client/encode_decode.go +++ b/tscap/example/gen/http/tscap/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -38,18 +39,24 @@ func (c *Client) BuildListRequest(ctx context.Context, v any) (*http.Request, er // list endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("tscap", "list", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("tscap", "list", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -63,7 +70,10 @@ func DecodeListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBod } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("tscap", "list", err) + } return nil, goahttp.ErrInvalidResponse("tscap", "list", resp.StatusCode, string(body)) } } @@ -104,18 +114,24 @@ func EncodeCreateRequest(encoder func(*http.Request) goahttp.Encoder) func(*http // create endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeCreateResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("tscap", "create", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("tscap", "create", err)) + } + }() } switch resp.StatusCode { case http.StatusCreated: @@ -129,7 +145,10 @@ func DecodeCreateResponse(decoder func(*http.Response) goahttp.Decoder, restoreB } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("tscap", "create", err) + } return nil, goahttp.ErrInvalidResponse("tscap", "create", resp.StatusCode, string(body)) } } @@ -164,24 +183,33 @@ func (c *Client) BuildAdminRequest(ctx context.Context, v any) (*http.Request, e // admin endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeAdminResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("tscap", "admin", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("tscap", "admin", err)) + } + }() } switch resp.StatusCode { case http.StatusNoContent: return nil, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("tscap", "admin", err) + } return nil, goahttp.ErrInvalidResponse("tscap", "admin", resp.StatusCode, string(body)) } } @@ -206,18 +234,24 @@ func (c *Client) BuildHealthRequest(ctx context.Context, v any) (*http.Request, // health endpoint. restoreBody controls whether the response body should be // restored after having been read. func DecodeHealthResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("tscap", "health", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("tscap", "health", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -231,7 +265,10 @@ func DecodeHealthResponse(decoder func(*http.Response) goahttp.Decoder, restoreB } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("tscap", "health", err) + } return nil, goahttp.ErrInvalidResponse("tscap", "health", resp.StatusCode, string(body)) } } diff --git a/tscap/example/gen/tscap/service.go b/tscap/example/gen/tscap/service.go index e472d60c0..5e2b0f843 100644 --- a/tscap/example/gen/tscap/service.go +++ b/tscap/example/gen/tscap/service.go @@ -8,9 +8,7 @@ package tscap -import ( - "context" -) +import "context" // A service demonstrating Tailscale app capabilities type Service interface { diff --git a/types/generate.go b/types/generate.go index 4885c01df..7ea78e926 100644 --- a/types/generate.go +++ b/types/generate.go @@ -1,160 +1,348 @@ +// This file plans and writes standalone Go types for every named type in a +// Goa design. Planning records names, imports, field layouts, and validations +// before Goa freezes the generated packages. Rendering only formats those +// recorded values, so it never changes the evaluated design. package types import ( + "cmp" + "fmt" + "path" "path/filepath" - "regexp" + "sort" "goa.design/goa/v3/codegen" - "goa.design/goa/v3/codegen/service" + "goa.design/goa/v3/codegen/generator" "goa.design/goa/v3/eval" "goa.design/goa/v3/expr" ) type ( + // generationPlan stores everything needed to write one types.go file after + // Goa has chosen the final declaration and import names. + generationPlan struct { + pkg *codegen.GeneratedPackage + imports map[string]struct{} + types []*typePlan + hasGoaRoot bool + } + + // typePlan stores one named type and its optional validation function. + typePlan struct { + name string + description string + declaration *codegen.TypeDeclaration + definition *codegen.GoTypePlan + reference *codegen.GoTypePlan + validator *codegen.NameDeclaration + validation *codegen.ValidationPlan + } + + // typeData contains the final strings used by the type template. + typeData struct { + Name string + Description string + Definition string + } + + // validateData contains the final strings used by the validation template. validateData struct { VarName string Name string ValidateDef string Ref string } + + // validatorNameOrder gives each generated validator a stable position when + // another plugin requests the same function name. + validatorNameOrder string ) -// Name of directory that contains generated types. +// Gendir is the directory that contains the generated standalone types. const Gendir = "types" -// init registers the plugin generator function. +// init registers the Go and Protocol Buffer type generators. func init() { - codegen.RegisterPlugin("types", "gen", nil, Generate) + generator.RegisterPlugin("types", "gen", newPlugin) codegen.RegisterPlugin("types-proto", "gen", nil, GenerateProto) } -// Generate produces the documentation JSON file. +// Generate adds the standalone Go types to files. Goa generation commands use +// the planning-aware plugin registered by init; this function remains for code +// that invoked the released plugin callback directly. func Generate(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { - for _, root := range roots { - if r, ok := root.(*expr.RootExpr); ok { - files = append(files, typesFile(genpkg, r)) - } + generation, err := codegen.NewGeneration(genpkg, roots) + if err != nil { + return nil, err + } + planned, err := planTypes(generation) + if err != nil { + return nil, err } - return files, nil + if err := generation.Freeze(); err != nil { + return nil, err + } + return generateTypes(planned, files) } -func typesFile(genpkg string, r *expr.RootExpr) *codegen.File { - types := r.Types - path := filepath.Join(codegen.Gendir, "types", "types.go") - header := codegen.Header("Data types", "types", - []*codegen.ImportSpec{ - codegen.GoaImport(""), - {Path: "unicode/utf8"}, +// newPlugin creates isolated planning state for one Goa generation command. +func newPlugin() generator.Plugin { + var planned *generationPlan + return generator.Plugin{ + Plan: func(plan *generator.Plan) error { + var err error + planned, err = planTypes(plan.Generation()) + return err + }, + Generate: func(_ *generator.Plan, files []*codegen.File) ([]*codegen.File, error) { + return generateTypes(planned, files) }, - ) - sections := []*codegen.SectionTemplate{header} - - // Create dummy service so we can leverage Goa's code generation. - svc := &expr.ServiceExpr{Name: "dummy"} - expr.Root.Services = []*expr.ServiceExpr{svc} - - // Create a dummy method for each type. - for _, t := range types { - svc.Methods = append(svc.Methods, &expr.MethodExpr{ - Name: t.Name() + "M", - Payload: &expr.AttributeExpr{Type: t}, - StreamingPayload: &expr.AttributeExpr{Type: expr.Empty}, - Result: &expr.AttributeExpr{Type: expr.Empty}, - Service: svc, - }) } +} - // Generate the code and retrieve the relevant sections. - services := service.NewServicesData(r) - files := service.Files(genpkg, svc, services, make(map[string][]string)) - for _, f := range files { - for _, section := range f.SectionTemplates { - sn := section.Name - if sn == "service-payload" || - sn == "service-union-value-method" || - sn == "service-user-type" { - if sn == "service-payload" { - // Override the payload comment with the original type description. - section.FuncMap = map[string]interface{}{ - "comment": func(s string) string { return getDescription(s, types) }, - } - } - sections = append(sections, section) - } +// planTypes declares every standalone type, validator, and import before Goa +// chooses their final names. It reads each evaluated design without changing +// its services or named types. +func planTypes(generation *codegen.Generation) (*generationPlan, error) { + planned := &generationPlan{imports: make(map[string]struct{})} + var userTypes []expr.UserType + for _, evaluated := range generation.Roots() { + root, ok := evaluated.(*expr.RootExpr) + if !ok { + continue } + planned.hasGoaRoot = true + userTypes = append(userTypes, root.Types...) + } + if !planned.hasGoaRoot { + return planned, nil } - var vdata []validateData - scope := codegen.NewNameScope() - attCtx := codegen.AttributeContext{Scope: codegen.NewAttributeScope(scope)} - addValidation := func(t expr.UserType) { - def := codegen.ValidationCode(t.Attribute(), t, &attCtx, true, expr.IsAlias(t), false, "v") - if def == "" { - return + generatedPackage, err := generation.ClaimPackage(path.Join(generation.GenPkg(), Gendir)) + if err != nil { + return nil, err + } + planned.pkg = generatedPackage + + declarations := make(map[expr.UserType]*codegen.TypeDeclaration, len(userTypes)) + for _, userType := range userTypes { + origin := userType.Origin() + if _, exists := declarations[origin]; exists { + continue + } + declaration, err := generatedPackage.DeclareUserType(userType) + if err != nil { + return nil, err + } + declarations[origin] = declaration + } + + policy := codegen.GoLayoutPolicy{UseDefault: true, SumType: true} + bindType := typeBinder(generatedPackage, declarations) + validators := make(map[expr.UserType]*codegen.NameDeclaration, len(declarations)) + for _, userType := range userTypes { + origin := userType.Origin() + if _, exists := validators[origin]; exists || !codegen.NeedsValidation(userType.Attribute(), policy) { + continue } - vdata = append(vdata, validateData{ - VarName: codegen.Goify(t.Name(), true), - Name: t.Name(), - ValidateDef: def, - Ref: scope.GoTypeRef(&expr.AttributeExpr{Type: t}), + validator, err := generatedPackage.DeclareDependentName( + codegen.NameFunction, + declarations[origin].Declaration(), + "Validate", + "", + validatorNameOrder(userType.Name()), + ) + if err != nil { + return nil, err + } + validators[origin] = validator + } + + seen := make(map[expr.UserType]struct{}, len(declarations)) + for _, userType := range userTypes { + origin := userType.Origin() + if _, exists := seen[origin]; exists { + continue + } + seen[origin] = struct{}{} + definition, err := codegen.PlanGoType(userType.Attribute(), codegen.GoTypePlanOptions{ + Owner: generatedPackage.ImportPath(), + Policy: policy, + Bind: bindType, + }) + if err != nil { + return nil, err + } + reference, err := codegen.PlanGoType(&expr.AttributeExpr{Type: userType}, codegen.GoTypePlanOptions{ + Owner: generatedPackage.ImportPath(), + Policy: policy, + Bind: bindType, }) + if err != nil { + return nil, err + } + current := &typePlan{ + name: userType.Name(), + description: userType.Attribute().Description, + declaration: declarations[origin], + definition: definition, + reference: reference, + validator: validators[origin], + } + if current.validator != nil { + current.validation, err = codegen.NewValidationPlan( + userType.Attribute(), + definition, + codegen.ValidationPlanOptions{ + Required: true, + Alias: expr.IsAlias(userType), + Bind: validatorBinder(validators), + }, + ) + if err != nil { + return nil, err + } + } + if err := planImports(planned, definition, current.validation); err != nil { + return nil, err + } + planned.types = append(planned.types, current) } - seen := make(map[string]struct{}) - for _, t := range r.Types { - collectUserTypes(t, addValidation, seen) + return planned, nil +} + +// generateTypes appends the planned types.go file after Goa has frozen all +// package names. A generation with no Goa service-design root adds no file. +func generateTypes(planned *generationPlan, files []*codegen.File) ([]*codegen.File, error) { + if !planned.hasGoaRoot { + return files, nil } - sections = append(sections, &codegen.SectionTemplate{ - Name: "type-validation", - Source: validateT, - Data: vdata, + return append(files, typesFile(planned)), nil +} + +// typesFile formats recorded layouts and validations without reading the +// evaluated design again. +func typesFile(planned *generationPlan) *codegen.File { + qualifier := planned.pkg.ImportName + owner := planned.pkg.ImportPath() + types := make([]typeData, len(planned.types)) + var validations []validateData + for index, current := range planned.types { + types[index] = typeData{ + Name: current.declaration.Name(), + Description: current.description, + Definition: current.definition.Link(owner, qualifier).Def(), + } + if current.validation == nil { + continue + } + linked, err := current.validation.Link(current.definition.Link(owner, qualifier)) + if err != nil { + panic(err) + } + validations = append(validations, validateData{ + VarName: current.validator.Name(), + Name: current.name, + ValidateDef: linked.Render("v", "v"), + Ref: current.reference.Link(owner, qualifier).Ref(), + }) + } + sort.Slice(types, func(left, right int) bool { + return types[left].Name < types[right].Name }) - return &codegen.File{Path: path, SectionTemplates: sections} + paths := make([]string, 0, len(planned.imports)) + for importPath := range planned.imports { + paths = append(paths, importPath) + } + sort.Strings(paths) + imports := make([]*codegen.ImportSpec, len(paths)) + for index, importPath := range paths { + imports[index] = planned.pkg.Import(importPath) + } + return &codegen.File{ + Path: filepath.Join(codegen.Gendir, Gendir, "types.go"), + SectionTemplates: []*codegen.SectionTemplate{ + codegen.Header("Data types", "types", imports), + {Name: "type-definitions", Source: typesT, Data: types}, + {Name: "type-validation", Source: validateT, Data: validations}, + }, + } } -// collectUserTypes traverses the given data type recursively and calls back the -// given function for each attribute using a user type. -func collectUserTypes(dt expr.DataType, cb func(expr.UserType), seen map[string]struct{}) { - if dt == expr.Empty { - return - } - switch actual := dt.(type) { - case *expr.Object: - for _, nat := range *actual { - collectUserTypes(nat.Attribute.Type, cb, seen) - } - case *expr.Array: - collectUserTypes(actual.ElemType.Type, cb, seen) - case *expr.Map: - collectUserTypes(actual.KeyType.Type, cb, seen) - collectUserTypes(actual.ElemType.Type, cb, seen) - case expr.UserType: - if _, ok := seen[actual.ID()]; ok { - return - } - seen[actual.ID()] = struct{}{} - cb(actual) - collectUserTypes(actual.Attribute().Type, cb, seen) +// typeBinder returns the type declaration already assigned to each named +// design type. Standalone OneOf values are rejected because this plugin has no +// public OneOf representation to preserve. +func typeBinder(generatedPackage *codegen.GeneratedPackage, declarations map[expr.UserType]*codegen.TypeDeclaration) codegen.GoTypeBinder { + return func(request codegen.GoTypeBindingRequest) (codegen.GoTypeBinding, error) { + if request.Kind == codegen.GoUnion { + return codegen.GoTypeBinding{}, fmt.Errorf("types plugin does not support Goa OneOf types") + } + userType, ok := request.Attribute.Type.(expr.UserType) + if !ok { + return codegen.GoTypeBinding{}, fmt.Errorf("types plugin cannot bind %s as a named type", request.Kind) + } + declaration := declarations[userType.Origin()] + if declaration == nil { + return codegen.GoTypeBinding{}, fmt.Errorf("types plugin has no declaration for type %q", userType.Name()) + } + return codegen.GoTypeBinding{Owner: generatedPackage.ImportPath(), Type: declaration}, nil } } -var nameRegex = regexp.MustCompile(`([^\s]*)`) +// validatorBinder returns the validation function already assigned to each +// nested named type that needs validation. +func validatorBinder(validators map[expr.UserType]*codegen.NameDeclaration) codegen.ValidatorDeclarationBinder { + return func(request codegen.ValidatorBindingRequest) (*codegen.NameDeclaration, error) { + userType, ok := request.Attribute.Type.(expr.UserType) + if !ok { + return nil, fmt.Errorf("types plugin cannot validate unnamed nested type") + } + validator := validators[userType.Origin()] + if validator == nil { + return nil, fmt.Errorf("types plugin has no validator for type %q", userType.Name()) + } + return validator, nil + } +} -func getDescription(comment string, types []expr.UserType) string { - name := nameRegex.FindStringSubmatch(comment)[1] - for _, t := range types { - if t.Name() == name { - return codegen.Comment(t.Attribute().Description) +// planImports registers each package referenced by a type definition or its +// validation code and remembers which import lines the file must contain. +func planImports(planned *generationPlan, definition *codegen.GoTypePlan, validation *codegen.ValidationPlan) error { + imports := definition.ImportPreferences() + if validation != nil { + imports = append(imports, validation.ImportPreferences()...) + } + for _, goImport := range imports { + if goImport.Path == planned.pkg.ImportPath() { + continue } + if err := planned.pkg.DeclareImport(&codegen.ImportSpec{Name: goImport.Name, Path: goImport.Path}); err != nil { + return err + } + planned.imports[goImport.Path] = struct{}{} } - return "" + return nil +} + +// ComparePackageName orders validators by the authored type name they check. +func (o validatorNameOrder) ComparePackageName(other codegen.PackageNameOrder) int { + return cmp.Compare(o, other.(validatorNameOrder)) } -const validateT = `{{range . }}{{ printf "Validate%s runs the validations defined on %s" .VarName .Name | comment }} -func Validate{{ .VarName }}(v {{ .Ref }}) (err error) { +const typesT = `{{ range . }} +{{- if .Description }} +{{ .Description | comment }} +{{- end }} +type {{ .Name }} {{ .Definition }} +{{ end }} +` + +const validateT = `{{ range . }} +{{ printf "%s runs the validations defined on %s" .VarName .Name | comment }} +func {{ .VarName }}(v {{ .Ref }}) (err error) { {{ .ValidateDef }} return } {{ end }} - ` diff --git a/types/generate_test.go b/types/generate_test.go index da5fb4309..ac33b4c7e 100644 --- a/types/generate_test.go +++ b/types/generate_test.go @@ -1,18 +1,17 @@ package types import ( - "bytes" "flag" "fmt" "os" "path/filepath" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "goa.design/goa/v3/codegen" "goa.design/goa/v3/eval" + "goa.design/goa/v3/expr" "goa.design/plugins/v3/types/testdata" ) @@ -28,6 +27,7 @@ func TestTypes(t *testing.T) { {"noval", testdata.NoValidation}, {"required", testdata.Require}, {"validation", testdata.Validation}, + {"validator_collision", testdata.ValidatorCollision}, {"multiple", testdata.Multiple}, {"alias", testdata.Alias}, {"example", testdata.Exampl}, @@ -37,23 +37,34 @@ func TestTypes(t *testing.T) { for _, c := range cases { t.Run(c.Name, func(t *testing.T) { root := codegen.RunDSL(t, c.DSL) - fs, err := Generate("", []eval.Root{root}, nil) + fs, err := Generate("generated.local/gen", []eval.Root{root}, nil) require.NoError(t, err) require.NotEmpty(t, fs) require.NotEmpty(t, fs[0].SectionTemplates) - var buf bytes.Buffer - for _, s := range fs[0].SectionTemplates { - require.NoError(t, s.Write(&buf)) - } - got := buf.String()[strings.Index(buf.String(), "\n")+1:] + filename, err := fs[0].Render(t.TempDir()) + require.NoError(t, err) + got, err := os.ReadFile(filename) + require.NoError(t, err) golden := filepath.Join("testdata", fmt.Sprintf("%s.go_", c.Name)) if *update { - os.WriteFile(golden, buf.Bytes(), 0644) + require.NoError(t, os.WriteFile(golden, got, 0644)) return } expected, err := os.ReadFile(golden) require.NoError(t, err) - assert.Equal(t, string(expected), got) + assert.Equal(t, string(expected), string(got)) }) } } + +// TestGeneratePreservesEvaluatedServices catches generators that add a fake +// service to the shared design while borrowing Goa's service templates. +func TestGeneratePreservesEvaluatedServices(t *testing.T) { + root := codegen.RunDSL(t, testdata.Empt) + services := append([]*expr.ServiceExpr(nil), root.Services...) + + _, err := Generate("generated.local/gen", []eval.Root{root}, nil) + + require.NoError(t, err) + assert.Equal(t, services, root.Services) +} diff --git a/types/testdata/alias.go_ b/types/testdata/alias.go_ index cd1c386cc..4cc291aa4 100644 --- a/types/testdata/alias.go_ +++ b/types/testdata/alias.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -7,18 +8,17 @@ package types import ( - goa "goa.design/goa/v3/pkg" "unicode/utf8" -) + goa "goa.design/goa/v3/pkg" +) type Alias string + // ValidateAlias runs the validations defined on Alias func ValidateAlias(v Alias) (err error) { if utf8.RuneCountInString(string(v)) < 10 { - err = goa.MergeErrors(err, goa.InvalidLengthError("v", string(v), utf8.RuneCountInString(string(v)), 10, true)) -} + err = goa.MergeErrors(err, goa.InvalidLengthError("v", string(v), utf8.RuneCountInString(string(v)), 10, true)) + } return } - - diff --git a/types/testdata/array.go_ b/types/testdata/array.go_ index 593b9f122..b73c906cc 100644 --- a/types/testdata/array.go_ +++ b/types/testdata/array.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -6,11 +7,7 @@ package types -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) - +import goa "goa.design/goa/v3/pkg" type Array struct { Array []*Item @@ -19,12 +16,11 @@ type Array struct { type Item struct { Name string } + // ValidateArray runs the validations defined on Array func ValidateArray(v *Array) (err error) { if v.Array == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("array", "v")) -} + err = goa.MergeErrors(err, goa.MissingFieldError("array", "v")) + } return } - - diff --git a/types/testdata/dsls.go b/types/testdata/dsls.go index 5aa35582c..751a3547b 100644 --- a/types/testdata/dsls.go +++ b/types/testdata/dsls.go @@ -29,6 +29,20 @@ var Validation = func() { }) } +// ValidatorCollision defines a generated validator whose usual name matches +// another authored type. +var ValidatorCollision = func() { + var _ = Type("ValidateFoo", func() { + Attribute("value", String) + }) + + var _ = Type("Foo", func() { + Attribute("value", String, func() { + MinLength(1) + }) + }) +} + var Multiple = func() { var AType = Type("AType", func() { Attribute("attr", String, func() { diff --git a/types/testdata/empty.go_ b/types/testdata/empty.go_ index 676d6e28c..b5be070d4 100644 --- a/types/testdata/empty.go_ +++ b/types/testdata/empty.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -5,11 +6,3 @@ // goa package types - -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) - - - diff --git a/types/testdata/example.go_ b/types/testdata/example.go_ index fc5d95e84..5699a4fbd 100644 --- a/types/testdata/example.go_ +++ b/types/testdata/example.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -6,10 +7,7 @@ package types -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) +import goa "goa.design/goa/v3/pkg" // My type type MyType struct { @@ -18,12 +16,11 @@ type MyType struct { // Name Name string } + // ValidateMyType runs the validations defined on MyType func ValidateMyType(v *MyType) (err error) { if v.Age < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("v.age", v.Age, 0, true)) -} + err = goa.MergeErrors(err, goa.InvalidRangeError("v.age", v.Age, 0, true)) + } return } - - diff --git a/types/testdata/multiple.go_ b/types/testdata/multiple.go_ index af1f6c04e..7b17b9b08 100644 --- a/types/testdata/multiple.go_ +++ b/types/testdata/multiple.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -6,55 +7,52 @@ package types -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) - +import goa "goa.design/goa/v3/pkg" type AType struct { Attr *string } type Composite struct { - Attr *AType + Attr *AType Other *OtherType } type OtherType struct { Attr string } + // ValidateAType runs the validations defined on AType func ValidateAType(v *AType) (err error) { if v.Attr != nil { -err = goa.MergeErrors(err, goa.ValidatePattern("v.attr", *v.Attr, "^[a-zA-Z0-9]*$")) -} + err = goa.MergeErrors(err, goa.ValidatePattern("v.attr", *v.Attr, "^[a-zA-Z0-9]*$")) + } return } + // ValidateOtherType runs the validations defined on OtherType func ValidateOtherType(v *OtherType) (err error) { err = goa.MergeErrors(err, goa.ValidatePattern("v.attr", v.Attr, "^[a-zA-Z0-9]*$")) return } + // ValidateComposite runs the validations defined on Composite func ValidateComposite(v *Composite) (err error) { if v.Attr == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("attr", "v")) -} -if v.Other == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("other", "v")) -} -if v.Attr != nil { - if err2 := ValidateAType(v.Attr); err2 != nil { - err = goa.MergeErrors(err, err2) -} -} -if v.Other != nil { - if err2 := ValidateOtherType(v.Other); err2 != nil { - err = goa.MergeErrors(err, err2) -} -} + err = goa.MergeErrors(err, goa.MissingFieldError("attr", "v")) + } + if v.Other == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("other", "v")) + } + if v.Attr != nil { + if err2 := ValidateAType(v.Attr); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + if v.Other != nil { + if err2 := ValidateOtherType(v.Other); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } return } - - diff --git a/types/testdata/noval.go_ b/types/testdata/noval.go_ index 92d9486ee..bd654fcd7 100644 --- a/types/testdata/noval.go_ +++ b/types/testdata/noval.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -6,14 +7,6 @@ package types -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) - - type NoVal struct { Attr *string } - - diff --git a/types/testdata/recArray.go_ b/types/testdata/recArray.go_ index b4530919a..33cb8e0f3 100644 --- a/types/testdata/recArray.go_ +++ b/types/testdata/recArray.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -6,11 +7,7 @@ package types -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) - +import goa "goa.design/goa/v3/pkg" type ArrayArray struct { Array []*ArrayItem @@ -19,26 +16,26 @@ type ArrayArray struct { type ArrayItem struct { Names []string } + // ValidateArrayItem runs the validations defined on ArrayItem func ValidateArrayItem(v *ArrayItem) (err error) { if v.Names == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("names", "v")) -} + err = goa.MergeErrors(err, goa.MissingFieldError("names", "v")) + } return } + // ValidateArrayArray runs the validations defined on ArrayArray func ValidateArrayArray(v *ArrayArray) (err error) { if v.Array == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("array", "v")) -} -for _, e := range v.Array { -if e != nil { - if err2 := ValidateArrayItem(e); err2 != nil { - err = goa.MergeErrors(err, err2) -} -} -} + err = goa.MergeErrors(err, goa.MissingFieldError("array", "v")) + } + for _, e := range v.Array { + if e != nil { + if err2 := ValidateArrayItem(e); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } return } - - diff --git a/types/testdata/required.go_ b/types/testdata/required.go_ index 5955eccb6..a4f40ec89 100644 --- a/types/testdata/required.go_ +++ b/types/testdata/required.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -6,21 +7,16 @@ package types -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) - +import goa "goa.design/goa/v3/pkg" type Require struct { Attr []string } + // ValidateRequire runs the validations defined on Require func ValidateRequire(v *Require) (err error) { if v.Attr == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("attr", "v")) -} + err = goa.MergeErrors(err, goa.MissingFieldError("attr", "v")) + } return } - - diff --git a/types/testdata/validation.go_ b/types/testdata/validation.go_ index b1b73d1af..d919eeb62 100644 --- a/types/testdata/validation.go_ +++ b/types/testdata/validation.go_ @@ -1,3 +1,4 @@ +// Code generated by goa, DO NOT EDIT. // // Data types // @@ -6,21 +7,16 @@ package types -import ( - goa "goa.design/goa/v3/pkg" - "unicode/utf8" -) - +import goa "goa.design/goa/v3/pkg" type Validation struct { Attr *string } + // ValidateValidation runs the validations defined on Validation func ValidateValidation(v *Validation) (err error) { if v.Attr != nil { -err = goa.MergeErrors(err, goa.ValidatePattern("v.attr", *v.Attr, "^[a-zA-Z0-9]*$")) -} + err = goa.MergeErrors(err, goa.ValidatePattern("v.attr", *v.Attr, "^[a-zA-Z0-9]*$")) + } return } - - diff --git a/types/testdata/validator_collision.go_ b/types/testdata/validator_collision.go_ new file mode 100644 index 000000000..2c8e259ab --- /dev/null +++ b/types/testdata/validator_collision.go_ @@ -0,0 +1,32 @@ +// Code generated by goa, DO NOT EDIT. +// +// Data types +// +// Command: +// goa + +package types + +import ( + "unicode/utf8" + + goa "goa.design/goa/v3/pkg" +) + +type Foo struct { + Value *string +} + +type ValidateFoo struct { + Value *string +} + +// ValidateFoo2 runs the validations defined on Foo +func ValidateFoo2(v *Foo) (err error) { + if v.Value != nil { + if utf8.RuneCountInString(*v.Value) < 1 { + err = goa.MergeErrors(err, goa.InvalidLengthError("v.value", *v.Value, utf8.RuneCountInString(*v.Value), 1, true)) + } + } + return +} diff --git a/zaplogger/examples/calc/cmd/calc-cli/http.go b/zaplogger/examples/calc/cmd/calc-cli/http.go index de8473246..2aca23dcb 100644 --- a/zaplogger/examples/calc/cmd/calc-cli/http.go +++ b/zaplogger/examples/calc/cmd/calc-cli/http.go @@ -1,16 +1,18 @@ package main import ( + "context" + "flag" "fmt" + "io" "net/http" "time" goahttp "goa.design/goa/v3/http" - goa "goa.design/goa/v3/pkg" cli "goa.design/plugins/v3/zaplogger/examples/calc/gen/http/cli/calc" ) -func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { +func doHTTP(ctx context.Context, scheme, host string, timeout int, debug bool, stdout io.Writer) error { var ( doer goahttp.Doer ) @@ -30,13 +32,17 @@ func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, er debug, ) if err != nil { - return nil, nil, fmt.Errorf("parse endpoint: %w", err) + return fmt.Errorf("parse endpoint: %w", err) } - return endpoint, payload, nil -} -func httpUsageCommands() []string { - return cli.UsageCommands() + switch flag.Arg(0) { + case "calc": + switch flag.Arg(1) { + case "add": + return writeEndpointResult(ctx, stdout, endpoint, payload) + } + } + panic("parsed HTTP command has no generated result writer") } func httpUsageExamples() string { diff --git a/zaplogger/examples/calc/cmd/calc-cli/main.go b/zaplogger/examples/calc/cmd/calc-cli/main.go index 86bbccf32..3cd8db60c 100644 --- a/zaplogger/examples/calc/cmd/calc-cli/main.go +++ b/zaplogger/examples/calc/cmd/calc-cli/main.go @@ -6,10 +6,9 @@ import ( "errors" "flag" "fmt" + "io" "net/url" "os" - "slices" - "sort" "strings" goa "goa.design/goa/v3/pkg" @@ -66,14 +65,12 @@ func main() { } var ( - endpoint goa.Endpoint - payload any - err error + err error ) { switch scheme { case "http", "https": - endpoint, payload, err = doHTTP(scheme, host, timeout, debug) + err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|grpcs|http|https)\n", scheme) os.Exit(1) @@ -88,23 +85,36 @@ func main() { os.Exit(1) } - data, err := endpoint(context.Background(), payload) +} + +// writeEndpointResult calls one normal endpoint and writes its result as JSON. +func writeEndpointResult(ctx context.Context, stdout io.Writer, endpoint goa.Endpoint, payload any) error { + data, err := endpoint(ctx, payload) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return err } + return writeJSON(stdout, data) +} - if data != nil { - m, _ := json.MarshalIndent(data, "", " ") - fmt.Println(string(m)) +// writeJSON writes one indented JSON value followed by a newline. +func writeJSON(stdout io.Writer, data any) error { + if data == nil { + return nil } + encoded, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("encode result: %w", err) + } + if _, err := fmt.Fprintln(stdout, string(encoded)); err != nil { + return fmt.Errorf("write result: %w", err) + } + return nil } func usage() { - var usageCommands []string - usageCommands = append(usageCommands, httpUsageCommands()...) - sort.Strings(usageCommands) - usageCommands = slices.Compact(usageCommands) + usageCommands := []string{ + "calc add", + } fmt.Fprintf(os.Stderr, `%s is a command line client for the calc API. Usage: diff --git a/zaplogger/examples/calc/gen/calc/service.go b/zaplogger/examples/calc/gen/calc/service.go index 3f5b9390b..2323f29c0 100644 --- a/zaplogger/examples/calc/gen/calc/service.go +++ b/zaplogger/examples/calc/gen/calc/service.go @@ -8,9 +8,7 @@ package calc -import ( - "context" -) +import "context" // The calc service performs operations on numbers type Service interface { diff --git a/zaplogger/examples/calc/gen/http/calc/client/encode_decode.go b/zaplogger/examples/calc/gen/http/calc/client/encode_decode.go index c25acea4c..34784a966 100644 --- a/zaplogger/examples/calc/gen/http/calc/client/encode_decode.go +++ b/zaplogger/examples/calc/gen/http/calc/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "errors" "io" "net/http" "net/url" @@ -50,18 +51,24 @@ func (c *Client) BuildAddRequest(ctx context.Context, v any) (*http.Request, err // endpoint. restoreBody controls whether the response body should be restored // after having been read. func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (any, error) { + return func(resp *http.Response) (result any, decodeErr error) { + responseBody := resp.Body if restoreBody { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + b, readErr := io.ReadAll(responseBody) + closeErr := responseBody.Close() + if err := errors.Join(readErr, closeErr); err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) } resp.Body = io.NopCloser(bytes.NewBuffer(b)) defer func() { resp.Body = io.NopCloser(bytes.NewBuffer(b)) }() } else { - defer resp.Body.Close() + defer func() { + if err := responseBody.Close(); err != nil { + decodeErr = errors.Join(decodeErr, goahttp.ErrDecodingError("calc", "add", err)) + } + }() } switch resp.StatusCode { case http.StatusOK: @@ -75,7 +82,10 @@ func DecodeAddResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody } return body, nil default: - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, goahttp.ErrDecodingError("calc", "add", err) + } return nil, goahttp.ErrInvalidResponse("calc", "add", resp.StatusCode, string(body)) } } diff --git a/zaplogger/examples/calc/gen/http/calc/server/server.go b/zaplogger/examples/calc/gen/http/calc/server/server.go index ee3c89b33..6a2b569a1 100644 --- a/zaplogger/examples/calc/gen/http/calc/server/server.go +++ b/zaplogger/examples/calc/gen/http/calc/server/server.go @@ -140,15 +140,14 @@ func NewAddHandler( }) } -// appendFS is a custom implementation of fs.FS that appends a specified prefix -// to the file paths before delegating the Open call to the underlying fs.FS. +// appendFS adds a fixed directory to file paths before opening them. type appendFS struct { prefix string fs http.FileSystem } // Open opens the named file, appending the prefix to the file path before -// passing it to the underlying fs.FS. +// passing it to the underlying file system. func (s appendFS) Open(name string) (http.File, error) { switch name { case "/swagger.json": @@ -157,8 +156,7 @@ func (s appendFS) Open(name string) (http.File, error) { return s.fs.Open(path.Join(s.prefix, name)) } -// appendPrefix returns a new fs.FS that appends the specified prefix to file paths -// before delegating to the provided embed.FS. +// appendPrefix returns a file system that adds prefix before opening each path. func appendPrefix(fsys http.FileSystem, prefix string) http.FileSystem { return appendFS{prefix: prefix, fs: fsys} } diff --git a/zaplogger/examples/calc/gen/http/cli/calc/cli.go b/zaplogger/examples/calc/gen/http/cli/calc/cli.go index b26c590f6..422af126d 100644 --- a/zaplogger/examples/calc/gen/http/cli/calc/cli.go +++ b/zaplogger/examples/calc/gen/http/cli/calc/cli.go @@ -30,7 +30,7 @@ func UsageCommands() []string { // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "calc add --a 7101074584653383974 --b 5365422230106384248" + "\n" + + return os.Args[0] + " " + "calc add --a 2760442282490901434 --b 8070273224127067687" + "\n" + "" } @@ -155,5 +155,5 @@ func calcAddUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calc add --a 7101074584653383974 --b 5365422230106384248") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "calc add --a 2760442282490901434 --b 8070273224127067687") } diff --git a/zaplogger/examples/calc/gen/http/openapi3.2.json b/zaplogger/examples/calc/gen/http/openapi3.2.json index 462bfc946..1580e6744 100644 --- a/zaplogger/examples/calc/gen/http/openapi3.2.json +++ b/zaplogger/examples/calc/gen/http/openapi3.2.json @@ -1 +1 @@ -{"openapi":"3.2.0","info":{"title":"Calculator Service","description":"HTTP service for adding numbers, a goa teaser","version":"0.0.1"},"servers":[{"url":"http://localhost:8000/calc","name":"calc-development","description":"calc hosts the Calculator Service."},{"url":"https://{version}.goa.design/calc","name":"calc-production","description":"calc hosts the Calculator Service.","variables":{"version":{"default":"v1"}}}],"paths":{"/add/{a}/{b}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"a","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":7101074584653383974,"format":"int64"},"example":2989948904695875606},{"name":"b","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":5365422230106384248,"format":"int64"},"example":8103102529540563611}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":6252541931587653406,"format":"int64"},"example":6252541931587653406}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs operations on numbers"}]} \ No newline at end of file +{"openapi":"3.2.0","info":{"title":"Calculator Service","description":"HTTP service for adding numbers, a goa teaser","version":"0.0.1"},"servers":[{"url":"http://localhost:8000/calc","name":"calc-development","description":"calc hosts the Calculator Service."},{"url":"https://{version}.goa.design/calc","name":"calc-production","description":"calc hosts the Calculator Service.","variables":{"version":{"default":"v1","description":"API version"}}}],"paths":{"/add/{a}/{b}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"a","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":2760442282490901434,"format":"int64"},"example":2760442282490901434},{"name":"b","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":8070273224127067687,"format":"int64"},"example":8070273224127067687}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":7371373013862544951,"format":"int64"},"example":7371373013862544951}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs operations on numbers"}]} \ No newline at end of file diff --git a/zaplogger/examples/calc/gen/http/openapi3.2.yaml b/zaplogger/examples/calc/gen/http/openapi3.2.yaml index daba6c4f0..ef766a296 100644 --- a/zaplogger/examples/calc/gen/http/openapi3.2.yaml +++ b/zaplogger/examples/calc/gen/http/openapi3.2.yaml @@ -13,6 +13,7 @@ servers: variables: version: default: v1 + description: API version paths: /add/{a}/{b}: get: @@ -28,9 +29,9 @@ paths: schema: type: integer description: Left operand - example: 7101074584653383974 + example: 2760442282490901434 format: int64 - example: 2989948904695875606 + example: 2760442282490901434 - name: b in: path description: Right operand @@ -38,9 +39,9 @@ paths: schema: type: integer description: Right operand - example: 5365422230106384248 + example: 8070273224127067687 format: int64 - example: 8103102529540563611 + example: 8070273224127067687 responses: "200": description: OK response. @@ -48,9 +49,9 @@ paths: application/json: schema: type: integer - example: 6252541931587653406 + example: 7371373013862544951 format: int64 - example: 6252541931587653406 + example: 7371373013862544951 /swagger.json: get: tags: diff --git a/zaplogger/examples/calc/gen/http/openapi3.json b/zaplogger/examples/calc/gen/http/openapi3.json index abefd6f8b..afe9ce4a2 100644 --- a/zaplogger/examples/calc/gen/http/openapi3.json +++ b/zaplogger/examples/calc/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Calculator Service","description":"HTTP service for adding numbers, a goa teaser","version":"0.0.1"},"servers":[{"url":"http://localhost:8000/calc","description":"calc hosts the Calculator Service."},{"url":"https://{version}.goa.design/calc","description":"calc hosts the Calculator Service.","variables":{"version":{"default":"v1"}}}],"paths":{"/add/{a}/{b}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"a","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":7101074584653383974,"format":"int64"},"example":2989948904695875606},{"name":"b","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":5365422230106384248,"format":"int64"},"example":8103102529540563611}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":6252541931587653406,"format":"int64"},"example":6252541931587653406}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs operations on numbers"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Calculator Service","description":"HTTP service for adding numbers, a goa teaser","version":"0.0.1"},"servers":[{"url":"http://localhost:8000/calc","description":"calc hosts the Calculator Service."},{"url":"https://{version}.goa.design/calc","description":"calc hosts the Calculator Service.","variables":{"version":{"default":"v1","description":"API version"}}}],"paths":{"/add/{a}/{b}":{"get":{"tags":["calc"],"summary":"add calc","operationId":"calc#add","parameters":[{"name":"a","in":"path","description":"Left operand","required":true,"schema":{"type":"integer","description":"Left operand","example":2760442282490901434,"format":"int64"},"example":2760442282490901434},{"name":"b","in":"path","description":"Right operand","required":true,"schema":{"type":"integer","description":"Right operand","example":8070273224127067687,"format":"int64"},"example":8070273224127067687}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":7371373013862544951,"format":"int64"},"example":7371373013862544951}}}}}},"/swagger.json":{"get":{"tags":["calc"],"summary":"Download ../../gen/http/openapi.json","operationId":"calc#/swagger.json","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"calc","description":"The calc service performs operations on numbers"}]} \ No newline at end of file diff --git a/zaplogger/examples/calc/gen/http/openapi3.yaml b/zaplogger/examples/calc/gen/http/openapi3.yaml index 95bff2e21..f2c51a110 100644 --- a/zaplogger/examples/calc/gen/http/openapi3.yaml +++ b/zaplogger/examples/calc/gen/http/openapi3.yaml @@ -11,6 +11,7 @@ servers: variables: version: default: v1 + description: API version paths: /add/{a}/{b}: get: @@ -26,9 +27,9 @@ paths: schema: type: integer description: Left operand - example: 7101074584653383974 + example: 2760442282490901434 format: int64 - example: 2989948904695875606 + example: 2760442282490901434 - name: b in: path description: Right operand @@ -36,9 +37,9 @@ paths: schema: type: integer description: Right operand - example: 5365422230106384248 + example: 8070273224127067687 format: int64 - example: 8103102529540563611 + example: 8070273224127067687 responses: "200": description: OK response. @@ -46,9 +47,9 @@ paths: application/json: schema: type: integer - example: 6252541931587653406 + example: 7371373013862544951 format: int64 - example: 6252541931587653406 + example: 7371373013862544951 /swagger.json: get: tags: diff --git a/zaplogger/update.go b/zaplogger/update.go index 8864007f2..42c02bc77 100644 --- a/zaplogger/update.go +++ b/zaplogger/update.go @@ -1,3 +1,6 @@ +// This file changes Goa's starter service and server examples to create and +// pass a Zap logger. The plugin updates both sides of the constructor call so +// every generated example remains ready to build. package zaplogger import ( @@ -7,27 +10,27 @@ import ( "goa.design/goa/v3/eval" ) -// Register the plugin Generator functions. +// init registers the example update after Goa has built its starter files. func init() { codegen.RegisterPluginLast("zaplogger", "example", nil, UpdateExample) } -// UpdateExample modifies the example generated files by replacing -// the log import reference when needed -// It also modify the initially generated main and service files -func UpdateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) { +// UpdateExample replaces the starter logger in each generated service and +// passes the matching Zap logger from the server main. +func UpdateExample(_ string, _ []eval.Root, files []*codegen.File) ([]*codegen.File, error) { for _, f := range files { updateExample(f) } return files, nil } +// updateExample replaces the matching sections in one generated starter file. func updateExample(file *codegen.File) { for _, section := range file.SectionTemplates { switch section.Name { case "server-main-services": codegen.AddImport(file.SectionTemplates[0], &codegen.ImportSpec{Path: "go.uber.org/zap"}) - oldinit := "{{ .VarName }}Svc = {{ $.APIPkg }}.New{{ .StructName }}()" + oldinit := "{{ .ServiceVar }} = {{ $.APIPkg }}.{{ .ExampleConstructorDeclaration.Name }}()" section.Source = strings.Replace(section.Source, oldinit, initT, 1) case "basic-service-struct": codegen.AddImport(file.SectionTemplates[0], &codegen.ImportSpec{Path: "go.uber.org/zap"}) @@ -55,7 +58,7 @@ const ( l, _ := zap.NewProduction() zlog = l.Sugar().With(zap.String("service", {{ printf "%q" .Name }})) } - {{ .VarName }}Svc = {{ $.APIPkg }}.New{{ .StructName }}(zlog)` + {{ .ServiceVar }} = {{ $.APIPkg }}.{{ .ExampleConstructorDeclaration.Name }}(zlog)` basicServiceInitT = ` {{ printf "New%s returns the %s service implementation." .StructName .Name | comment }} diff --git a/zaplogger/update_test.go b/zaplogger/update_test.go new file mode 100644 index 000000000..20d8e82e4 --- /dev/null +++ b/zaplogger/update_test.go @@ -0,0 +1,55 @@ +// This file verifies that the Zap logger plugin changes the starter service +// constructor and every generated call to that constructor together. +package zaplogger + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/generator" + "goa.design/goa/v3/eval" + + "goa.design/plugins/v3/zaplogger/testdata" +) + +func TestExamplePassesZapLoggerToServiceConstructor(t *testing.T) { + root := codegen.RunDSL(t, testdata.SimpleServiceDSL) + files, err := generator.Example("generated.local/gen", []eval.Root{root}) + require.NoError(t, err) + files, err = UpdateExample("generated.local/gen", []eval.Root{root}, files) + require.NoError(t, err) + + dir := t.TempDir() + for _, file := range files { + _, err := file.Render(dir) + require.NoError(t, err) + } + + serviceSource, err := os.ReadFile(filepath.Join(dir, "simple_service.go")) + require.NoError(t, err) + require.Contains(t, string(serviceSource), "func NewSimpleService(logger *zap.SugaredLogger)") + + var mainSource []byte + err = filepath.WalkDir(dir, func(path string, entry os.DirEntry, err error) error { + if err != nil { + return err + } + if entry.Name() == "main.go" && strings.Contains(path, "cmd") { + source, err := os.ReadFile(path) + if err != nil { + return err + } + if strings.Contains(string(source), "Initialize the services") { + mainSource = source + } + } + return nil + }) + require.NoError(t, err) + require.NotEmpty(t, mainSource) + require.Contains(t, string(mainSource), "simpleServiceSvc = testapi.NewSimpleService(zlog)") +} From 1752ccb2e681db9f5239bc6e44f3886635d13c5f Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Mon, 24 Aug 2026 19:18:03 -0700 Subject: [PATCH 02/30] Pin plugins to final Goa generation plan --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 6fc66ca0a..541b7579e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c + goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 003ba9a70..925d50195 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 1a11bd2fa..166f6547c 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c + goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index ea0400bda..819545713 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 8e83a81a8..a3a1b7b64 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c + goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index ea0400bda..819545713 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 69806bf28..a579f83f9 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c + goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index ea0400bda..819545713 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c h1:Vx1gpjJwT+dh+JYA2L7zHH9r1Y5nT/9KYQ4YnSA6l7Q= -goa.design/goa/v3 v3.30.1-0.20260825005549-6e7f0cbd9e6c/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= +goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From ee861b84df1e8452362b46076739c99445a08fc0 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Mon, 24 Aug 2026 19:21:48 -0700 Subject: [PATCH 03/30] Use the pinned Goa command in plugin CI --- Makefile | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 8e864f5ca..4c005b1b6 100644 --- a/Makefile +++ b/Makefile @@ -3,15 +3,13 @@ # Makefile for goa v3 plugins GOOS=$(shell go env GOOS) -GOA:=$(shell goa version 2> /dev/null) +GOA_VERSION:=$(shell go list -m -f '{{.Version}}' goa.design/goa/v3) -# Only list test and build dependencies -# Standard dependencies are installed via go get +# These commands install the tools used to build and test the plugins. DEPEND=\ google.golang.org/protobuf/cmd/protoc-gen-go@latest \ google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \ - honnef.co/go/tools/cmd/staticcheck@latest \ - goa.design/goa/v3/cmd/goa@v3 + honnef.co/go/tools/cmd/staticcheck@latest # Add new plugins here to enable make PLUGINS=\ @@ -64,15 +62,10 @@ depend: fi check-goa: -ifdef GOA + @# Use the Goa version in go.mod so generation and compilation use the same API. + go install goa.design/goa/v3/cmd/goa@$(GOA_VERSION) go mod download - @echo $(GOA) -else - go get -u goa.design/goa/v3@v3 - go get -u goa.design/goa/v3/...@v3 - go mod download - @echo $(GOA) -endif + @echo "Goa $(GOA_VERSION)" tidy: @go mod tidy From 240d8a0a5613f209744c29570b04a4280411fb26 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Mon, 24 Aug 2026 21:06:53 -0700 Subject: [PATCH 04/30] chore: verify plugins with corrected Goa --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 541b7579e..5da983465 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a + goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 925d50195..dc261833a 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 166f6547c..9e5b542d4 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a + goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 819545713..2cea50a3a 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index a3a1b7b64..2f4ec16c9 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a + goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 819545713..2cea50a3a 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index a579f83f9..8669949ca 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a + goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 819545713..2cea50a3a 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a h1:Liew9qCrjbMl9a+5WTXlLPYKaXcBRiXVSYRWAGlJvhc= -goa.design/goa/v3 v3.30.1-0.20260825014826-1b7182f77f9a/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= +goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From bc0ecf7c7ca6a39aa72f4d57af8b4520208ac3d9 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Tue, 25 Aug 2026 05:17:42 -0700 Subject: [PATCH 05/30] Regenerate plugins with corrected Goa --- .../examples/calc/gen/http/calc/client/cli.go | 12 +- cors/examples/calc/gen/http/cli/calc/cli.go | 28 +- .../examples/calc/gen/http/calc/client/cli.go | 12 +- docs/examples/calc/gen/http/cli/calc/cli.go | 28 +- go.mod | 2 +- go.sum | 4 +- .../examples/calc/gen/http/calc/client/cli.go | 12 +- goakit/examples/calc/gen/http/cli/calc/cli.go | 28 +- .../archiver/gen/http/archiver/client/cli.go | 14 +- .../gen/http/archiver/client/encode_decode.go | 8 + .../gen/http/archiver/client/types.go | 45 ++ .../archiver/gen/http/cli/archiver/cli.go | 30 +- .../fetcher/gen/http/cli/fetcher/cli.go | 25 +- .../fetcher/gen/http/fetcher/client/cli.go | 9 +- .../gen/http/fetcher/client/encode_decode.go | 4 + .../fetcher/gen/http/fetcher/client/types.go | 19 + .../examples/calc/gen/http/calc/client/cli.go | 12 +- i18n/examples/calc/gen/http/cli/calc/cli.go | 28 +- .../gen/grpc/calculator/client/cli.go | 24 +- .../calculator/gen/grpc/cli/calculator/cli.go | 40 +- .../gen/http/calculator/client/cli.go | 28 +- .../calculator/gen/http/cli/calculator/cli.go | 40 +- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 +- .../gen/grpc/cli/test_http_grpc/cli.go | 40 +- .../gen/grpc/test_http_grpc/client/cli.go | 24 +- .../gen/http/cli/test_http_grpc/cli.go | 35 +- .../gen/http/test_http_grpc/client/cli.go | 21 +- .../gen/http/test_http_grpc/client/client.go | 8 +- .../gen/http/test_http_grpc/client/sse.go | 394 ++++++++++-------- .../gen/http/test_http_grpc/server/server.go | 8 + .../gen/http/test_http_grpc/server/sse.go | 118 ++++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 +- .../gen/jsonrpc/cli/test_jsonrpc/cli.go | 30 +- .../gen/jsonrpc/test_jsonrpc/client/cli.go | 14 +- .../gen/jsonrpc/test_jsonrpc/client/client.go | 10 +- .../test_jsonrpc/client/encode_decode.go | 71 ++-- .../gen/jsonrpc/test_jsonrpc/server/server.go | 71 +++- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 +- .../example/gen/http/cli/tscap_example/cli.go | 30 +- tscap/example/gen/http/tscap/client/cli.go | 14 +- .../examples/calc/gen/http/calc/client/cli.go | 12 +- .../examples/calc/gen/http/cli/calc/cli.go | 28 +- 45 files changed, 1001 insertions(+), 397 deletions(-) diff --git a/cors/examples/calc/gen/http/calc/client/cli.go b/cors/examples/calc/gen/http/calc/client/cli.go index bb536ebd2..d75d14a9e 100644 --- a/cors/examples/calc/gen/http/calc/client/cli.go +++ b/cors/examples/calc/gen/http/calc/client/cli.go @@ -16,12 +16,15 @@ import ( ) // BuildAddPayload builds the payload for the calc add endpoint from CLI flags. -func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) { +func BuildAddPayload(calcAddA *string, calcAddB *string) (*calc.AddPayload, error) { var err error var a int { + if calcAddA == nil { + return nil, fmt.Errorf("missing required flag --a") + } var v int64 - v, err = strconv.ParseInt(calcAddA, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddA, 10, strconv.IntSize) a = int(v) if err != nil { return nil, fmt.Errorf("invalid value for a, must be INT") @@ -29,8 +32,11 @@ func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) } var b int { + if calcAddB == nil { + return nil, fmt.Errorf("missing required flag --b") + } var v int64 - v, err = strconv.ParseInt(calcAddB, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddB, 10, strconv.IntSize) b = int(v) if err != nil { return nil, fmt.Errorf("invalid value for b, must be INT") diff --git a/cors/examples/calc/gen/http/cli/calc/cli.go b/cors/examples/calc/gen/http/cli/calc/cli.go index 56e90872d..be4c76340 100644 --- a/cors/examples/calc/gen/http/cli/calc/cli.go +++ b/cors/examples/calc/gen/http/cli/calc/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -47,9 +66,12 @@ func ParseEndpoint( calcFlags = flag.NewFlagSet("calc", flag.ContinueOnError) calcAddFlags = flag.NewFlagSet("add", flag.ExitOnError) - calcAddAFlag = calcAddFlags.String("a", "REQUIRED", "Left operand") - calcAddBFlag = calcAddFlags.String("b", "REQUIRED", "Right operand") + calcAddAFlag = new(cliStringFlag) + calcAddBFlag = new(cliStringFlag) ) + calcAddFlags.Var(calcAddAFlag, "a", "Left operand") + calcAddFlags.Var(calcAddBFlag, "b", "Right operand") + calcFlags.Usage = calcUsage calcAddFlags.Usage = calcAddUsage @@ -117,7 +139,7 @@ func ParseEndpoint( switch epn { case "add": endpoint = c.Add() - data, err = calcc.BuildAddPayload(*calcAddAFlag, *calcAddBFlag) + data, err = calcc.BuildAddPayload(calcAddAFlag.value, calcAddBFlag.value) } } } diff --git a/docs/examples/calc/gen/http/calc/client/cli.go b/docs/examples/calc/gen/http/calc/client/cli.go index cb8a29e51..c52f77882 100644 --- a/docs/examples/calc/gen/http/calc/client/cli.go +++ b/docs/examples/calc/gen/http/calc/client/cli.go @@ -16,12 +16,15 @@ import ( ) // BuildAddPayload builds the payload for the calc add endpoint from CLI flags. -func BuildAddPayload(calcAddLeft string, calcAddRight string) (*calc.AddPayload, error) { +func BuildAddPayload(calcAddLeft *string, calcAddRight *string) (*calc.AddPayload, error) { var err error var left int { + if calcAddLeft == nil { + return nil, fmt.Errorf("missing required flag --left") + } var v int64 - v, err = strconv.ParseInt(calcAddLeft, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddLeft, 10, strconv.IntSize) left = int(v) if err != nil { return nil, fmt.Errorf("invalid value for left, must be INT") @@ -29,8 +32,11 @@ func BuildAddPayload(calcAddLeft string, calcAddRight string) (*calc.AddPayload, } var right int { + if calcAddRight == nil { + return nil, fmt.Errorf("missing required flag --right") + } var v int64 - v, err = strconv.ParseInt(calcAddRight, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddRight, 10, strconv.IntSize) right = int(v) if err != nil { return nil, fmt.Errorf("invalid value for right, must be INT") diff --git a/docs/examples/calc/gen/http/cli/calc/cli.go b/docs/examples/calc/gen/http/cli/calc/cli.go index a8a4caf95..444523941 100644 --- a/docs/examples/calc/gen/http/cli/calc/cli.go +++ b/docs/examples/calc/gen/http/cli/calc/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -47,9 +66,12 @@ func ParseEndpoint( calcFlags = flag.NewFlagSet("calc", flag.ContinueOnError) calcAddFlags = flag.NewFlagSet("add", flag.ExitOnError) - calcAddLeftFlag = calcAddFlags.String("left", "REQUIRED", "Left operand") - calcAddRightFlag = calcAddFlags.String("right", "REQUIRED", "Right operand") + calcAddLeftFlag = new(cliStringFlag) + calcAddRightFlag = new(cliStringFlag) ) + calcAddFlags.Var(calcAddLeftFlag, "left", "Left operand") + calcAddFlags.Var(calcAddRightFlag, "right", "Right operand") + calcFlags.Usage = calcUsage calcAddFlags.Usage = calcAddUsage @@ -117,7 +139,7 @@ func ParseEndpoint( switch epn { case "add": endpoint = c.Add() - data, err = calcc.BuildAddPayload(*calcAddLeftFlag, *calcAddRightFlag) + data, err = calcc.BuildAddPayload(calcAddLeftFlag.value, calcAddRightFlag.value) } } } diff --git a/go.mod b/go.mod index 5da983465..46cbae9a5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 + goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index dc261833a..3755b0349 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/gen/http/calc/client/cli.go b/goakit/examples/calc/gen/http/calc/client/cli.go index a9a4d0a76..b6d11d78b 100644 --- a/goakit/examples/calc/gen/http/calc/client/cli.go +++ b/goakit/examples/calc/gen/http/calc/client/cli.go @@ -16,12 +16,15 @@ import ( ) // BuildAddPayload builds the payload for the calc add endpoint from CLI flags. -func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) { +func BuildAddPayload(calcAddA *string, calcAddB *string) (*calc.AddPayload, error) { var err error var a int { + if calcAddA == nil { + return nil, fmt.Errorf("missing required flag --a") + } var v int64 - v, err = strconv.ParseInt(calcAddA, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddA, 10, strconv.IntSize) a = int(v) if err != nil { return nil, fmt.Errorf("invalid value for a, must be INT") @@ -29,8 +32,11 @@ func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) } var b int { + if calcAddB == nil { + return nil, fmt.Errorf("missing required flag --b") + } var v int64 - v, err = strconv.ParseInt(calcAddB, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddB, 10, strconv.IntSize) b = int(v) if err != nil { return nil, fmt.Errorf("invalid value for b, must be INT") diff --git a/goakit/examples/calc/gen/http/cli/calc/cli.go b/goakit/examples/calc/gen/http/cli/calc/cli.go index bb35de14f..36dd78690 100644 --- a/goakit/examples/calc/gen/http/cli/calc/cli.go +++ b/goakit/examples/calc/gen/http/cli/calc/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -47,9 +66,12 @@ func ParseEndpoint( calcFlags = flag.NewFlagSet("calc", flag.ContinueOnError) calcAddFlags = flag.NewFlagSet("add", flag.ExitOnError) - calcAddAFlag = calcAddFlags.String("a", "REQUIRED", "Left operand") - calcAddBFlag = calcAddFlags.String("b", "REQUIRED", "Right operand") + calcAddAFlag = new(cliStringFlag) + calcAddBFlag = new(cliStringFlag) ) + calcAddFlags.Var(calcAddAFlag, "a", "Left operand") + calcAddFlags.Var(calcAddBFlag, "b", "Right operand") + calcFlags.Usage = calcUsage calcAddFlags.Usage = calcAddUsage @@ -117,7 +139,7 @@ func ParseEndpoint( switch epn { case "add": endpoint = c.Add() - data, err = calcc.BuildAddPayload(*calcAddAFlag, *calcAddBFlag) + data, err = calcc.BuildAddPayload(calcAddAFlag.value, calcAddBFlag.value) } } } diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go b/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go index 0a2f480a7..e46753cf4 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/client/cli.go @@ -19,11 +19,14 @@ import ( // BuildArchivePayload builds the payload for the archiver archive endpoint // from CLI flags. -func BuildArchivePayload(archiverArchiveBody string) (*archiver.ArchivePayload, error) { +func BuildArchivePayload(archiverArchiveBody *string) (*archiver.ArchivePayload, error) { var err error var body ArchiveRequestBody { - err = json.Unmarshal([]byte(archiverArchiveBody), &body) + if archiverArchiveBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*archiverArchiveBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"body\": \"In nisi sit nihil totam dolorum.\",\n \"status\": 200\n }'") } @@ -44,12 +47,15 @@ func BuildArchivePayload(archiverArchiveBody string) (*archiver.ArchivePayload, // BuildReadPayload builds the payload for the archiver read endpoint from CLI // flags. -func BuildReadPayload(archiverReadID string) (*archiver.ReadPayload, error) { +func BuildReadPayload(archiverReadID *string) (*archiver.ReadPayload, error) { var err error var id int { + if archiverReadID == nil { + return nil, fmt.Errorf("missing required flag --id") + } var v int64 - v, err = strconv.ParseInt(archiverReadID, 10, strconv.IntSize) + v, err = strconv.ParseInt(*archiverReadID, 10, strconv.IntSize) id = int(v) if err != nil { return nil, fmt.Errorf("invalid value for id, must be INT") diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go b/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go index b7bed7ba9..3ad7955eb 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/client/encode_decode.go @@ -85,6 +85,10 @@ func DecodeArchiveResponse(decoder func(*http.Response) goahttp.Decoder, restore if err != nil { return nil, goahttp.ErrDecodingError("archiver", "archive", err) } + err = ValidateArchiveResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("archiver", "archive", err) + } p := NewArchiveMediaViewOK(&body) view := "default" vres := &archiverviews.ArchiveMedia{Projected: p, View: view} @@ -165,6 +169,10 @@ func DecodeReadResponse(decoder func(*http.Response) goahttp.Decoder, restoreBod if err != nil { return nil, goahttp.ErrDecodingError("archiver", "read", err) } + err = ValidateReadResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("archiver", "read", err) + } p := NewReadArchiveMediaOK(&body) view := "default" vres := &archiverviews.ArchiveMedia{Projected: p, View: view} diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go b/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go index e0d7f84b6..01a895ec8 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/client/types.go @@ -143,6 +143,51 @@ func NewReadBadRequest(body *ReadBadRequestResponseBody) *goa.ServiceError { return v } +// ValidateArchiveResponseBody runs the validations defined on +// ArchiveResponseBody +func ValidateArchiveResponseBody(body *ArchiveResponseBody) (err error) { + if body.Href == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("href", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Body == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("body", "body")) + } + if body.Href != nil { + err = goa.MergeErrors(err, goa.ValidatePattern("body.href", *body.Href, "^/archive/[0-9]+$")) + } + if body.Status != nil { + if *body.Status < 0 { + err = goa.MergeErrors(err, goa.InvalidRangeError("body.status", *body.Status, 0, true)) + } + } + return +} + +// ValidateReadResponseBody runs the validations defined on ReadResponseBody +func ValidateReadResponseBody(body *ReadResponseBody) (err error) { + if body.Href == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("href", "body")) + } + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.Body == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("body", "body")) + } + if body.Href != nil { + err = goa.MergeErrors(err, goa.ValidatePattern("body.href", *body.Href, "^/archive/[0-9]+$")) + } + if body.Status != nil { + if *body.Status < 0 { + err = goa.MergeErrors(err, goa.InvalidRangeError("body.status", *body.Status, 0, true)) + } + } + return +} + // ValidateReadNotFoundResponseBody runs the validations defined on // ReadNotFoundResponseBody func ValidateReadNotFoundResponseBody(body *ReadNotFoundResponseBody) (err error) { diff --git a/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go b/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go index 861e6b1c0..e1c93c41b 100644 --- a/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go +++ b/goakit/examples/fetcher/archiver/gen/http/cli/archiver/cli.go @@ -37,6 +37,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -50,15 +69,18 @@ func ParseEndpoint( archiverFlags = flag.NewFlagSet("archiver", flag.ContinueOnError) archiverArchiveFlags = flag.NewFlagSet("archive", flag.ExitOnError) - archiverArchiveBodyFlag = archiverArchiveFlags.String("body", "REQUIRED", "") + archiverArchiveBodyFlag = new(cliStringFlag) archiverReadFlags = flag.NewFlagSet("read", flag.ExitOnError) - archiverReadIDFlag = archiverReadFlags.String("id", "REQUIRED", "ID of archive") + archiverReadIDFlag = new(cliStringFlag) healthFlags = flag.NewFlagSet("health", flag.ContinueOnError) healthShowFlags = flag.NewFlagSet("show", flag.ExitOnError) ) + archiverArchiveFlags.Var(archiverArchiveBodyFlag, "body", "") + archiverReadFlags.Var(archiverReadIDFlag, "id", "ID of archive") + archiverFlags.Usage = archiverUsage archiverArchiveFlags.Usage = archiverArchiveUsage archiverReadFlags.Usage = archiverReadUsage @@ -142,10 +164,10 @@ func ParseEndpoint( switch epn { case "archive": endpoint = c.Archive() - data, err = archiverc.BuildArchivePayload(*archiverArchiveBodyFlag) + data, err = archiverc.BuildArchivePayload(archiverArchiveBodyFlag.value) case "read": endpoint = c.Read() - data, err = archiverc.BuildReadPayload(*archiverReadIDFlag) + data, err = archiverc.BuildReadPayload(archiverReadIDFlag.value) } case "health": c := healthc.NewClient(scheme, host, doer, enc, dec, restore) diff --git a/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go b/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go index d782c4ea4..7670b6f9f 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go +++ b/goakit/examples/fetcher/fetcher/gen/http/cli/fetcher/cli.go @@ -37,6 +37,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -50,12 +69,14 @@ func ParseEndpoint( fetcherFlags = flag.NewFlagSet("fetcher", flag.ContinueOnError) fetcherFetchFlags = flag.NewFlagSet("fetch", flag.ExitOnError) - fetcherFetchURLFlag = fetcherFetchFlags.String("url", "REQUIRED", "URL to be fetched") + fetcherFetchURLFlag = new(cliStringFlag) healthFlags = flag.NewFlagSet("health", flag.ContinueOnError) healthShowFlags = flag.NewFlagSet("show", flag.ExitOnError) ) + fetcherFetchFlags.Var(fetcherFetchURLFlag, "url", "URL to be fetched") + fetcherFlags.Usage = fetcherUsage fetcherFetchFlags.Usage = fetcherFetchUsage @@ -135,7 +156,7 @@ func ParseEndpoint( switch epn { case "fetch": endpoint = c.Fetch() - data, err = fetcherc.BuildFetchPayload(*fetcherFetchURLFlag) + data, err = fetcherc.BuildFetchPayload(fetcherFetchURLFlag.value) } case "health": c := healthc.NewClient(scheme, host, doer, enc, dec, restore) diff --git a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/cli.go b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/cli.go index ecd3db4de..252873ef6 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/cli.go +++ b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/cli.go @@ -9,17 +9,22 @@ package client import ( + "fmt" + goa "goa.design/goa/v3/pkg" fetcher "goa.design/plugins/v3/goakit/examples/fetcher/fetcher/gen/fetcher" ) // BuildFetchPayload builds the payload for the fetcher fetch endpoint from CLI // flags. -func BuildFetchPayload(fetcherFetchURL string) (*fetcher.FetchPayload, error) { +func BuildFetchPayload(fetcherFetchURL *string) (*fetcher.FetchPayload, error) { var err error var url_ string { - url_ = fetcherFetchURL + if fetcherFetchURL == nil { + return nil, fmt.Errorf("missing required flag --url") + } + url_ = *fetcherFetchURL err = goa.MergeErrors(err, goa.ValidateFormat("url", url_, goa.FormatURI)) if err != nil { return nil, err diff --git a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go index 82d56ae3d..6fff31a1c 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go +++ b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/encode_decode.go @@ -83,6 +83,10 @@ func DecodeFetchResponse(decoder func(*http.Response) goahttp.Decoder, restoreBo if err != nil { return nil, goahttp.ErrDecodingError("fetcher", "fetch", err) } + err = ValidateFetchResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("fetcher", "fetch", err) + } p := NewFetchMediaViewOK(&body) view := "default" vres := &fetcherviews.FetchMedia{Projected: p, View: view} diff --git a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go index 7c01e9588..f82d7da8c 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go +++ b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/types.go @@ -98,6 +98,25 @@ func NewFetchInternalError(body *FetchInternalErrorResponseBody) *goa.ServiceErr return v } +// ValidateFetchResponseBody runs the validations defined on FetchResponseBody +func ValidateFetchResponseBody(body *FetchResponseBody) (err error) { + if body.Status == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) + } + if body.ArchiveHref == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("archive_href", "body")) + } + if body.Status != nil { + if *body.Status < 0 { + err = goa.MergeErrors(err, goa.InvalidRangeError("body.status", *body.Status, 0, true)) + } + } + if body.ArchiveHref != nil { + err = goa.MergeErrors(err, goa.ValidatePattern("body.archive_href", *body.ArchiveHref, "^/archive/[0-9]+$")) + } + return +} + // ValidateFetchBadRequestResponseBody runs the validations defined on // FetchBadRequestResponseBody func ValidateFetchBadRequestResponseBody(body *FetchBadRequestResponseBody) (err error) { diff --git a/i18n/examples/calc/gen/http/calc/client/cli.go b/i18n/examples/calc/gen/http/calc/client/cli.go index 8d8ac6616..fca899e5e 100644 --- a/i18n/examples/calc/gen/http/calc/client/cli.go +++ b/i18n/examples/calc/gen/http/calc/client/cli.go @@ -16,12 +16,15 @@ import ( ) // BuildAddPayload builds the payload for the calc add endpoint from CLI flags. -func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) { +func BuildAddPayload(calcAddA *string, calcAddB *string) (*calc.AddPayload, error) { var err error var a int { + if calcAddA == nil { + return nil, fmt.Errorf("missing required flag --a") + } var v int64 - v, err = strconv.ParseInt(calcAddA, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddA, 10, strconv.IntSize) a = int(v) if err != nil { return nil, fmt.Errorf("invalid value for a, must be INT") @@ -29,8 +32,11 @@ func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) } var b int { + if calcAddB == nil { + return nil, fmt.Errorf("missing required flag --b") + } var v int64 - v, err = strconv.ParseInt(calcAddB, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddB, 10, strconv.IntSize) b = int(v) if err != nil { return nil, fmt.Errorf("invalid value for b, must be INT") diff --git a/i18n/examples/calc/gen/http/cli/calc/cli.go b/i18n/examples/calc/gen/http/cli/calc/cli.go index e5d589169..1115879d0 100644 --- a/i18n/examples/calc/gen/http/cli/calc/cli.go +++ b/i18n/examples/calc/gen/http/cli/calc/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -47,9 +66,12 @@ func ParseEndpoint( calcFlags = flag.NewFlagSet("calc", flag.ContinueOnError) calcAddFlags = flag.NewFlagSet("add", flag.ExitOnError) - calcAddAFlag = calcAddFlags.String("a", "REQUIRED", "") - calcAddBFlag = calcAddFlags.String("b", "REQUIRED", "") + calcAddAFlag = new(cliStringFlag) + calcAddBFlag = new(cliStringFlag) ) + calcAddFlags.Var(calcAddAFlag, "a", "") + calcAddFlags.Var(calcAddBFlag, "b", "") + calcFlags.Usage = calcUsage calcAddFlags.Usage = calcAddUsage @@ -117,7 +139,7 @@ func ParseEndpoint( switch epn { case "add": endpoint = c.Add() - data, err = calcc.BuildAddPayload(*calcAddAFlag, *calcAddBFlag) + data, err = calcc.BuildAddPayload(calcAddAFlag.value, calcAddBFlag.value) } } } diff --git a/testing/examples/calculator/gen/grpc/calculator/client/cli.go b/testing/examples/calculator/gen/grpc/calculator/client/cli.go index bdbcc5a8c..34e017252 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/cli.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/cli.go @@ -18,12 +18,12 @@ import ( // BuildAddPayload builds the payload for the calculator add endpoint from CLI // flags. -func BuildAddPayload(calculatorAddMessage string) (*calculator.AddPayload, error) { +func BuildAddPayload(calculatorAddMessage *string) (*calculator.AddPayload, error) { var err error var message calculatorpb.AddRequest { - if calculatorAddMessage != "" { - err = protojson.Unmarshal([]byte(calculatorAddMessage), &message) + if calculatorAddMessage != nil { + err = protojson.Unmarshal([]byte(*calculatorAddMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"a\": 0.22091916525730942,\n \"b\": 0.26780525886889645\n }'") } @@ -43,12 +43,12 @@ func BuildAddPayload(calculatorAddMessage string) (*calculator.AddPayload, error // BuildDividePayload builds the payload for the calculator divide endpoint // from CLI flags. -func BuildDividePayload(calculatorDivideMessage string) (*calculator.DividePayload, error) { +func BuildDividePayload(calculatorDivideMessage *string) (*calculator.DividePayload, error) { var err error var message calculatorpb.DivideRequest { - if calculatorDivideMessage != "" { - err = protojson.Unmarshal([]byte(calculatorDivideMessage), &message) + if calculatorDivideMessage != nil { + err = protojson.Unmarshal([]byte(*calculatorDivideMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 0.7331112436485956,\n \"divisor\": 0.7550692456980171\n }'") } @@ -68,12 +68,12 @@ func BuildDividePayload(calculatorDivideMessage string) (*calculator.DividePaylo // BuildFactorialPayload builds the payload for the calculator factorial // endpoint from CLI flags. -func BuildFactorialPayload(calculatorFactorialMessage string) (*calculator.FactorialPayload, error) { +func BuildFactorialPayload(calculatorFactorialMessage *string) (*calculator.FactorialPayload, error) { var err error var message calculatorpb.FactorialRequest { - if calculatorFactorialMessage != "" { - err = protojson.Unmarshal([]byte(calculatorFactorialMessage), &message) + if calculatorFactorialMessage != nil { + err = protojson.Unmarshal([]byte(*calculatorFactorialMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"n\": 0\n }'") } @@ -92,12 +92,12 @@ func BuildFactorialPayload(calculatorFactorialMessage string) (*calculator.Facto // BuildStatisticsPayload builds the payload for the calculator statistics // endpoint from CLI flags. -func BuildStatisticsPayload(calculatorStatisticsMessage string) (*calculator.StatisticsPayload, error) { +func BuildStatisticsPayload(calculatorStatisticsMessage *string) (*calculator.StatisticsPayload, error) { var err error var message calculatorpb.StatisticsRequest { - if calculatorStatisticsMessage != "" { - err = protojson.Unmarshal([]byte(calculatorStatisticsMessage), &message) + if calculatorStatisticsMessage != nil { + err = protojson.Unmarshal([]byte(*calculatorStatisticsMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"numbers\": [\n 0.3811399958331969,\n 0.21022254932918344,\n 0.7555995055194686\n ]\n }'") } diff --git a/testing/examples/calculator/gen/grpc/cli/calculator/cli.go b/testing/examples/calculator/gen/grpc/cli/calculator/cli.go index 5bd39e50b..8200bb07a 100644 --- a/testing/examples/calculator/gen/grpc/cli/calculator/cli.go +++ b/testing/examples/calculator/gen/grpc/cli/calculator/cli.go @@ -33,6 +33,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -43,19 +62,24 @@ func ParseEndpoint( calculatorFlags = flag.NewFlagSet("calculator", flag.ContinueOnError) calculatorAddFlags = flag.NewFlagSet("add", flag.ExitOnError) - calculatorAddMessageFlag = calculatorAddFlags.String("message", "", "") + calculatorAddMessageFlag = new(cliStringFlag) calculatorDivideFlags = flag.NewFlagSet("divide", flag.ExitOnError) - calculatorDivideMessageFlag = calculatorDivideFlags.String("message", "", "") + calculatorDivideMessageFlag = new(cliStringFlag) calculatorFactorialFlags = flag.NewFlagSet("factorial", flag.ExitOnError) - calculatorFactorialMessageFlag = calculatorFactorialFlags.String("message", "", "") + calculatorFactorialMessageFlag = new(cliStringFlag) calculatorStatisticsFlags = flag.NewFlagSet("statistics", flag.ExitOnError) - calculatorStatisticsMessageFlag = calculatorStatisticsFlags.String("message", "", "") + calculatorStatisticsMessageFlag = new(cliStringFlag) calculatorBatchAddFlags = flag.NewFlagSet("batch-add", flag.ExitOnError) ) + calculatorAddFlags.Var(calculatorAddMessageFlag, "message", "") + calculatorDivideFlags.Var(calculatorDivideMessageFlag, "message", "") + calculatorFactorialFlags.Var(calculatorFactorialMessageFlag, "message", "") + calculatorStatisticsFlags.Var(calculatorStatisticsMessageFlag, "message", "") + calculatorFlags.Usage = calculatorUsage calculatorAddFlags.Usage = calculatorAddUsage calculatorDivideFlags.Usage = calculatorDivideUsage @@ -139,16 +163,16 @@ func ParseEndpoint( switch epn { case "add": endpoint = c.Add() - data, err = calculatorc.BuildAddPayload(*calculatorAddMessageFlag) + data, err = calculatorc.BuildAddPayload(calculatorAddMessageFlag.value) case "divide": endpoint = c.Divide() - data, err = calculatorc.BuildDividePayload(*calculatorDivideMessageFlag) + data, err = calculatorc.BuildDividePayload(calculatorDivideMessageFlag.value) case "factorial": endpoint = c.Factorial() - data, err = calculatorc.BuildFactorialPayload(*calculatorFactorialMessageFlag) + data, err = calculatorc.BuildFactorialPayload(calculatorFactorialMessageFlag.value) case "statistics": endpoint = c.Statistics() - data, err = calculatorc.BuildStatisticsPayload(*calculatorStatisticsMessageFlag) + data, err = calculatorc.BuildStatisticsPayload(calculatorStatisticsMessageFlag.value) case "batch-add": endpoint = c.BatchAdd() } diff --git a/testing/examples/calculator/gen/http/calculator/client/cli.go b/testing/examples/calculator/gen/http/calculator/client/cli.go index 8f4dd6cf3..52351b8b8 100644 --- a/testing/examples/calculator/gen/http/calculator/client/cli.go +++ b/testing/examples/calculator/gen/http/calculator/client/cli.go @@ -18,11 +18,14 @@ import ( // BuildAddPayload builds the payload for the calculator add endpoint from CLI // flags. -func BuildAddPayload(calculatorAddBody string) (*calculator.AddPayload, error) { +func BuildAddPayload(calculatorAddBody *string) (*calculator.AddPayload, error) { var err error var body AddRequestBody { - err = json.Unmarshal([]byte(calculatorAddBody), &body) + if calculatorAddBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*calculatorAddBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"a\": 0.490314453132986,\n \"b\": 0.6743292888632025\n }'") } @@ -37,11 +40,14 @@ func BuildAddPayload(calculatorAddBody string) (*calculator.AddPayload, error) { // BuildDividePayload builds the payload for the calculator divide endpoint // from CLI flags. -func BuildDividePayload(calculatorDivideBody string) (*calculator.DividePayload, error) { +func BuildDividePayload(calculatorDivideBody *string) (*calculator.DividePayload, error) { var err error var body DivideRequestBody { - err = json.Unmarshal([]byte(calculatorDivideBody), &body) + if calculatorDivideBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*calculatorDivideBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 0.7333985315892829,\n \"divisor\": 0.2998442358280339\n }'") } @@ -56,11 +62,14 @@ func BuildDividePayload(calculatorDivideBody string) (*calculator.DividePayload, // BuildFactorialPayload builds the payload for the calculator factorial // endpoint from CLI flags. -func BuildFactorialPayload(calculatorFactorialBody string) (*calculator.FactorialPayload, error) { +func BuildFactorialPayload(calculatorFactorialBody *string) (*calculator.FactorialPayload, error) { var err error var body FactorialRequestBody { - err = json.Unmarshal([]byte(calculatorFactorialBody), &body) + if calculatorFactorialBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*calculatorFactorialBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"n\": 12\n }'") } @@ -83,11 +92,14 @@ func BuildFactorialPayload(calculatorFactorialBody string) (*calculator.Factoria // BuildStatisticsPayload builds the payload for the calculator statistics // endpoint from CLI flags. -func BuildStatisticsPayload(calculatorStatisticsBody string) (*calculator.StatisticsPayload, error) { +func BuildStatisticsPayload(calculatorStatisticsBody *string) (*calculator.StatisticsPayload, error) { var err error var body StatisticsRequestBody { - err = json.Unmarshal([]byte(calculatorStatisticsBody), &body) + if calculatorStatisticsBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*calculatorStatisticsBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"numbers\": [\n 0.1819497783049658,\n 0.6452245232691431,\n 0.01204206152248697\n ]\n }'") } diff --git a/testing/examples/calculator/gen/http/cli/calculator/cli.go b/testing/examples/calculator/gen/http/cli/calculator/cli.go index 9d5918dfd..dc64877d3 100644 --- a/testing/examples/calculator/gen/http/cli/calculator/cli.go +++ b/testing/examples/calculator/gen/http/cli/calculator/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -49,19 +68,24 @@ func ParseEndpoint( calculatorFlags = flag.NewFlagSet("calculator", flag.ContinueOnError) calculatorAddFlags = flag.NewFlagSet("add", flag.ExitOnError) - calculatorAddBodyFlag = calculatorAddFlags.String("body", "REQUIRED", "") + calculatorAddBodyFlag = new(cliStringFlag) calculatorDivideFlags = flag.NewFlagSet("divide", flag.ExitOnError) - calculatorDivideBodyFlag = calculatorDivideFlags.String("body", "REQUIRED", "") + calculatorDivideBodyFlag = new(cliStringFlag) calculatorFactorialFlags = flag.NewFlagSet("factorial", flag.ExitOnError) - calculatorFactorialBodyFlag = calculatorFactorialFlags.String("body", "REQUIRED", "") + calculatorFactorialBodyFlag = new(cliStringFlag) calculatorStatisticsFlags = flag.NewFlagSet("statistics", flag.ExitOnError) - calculatorStatisticsBodyFlag = calculatorStatisticsFlags.String("body", "REQUIRED", "") + calculatorStatisticsBodyFlag = new(cliStringFlag) calculatorBatchAddFlags = flag.NewFlagSet("batch-add", flag.ExitOnError) ) + calculatorAddFlags.Var(calculatorAddBodyFlag, "body", "") + calculatorDivideFlags.Var(calculatorDivideBodyFlag, "body", "") + calculatorFactorialFlags.Var(calculatorFactorialBodyFlag, "body", "") + calculatorStatisticsFlags.Var(calculatorStatisticsBodyFlag, "body", "") + calculatorFlags.Usage = calculatorUsage calculatorAddFlags.Usage = calculatorAddUsage calculatorDivideFlags.Usage = calculatorDivideUsage @@ -145,16 +169,16 @@ func ParseEndpoint( switch epn { case "add": endpoint = c.Add() - data, err = calculatorc.BuildAddPayload(*calculatorAddBodyFlag) + data, err = calculatorc.BuildAddPayload(calculatorAddBodyFlag.value) case "divide": endpoint = c.Divide() - data, err = calculatorc.BuildDividePayload(*calculatorDivideBodyFlag) + data, err = calculatorc.BuildDividePayload(calculatorDivideBodyFlag.value) case "factorial": endpoint = c.Factorial() - data, err = calculatorc.BuildFactorialPayload(*calculatorFactorialBodyFlag) + data, err = calculatorc.BuildFactorialPayload(calculatorFactorialBodyFlag.value) case "statistics": endpoint = c.Statistics() - data, err = calculatorc.BuildStatisticsPayload(*calculatorStatisticsBodyFlag) + data, err = calculatorc.BuildStatisticsPayload(calculatorStatisticsBodyFlag.value) case "batch-add": endpoint = c.BatchAdd() } diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 9e5b542d4..6f1d5e1a2 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 + goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 2cea50a3a..d0f0846a3 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go b/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go index 269bd06f5..8f41f4c42 100644 --- a/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go +++ b/testing/examples/httpgrpc/gen/grpc/cli/test_http_grpc/cli.go @@ -33,6 +33,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -43,20 +62,20 @@ func ParseEndpoint( testHTTPGrpcFlags = flag.NewFlagSet("test-http-grpc", flag.ContinueOnError) testHTTPGrpcGrpcNoStreamFlags = flag.NewFlagSet("grpc-no-stream", flag.ExitOnError) - testHTTPGrpcGrpcNoStreamMessageFlag = testHTTPGrpcGrpcNoStreamFlags.String("message", "", "") + testHTTPGrpcGrpcNoStreamMessageFlag = new(cliStringFlag) testHTTPGrpcGrpcNoStreamErrorDivByZeroFlags = flag.NewFlagSet("grpc-no-stream-error-div-by-zero", flag.ExitOnError) - testHTTPGrpcGrpcNoStreamErrorDivByZeroMessageFlag = testHTTPGrpcGrpcNoStreamErrorDivByZeroFlags.String("message", "", "") + testHTTPGrpcGrpcNoStreamErrorDivByZeroMessageFlag = new(cliStringFlag) testHTTPGrpcGrpcServerStreamFlags = flag.NewFlagSet("grpc-server-stream", flag.ExitOnError) - testHTTPGrpcGrpcServerStreamMessageFlag = testHTTPGrpcGrpcServerStreamFlags.String("message", "", "") + testHTTPGrpcGrpcServerStreamMessageFlag = new(cliStringFlag) testHTTPGrpcGrpcClientStreamFlags = flag.NewFlagSet("grpc-client-stream", flag.ExitOnError) testHTTPGrpcGrpcBidiStreamFlags = flag.NewFlagSet("grpc-bidi-stream", flag.ExitOnError) testHTTPGrpcMixedNoStreamFlags = flag.NewFlagSet("mixed-no-stream", flag.ExitOnError) - testHTTPGrpcMixedNoStreamMessageFlag = testHTTPGrpcMixedNoStreamFlags.String("message", "", "") + testHTTPGrpcMixedNoStreamMessageFlag = new(cliStringFlag) testHTTPGrpcMixedServerStreamFlags = flag.NewFlagSet("mixed-server-stream", flag.ExitOnError) @@ -64,6 +83,11 @@ func ParseEndpoint( testHTTPGrpcMixedBidiStreamWsGrpcFlags = flag.NewFlagSet("mixed-bidi-stream-ws-grpc", flag.ExitOnError) ) + testHTTPGrpcGrpcNoStreamFlags.Var(testHTTPGrpcGrpcNoStreamMessageFlag, "message", "") + testHTTPGrpcGrpcNoStreamErrorDivByZeroFlags.Var(testHTTPGrpcGrpcNoStreamErrorDivByZeroMessageFlag, "message", "") + testHTTPGrpcGrpcServerStreamFlags.Var(testHTTPGrpcGrpcServerStreamMessageFlag, "message", "") + testHTTPGrpcMixedNoStreamFlags.Var(testHTTPGrpcMixedNoStreamMessageFlag, "message", "") + testHTTPGrpcFlags.Usage = testHTTPGrpcUsage testHTTPGrpcGrpcNoStreamFlags.Usage = testHTTPGrpcGrpcNoStreamUsage testHTTPGrpcGrpcNoStreamErrorDivByZeroFlags.Usage = testHTTPGrpcGrpcNoStreamErrorDivByZeroUsage @@ -163,20 +187,20 @@ func ParseEndpoint( switch epn { case "grpc-no-stream": endpoint = c.GrpcNoStream() - data, err = testhttpgrpcc.BuildGrpcNoStreamPayload(*testHTTPGrpcGrpcNoStreamMessageFlag) + data, err = testhttpgrpcc.BuildGrpcNoStreamPayload(testHTTPGrpcGrpcNoStreamMessageFlag.value) case "grpc-no-stream-error-div-by-zero": endpoint = c.GrpcNoStreamErrorDivByZero() - data, err = testhttpgrpcc.BuildGrpcNoStreamErrorDivByZeroPayload(*testHTTPGrpcGrpcNoStreamErrorDivByZeroMessageFlag) + data, err = testhttpgrpcc.BuildGrpcNoStreamErrorDivByZeroPayload(testHTTPGrpcGrpcNoStreamErrorDivByZeroMessageFlag.value) case "grpc-server-stream": endpoint = c.GrpcServerStream() - data, err = testhttpgrpcc.BuildGrpcServerStreamPayload(*testHTTPGrpcGrpcServerStreamMessageFlag) + data, err = testhttpgrpcc.BuildGrpcServerStreamPayload(testHTTPGrpcGrpcServerStreamMessageFlag.value) case "grpc-client-stream": endpoint = c.GrpcClientStream() case "grpc-bidi-stream": endpoint = c.GrpcBidiStream() case "mixed-no-stream": endpoint = c.MixedNoStream() - data, err = testhttpgrpcc.BuildMixedNoStreamPayload(*testHTTPGrpcMixedNoStreamMessageFlag) + data, err = testhttpgrpcc.BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamMessageFlag.value) case "mixed-server-stream": endpoint = c.MixedServerStream() case "mixed-client-stream-ws-grpc": diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go index 019d4e90f..59b886414 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/cli.go @@ -18,12 +18,12 @@ import ( // BuildGrpcNoStreamPayload builds the payload for the test-http-grpc // grpc_no_stream endpoint from CLI flags. -func BuildGrpcNoStreamPayload(testHTTPGrpcGrpcNoStreamMessage string) (*testhttpgrpc.GrpcNoStreamPayload, error) { +func BuildGrpcNoStreamPayload(testHTTPGrpcGrpcNoStreamMessage *string) (*testhttpgrpc.GrpcNoStreamPayload, error) { var err error var message test_http_grpcpb.GrpcNoStreamRequest { - if testHTTPGrpcGrpcNoStreamMessage != "" { - err = protojson.Unmarshal([]byte(testHTTPGrpcGrpcNoStreamMessage), &message) + if testHTTPGrpcGrpcNoStreamMessage != nil { + err = protojson.Unmarshal([]byte(*testHTTPGrpcGrpcNoStreamMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Ratione earum et ut temporibus tempora praesentium.\"\n }'") } @@ -42,12 +42,12 @@ func BuildGrpcNoStreamPayload(testHTTPGrpcGrpcNoStreamMessage string) (*testhttp // BuildGrpcNoStreamErrorDivByZeroPayload builds the payload for the // test-http-grpc grpc_no_stream_error_div_by_zero endpoint from CLI flags. -func BuildGrpcNoStreamErrorDivByZeroPayload(testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage string) (*testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload, error) { +func BuildGrpcNoStreamErrorDivByZeroPayload(testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage *string) (*testhttpgrpc.GrpcNoStreamErrorDivByZeroPayload, error) { var err error var message test_http_grpcpb.GrpcNoStreamErrorDivByZeroRequest { - if testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage != "" { - err = protojson.Unmarshal([]byte(testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage), &message) + if testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage != nil { + err = protojson.Unmarshal([]byte(*testHTTPGrpcGrpcNoStreamErrorDivByZeroMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"dividend\": 238.4063147460167,\n \"divisor\": -73.4004594816786\n }'") } @@ -67,12 +67,12 @@ func BuildGrpcNoStreamErrorDivByZeroPayload(testHTTPGrpcGrpcNoStreamErrorDivByZe // BuildGrpcServerStreamPayload builds the payload for the test-http-grpc // grpc_server_stream endpoint from CLI flags. -func BuildGrpcServerStreamPayload(testHTTPGrpcGrpcServerStreamMessage string) (*testhttpgrpc.GrpcServerStreamPayload, error) { +func BuildGrpcServerStreamPayload(testHTTPGrpcGrpcServerStreamMessage *string) (*testhttpgrpc.GrpcServerStreamPayload, error) { var err error var message test_http_grpcpb.GrpcServerStreamRequest { - if testHTTPGrpcGrpcServerStreamMessage != "" { - err = protojson.Unmarshal([]byte(testHTTPGrpcGrpcServerStreamMessage), &message) + if testHTTPGrpcGrpcServerStreamMessage != nil { + err = protojson.Unmarshal([]byte(*testHTTPGrpcGrpcServerStreamMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"from\": 436641374502740749\n }'") } @@ -91,12 +91,12 @@ func BuildGrpcServerStreamPayload(testHTTPGrpcGrpcServerStreamMessage string) (* // BuildMixedNoStreamPayload builds the payload for the test-http-grpc // mixed_no_stream endpoint from CLI flags. -func BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamMessage string) (*testhttpgrpc.MixedNoStreamPayload, error) { +func BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamMessage *string) (*testhttpgrpc.MixedNoStreamPayload, error) { var err error var message test_http_grpcpb.MixedNoStreamRequest { - if testHTTPGrpcMixedNoStreamMessage != "" { - err = protojson.Unmarshal([]byte(testHTTPGrpcMixedNoStreamMessage), &message) + if testHTTPGrpcMixedNoStreamMessage != nil { + err = protojson.Unmarshal([]byte(*testHTTPGrpcMixedNoStreamMessage), &message) if err != nil { return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Ut non id provident tempora suscipit quo.\"\n }'") } diff --git a/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go b/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go index e17054102..e49d5c0cd 100644 --- a/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go +++ b/testing/examples/httpgrpc/gen/http/cli/test_http_grpc/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -49,10 +68,10 @@ func ParseEndpoint( testHTTPGrpcFlags = flag.NewFlagSet("test-http-grpc", flag.ContinueOnError) testHTTPGrpcHTTPNoStreamFlags = flag.NewFlagSet("http-no-stream", flag.ExitOnError) - testHTTPGrpcHTTPNoStreamBodyFlag = testHTTPGrpcHTTPNoStreamFlags.String("body", "REQUIRED", "") + testHTTPGrpcHTTPNoStreamBodyFlag = new(cliStringFlag) testHTTPGrpcHTTPNoStreamErrorFlags = flag.NewFlagSet("http-no-stream-error", flag.ExitOnError) - testHTTPGrpcHTTPNoStreamErrorBodyFlag = testHTTPGrpcHTTPNoStreamErrorFlags.String("body", "REQUIRED", "") + testHTTPGrpcHTTPNoStreamErrorBodyFlag = new(cliStringFlag) testHTTPGrpcHTTPServerStreamSseFlags = flag.NewFlagSet("http-server-stream-sse", flag.ExitOnError) @@ -63,7 +82,7 @@ func ParseEndpoint( testHTTPGrpcHTTPBidiStreamWsFlags = flag.NewFlagSet("http-bidi-stream-ws", flag.ExitOnError) testHTTPGrpcMixedNoStreamFlags = flag.NewFlagSet("mixed-no-stream", flag.ExitOnError) - testHTTPGrpcMixedNoStreamBodyFlag = testHTTPGrpcMixedNoStreamFlags.String("body", "REQUIRED", "") + testHTTPGrpcMixedNoStreamBodyFlag = new(cliStringFlag) testHTTPGrpcMixedServerStreamFlags = flag.NewFlagSet("mixed-server-stream", flag.ExitOnError) @@ -71,6 +90,10 @@ func ParseEndpoint( testHTTPGrpcMixedBidiStreamWsGrpcFlags = flag.NewFlagSet("mixed-bidi-stream-ws-grpc", flag.ExitOnError) ) + testHTTPGrpcHTTPNoStreamFlags.Var(testHTTPGrpcHTTPNoStreamBodyFlag, "body", "") + testHTTPGrpcHTTPNoStreamErrorFlags.Var(testHTTPGrpcHTTPNoStreamErrorBodyFlag, "body", "") + testHTTPGrpcMixedNoStreamFlags.Var(testHTTPGrpcMixedNoStreamBodyFlag, "body", "") + testHTTPGrpcFlags.Usage = testHTTPGrpcUsage testHTTPGrpcHTTPNoStreamFlags.Usage = testHTTPGrpcHTTPNoStreamUsage testHTTPGrpcHTTPNoStreamErrorFlags.Usage = testHTTPGrpcHTTPNoStreamErrorUsage @@ -174,10 +197,10 @@ func ParseEndpoint( switch epn { case "http-no-stream": endpoint = c.HTTPNoStream() - data, err = testhttpgrpcc.BuildHTTPNoStreamPayload(*testHTTPGrpcHTTPNoStreamBodyFlag) + data, err = testhttpgrpcc.BuildHTTPNoStreamPayload(testHTTPGrpcHTTPNoStreamBodyFlag.value) case "http-no-stream-error": endpoint = c.HTTPNoStreamError() - data, err = testhttpgrpcc.BuildHTTPNoStreamErrorPayload(*testHTTPGrpcHTTPNoStreamErrorBodyFlag) + data, err = testhttpgrpcc.BuildHTTPNoStreamErrorPayload(testHTTPGrpcHTTPNoStreamErrorBodyFlag.value) case "http-server-stream-sse": endpoint = c.HTTPServerStreamSse() case "http-server-stream-ws": @@ -188,7 +211,7 @@ func ParseEndpoint( endpoint = c.HTTPBidiStreamWs() case "mixed-no-stream": endpoint = c.MixedNoStream() - data, err = testhttpgrpcc.BuildMixedNoStreamPayload(*testHTTPGrpcMixedNoStreamBodyFlag) + data, err = testhttpgrpcc.BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamBodyFlag.value) case "mixed-server-stream": endpoint = c.MixedServerStream() case "mixed-client-stream-ws-grpc": diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go index 9654f5fd2..e00e00490 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/cli.go @@ -17,11 +17,14 @@ import ( // BuildHTTPNoStreamPayload builds the payload for the test-http-grpc // http_no_stream endpoint from CLI flags. -func BuildHTTPNoStreamPayload(testHTTPGrpcHTTPNoStreamBody string) (*testhttpgrpc.HTTPNoStreamPayload, error) { +func BuildHTTPNoStreamPayload(testHTTPGrpcHTTPNoStreamBody *string) (*testhttpgrpc.HTTPNoStreamPayload, error) { var err error var body HTTPNoStreamRequestBody { - err = json.Unmarshal([]byte(testHTTPGrpcHTTPNoStreamBody), &body) + if testHTTPGrpcHTTPNoStreamBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*testHTTPGrpcHTTPNoStreamBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Expedita eum recusandae non.\"\n }'") } @@ -35,11 +38,14 @@ func BuildHTTPNoStreamPayload(testHTTPGrpcHTTPNoStreamBody string) (*testhttpgrp // BuildHTTPNoStreamErrorPayload builds the payload for the test-http-grpc // http_no_stream_error endpoint from CLI flags. -func BuildHTTPNoStreamErrorPayload(testHTTPGrpcHTTPNoStreamErrorBody string) (*testhttpgrpc.HTTPNoStreamErrorPayload, error) { +func BuildHTTPNoStreamErrorPayload(testHTTPGrpcHTTPNoStreamErrorBody *string) (*testhttpgrpc.HTTPNoStreamErrorPayload, error) { var err error var body HTTPNoStreamErrorRequestBody { - err = json.Unmarshal([]byte(testHTTPGrpcHTTPNoStreamErrorBody), &body) + if testHTTPGrpcHTTPNoStreamErrorBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*testHTTPGrpcHTTPNoStreamErrorBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Ipsum asperiores omnis culpa.\"\n }'") } @@ -53,11 +59,14 @@ func BuildHTTPNoStreamErrorPayload(testHTTPGrpcHTTPNoStreamErrorBody string) (*t // BuildMixedNoStreamPayload builds the payload for the test-http-grpc // mixed_no_stream endpoint from CLI flags. -func BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamBody string) (*testhttpgrpc.MixedNoStreamPayload, error) { +func BuildMixedNoStreamPayload(testHTTPGrpcMixedNoStreamBody *string) (*testhttpgrpc.MixedNoStreamPayload, error) { var err error var body MixedNoStreamRequestBody { - err = json.Unmarshal([]byte(testHTTPGrpcMixedNoStreamBody), &body) + if testHTTPGrpcMixedNoStreamBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*testHTTPGrpcMixedNoStreamBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Repellendus fuga quisquam sunt eligendi ad praesentium.\"\n }'") } diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go index 2805c4df0..284aee00e 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/client.go @@ -12,8 +12,8 @@ import ( "context" "errors" "fmt" + "mime" "net/http" - "strings" "time" "github.com/gorilla/websocket" @@ -182,7 +182,8 @@ func (c *Client) HTTPServerStreamSse() goa.Endpoint { } contentType := resp.Header.Get("Content-Type") - if contentType != "" && !strings.HasPrefix(contentType, "text/event-stream") { + mediaType, _, mediaTypeErr := mime.ParseMediaType(contentType) + if mediaTypeErr != nil || mediaType != "text/event-stream" { contentTypeErr := fmt.Errorf("unexpected content type: %s (expected text/event-stream)", contentType) if err := resp.Body.Close(); err != nil { return nil, errors.Join(contentTypeErr, goahttp.ErrDecodingError("test-http-grpc", "http_server_stream_sse", err)) @@ -330,7 +331,8 @@ func (c *Client) MixedServerStream() goa.Endpoint { } contentType := resp.Header.Get("Content-Type") - if contentType != "" && !strings.HasPrefix(contentType, "text/event-stream") { + mediaType, _, mediaTypeErr := mime.ParseMediaType(contentType) + if mediaTypeErr != nil || mediaType != "text/event-stream" { contentTypeErr := fmt.Errorf("unexpected content type: %s (expected text/event-stream)", contentType) if err := resp.Body.Close(); err != nil { return nil, errors.Join(contentTypeErr, goahttp.ErrDecodingError("test-http-grpc", "mixed_server_stream", err)) diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go index 5d2a5658c..4d6de4d05 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/client/sse.go @@ -37,6 +37,8 @@ type ( resp *http.Response decoder func(*http.Response) goahttp.Decoder buffer []byte // Buffer for unprocessed data + // started records whether the first stream line was read. + started bool lock sync.Mutex closed bool } @@ -65,25 +67,28 @@ func (s *HTTPServerStreamSseStreamImpl) Recv() (*testhttpgrpc.HTTPServerStreamSs // RecvWithContext reads and returns the next event from the SSE stream, respecting context cancellation. func (s *HTTPServerStreamSseStreamImpl) RecvWithContext(ctx context.Context) (event *testhttpgrpc.HTTPServerStreamSseResult, err error) { - var byts []byte - byts, err = s.readEvent(ctx) - if err != nil { - if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - // Clean up on EOF or context cancellation. io.EOF - // propagates to the caller to signal end of stream. - s.Close() + for { + var byts []byte + byts, err = s.readEvent(ctx) + if err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + // Clean up on EOF or context cancellation. io.EOF + // propagates to the caller to signal end of stream. + s.Close() + } + return + } + var hasData bool + event, hasData, err = s.processEvent(byts) + if err != nil || hasData { + return } - return } - return s.processEvent(byts) } // readEvent reads a single SSE event from the stream, respecting context -// cancellation. It first checks the internal buffer for a complete event -// (delimited by double newlines). If no complete event is found, it reads from -// the HTTP response body until it either finds an event boundary, reaches EOF, -// or encounters an error. Any data after the event boundary is saved in the -// buffer for the next call. +// cancellation. It accepts CR, LF, and CRLF line endings. A blank line ends an +// event; bytes after that line remain buffered for the next call. func (s *HTTPServerStreamSseStreamImpl) readEvent(ctx context.Context) ([]byte, error) { const bufSize = 4096 // 4KB buffer size @@ -93,9 +98,7 @@ func (s *HTTPServerStreamSseStreamImpl) readEvent(ctx context.Context) ([]byte, return event, nil } - // Initialize with any data from buffer eventData := event - wasNewline := len(eventData) > 0 && eventData[len(eventData)-1] == '\n' buf := make([]byte, bufSize) // Read data in chunks until we find an event or hit EOF. A stream that @@ -126,27 +129,14 @@ func (s *HTTPServerStreamSseStreamImpl) readEvent(ctx context.Context) ([]byte, return nil, err } - // Process data if we got any if n > 0 { - // Look for event boundary in this chunk - for i := 0; i < n; i++ { - b := buf[i] - eventData = append(eventData, b) - - // Check for double newlines (event boundary) - if b == '\n' && wasNewline { - // Save any remaining data for next read - if i+1 < n { - s.lock.Lock() - s.buffer = append(s.buffer[:0], buf[i+1:n]...) - s.lock.Unlock() - } - return eventData, nil - } - - // Update newline tracking - wasNewline = (b == '\n') - } + eventData = append(eventData, buf[:n]...) + } + if end := s.eventEnd(eventData, errors.Is(err, io.EOF)); end >= 0 { + s.lock.Lock() + s.buffer = append(s.buffer[:0], eventData[end:]...) + s.lock.Unlock() + return eventData[:end], nil } // Discard any partial frame at EOF: an event that ends before its @@ -157,11 +147,8 @@ func (s *HTTPServerStreamSseStreamImpl) readEvent(ctx context.Context) ([]byte, } } -// checkBuffer examines the internal buffer for a complete SSE event (delimited -// by double newlines). It returns two values: the event data (or all buffer -// contents if no complete event is found), and a boolean indicating whether a -// complete event was found. If a complete event is found, any remaining data -// after the event is kept in the buffer for the next call. +// checkBuffer returns the first complete event already read from the response. +// When no blank line is present, it returns a copy that readEvent can extend. func (s *HTTPServerStreamSseStreamImpl) checkBuffer() ([]byte, bool) { s.lock.Lock() defer s.lock.Unlock() @@ -171,25 +158,11 @@ func (s *HTTPServerStreamSseStreamImpl) checkBuffer() ([]byte, bool) { return nil, false } - // Look for double newline in buffer - for i := 0; i < len(s.buffer)-1; i++ { - if s.buffer[i] == '\n' && s.buffer[i+1] == '\n' { - // Found complete event. Copy it out: compacting the buffer - // below would otherwise overwrite the returned bytes, since - // both slices share the same backing array. - eventEnd := i + 2 // Include both newlines - eventData := make([]byte, eventEnd) - copy(eventData, s.buffer[:eventEnd]) - - // Save remaining data for next time - if eventEnd < len(s.buffer) { - s.buffer = append(s.buffer[:0], s.buffer[eventEnd:]...) - } else { - s.buffer = s.buffer[:0] - } - - return eventData, true - } + if end := s.eventEnd(s.buffer, false); end >= 0 { + eventData := make([]byte, end) + copy(eventData, s.buffer[:end]) + s.buffer = append(s.buffer[:0], s.buffer[end:]...) + return eventData, true } // No complete event found, return a copy of the buffer contents: the @@ -201,6 +174,54 @@ func (s *HTTPServerStreamSseStreamImpl) checkBuffer() ([]byte, bool) { return eventData, false } +// eventEnd returns the byte after the blank line that ends the first event. +// A CR at the end waits for the next byte unless the response has ended. +func (s *HTTPServerStreamSseStreamImpl) eventEnd(data []byte, atEOF bool) int { + lineStart := 0 + for i := 0; i < len(data); { + if data[i] != '\r' && data[i] != '\n' { + i++ + continue + } + lineEnd := i + 1 + if data[i] == '\r' { + if lineEnd == len(data) && !atEOF { + return -1 + } + if lineEnd < len(data) && data[lineEnd] == '\n' { + lineEnd++ + } + } + if i == lineStart { + return lineEnd + } + lineStart = lineEnd + i = lineEnd + } + return -1 +} + +// eventLines splits one complete event using every line ending allowed by SSE. +func (s *HTTPServerStreamSseStreamImpl) eventLines(data []byte) [][]byte { + var lines [][]byte + for len(data) > 0 { + lineEnd := 0 + for lineEnd < len(data) && data[lineEnd] != '\r' && data[lineEnd] != '\n' { + lineEnd++ + } + lines = append(lines, data[:lineEnd]) + if lineEnd == len(data) { + break + } + next := lineEnd + 1 + if data[lineEnd] == '\r' && next < len(data) && data[next] == '\n' { + next++ + } + data = data[next:] + } + return lines +} + // Close closes the SSE stream and releases any associated resources. func (s *HTTPServerStreamSseStreamImpl) Close() error { s.lock.Lock() @@ -213,39 +234,47 @@ func (s *HTTPServerStreamSseStreamImpl) Close() error { } // processEvent processes a raw SSE event into the expected type -func (s *HTTPServerStreamSseStreamImpl) processEvent(eventData []byte) (event *testhttpgrpc.HTTPServerStreamSseResult, err error) { - event = new(testhttpgrpc.HTTPServerStreamSseResult) +func (s *HTTPServerStreamSseStreamImpl) processEvent(eventData []byte) (event *testhttpgrpc.HTTPServerStreamSseResult, hasData bool, err error) { + var body HTTPServerStreamSseResponseBody var dataLines []string - for _, line := range bytes.Split(eventData, []byte("\n")) { + for _, line := range s.eventLines(eventData) { + if !s.started { + line = bytes.TrimPrefix(line, []byte{0xef, 0xbb, 0xbf}) + s.started = true + } if len(line) == 0 { continue } - if bytes.HasPrefix(line, []byte("data:")) { - dataLines = append(dataLines, s.trimHeader(line[len("data:"):])) + field, value, _ := bytes.Cut(line, []byte(":")) + if len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + if bytes.Equal(field, []byte("data")) { + dataLines = append(dataLines, string(value)) continue } } - if len(dataLines) > 0 { - dataContent := strings.Join(dataLines, "\n") - // Decode the event data into the result value returned by Recv. - respBody := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(dataContent))), - } - err = s.decoder(respBody).Decode(event) - if err != nil { - return - } + if len(dataLines) == 0 { + return event, false, nil } - return -} - -// trimHeader removes the optional space after an SSE field name. -func (s *HTTPServerStreamSseStreamImpl) trimHeader(data []byte) string { - if len(data) > 0 && data[0] == ' ' { - data = data[1:] + dataContent := strings.Join(dataLines, "\n") + // Decode the event data into the HTTP response type so required + // primitive fields retain their presence for validation. + respBody := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(dataContent))), + } + err = s.decoder(respBody).Decode(&body) + if err != nil { + return } - return string(data) + err = ValidateHTTPServerStreamSseResponseBody(&body) + if err != nil { + return event, false, goahttp.ErrValidationError("test-http-grpc", "http_server_stream_sse", err) + } + event = NewHTTPServerStreamSseResultOK(&body) + hasData = true + return } // MixedServerStreamClientStream is the interface for reading Server-Sent Events. @@ -264,6 +293,8 @@ type ( resp *http.Response decoder func(*http.Response) goahttp.Decoder buffer []byte // Buffer for unprocessed data + // started records whether the first stream line was read. + started bool lock sync.Mutex closed bool } @@ -292,25 +323,28 @@ func (s *MixedServerStreamStreamImpl) Recv() (*testhttpgrpc.MixedServerStreamRes // RecvWithContext reads and returns the next event from the SSE stream, respecting context cancellation. func (s *MixedServerStreamStreamImpl) RecvWithContext(ctx context.Context) (event *testhttpgrpc.MixedServerStreamResult, err error) { - var byts []byte - byts, err = s.readEvent(ctx) - if err != nil { - if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - // Clean up on EOF or context cancellation. io.EOF - // propagates to the caller to signal end of stream. - s.Close() + for { + var byts []byte + byts, err = s.readEvent(ctx) + if err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + // Clean up on EOF or context cancellation. io.EOF + // propagates to the caller to signal end of stream. + s.Close() + } + return + } + var hasData bool + event, hasData, err = s.processEvent(byts) + if err != nil || hasData { + return } - return } - return s.processEvent(byts) } // readEvent reads a single SSE event from the stream, respecting context -// cancellation. It first checks the internal buffer for a complete event -// (delimited by double newlines). If no complete event is found, it reads from -// the HTTP response body until it either finds an event boundary, reaches EOF, -// or encounters an error. Any data after the event boundary is saved in the -// buffer for the next call. +// cancellation. It accepts CR, LF, and CRLF line endings. A blank line ends an +// event; bytes after that line remain buffered for the next call. func (s *MixedServerStreamStreamImpl) readEvent(ctx context.Context) ([]byte, error) { const bufSize = 4096 // 4KB buffer size @@ -320,9 +354,7 @@ func (s *MixedServerStreamStreamImpl) readEvent(ctx context.Context) ([]byte, er return event, nil } - // Initialize with any data from buffer eventData := event - wasNewline := len(eventData) > 0 && eventData[len(eventData)-1] == '\n' buf := make([]byte, bufSize) // Read data in chunks until we find an event or hit EOF. A stream that @@ -353,27 +385,14 @@ func (s *MixedServerStreamStreamImpl) readEvent(ctx context.Context) ([]byte, er return nil, err } - // Process data if we got any if n > 0 { - // Look for event boundary in this chunk - for i := 0; i < n; i++ { - b := buf[i] - eventData = append(eventData, b) - - // Check for double newlines (event boundary) - if b == '\n' && wasNewline { - // Save any remaining data for next read - if i+1 < n { - s.lock.Lock() - s.buffer = append(s.buffer[:0], buf[i+1:n]...) - s.lock.Unlock() - } - return eventData, nil - } - - // Update newline tracking - wasNewline = (b == '\n') - } + eventData = append(eventData, buf[:n]...) + } + if end := s.eventEnd(eventData, errors.Is(err, io.EOF)); end >= 0 { + s.lock.Lock() + s.buffer = append(s.buffer[:0], eventData[end:]...) + s.lock.Unlock() + return eventData[:end], nil } // Discard any partial frame at EOF: an event that ends before its @@ -384,11 +403,8 @@ func (s *MixedServerStreamStreamImpl) readEvent(ctx context.Context) ([]byte, er } } -// checkBuffer examines the internal buffer for a complete SSE event (delimited -// by double newlines). It returns two values: the event data (or all buffer -// contents if no complete event is found), and a boolean indicating whether a -// complete event was found. If a complete event is found, any remaining data -// after the event is kept in the buffer for the next call. +// checkBuffer returns the first complete event already read from the response. +// When no blank line is present, it returns a copy that readEvent can extend. func (s *MixedServerStreamStreamImpl) checkBuffer() ([]byte, bool) { s.lock.Lock() defer s.lock.Unlock() @@ -398,25 +414,11 @@ func (s *MixedServerStreamStreamImpl) checkBuffer() ([]byte, bool) { return nil, false } - // Look for double newline in buffer - for i := 0; i < len(s.buffer)-1; i++ { - if s.buffer[i] == '\n' && s.buffer[i+1] == '\n' { - // Found complete event. Copy it out: compacting the buffer - // below would otherwise overwrite the returned bytes, since - // both slices share the same backing array. - eventEnd := i + 2 // Include both newlines - eventData := make([]byte, eventEnd) - copy(eventData, s.buffer[:eventEnd]) - - // Save remaining data for next time - if eventEnd < len(s.buffer) { - s.buffer = append(s.buffer[:0], s.buffer[eventEnd:]...) - } else { - s.buffer = s.buffer[:0] - } - - return eventData, true - } + if end := s.eventEnd(s.buffer, false); end >= 0 { + eventData := make([]byte, end) + copy(eventData, s.buffer[:end]) + s.buffer = append(s.buffer[:0], s.buffer[end:]...) + return eventData, true } // No complete event found, return a copy of the buffer contents: the @@ -428,6 +430,54 @@ func (s *MixedServerStreamStreamImpl) checkBuffer() ([]byte, bool) { return eventData, false } +// eventEnd returns the byte after the blank line that ends the first event. +// A CR at the end waits for the next byte unless the response has ended. +func (s *MixedServerStreamStreamImpl) eventEnd(data []byte, atEOF bool) int { + lineStart := 0 + for i := 0; i < len(data); { + if data[i] != '\r' && data[i] != '\n' { + i++ + continue + } + lineEnd := i + 1 + if data[i] == '\r' { + if lineEnd == len(data) && !atEOF { + return -1 + } + if lineEnd < len(data) && data[lineEnd] == '\n' { + lineEnd++ + } + } + if i == lineStart { + return lineEnd + } + lineStart = lineEnd + i = lineEnd + } + return -1 +} + +// eventLines splits one complete event using every line ending allowed by SSE. +func (s *MixedServerStreamStreamImpl) eventLines(data []byte) [][]byte { + var lines [][]byte + for len(data) > 0 { + lineEnd := 0 + for lineEnd < len(data) && data[lineEnd] != '\r' && data[lineEnd] != '\n' { + lineEnd++ + } + lines = append(lines, data[:lineEnd]) + if lineEnd == len(data) { + break + } + next := lineEnd + 1 + if data[lineEnd] == '\r' && next < len(data) && data[next] == '\n' { + next++ + } + data = data[next:] + } + return lines +} + // Close closes the SSE stream and releases any associated resources. func (s *MixedServerStreamStreamImpl) Close() error { s.lock.Lock() @@ -440,37 +490,45 @@ func (s *MixedServerStreamStreamImpl) Close() error { } // processEvent processes a raw SSE event into the expected type -func (s *MixedServerStreamStreamImpl) processEvent(eventData []byte) (event *testhttpgrpc.MixedServerStreamResult, err error) { - event = new(testhttpgrpc.MixedServerStreamResult) +func (s *MixedServerStreamStreamImpl) processEvent(eventData []byte) (event *testhttpgrpc.MixedServerStreamResult, hasData bool, err error) { + var body MixedServerStreamResponseBody var dataLines []string - for _, line := range bytes.Split(eventData, []byte("\n")) { + for _, line := range s.eventLines(eventData) { + if !s.started { + line = bytes.TrimPrefix(line, []byte{0xef, 0xbb, 0xbf}) + s.started = true + } if len(line) == 0 { continue } - if bytes.HasPrefix(line, []byte("data:")) { - dataLines = append(dataLines, s.trimHeader(line[len("data:"):])) + field, value, _ := bytes.Cut(line, []byte(":")) + if len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + if bytes.Equal(field, []byte("data")) { + dataLines = append(dataLines, string(value)) continue } } - if len(dataLines) > 0 { - dataContent := strings.Join(dataLines, "\n") - // Decode the event data into the result value returned by Recv. - respBody := &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(dataContent))), - } - err = s.decoder(respBody).Decode(event) - if err != nil { - return - } + if len(dataLines) == 0 { + return event, false, nil } - return -} - -// trimHeader removes the optional space after an SSE field name. -func (s *MixedServerStreamStreamImpl) trimHeader(data []byte) string { - if len(data) > 0 && data[0] == ' ' { - data = data[1:] + dataContent := strings.Join(dataLines, "\n") + // Decode the event data into the HTTP response type so required + // primitive fields retain their presence for validation. + respBody := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(dataContent))), + } + err = s.decoder(respBody).Decode(&body) + if err != nil { + return } - return string(data) + err = ValidateMixedServerStreamResponseBody(&body) + if err != nil { + return event, false, goahttp.ErrValidationError("test-http-grpc", "mixed_server_stream", err) + } + event = NewMixedServerStreamResultOK(&body) + hasData = true + return } diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go index 682878e58..ddb49afc4 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/server.go @@ -272,6 +272,10 @@ func NewHTTPServerStreamSseHandler( }, } _, err = endpoint(ctx, v) + if err == nil { + stream := v.Stream.(*HTTPServerStreamSseServerStream) + err = stream.finish() + } if err != nil { stream := v.Stream.(*HTTPServerStreamSseServerStream) if stream.attempted { @@ -580,6 +584,10 @@ func NewMixedServerStreamHandler( }, } _, err = endpoint(ctx, v) + if err == nil { + stream := v.Stream.(*MixedServerStreamServerStream) + err = stream.finish() + } if err != nil { stream := v.Stream.(*MixedServerStreamServerStream) if stream.attempted { diff --git a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go index 2dd668c3f..b7b29a7bb 100644 --- a/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go +++ b/testing/examples/httpgrpc/gen/http/test_http_grpc/server/sse.go @@ -32,6 +32,34 @@ type HTTPServerStreamSseServerStream struct { attempted bool } +// start writes the headers that identify a successful SSE response. +func (s *HTTPServerStreamSseServerStream) start() { + s.once.Do(func() { + header := s.w.Header() + if header.Get("Content-Type") == "" { + header.Set("Content-Type", "text/event-stream") + } + if header.Get("Cache-Control") == "" { + header.Set("Cache-Control", "no-cache") + } + if header.Get("Connection") == "" { + header.Set("Connection", "keep-alive") + } + s.w.WriteHeader(http.StatusOK) + s.attempted = true + }) +} + +// finish writes an empty successful SSE response when the service sent no +// events. +func (s *HTTPServerStreamSseServerStream) finish() error { + if s.attempted { + return nil + } + s.start() + return nil +} + // Send Send streams instances of "testhttpgrpc.HTTPServerStreamSseResult" to // the "http_server_stream_sse" endpoint SSE connection. func (s *HTTPServerStreamSseServerStream) Send(v *testhttpgrpc.HTTPServerStreamSseResult) error { @@ -52,22 +80,27 @@ func (s *HTTPServerStreamSseServerStream) SendWithContext(ctx context.Context, v return err } data = string(byts) - s.once.Do(func() { - header := s.w.Header() - if header.Get("Content-Type") == "" { - header.Set("Content-Type", "text/event-stream") + s.start() + + remaining := data + for { + lineEnd := 0 + for lineEnd < len(remaining) && remaining[lineEnd] != '\r' && remaining[lineEnd] != '\n' { + lineEnd++ } - if header.Get("Cache-Control") == "" { - header.Set("Cache-Control", "no-cache") + if _, err := fmt.Fprintf(s.w, "data: %s\n", remaining[:lineEnd]); err != nil { + return err } - if header.Get("Connection") == "" { - header.Set("Connection", "keep-alive") + if lineEnd == len(remaining) { + break } - s.w.WriteHeader(http.StatusOK) - s.attempted = true - }) - - if _, err := fmt.Fprintf(s.w, "data: %s\n\n", data); err != nil { + next := lineEnd + 1 + if remaining[lineEnd] == '\r' && next < len(remaining) && remaining[next] == '\n' { + next++ + } + remaining = remaining[next:] + } + if _, err := fmt.Fprintln(s.w); err != nil { return err } @@ -97,6 +130,34 @@ type MixedServerStreamServerStream struct { attempted bool } +// start writes the headers that identify a successful SSE response. +func (s *MixedServerStreamServerStream) start() { + s.once.Do(func() { + header := s.w.Header() + if header.Get("Content-Type") == "" { + header.Set("Content-Type", "text/event-stream") + } + if header.Get("Cache-Control") == "" { + header.Set("Cache-Control", "no-cache") + } + if header.Get("Connection") == "" { + header.Set("Connection", "keep-alive") + } + s.w.WriteHeader(http.StatusOK) + s.attempted = true + }) +} + +// finish writes an empty successful SSE response when the service sent no +// events. +func (s *MixedServerStreamServerStream) finish() error { + if s.attempted { + return nil + } + s.start() + return nil +} + // Send Send streams instances of "testhttpgrpc.MixedServerStreamResult" to the // "mixed_server_stream" endpoint SSE connection. func (s *MixedServerStreamServerStream) Send(v *testhttpgrpc.MixedServerStreamResult) error { @@ -117,22 +178,27 @@ func (s *MixedServerStreamServerStream) SendWithContext(ctx context.Context, v * return err } data = string(byts) - s.once.Do(func() { - header := s.w.Header() - if header.Get("Content-Type") == "" { - header.Set("Content-Type", "text/event-stream") + s.start() + + remaining := data + for { + lineEnd := 0 + for lineEnd < len(remaining) && remaining[lineEnd] != '\r' && remaining[lineEnd] != '\n' { + lineEnd++ } - if header.Get("Cache-Control") == "" { - header.Set("Cache-Control", "no-cache") + if _, err := fmt.Fprintf(s.w, "data: %s\n", remaining[:lineEnd]); err != nil { + return err } - if header.Get("Connection") == "" { - header.Set("Connection", "keep-alive") + if lineEnd == len(remaining) { + break } - s.w.WriteHeader(http.StatusOK) - s.attempted = true - }) - - if _, err := fmt.Fprintf(s.w, "data: %s\n\n", data); err != nil { + next := lineEnd + 1 + if remaining[lineEnd] == '\r' && next < len(remaining) && remaining[next] == '\n' { + next++ + } + remaining = remaining[next:] + } + if _, err := fmt.Fprintln(s.w); err != nil { return err } diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 2f4ec16c9..d56039f28 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 + goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 2cea50a3a..d0f0846a3 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go b/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go index 73ef0a28b..2be5c24f1 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/cli/test_jsonrpc/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -47,11 +66,14 @@ func ParseEndpoint( testJsonrpcFlags = flag.NewFlagSet("test-jsonrpc", flag.ContinueOnError) testJsonrpcJsonrpcNoStreamFlags = flag.NewFlagSet("jsonrpc-no-stream", flag.ExitOnError) - testJsonrpcJsonrpcNoStreamBodyFlag = testJsonrpcJsonrpcNoStreamFlags.String("body", "REQUIRED", "") + testJsonrpcJsonrpcNoStreamBodyFlag = new(cliStringFlag) testJsonrpcJsonrpcNoStreamErrorFlags = flag.NewFlagSet("jsonrpc-no-stream-error", flag.ExitOnError) - testJsonrpcJsonrpcNoStreamErrorBodyFlag = testJsonrpcJsonrpcNoStreamErrorFlags.String("body", "REQUIRED", "") + testJsonrpcJsonrpcNoStreamErrorBodyFlag = new(cliStringFlag) ) + testJsonrpcJsonrpcNoStreamFlags.Var(testJsonrpcJsonrpcNoStreamBodyFlag, "body", "") + testJsonrpcJsonrpcNoStreamErrorFlags.Var(testJsonrpcJsonrpcNoStreamErrorBodyFlag, "body", "") + testJsonrpcFlags.Usage = testJsonrpcUsage testJsonrpcJsonrpcNoStreamFlags.Usage = testJsonrpcJsonrpcNoStreamUsage testJsonrpcJsonrpcNoStreamErrorFlags.Usage = testJsonrpcJsonrpcNoStreamErrorUsage @@ -123,10 +145,10 @@ func ParseEndpoint( switch epn { case "jsonrpc-no-stream": endpoint = c.JsonrpcNoStream() - data, err = testjsonrpcc.BuildJsonrpcNoStreamPayload(*testJsonrpcJsonrpcNoStreamBodyFlag) + data, err = testjsonrpcc.BuildJsonrpcNoStreamPayload(testJsonrpcJsonrpcNoStreamBodyFlag.value) case "jsonrpc-no-stream-error": endpoint = c.JsonrpcNoStreamError() - data, err = testjsonrpcc.BuildJsonrpcNoStreamErrorPayload(*testJsonrpcJsonrpcNoStreamErrorBodyFlag) + data, err = testjsonrpcc.BuildJsonrpcNoStreamErrorPayload(testJsonrpcJsonrpcNoStreamErrorBodyFlag.value) } } } diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go index 292d7249c..30f338294 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/cli.go @@ -17,11 +17,14 @@ import ( // BuildJsonrpcNoStreamPayload builds the payload for the test-jsonrpc // jsonrpc_no_stream endpoint from CLI flags. -func BuildJsonrpcNoStreamPayload(testJsonrpcJsonrpcNoStreamBody string) (*testjsonrpc.JsonrpcNoStreamPayload, error) { +func BuildJsonrpcNoStreamPayload(testJsonrpcJsonrpcNoStreamBody *string) (*testjsonrpc.JsonrpcNoStreamPayload, error) { var err error var body JsonrpcNoStreamRequestBody { - err = json.Unmarshal([]byte(testJsonrpcJsonrpcNoStreamBody), &body) + if testJsonrpcJsonrpcNoStreamBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*testJsonrpcJsonrpcNoStreamBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Enim eligendi ut.\"\n }'") } @@ -35,11 +38,14 @@ func BuildJsonrpcNoStreamPayload(testJsonrpcJsonrpcNoStreamBody string) (*testjs // BuildJsonrpcNoStreamErrorPayload builds the payload for the test-jsonrpc // jsonrpc_no_stream_error endpoint from CLI flags. -func BuildJsonrpcNoStreamErrorPayload(testJsonrpcJsonrpcNoStreamErrorBody string) (*testjsonrpc.JsonrpcNoStreamErrorPayload, error) { +func BuildJsonrpcNoStreamErrorPayload(testJsonrpcJsonrpcNoStreamErrorBody *string) (*testjsonrpc.JsonrpcNoStreamErrorPayload, error) { var err error var body JsonrpcNoStreamErrorRequestBody { - err = json.Unmarshal([]byte(testJsonrpcJsonrpcNoStreamErrorBody), &body) + if testJsonrpcJsonrpcNoStreamErrorBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*testJsonrpcJsonrpcNoStreamErrorBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"msg\": \"Voluptatem fuga.\"\n }'") } diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go index 1dfa42306..a1e4b4b0f 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/client.go @@ -68,14 +68,15 @@ func (c *Client) JsonrpcNoStream() goa.Endpoint { if err != nil { return nil, err } - if err := encodeRequest(req, v); err != nil { + requestID, err := encodeRequest(req, v) + if err != nil { return nil, err } resp, err := c.Doer.Do(req) if err != nil { return nil, goahttp.ErrRequestError("test-jsonrpc", "jsonrpc_no_stream", err) } - return decodeResponse(resp) + return decodeResponse(resp, requestID) } } @@ -91,13 +92,14 @@ func (c *Client) JsonrpcNoStreamError() goa.Endpoint { if err != nil { return nil, err } - if err := encodeRequest(req, v); err != nil { + requestID, err := encodeRequest(req, v) + if err != nil { return nil, err } resp, err := c.Doer.Do(req) if err != nil { return nil, goahttp.ErrRequestError("test-jsonrpc", "jsonrpc_no_stream_error", err) } - return decodeResponse(resp) + return decodeResponse(resp, requestID) } } diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go index 5df410cc6..61a8b0307 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/client/encode_decode.go @@ -38,12 +38,13 @@ func (c *Client) BuildJsonrpcNoStreamRequest(ctx context.Context, v any) (*http. } // EncodeJsonrpcNoStreamRequest returns an encoder for requests sent to the -// test-jsonrpc jsonrpc_no_stream server. -func EncodeJsonrpcNoStreamRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { - return func(req *http.Request, v any) error { +// test-jsonrpc service jsonrpc_no_stream JSON-RPC method. The encoder returns +// the request ID written into the JSON-RPC message. +func EncodeJsonrpcNoStreamRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) (string, error) { + return func(req *http.Request, v any) (string, error) { p, ok := v.(*testjsonrpc.JsonrpcNoStreamPayload) if !ok { - return goahttp.ErrInvalidType("test-jsonrpc", "jsonrpc_no_stream", "*testjsonrpc.JsonrpcNoStreamPayload", v) + return "", goahttp.ErrInvalidType("test-jsonrpc", "jsonrpc_no_stream", "*testjsonrpc.JsonrpcNoStreamPayload", v) } b := NewJsonrpcNoStreamRequestBody(p) body := &jsonrpc.Request{ @@ -51,21 +52,21 @@ func EncodeJsonrpcNoStreamRequest(encoder func(*http.Request) goahttp.Encoder) f Method: "jsonrpc_no_stream", Params: b, } - // No ID field in payload - always send as a request with generated ID - id := uuid.New().String() - body.ID = id + requestID := uuid.New().String() + body.ID = requestID if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("test-jsonrpc", "jsonrpc_no_stream", err) + return "", goahttp.ErrEncodingError("test-jsonrpc", "jsonrpc_no_stream", err) } - return nil + return requestID, nil } } // DecodeJsonrpcNoStreamResponse returns a decoder for responses returned by -// the test-jsonrpc service jsonrpc_no_stream JSON-RPC method. restoreBody -// controls whether the response body should be restored after having been read. -func DecodeJsonrpcNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (result any, decodeErr error) { +// the test-jsonrpc service jsonrpc_no_stream JSON-RPC method. The decoder +// rejects responses that do not repeat requestID. restoreBody controls whether +// the response body should be restored after having been read. +func DecodeJsonrpcNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response, string) (any, error) { + return func(resp *http.Response, requestID string) (result any, decodeErr error) { responseBody := resp.Body if restoreBody { b, readErr := io.ReadAll(responseBody) @@ -97,12 +98,12 @@ func DecodeJsonrpcNoStreamResponse(decoder func(*http.Response) goahttp.Decoder, if err := decoder(resp).Decode(&jresp); err != nil { return nil, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream", err) } + if err := jresp.Validate(requestID); err != nil { + return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream", resp.StatusCode, err.Error()) + } if jresp.Error != nil { - switch jresp.Error.Code { - default: - return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream", resp.StatusCode, string(jresp.Error.Data)) - } + return nil, jresp.Error } resp.Body = io.NopCloser(bytes.NewBuffer(jresp.Result)) var ( @@ -139,12 +140,13 @@ func (c *Client) BuildJsonrpcNoStreamErrorRequest(ctx context.Context, v any) (* } // EncodeJsonrpcNoStreamErrorRequest returns an encoder for requests sent to -// the test-jsonrpc jsonrpc_no_stream_error server. -func EncodeJsonrpcNoStreamErrorRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { - return func(req *http.Request, v any) error { +// the test-jsonrpc service jsonrpc_no_stream_error JSON-RPC method. The +// encoder returns the request ID written into the JSON-RPC message. +func EncodeJsonrpcNoStreamErrorRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) (string, error) { + return func(req *http.Request, v any) (string, error) { p, ok := v.(*testjsonrpc.JsonrpcNoStreamErrorPayload) if !ok { - return goahttp.ErrInvalidType("test-jsonrpc", "jsonrpc_no_stream_error", "*testjsonrpc.JsonrpcNoStreamErrorPayload", v) + return "", goahttp.ErrInvalidType("test-jsonrpc", "jsonrpc_no_stream_error", "*testjsonrpc.JsonrpcNoStreamErrorPayload", v) } b := NewJsonrpcNoStreamErrorRequestBody(p) body := &jsonrpc.Request{ @@ -152,22 +154,21 @@ func EncodeJsonrpcNoStreamErrorRequest(encoder func(*http.Request) goahttp.Encod Method: "jsonrpc_no_stream_error", Params: b, } - // No ID field in payload - always send as a request with generated ID - id := uuid.New().String() - body.ID = id + requestID := uuid.New().String() + body.ID = requestID if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("test-jsonrpc", "jsonrpc_no_stream_error", err) + return "", goahttp.ErrEncodingError("test-jsonrpc", "jsonrpc_no_stream_error", err) } - return nil + return requestID, nil } } // DecodeJsonrpcNoStreamErrorResponse returns a decoder for responses returned -// by the test-jsonrpc service jsonrpc_no_stream_error JSON-RPC method. -// restoreBody controls whether the response body should be restored after -// having been read. -func DecodeJsonrpcNoStreamErrorResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { - return func(resp *http.Response) (result any, decodeErr error) { +// by the test-jsonrpc service jsonrpc_no_stream_error JSON-RPC method. The +// decoder rejects responses that do not repeat requestID. restoreBody controls +// whether the response body should be restored after having been read. +func DecodeJsonrpcNoStreamErrorResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response, string) (any, error) { + return func(resp *http.Response, requestID string) (result any, decodeErr error) { responseBody := resp.Body if restoreBody { b, readErr := io.ReadAll(responseBody) @@ -199,12 +200,12 @@ func DecodeJsonrpcNoStreamErrorResponse(decoder func(*http.Response) goahttp.Dec if err := decoder(resp).Decode(&jresp); err != nil { return nil, goahttp.ErrDecodingError("test-jsonrpc", "jsonrpc_no_stream_error", err) } + if err := jresp.Validate(requestID); err != nil { + return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream_error", resp.StatusCode, err.Error()) + } if jresp.Error != nil { - switch jresp.Error.Code { - default: - return nil, goahttp.ErrInvalidResponse("test-jsonrpc", "jsonrpc_no_stream_error", resp.StatusCode, string(jresp.Error.Data)) - } + return nil, jresp.Error } resp.Body = io.NopCloser(bytes.NewBuffer(jresp.Result)) var ( diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go index 031626713..86c05fe35 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go @@ -22,6 +22,33 @@ import ( testjsonrpc "goa.design/plugins/v3/testing/examples/jsonrpc/gen/test_jsonrpc" ) +type ( + + // noOutputResponseWriter accepts notification output without storing or + // sending it. + noOutputResponseWriter struct { + header http.Header + } +) + +// Header returns the private headers written while a notification is encoded. +func (w *noOutputResponseWriter) Header() http.Header { + return w.header +} + +// Write accepts notification bytes without storing or sending them. +func (w *noOutputResponseWriter) Write(data []byte) (int, error) { + return len(data), nil +} + +// WriteHeader accepts a notification status without sending it. +func (w *noOutputResponseWriter) WriteHeader(int) { +} + +// Flush completes successfully because notification bytes are not sent. +func (w *noOutputResponseWriter) Flush() { +} + // Server handles JSON-RPC requests for the test-jsonrpc service. type Server struct { http.Handler @@ -181,7 +208,7 @@ func (s *Server) processRequest(ctx context.Context, r *http.Request, req *jsonr return } - if req.Method == "" { + if !req.HasMethod { s.encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidRequest, "Missing method field", nil) return } @@ -261,13 +288,18 @@ func NewJsonrpcNoStreamHandler( return func(ctx context.Context, r *http.Request, req *jsonrpc.RawRequest, w http.ResponseWriter) error { ctx = context.WithValue(ctx, goa.MethodKey, "jsonrpc_no_stream") ctx = context.WithValue(ctx, goa.ServiceKey, "test-jsonrpc") + outputWriter := w + if !req.HasID { + // A notification runs the same service and error reporting code without + // sending a response. + outputWriter = &noOutputResponseWriter{header: make(http.Header)} + } params, err := decodeParams(r, req) if err != nil { if req.HasID { encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) } else { - // A notification receives no JSON-RPC response, so pass the decode error to the configured error handler. - errhandler(ctx, w, fmt.Errorf("failed to decode parameters: %w", err)) + errhandler(ctx, outputWriter, fmt.Errorf("failed to decode parameters: %w", err)) } return nil } @@ -276,8 +308,7 @@ func NewJsonrpcNoStreamHandler( if req.HasID { encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) } else { - // A notification receives no JSON-RPC response, so pass the service error to the configured error handler. - errhandler(ctx, w, fmt.Errorf("endpoint error: %w", err)) + errhandler(ctx, outputWriter, fmt.Errorf("endpoint error: %w", err)) } return nil } @@ -286,15 +317,10 @@ func NewJsonrpcNoStreamHandler( return nil } - // For methods with results, determine the ID to use for the response - var id any - // No ID field in result - use request ID - id = req.ID - - // Send response with the result + // The response repeats the exact request ID. // Build the response body with the fields and JSON names declared by the service. body := NewJsonrpcNoStreamResponseBody(res.(*testjsonrpc.JsonrpcNoStreamResult)) - response := jsonrpc.MakeSuccessResponse(id, body) + response := jsonrpc.MakeSuccessResponse(req.ID, body) if err := encoder(ctx, w).Encode(response); err != nil { errhandler(ctx, w, fmt.Errorf("failed to encode JSON-RPC response: %w", err)) } @@ -315,13 +341,18 @@ func NewJsonrpcNoStreamErrorHandler( return func(ctx context.Context, r *http.Request, req *jsonrpc.RawRequest, w http.ResponseWriter) error { ctx = context.WithValue(ctx, goa.MethodKey, "jsonrpc_no_stream_error") ctx = context.WithValue(ctx, goa.ServiceKey, "test-jsonrpc") + outputWriter := w + if !req.HasID { + // A notification runs the same service and error reporting code without + // sending a response. + outputWriter = &noOutputResponseWriter{header: make(http.Header)} + } params, err := decodeParams(r, req) if err != nil { if req.HasID { encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) } else { - // A notification receives no JSON-RPC response, so pass the decode error to the configured error handler. - errhandler(ctx, w, fmt.Errorf("failed to decode parameters: %w", err)) + errhandler(ctx, outputWriter, fmt.Errorf("failed to decode parameters: %w", err)) } return nil } @@ -330,8 +361,7 @@ func NewJsonrpcNoStreamErrorHandler( if req.HasID { encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) } else { - // A notification receives no JSON-RPC response, so pass the service error to the configured error handler. - errhandler(ctx, w, fmt.Errorf("endpoint error: %w", err)) + errhandler(ctx, outputWriter, fmt.Errorf("endpoint error: %w", err)) } return nil } @@ -340,15 +370,10 @@ func NewJsonrpcNoStreamErrorHandler( return nil } - // For methods with results, determine the ID to use for the response - var id any - // No ID field in result - use request ID - id = req.ID - - // Send response with the result + // The response repeats the exact request ID. // Build the response body with the fields and JSON names declared by the service. body := NewJsonrpcNoStreamErrorResponseBody(res.(*testjsonrpc.JsonrpcNoStreamErrorResult)) - response := jsonrpc.MakeSuccessResponse(id, body) + response := jsonrpc.MakeSuccessResponse(req.ID, body) if err := encoder(ctx, w).Encode(response); err != nil { errhandler(ctx, w, fmt.Errorf("failed to encode JSON-RPC response: %w", err)) } diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 8669949ca..9f49ea7c1 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 + goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 2cea50a3a..d0f0846a3 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8 h1:CjoyylexZ1s2rePbKaePU2Q+VwWwYjvhXXwovd6x4Hc= -goa.design/goa/v3 v3.30.1-0.20260825034953-833f95e1fdc8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= +goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/tscap/example/gen/http/cli/tscap_example/cli.go b/tscap/example/gen/http/cli/tscap_example/cli.go index 6d94604fd..50c4acc64 100644 --- a/tscap/example/gen/http/cli/tscap_example/cli.go +++ b/tscap/example/gen/http/cli/tscap_example/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -49,13 +68,16 @@ func ParseEndpoint( tscapListFlags = flag.NewFlagSet("list", flag.ExitOnError) tscapCreateFlags = flag.NewFlagSet("create", flag.ExitOnError) - tscapCreateBodyFlag = tscapCreateFlags.String("body", "REQUIRED", "") + tscapCreateBodyFlag = new(cliStringFlag) tscapAdminFlags = flag.NewFlagSet("admin", flag.ExitOnError) - tscapAdminIDFlag = tscapAdminFlags.String("id", "REQUIRED", "Item ID") + tscapAdminIDFlag = new(cliStringFlag) tscapHealthFlags = flag.NewFlagSet("health", flag.ExitOnError) ) + tscapCreateFlags.Var(tscapCreateBodyFlag, "body", "") + tscapAdminFlags.Var(tscapAdminIDFlag, "id", "Item ID") + tscapFlags.Usage = tscapUsage tscapListFlags.Usage = tscapListUsage tscapCreateFlags.Usage = tscapCreateUsage @@ -137,10 +159,10 @@ func ParseEndpoint( endpoint = c.List() case "create": endpoint = c.Create() - data, err = tscapc.BuildCreatePayload(*tscapCreateBodyFlag) + data, err = tscapc.BuildCreatePayload(tscapCreateBodyFlag.value) case "admin": endpoint = c.Admin() - data, err = tscapc.BuildAdminPayload(*tscapAdminIDFlag) + data, err = tscapc.BuildAdminPayload(tscapAdminIDFlag.value) case "health": endpoint = c.Health() } diff --git a/tscap/example/gen/http/tscap/client/cli.go b/tscap/example/gen/http/tscap/client/cli.go index 1b3a259e9..1d2b57537 100644 --- a/tscap/example/gen/http/tscap/client/cli.go +++ b/tscap/example/gen/http/tscap/client/cli.go @@ -17,11 +17,14 @@ import ( // BuildCreatePayload builds the payload for the tscap create endpoint from CLI // flags. -func BuildCreatePayload(tscapCreateBody string) (*tscap.CreatePayload, error) { +func BuildCreatePayload(tscapCreateBody *string) (*tscap.CreatePayload, error) { var err error var body CreateRequestBody { - err = json.Unmarshal([]byte(tscapCreateBody), &body) + if tscapCreateBody == nil { + return nil, fmt.Errorf("missing required flag --body") + } + err = json.Unmarshal([]byte(*tscapCreateBody), &body) if err != nil { return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"name\": \"Quo omnis quia ipsum.\"\n }'") } @@ -35,10 +38,13 @@ func BuildCreatePayload(tscapCreateBody string) (*tscap.CreatePayload, error) { // BuildAdminPayload builds the payload for the tscap admin endpoint from CLI // flags. -func BuildAdminPayload(tscapAdminID string) (*tscap.AdminPayload, error) { +func BuildAdminPayload(tscapAdminID *string) (*tscap.AdminPayload, error) { var id string { - id = tscapAdminID + if tscapAdminID == nil { + return nil, fmt.Errorf("missing required flag --id") + } + id = *tscapAdminID } v := &tscap.AdminPayload{} v.ID = id diff --git a/zaplogger/examples/calc/gen/http/calc/client/cli.go b/zaplogger/examples/calc/gen/http/calc/client/cli.go index 72539ff27..03c14823a 100644 --- a/zaplogger/examples/calc/gen/http/calc/client/cli.go +++ b/zaplogger/examples/calc/gen/http/calc/client/cli.go @@ -16,12 +16,15 @@ import ( ) // BuildAddPayload builds the payload for the calc add endpoint from CLI flags. -func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) { +func BuildAddPayload(calcAddA *string, calcAddB *string) (*calc.AddPayload, error) { var err error var a int { + if calcAddA == nil { + return nil, fmt.Errorf("missing required flag --a") + } var v int64 - v, err = strconv.ParseInt(calcAddA, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddA, 10, strconv.IntSize) a = int(v) if err != nil { return nil, fmt.Errorf("invalid value for a, must be INT") @@ -29,8 +32,11 @@ func BuildAddPayload(calcAddA string, calcAddB string) (*calc.AddPayload, error) } var b int { + if calcAddB == nil { + return nil, fmt.Errorf("missing required flag --b") + } var v int64 - v, err = strconv.ParseInt(calcAddB, 10, strconv.IntSize) + v, err = strconv.ParseInt(*calcAddB, 10, strconv.IntSize) b = int(v) if err != nil { return nil, fmt.Errorf("invalid value for b, must be INT") diff --git a/zaplogger/examples/calc/gen/http/cli/calc/cli.go b/zaplogger/examples/calc/gen/http/cli/calc/cli.go index 422af126d..51a693b2f 100644 --- a/zaplogger/examples/calc/gen/http/cli/calc/cli.go +++ b/zaplogger/examples/calc/gen/http/cli/calc/cli.go @@ -34,6 +34,25 @@ func UsageExamples() string { "" } +// cliStringFlag keeps an omitted command-line flag distinct from an explicitly empty flag. +type cliStringFlag struct { + value *string +} + +// String returns the flag text shown by the standard flag package. +func (f *cliStringFlag) String() string { + if f.value == nil { + return "" + } + return *f.value +} + +// Set records that the user supplied the flag, even when value is empty. +func (f *cliStringFlag) Set(value string) error { + f.value = &value + return nil +} + // ParseEndpoint returns the endpoint and payload as specified on the command // line. func ParseEndpoint( @@ -47,9 +66,12 @@ func ParseEndpoint( calcFlags = flag.NewFlagSet("calc", flag.ContinueOnError) calcAddFlags = flag.NewFlagSet("add", flag.ExitOnError) - calcAddAFlag = calcAddFlags.String("a", "REQUIRED", "Left operand") - calcAddBFlag = calcAddFlags.String("b", "REQUIRED", "Right operand") + calcAddAFlag = new(cliStringFlag) + calcAddBFlag = new(cliStringFlag) ) + calcAddFlags.Var(calcAddAFlag, "a", "Left operand") + calcAddFlags.Var(calcAddBFlag, "b", "Right operand") + calcFlags.Usage = calcUsage calcAddFlags.Usage = calcAddUsage @@ -117,7 +139,7 @@ func ParseEndpoint( switch epn { case "add": endpoint = c.Add() - data, err = calcc.BuildAddPayload(*calcAddAFlag, *calcAddBFlag) + data, err = calcc.BuildAddPayload(calcAddAFlag.value, calcAddBFlag.value) } } } From b63938e6e766a5c345dffde4c32aed89b4cc5d57 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Tue, 25 Aug 2026 05:43:07 -0700 Subject: [PATCH 06/30] Pin plugins to final Goa generation plan --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 46cbae9a5..33078f7df 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 + goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 3755b0349..553663bc0 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 6f1d5e1a2..d90425a10 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 + goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index d0f0846a3..cd0d5f3ad 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index d56039f28..6e4e090e5 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 + goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index d0f0846a3..cd0d5f3ad 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 9f49ea7c1..374bada7b 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 + goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index d0f0846a3..cd0d5f3ad 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8 h1:MwRPnes61LS7Sa+TLAK/7zCCMZ3gmlcusA06NkuF+F0= -goa.design/goa/v3 v3.30.1-0.20260825121239-93fbcbe975c8/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= +goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 430d3dc8db7546c00b5bdc928d01d06f79dd2d0c Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Tue, 25 Aug 2026 11:12:32 -0700 Subject: [PATCH 07/30] chore: pin plugins to final Goa generator --- go.mod | 2 +- go.sum | 4 +- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 +- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 +- .../gen/jsonrpc/test_jsonrpc/server/server.go | 68 ++++++++----------- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 +- 9 files changed, 39 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 33078f7df..62db01ad9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 + goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 553663bc0..97d5fbcb0 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index d90425a10..f7d3edab1 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 + goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index cd0d5f3ad..7f8101d98 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 6e4e090e5..899d33142 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 + goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index cd0d5f3ad..7f8101d98 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go index 86c05fe35..f0ae1c7ec 100644 --- a/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go +++ b/testing/examples/jsonrpc/gen/jsonrpc/test_jsonrpc/server/server.go @@ -23,7 +23,6 @@ import ( ) type ( - // noOutputResponseWriter accepts notification output without storing or // sending it. noOutputResponseWriter struct { @@ -94,6 +93,13 @@ func New( // Service returns the name of the service served. func (s *Server) Service() string { return "test-jsonrpc" } +// reportRejectedNotification reports a notification sent to a request-only +// method without allowing the error handler to write a response. +func (s *Server) reportRejectedNotification(ctx context.Context, req *jsonrpc.RawRequest) { + outputWriter := &noOutputResponseWriter{header: make(http.Header)} + s.errhandler(ctx, outputWriter, fmt.Errorf("JSON-RPC notification cannot call request method %q", req.Method)) +} + // Use wraps the server handlers with the given middleware. func (s *Server) Use(m func(http.Handler) http.Handler) { s.Handler = m(s.Handler) @@ -215,10 +221,26 @@ func (s *Server) processRequest(ctx context.Context, r *http.Request, req *jsonr switch req.Method { case "jsonrpc_no_stream": + if !req.HasID { + s.reportRejectedNotification(ctx, req) + return + } + if req.ID == nil { + s.encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidRequest, "Invalid request", nil) + return + } if err := s.JsonrpcNoStream(ctx, r, req, w); err != nil { s.errhandler(ctx, w, fmt.Errorf("handler error for %s: %w", "jsonrpc_no_stream", err)) } case "jsonrpc_no_stream_error": + if !req.HasID { + s.reportRejectedNotification(ctx, req) + return + } + if req.ID == nil { + s.encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidRequest, "Invalid request", nil) + return + } if err := s.JsonrpcNoStreamError(ctx, r, req, w); err != nil { s.errhandler(ctx, w, fmt.Errorf("handler error for %s: %w", "jsonrpc_no_stream_error", err)) } @@ -288,32 +310,14 @@ func NewJsonrpcNoStreamHandler( return func(ctx context.Context, r *http.Request, req *jsonrpc.RawRequest, w http.ResponseWriter) error { ctx = context.WithValue(ctx, goa.MethodKey, "jsonrpc_no_stream") ctx = context.WithValue(ctx, goa.ServiceKey, "test-jsonrpc") - outputWriter := w - if !req.HasID { - // A notification runs the same service and error reporting code without - // sending a response. - outputWriter = &noOutputResponseWriter{header: make(http.Header)} - } params, err := decodeParams(r, req) if err != nil { - if req.HasID { - encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) - } else { - errhandler(ctx, outputWriter, fmt.Errorf("failed to decode parameters: %w", err)) - } + encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) return nil } res, err := endpoint(ctx, params) if err != nil { - if req.HasID { - encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) - } else { - errhandler(ctx, outputWriter, fmt.Errorf("endpoint error: %w", err)) - } - return nil - } - if !req.HasID { - // A notification has no ID field and receives no response. + encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) return nil } @@ -341,32 +345,14 @@ func NewJsonrpcNoStreamErrorHandler( return func(ctx context.Context, r *http.Request, req *jsonrpc.RawRequest, w http.ResponseWriter) error { ctx = context.WithValue(ctx, goa.MethodKey, "jsonrpc_no_stream_error") ctx = context.WithValue(ctx, goa.ServiceKey, "test-jsonrpc") - outputWriter := w - if !req.HasID { - // A notification runs the same service and error reporting code without - // sending a response. - outputWriter = &noOutputResponseWriter{header: make(http.Header)} - } params, err := decodeParams(r, req) if err != nil { - if req.HasID { - encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) - } else { - errhandler(ctx, outputWriter, fmt.Errorf("failed to decode parameters: %w", err)) - } + encodeJSONRPCError(ctx, w, req, jsonrpc.InvalidParams, err.Error(), nil, encoder, errhandler) return nil } res, err := endpoint(ctx, params) if err != nil { - if req.HasID { - encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) - } else { - errhandler(ctx, outputWriter, fmt.Errorf("endpoint error: %w", err)) - } - return nil - } - if !req.HasID { - // A notification has no ID field and receives no response. + encodeJSONRPCError(ctx, w, req, jsonrpc.InternalError, err.Error(), nil, encoder, errhandler) return nil } diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 374bada7b..eada1e066 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 + goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index cd0d5f3ad..7f8101d98 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709 h1:CUUmVjHssJLES+e+2BqBo29/usnQHEak2wtPcpJKqvs= -goa.design/goa/v3 v3.30.1-0.20260825122644-ed7b72688709/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= +goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 38c87b2869dc29776ed3b9657e1d86abdecd8f4f Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Tue, 25 Aug 2026 13:30:31 -0700 Subject: [PATCH 08/30] chore: verify plugins with scoped Goa transforms --- go.mod | 2 +- go.sum | 2 ++ testing/examples/calculator/go.mod | 2 +- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/jsonrpc/go.mod | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 62db01ad9..0ec0281f6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee + goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 97d5fbcb0..5b9e69914 100644 --- a/go.sum +++ b/go.sum @@ -83,6 +83,8 @@ goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 h1:iDfdv3DYb6jVKkTlg+pD8q0MZFuSuMA2lAtCQe9yfPA= +goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index f7d3edab1..bdb70626d 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee + goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 899d33142..24c0c3b7b 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee + goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index eada1e066..51b8fa925 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee + goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) From 63f5d72adadfae2729f67b15f1cf46314744c934 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Tue, 25 Aug 2026 13:50:29 -0700 Subject: [PATCH 09/30] chore: pin plugins to final scoped Goa transforms --- go.mod | 2 +- go.sum | 6 ++---- testing/examples/calculator/go.mod | 2 +- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/jsonrpc/go.mod | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 0ec0281f6..5b4fd6ecd 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 + goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 5b9e69914..613ccdae9 100644 --- a/go.sum +++ b/go.sum @@ -81,10 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 h1:iDfdv3DYb6jVKkTlg+pD8q0MZFuSuMA2lAtCQe9yfPA= -goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index bdb70626d..2beaaa13c 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 + goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 24c0c3b7b..0838f5d69 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 + goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 51b8fa925..c5e911502 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825202211-245bb1440866 + goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) From e9dddc70d91e85c590226b70d269f1b1d60a6167 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Tue, 25 Aug 2026 13:50:44 -0700 Subject: [PATCH 10/30] chore: refresh plugin example checksums --- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.sum | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 7f8101d98..d6a9985f4 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 7f8101d98..d6a9985f4 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 7f8101d98..d6a9985f4 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee h1:kHhMouwWXQwdReV0dGkgsx7WX0tzka0xnKZWHznWixQ= -goa.design/goa/v3 v3.30.1-0.20260825174000-15f11dc2fdee/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= +goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 0523f72ec7be164e7007782a7a458efecc816212 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Tue, 25 Aug 2026 22:39:23 -0700 Subject: [PATCH 11/30] chore: pin plugins to final Goa generation plan --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 2 ++ testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 2 ++ testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 2 ++ 8 files changed, 12 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5b4fd6ecd..ec7629f76 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade + goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 613ccdae9..66d950976 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 2beaaa13c..2a669bc3c 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade + goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index d6a9985f4..81f08deb4 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -66,6 +66,8 @@ goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 0838f5d69..301f35cdb 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade + goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index d6a9985f4..81f08deb4 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -66,6 +66,8 @@ goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index c5e911502..d20cbfdec 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade + goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index d6a9985f4..81f08deb4 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -66,6 +66,8 @@ goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= +goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From ad5f79a6786a4e04b48c14d1d9106b22b9f7431c Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 04:06:22 -0700 Subject: [PATCH 12/30] chore: regenerate plugins with final Goa plan --- cors/examples/calc/gen/http/calc/client/paths.go | 4 +--- cors/examples/calc/gen/http/calc/server/paths.go | 4 +--- docs/examples/calc/gen/http/calc/client/paths.go | 4 +--- docs/examples/calc/gen/http/calc/server/paths.go | 4 +--- go.mod | 2 +- go.sum | 4 ++-- goakit/examples/calc/gen/http/calc/client/paths.go | 4 +--- goakit/examples/calc/gen/http/calc/server/paths.go | 4 +--- .../fetcher/archiver/gen/http/archiver/client/paths.go | 4 +--- .../fetcher/archiver/gen/http/archiver/server/paths.go | 4 +--- .../examples/fetcher/fetcher/gen/http/fetcher/client/paths.go | 4 +--- .../examples/fetcher/fetcher/gen/http/fetcher/server/paths.go | 4 +--- i18n/examples/calc/gen/http/calc/client/paths.go | 4 +--- i18n/examples/calc/gen/http/calc/server/paths.go | 4 +--- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 2 ++ testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 2 ++ testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 2 ++ tscap/example/gen/http/tscap/client/paths.go | 4 +--- tscap/example/gen/http/tscap/server/paths.go | 4 +--- zaplogger/examples/calc/gen/http/calc/client/paths.go | 4 +--- zaplogger/examples/calc/gen/http/calc/server/paths.go | 4 +--- 24 files changed, 28 insertions(+), 54 deletions(-) diff --git a/cors/examples/calc/gen/http/calc/client/paths.go b/cors/examples/calc/gen/http/calc/client/paths.go index 7b153030d..014d0f138 100644 --- a/cors/examples/calc/gen/http/calc/client/paths.go +++ b/cors/examples/calc/gen/http/calc/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { diff --git a/cors/examples/calc/gen/http/calc/server/paths.go b/cors/examples/calc/gen/http/calc/server/paths.go index 3745cb4ff..1128e7eb4 100644 --- a/cors/examples/calc/gen/http/calc/server/paths.go +++ b/cors/examples/calc/gen/http/calc/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { diff --git a/docs/examples/calc/gen/http/calc/client/paths.go b/docs/examples/calc/gen/http/calc/client/paths.go index 32ac38d3f..a07b931e8 100644 --- a/docs/examples/calc/gen/http/calc/client/paths.go +++ b/docs/examples/calc/gen/http/calc/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(left int, right int) string { diff --git a/docs/examples/calc/gen/http/calc/server/paths.go b/docs/examples/calc/gen/http/calc/server/paths.go index 366ca05ee..22a58ff6f 100644 --- a/docs/examples/calc/gen/http/calc/server/paths.go +++ b/docs/examples/calc/gen/http/calc/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(left int, right int) string { diff --git a/go.mod b/go.mod index ec7629f76..f3e5942af 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 + goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 66d950976..b39d2ab58 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/gen/http/calc/client/paths.go b/goakit/examples/calc/gen/http/calc/client/paths.go index afbaa263b..9147e9ac7 100644 --- a/goakit/examples/calc/gen/http/calc/client/paths.go +++ b/goakit/examples/calc/gen/http/calc/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { diff --git a/goakit/examples/calc/gen/http/calc/server/paths.go b/goakit/examples/calc/gen/http/calc/server/paths.go index 186ece91a..2ebc59487 100644 --- a/goakit/examples/calc/gen/http/calc/server/paths.go +++ b/goakit/examples/calc/gen/http/calc/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/client/paths.go b/goakit/examples/fetcher/archiver/gen/http/archiver/client/paths.go index 6fb2a8f35..da0a1758f 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/client/paths.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // ArchiveArchiverPath returns the URL path to the archiver service archive HTTP endpoint. func ArchiveArchiverPath() string { diff --git a/goakit/examples/fetcher/archiver/gen/http/archiver/server/paths.go b/goakit/examples/fetcher/archiver/gen/http/archiver/server/paths.go index f90baeef4..4cc979020 100644 --- a/goakit/examples/fetcher/archiver/gen/http/archiver/server/paths.go +++ b/goakit/examples/fetcher/archiver/gen/http/archiver/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // ArchiveArchiverPath returns the URL path to the archiver service archive HTTP endpoint. func ArchiveArchiverPath() string { diff --git a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/paths.go b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/paths.go index 2f2a107aa..f2479690d 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/paths.go +++ b/goakit/examples/fetcher/fetcher/gen/http/fetcher/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // FetchFetcherPath returns the URL path to the fetcher service fetch HTTP endpoint. func FetchFetcherPath(url_ string) string { diff --git a/goakit/examples/fetcher/fetcher/gen/http/fetcher/server/paths.go b/goakit/examples/fetcher/fetcher/gen/http/fetcher/server/paths.go index b382b9f0a..fb5db6e0e 100644 --- a/goakit/examples/fetcher/fetcher/gen/http/fetcher/server/paths.go +++ b/goakit/examples/fetcher/fetcher/gen/http/fetcher/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // FetchFetcherPath returns the URL path to the fetcher service fetch HTTP endpoint. func FetchFetcherPath(url_ string) string { diff --git a/i18n/examples/calc/gen/http/calc/client/paths.go b/i18n/examples/calc/gen/http/calc/client/paths.go index c028f4aff..ddf8d49e5 100644 --- a/i18n/examples/calc/gen/http/calc/client/paths.go +++ b/i18n/examples/calc/gen/http/calc/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { diff --git a/i18n/examples/calc/gen/http/calc/server/paths.go b/i18n/examples/calc/gen/http/calc/server/paths.go index 5b6f32948..2be328d1c 100644 --- a/i18n/examples/calc/gen/http/calc/server/paths.go +++ b/i18n/examples/calc/gen/http/calc/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 2a669bc3c..9995541a3 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 + goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 81f08deb4..2861bb2a1 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -68,6 +68,8 @@ goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2 goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 301f35cdb..7a42c4480 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 + goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 81f08deb4..2861bb2a1 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -68,6 +68,8 @@ goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2 goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index d20cbfdec..f879c4a4c 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 + goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 81f08deb4..2861bb2a1 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -68,6 +68,8 @@ goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2 goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= +goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/tscap/example/gen/http/tscap/client/paths.go b/tscap/example/gen/http/tscap/client/paths.go index 92fc8a8a4..7089fe2d3 100644 --- a/tscap/example/gen/http/tscap/client/paths.go +++ b/tscap/example/gen/http/tscap/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // ListTscapPath returns the URL path to the tscap service list HTTP endpoint. func ListTscapPath() string { diff --git a/tscap/example/gen/http/tscap/server/paths.go b/tscap/example/gen/http/tscap/server/paths.go index 527106f16..55c62fbeb 100644 --- a/tscap/example/gen/http/tscap/server/paths.go +++ b/tscap/example/gen/http/tscap/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // ListTscapPath returns the URL path to the tscap service list HTTP endpoint. func ListTscapPath() string { diff --git a/zaplogger/examples/calc/gen/http/calc/client/paths.go b/zaplogger/examples/calc/gen/http/calc/client/paths.go index 8add0a44f..9bb981253 100644 --- a/zaplogger/examples/calc/gen/http/calc/client/paths.go +++ b/zaplogger/examples/calc/gen/http/calc/client/paths.go @@ -8,9 +8,7 @@ package client -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { diff --git a/zaplogger/examples/calc/gen/http/calc/server/paths.go b/zaplogger/examples/calc/gen/http/calc/server/paths.go index 86ab812cf..738a8a920 100644 --- a/zaplogger/examples/calc/gen/http/calc/server/paths.go +++ b/zaplogger/examples/calc/gen/http/calc/server/paths.go @@ -8,9 +8,7 @@ package server -import ( - "fmt" -) +import "fmt" // AddCalcPath returns the URL path to the calc service add HTTP endpoint. func AddCalcPath(a int, b int) string { From ca3013c9584fcffbace282bc893d924cc0d1858e Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 04:35:52 -0700 Subject: [PATCH 13/30] chore: repin plugins to final Goa plan --- arnz/example/gen/http/arnz/server/types.go | 4 +--- cors/examples/calc/gen/http/calc/server/types.go | 4 +--- docs/examples/calc/gen/http/calc/server/types.go | 4 +--- go.mod | 2 +- go.sum | 4 ++-- goakit/examples/calc/gen/http/calc/server/types.go | 4 +--- i18n/examples/calc/gen/http/calc/server/types.go | 4 +--- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 2 ++ testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 2 ++ testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 2 ++ tscap/example/gen/http/tscap/client/types.go | 4 +--- zaplogger/examples/calc/gen/http/calc/server/types.go | 4 +--- 15 files changed, 19 insertions(+), 27 deletions(-) diff --git a/arnz/example/gen/http/arnz/server/types.go b/arnz/example/gen/http/arnz/server/types.go index b23033c51..576ee7b13 100644 --- a/arnz/example/gen/http/arnz/server/types.go +++ b/arnz/example/gen/http/arnz/server/types.go @@ -8,9 +8,7 @@ package server -import ( - arnz "goa.design/plugins/v3/arnz/example/gen/arnz" -) +import arnz "goa.design/plugins/v3/arnz/example/gen/arnz" // CreateResponseBody is the type of the "Arnz" service "create" endpoint HTTP // response body. diff --git a/cors/examples/calc/gen/http/calc/server/types.go b/cors/examples/calc/gen/http/calc/server/types.go index 988acb2e9..548d55fb5 100644 --- a/cors/examples/calc/gen/http/calc/server/types.go +++ b/cors/examples/calc/gen/http/calc/server/types.go @@ -8,9 +8,7 @@ package server -import ( - calc "goa.design/plugins/v3/cors/examples/calc/gen/calc" -) +import calc "goa.design/plugins/v3/cors/examples/calc/gen/calc" // NewAddPayload builds a calc service add endpoint payload. func NewAddPayload(a int, b int) *calc.AddPayload { diff --git a/docs/examples/calc/gen/http/calc/server/types.go b/docs/examples/calc/gen/http/calc/server/types.go index ad3f60a3b..c8abf58c3 100644 --- a/docs/examples/calc/gen/http/calc/server/types.go +++ b/docs/examples/calc/gen/http/calc/server/types.go @@ -8,9 +8,7 @@ package server -import ( - calc "goa.design/plugins/v3/docs/examples/calc/gen/calc" -) +import calc "goa.design/plugins/v3/docs/examples/calc/gen/calc" // NewAddPayload builds a calc service add endpoint payload. func NewAddPayload(left int, right int) *calc.AddPayload { diff --git a/go.mod b/go.mod index f3e5942af..d9bece9c7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 + goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b39d2ab58..4daed029b 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/gen/http/calc/server/types.go b/goakit/examples/calc/gen/http/calc/server/types.go index 4681b3428..ca115bbda 100644 --- a/goakit/examples/calc/gen/http/calc/server/types.go +++ b/goakit/examples/calc/gen/http/calc/server/types.go @@ -8,9 +8,7 @@ package server -import ( - calc "goa.design/plugins/v3/goakit/examples/calc/gen/calc" -) +import calc "goa.design/plugins/v3/goakit/examples/calc/gen/calc" // NewAddPayload builds a calc service add endpoint payload. func NewAddPayload(a int, b int) *calc.AddPayload { diff --git a/i18n/examples/calc/gen/http/calc/server/types.go b/i18n/examples/calc/gen/http/calc/server/types.go index 79df03b2b..6413eb809 100644 --- a/i18n/examples/calc/gen/http/calc/server/types.go +++ b/i18n/examples/calc/gen/http/calc/server/types.go @@ -8,9 +8,7 @@ package server -import ( - calc "goa.design/plugins/v3/i18n/examples/calc/gen/calc" -) +import calc "goa.design/plugins/v3/i18n/examples/calc/gen/calc" // NewAddPayload builds a calc service add endpoint payload. func NewAddPayload(a int, b int) *calc.AddPayload { diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 9995541a3..4e284c073 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 + goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 2861bb2a1..3b112fb6a 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -70,6 +70,8 @@ goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6 goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 7a42c4480..7fc948c48 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 + goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 2861bb2a1..3b112fb6a 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -70,6 +70,8 @@ goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6 goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index f879c4a4c..0a06f9867 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 + goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 2861bb2a1..3b112fb6a 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -70,6 +70,8 @@ goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6 goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= +goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/tscap/example/gen/http/tscap/client/types.go b/tscap/example/gen/http/tscap/client/types.go index 3e144e689..5c070910b 100644 --- a/tscap/example/gen/http/tscap/client/types.go +++ b/tscap/example/gen/http/tscap/client/types.go @@ -8,9 +8,7 @@ package client -import ( - tscap "goa.design/plugins/v3/tscap/example/gen/tscap" -) +import tscap "goa.design/plugins/v3/tscap/example/gen/tscap" // CreateRequestBody is the type of the "tscap" service "create" endpoint HTTP // request body. diff --git a/zaplogger/examples/calc/gen/http/calc/server/types.go b/zaplogger/examples/calc/gen/http/calc/server/types.go index e6adc2d26..9328305d7 100644 --- a/zaplogger/examples/calc/gen/http/calc/server/types.go +++ b/zaplogger/examples/calc/gen/http/calc/server/types.go @@ -8,9 +8,7 @@ package server -import ( - calc "goa.design/plugins/v3/zaplogger/examples/calc/gen/calc" -) +import calc "goa.design/plugins/v3/zaplogger/examples/calc/gen/calc" // NewAddPayload builds a calc service add endpoint payload. func NewAddPayload(a int, b int) *calc.AddPayload { From 7c4ade02bdd60da42931a702a7b2bd17bbbc7656 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 04:45:07 -0700 Subject: [PATCH 14/30] chore: repin plugins to path alias fix --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 2 ++ testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 2 ++ testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 2 ++ 8 files changed, 12 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index d9bece9c7..d4e760658 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd + goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 4daed029b..17325dfbb 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 4e284c073..a35081b65 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd + goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 3b112fb6a..d83cf8289 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -72,6 +72,8 @@ goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 7fc948c48..35426587c 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd + goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 3b112fb6a..d83cf8289 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -72,6 +72,8 @@ goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 0a06f9867..60a2b0b79 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd + goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 3b112fb6a..d83cf8289 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -72,6 +72,8 @@ goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= +goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From bd3599af8b079ad7e98dd3034529c3195657e06f Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 05:25:01 -0700 Subject: [PATCH 15/30] chore: repin plugins to primitive path fix --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 12 ++---------- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 12 ++---------- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 12 ++---------- 8 files changed, 12 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index d4e760658..8f9b3ac24 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 + goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 17325dfbb..16f1325c5 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index a35081b65..306c1675e 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 + goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index d83cf8289..332da057f 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,16 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 35426587c..8eca13990 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 + goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index d83cf8289..332da057f 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,16 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 60a2b0b79..994e70fc6 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 + goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index d83cf8289..332da057f 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,16 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade h1:gTHq43P69svXt3KfmtpA2Tr5+4dCt03xVmSi5g1xqRc= -goa.design/goa/v3 v3.30.1-0.20260825203937-95933f068ade/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1 h1:mkRpmnh0fTpcA3oL/x8M6QqACRUnkrUEt4vg1RihEnQ= -goa.design/goa/v3 v3.30.1-0.20260826024846-d8c8ccda06b1/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459 h1:OCDO2Cx4RsP4CKqLfxEMP10q66fSTvXQ1VDhKdoW1ys= -goa.design/goa/v3 v3.30.1-0.20260826105706-c43f39c82459/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd h1:a2t9FUslz0MB7UAXo+CQ17oCTF8sZ8jktZn/j45xDNY= -goa.design/goa/v3 v3.30.1-0.20260826113009-6954f6b460cd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78 h1:/wP6y3spzEL2zvucGQvFS0dy7anYWnI53tz2vQpCN2M= -goa.design/goa/v3 v3.30.1-0.20260826114129-cb16d7250d78/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= +goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From b6ffdf1e0ab1739f181dd0221007f614570ff849 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 05:37:24 -0700 Subject: [PATCH 16/30] chore: pin plugins to latest Goa generation plan --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 8f9b3ac24..298ce70f4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 + goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 16f1325c5..d4d0025f4 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 306c1675e..194ce6b08 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 + goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 332da057f..5b5ce1566 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 8eca13990..75826aff1 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 + goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 332da057f..5b5ce1566 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 994e70fc6..ecb09e2a1 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 + goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 332da057f..5b5ce1566 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2 h1:qqU42d2tdZX1sNE6utS//I5CZmz8LS+T4joRDYN0ydY= -goa.design/goa/v3 v3.30.1-0.20260826121623-5f90d9cb72a2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= +goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 5356fa411200c95e0dd47319e47dff0f52c92d26 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 06:47:44 -0700 Subject: [PATCH 17/30] chore: pin plugins to final Goa generation plan --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 298ce70f4..2a0f84a06 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 + goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index d4d0025f4..ccbca0003 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 194ce6b08..6d8a21e05 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 + goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 5b5ce1566..bb55c5d13 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 75826aff1..4c082be3d 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 + goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 5b5ce1566..bb55c5d13 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index ecb09e2a1..350a67665 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 + goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 5b5ce1566..bb55c5d13 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13 h1:BrONgYjW/mvH/5lAShNY5rbQiLgOmxup9bQe4xSAD8U= -goa.design/goa/v3 v3.30.1-0.20260826122756-811e48b6ca13/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= +goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From d52274cfa0843d1d175e866fdb1fd6fb03575b10 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 09:29:39 -0700 Subject: [PATCH 18/30] chore: pin plugins to shared transform helpers --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2a0f84a06..e5dde55af 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd + goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index ccbca0003..bf4348bda 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 6d8a21e05..f31cf9ebf 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd + goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index bb55c5d13..e92ae081e 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 4c082be3d..84eaca78c 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd + goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index bb55c5d13..e92ae081e 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 350a67665..8ea20a1ab 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd + goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index bb55c5d13..e92ae081e 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd h1:lqsEHwRVNZB9E64QeKDODNRzD2YB14O8Q+gMpkO8cyw= -goa.design/goa/v3 v3.30.1-0.20260826132953-76b2e84da6dd/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= +goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From cf0093588a70cc22af2b50281eaa272eb02b913c Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 09:34:06 -0700 Subject: [PATCH 19/30] docs: fix plugin registration example --- doc.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/doc.go b/doc.go index 26cdc2e05..29ea23837 100644 --- a/doc.go +++ b/doc.go @@ -1,19 +1,19 @@ /* Packages in the Plugin directory contain plugins for [goa -v2](https://godoc.org/goa.design/goa/v3). Plugins can extend the goa DSL, generate +v3](https://pkg.go.dev/goa.design/goa/v3). Plugins can extend the goa DSL, generate new artifacts and modify the output of existing generators. -There are currently two plugins in the directory: +Examples include: - * The [goakit](https://godoc.org/goa.design/plugins/goakit) plugin generates + - The [goakit](https://pkg.go.dev/goa.design/plugins/v3/goakit) plugin generates code that integrates with the [go-kit](https://github.com/go-kit/kit) library. - * The [cors](https://godoc.org/goa.design/plugins/cors) plugin adds new DSL to - define CORS policies. The plugin generates HTTP server code that respond to + - The [cors](https://pkg.go.dev/goa.design/plugins/v3/cors) plugin adds new DSL to + define CORS policies. The plugin generates HTTP server code that responds to CORS requests in compliance with the policies defined in the design. -Writing a Plugin +# Writing a Plugin Writing a plugin consists of two steps: @@ -30,13 +30,13 @@ generation. The signature of the Generate function is: - func (genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) + func (genpkg string, roots []eval.Root, files []*codegen.File) ([]*codegen.File, error) where: - "genpkg" is the Go import path to the top level generated package ("gen") - "roots" is the set of design roots created by the DSL. - "files" is the current set of generated files. + "genpkg" is the Go import path to the top level generated package ("gen") + "roots" is the set of design roots created by the DSL. + "files" is the current set of generated files. The function must return the entire set of generated files (even the files that the plugin does not modify). @@ -47,16 +47,16 @@ package "init" function, for example: // Register the plugin. func init() { - codegen.RegisterPlugin("gen", Prepare, Generate) - codegen.RegisterPlugin("example", Prepare, Example) + codegen.RegisterPlugin("my-plugin", "gen", Prepare, Generate) + codegen.RegisterPlugin("my-plugin", "example", Prepare, Example) } -The first argument of RegisterPlugin must be one of "gen" or "example" and -specifies the "goa" command that triggers the call to the plugin functions. The -second argument is the Prepare function if any, nil otherwise. The last argument -is the code generator function if any, nil otherwise. +The first argument names the plugin. The second argument must be either "gen" or +"example" and identifies the goa command that runs the plugin. The third +argument is the Prepare function, or nil when the plugin does not change the +design. The last argument is the required Generate function. -Extending The DSL +# Extending The DSL A plugin may introduce new DSL "keywords" (typically Go package functions). The DSL functions initialize the content of a design root object (an object that From ce7dbffaf685de68d943f53e7e11326d94daeefa Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 10:41:23 -0700 Subject: [PATCH 20/30] chore: pin plugins to final Goa generation fixes --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index e5dde55af..d08437576 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d + goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index bf4348bda..5bbcc1140 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index f31cf9ebf..b5079a9d4 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d + goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index e92ae081e..c1c59ad0a 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 84eaca78c..4f5a2e1fe 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d + goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index e92ae081e..c1c59ad0a 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 8ea20a1ab..a824c7482 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d + goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index e92ae081e..c1c59ad0a 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d h1:iKDq3QEkUYUa3d85cp208gGwBH36e/NpgE37ezzwK5Q= -goa.design/goa/v3 v3.30.1-0.20260826161049-5e4c64c6087d/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= +goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 4cabf1e23ccbfdbaa9928992186222eacf27b8a4 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 11:55:28 -0700 Subject: [PATCH 21/30] chore: pin plugins to final Goa API --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d08437576..4385c9019 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 + goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 5bbcc1140..8054849d0 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index b5079a9d4..ed7c64777 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 + goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index c1c59ad0a..ccc2b55f8 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 4f5a2e1fe..b5b2a0f8e 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 + goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index c1c59ad0a..ccc2b55f8 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index a824c7482..3843d5e98 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 + goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index c1c59ad0a..ccc2b55f8 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849 h1:hJE2Y7DflXvYIMmkDg8I1LfmaFgOyynWh9Na7LW4acg= -goa.design/goa/v3 v3.30.1-0.20260826171120-899b46415849/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= +goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 1cecea59965edb6b0c0af643d7f9ee086d2d5d6f Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 12:13:38 -0700 Subject: [PATCH 22/30] chore: pin plugins to latest Goa API --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 4385c9019..49a1f56cb 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e + goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 8054849d0..8c2eebcf8 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index ed7c64777..c81d67c93 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e + goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index ccc2b55f8..00b033302 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index b5b2a0f8e..d177bf7dd 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e + goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index ccc2b55f8..00b033302 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 3843d5e98..cbaf3c45e 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e + goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index ccc2b55f8..00b033302 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e h1:QnJ0egMXHnPUfKUCLb9+BqwRDDAtOahNbKWU3bWbrgQ= -goa.design/goa/v3 v3.30.1-0.20260826184016-9e4a965c104e/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= +goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 5ec2ec8a90dfc23b6c0a111d98fce08e02f6828e Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 14:04:50 -0700 Subject: [PATCH 23/30] chore: pin plugins to wrapped transform fix --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 49a1f56cb..b87b6b921 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 + goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 8c2eebcf8..83f10ae7d 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index c81d67c93..9bab66a1e 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 + goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 00b033302..f1953ffe2 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index d177bf7dd..760f6292c 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 + goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 00b033302..f1953ffe2 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index cbaf3c45e..1323bc420 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 + goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 00b033302..f1953ffe2 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472 h1:mb8UEehe+YsY2g0z3TF9h6q50jIpsMBkYJfEodM8/TY= -goa.design/goa/v3 v3.30.1-0.20260826190237-ed9cac596472/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= +goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 3a4b508b97d4a183f26fa7f80da0b70bd4ad2e78 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Wed, 26 Aug 2026 15:16:13 -0700 Subject: [PATCH 24/30] chore: pin plugins to shared gRPC helpers --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 2 ++ testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 2 ++ testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 2 ++ 8 files changed, 12 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b87b6b921..e41bc5629 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 + goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 83f10ae7d..3ae4ec050 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 9bab66a1e..033a43b06 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 + goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index f1953ffe2..05654f3a0 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -66,6 +66,8 @@ goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 760f6292c..24887cf96 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 + goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index f1953ffe2..05654f3a0 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -66,6 +66,8 @@ goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 1323bc420..c65a5f103 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 + goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index f1953ffe2..05654f3a0 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -66,6 +66,8 @@ goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= +goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From ed5a289dcaf07220887331e6d2adc743e065946e Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Sun, 30 Aug 2026 09:58:56 -0700 Subject: [PATCH 25/30] Validate plugins against Goa preview --- arnz/example/gen/goa.json | 2 +- cors/examples/calc/gen/goa.json | 2 +- docs/examples/calc/gen/goa.json | 2 +- go.mod | 2 +- go.sum | 4 +- goakit/examples/calc/gen/goa.json | 2 +- goakit/examples/fetcher/archiver/gen/goa.json | 2 +- goakit/examples/fetcher/fetcher/gen/goa.json | 2 +- i18n/examples/calc/gen/goa.json | 2 +- testing/examples/calculator/design/design.go | 2 +- .../calculator/gen/calculator/service.go | 2 +- testing/examples/calculator/gen/goa.json | 2 +- .../gen/grpc/calculator/client/client.go | 29 ++++++-- .../grpc/calculator/client/encode_decode.go | 1 + testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 6 +- testing/examples/httpgrpc/gen/goa.json | 2 +- .../gen/grpc/test_http_grpc/client/client.go | 67 ++++++++++++++++--- .../test_http_grpc/client/encode_decode.go | 6 ++ testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 6 +- testing/examples/jsonrpc/gen/goa.json | 2 +- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 6 +- tscap/example/gen/goa.json | 2 +- zaplogger/examples/calc/gen/goa.json | 2 +- 26 files changed, 116 insertions(+), 45 deletions(-) diff --git a/arnz/example/gen/goa.json b/arnz/example/gen/goa.json index d279d6dad..924afff50 100644 --- a/arnz/example/gen/goa.json +++ b/arnz/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/cors/examples/calc/gen/goa.json b/cors/examples/calc/gen/goa.json index d279d6dad..924afff50 100644 --- a/cors/examples/calc/gen/goa.json +++ b/cors/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/docs/examples/calc/gen/goa.json b/docs/examples/calc/gen/goa.json index d279d6dad..924afff50 100644 --- a/docs/examples/calc/gen/goa.json +++ b/docs/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/go.mod b/go.mod index e41bc5629..30e689c64 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 3ae4ec050..2d951f080 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/gen/goa.json b/goakit/examples/calc/gen/goa.json index d279d6dad..924afff50 100644 --- a/goakit/examples/calc/gen/goa.json +++ b/goakit/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/goakit/examples/fetcher/archiver/gen/goa.json b/goakit/examples/fetcher/archiver/gen/goa.json index d279d6dad..924afff50 100644 --- a/goakit/examples/fetcher/archiver/gen/goa.json +++ b/goakit/examples/fetcher/archiver/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/goakit/examples/fetcher/fetcher/gen/goa.json b/goakit/examples/fetcher/fetcher/gen/goa.json index d279d6dad..924afff50 100644 --- a/goakit/examples/fetcher/fetcher/gen/goa.json +++ b/goakit/examples/fetcher/fetcher/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/i18n/examples/calc/gen/goa.json b/i18n/examples/calc/gen/goa.json index d279d6dad..924afff50 100644 --- a/i18n/examples/calc/gen/goa.json +++ b/i18n/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/testing/examples/calculator/design/design.go b/testing/examples/calculator/design/design.go index 17b428511..0ff7814e0 100644 --- a/testing/examples/calculator/design/design.go +++ b/testing/examples/calculator/design/design.go @@ -124,6 +124,6 @@ var _ = Service("calculator", func() { var CalculatorError = Type("CalculatorError", func() { Description("Calculator operation error") Field(1, "message", String, "Error message") - Field(2, "code", String, "Error code") + ErrorName(2, "code", String, "Error code") Required("message", "code") }) diff --git a/testing/examples/calculator/gen/calculator/service.go b/testing/examples/calculator/gen/calculator/service.go index b0f7cae4a..210319720 100644 --- a/testing/examples/calculator/gen/calculator/service.go +++ b/testing/examples/calculator/gen/calculator/service.go @@ -185,5 +185,5 @@ func (e *CalculatorError) ErrorName() string { // GoaErrorName returns the error name. func (e *CalculatorError) GoaErrorName() string { - return "division_by_zero" + return e.Code } diff --git a/testing/examples/calculator/gen/goa.json b/testing/examples/calculator/gen/goa.json index d279d6dad..924afff50 100644 --- a/testing/examples/calculator/gen/goa.json +++ b/testing/examples/calculator/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/testing/examples/calculator/gen/grpc/calculator/client/client.go b/testing/examples/calculator/gen/grpc/calculator/client/client.go index 01ddd6ce4..2a3eacd2a 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/client.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/client.go @@ -29,6 +29,7 @@ type Client struct { // interface. type BatchAddClientStream struct { stream calculatorpb.Calculator_BatchAddClient + ctx context.Context } // NewClient instantiates gRPC client for all the calculator service servers. @@ -48,11 +49,14 @@ func (c *Client) Add() goa.Endpoint { DecodeAddResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -69,11 +73,14 @@ func (c *Client) Divide() goa.Endpoint { DecodeDivideResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -90,11 +97,14 @@ func (c *Client) Factorial() goa.Endpoint { DecodeFactorialResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -111,11 +121,14 @@ func (c *Client) Statistics() goa.Endpoint { DecodeStatisticsResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -132,11 +145,14 @@ func (c *Client) BatchAdd() goa.Endpoint { DecodeBatchAddResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -149,6 +165,9 @@ func (s *BatchAddClientStream) Recv() (*calculator.BatchAddResult, error) { var res *calculator.BatchAddResult v, err := s.stream.Recv() if err != nil { + if ctxErr := goagrpc.ContextError(s.ctx, err); ctxErr != nil { + return res, ctxErr + } return res, err } if err = ValidateBatchAddResponse(v); err != nil { diff --git a/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go b/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go index e91d7c05f..47e39c7e5 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/encode_decode.go @@ -185,5 +185,6 @@ func BuildBatchAddFunc(grpccli calculatorpb.CalculatorClient, cliopts ...grpc.Ca func DecodeBatchAddResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { return &BatchAddClientStream{ stream: v.(calculatorpb.Calculator_BatchAddClient), + ctx: ctx, }, nil } diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 033a43b06..b90832efd 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 05654f3a0..5b0064e8b 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,10 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/gen/goa.json b/testing/examples/httpgrpc/gen/goa.json index d279d6dad..924afff50 100644 --- a/testing/examples/httpgrpc/gen/goa.json +++ b/testing/examples/httpgrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go index 964e045cb..9d22579b3 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/client.go @@ -29,36 +29,42 @@ type Client struct { // testhttpgrpc.GrpcServerStreamClientStream interface. type GrpcServerStreamClientStream struct { stream test_http_grpcpb.TestHTTPGrpc_GrpcServerStreamClient + ctx context.Context } // GrpcClientStreamClientStream implements the // testhttpgrpc.GrpcClientStreamClientStream interface. type GrpcClientStreamClientStream struct { stream test_http_grpcpb.TestHTTPGrpc_GrpcClientStreamClient + ctx context.Context } // GrpcBidiStreamClientStream implements the // testhttpgrpc.GrpcBidiStreamClientStream interface. type GrpcBidiStreamClientStream struct { stream test_http_grpcpb.TestHTTPGrpc_GrpcBidiStreamClient + ctx context.Context } // MixedServerStreamClientStream implements the // testhttpgrpc.MixedServerStreamClientStream interface. type MixedServerStreamClientStream struct { stream test_http_grpcpb.TestHTTPGrpc_MixedServerStreamClient + ctx context.Context } // MixedClientStreamWsGrpcClientStream implements the // testhttpgrpc.MixedClientStreamWsGrpcClientStream interface. type MixedClientStreamWsGrpcClientStream struct { stream test_http_grpcpb.TestHTTPGrpc_MixedClientStreamWsGrpcClient + ctx context.Context } // MixedBidiStreamWsGrpcClientStream implements the // testhttpgrpc.MixedBidiStreamWsGrpcClientStream interface. type MixedBidiStreamWsGrpcClientStream struct { stream test_http_grpcpb.TestHTTPGrpc_MixedBidiStreamWsGrpcClient + ctx context.Context } // NewClient instantiates gRPC client for all the test-http-grpc service @@ -80,11 +86,14 @@ func (c *Client) GrpcNoStream() goa.Endpoint { DecodeGrpcNoStreamResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -111,6 +120,9 @@ func (c *Client) GrpcNoStreamErrorDivByZero() goa.Endpoint { case *goapb.ErrorResponse: return nil, goagrpc.NewServiceError(message) default: + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } } @@ -128,11 +140,14 @@ func (c *Client) GrpcServerStream() goa.Endpoint { DecodeGrpcServerStreamResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -149,11 +164,14 @@ func (c *Client) GrpcClientStream() goa.Endpoint { DecodeGrpcClientStreamResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -170,11 +188,14 @@ func (c *Client) GrpcBidiStream() goa.Endpoint { DecodeGrpcBidiStreamResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -191,11 +212,14 @@ func (c *Client) MixedNoStream() goa.Endpoint { DecodeMixedNoStreamResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -212,11 +236,14 @@ func (c *Client) MixedServerStream() goa.Endpoint { DecodeMixedServerStreamResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -233,11 +260,14 @@ func (c *Client) MixedClientStreamWsGrpc() goa.Endpoint { DecodeMixedClientStreamWsGrpcResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -254,11 +284,14 @@ func (c *Client) MixedBidiStreamWsGrpc() goa.Endpoint { DecodeMixedBidiStreamWsGrpcResponse) res, err := inv.Invoke(ctx, v) if err != nil { - // Try to decode a Goa error response detail before falling back to Fault. + // Decode a Goa error detail before returning a matching context error or falling back to Fault. resp := goagrpc.DecodeError(err) if eresp, ok := resp.(*goapb.ErrorResponse); ok { return nil, goagrpc.NewServiceError(eresp) } + if ctxErr := goagrpc.ContextError(ctx, err); ctxErr != nil { + return nil, ctxErr + } return nil, goa.Fault("%s", err.Error()) } return res, nil @@ -271,6 +304,9 @@ func (s *GrpcServerStreamClientStream) Recv() (*testhttpgrpc.GrpcServerStreamRes var res *testhttpgrpc.GrpcServerStreamResult v, err := s.stream.Recv() if err != nil { + if ctxErr := goagrpc.ContextError(s.ctx, err); ctxErr != nil { + return res, ctxErr + } return res, err } if err = ValidateGrpcServerStreamResponse(v); err != nil { @@ -292,6 +328,9 @@ func (s *GrpcClientStreamClientStream) CloseAndRecv() (*testhttpgrpc.GrpcClientS var res *testhttpgrpc.GrpcClientStreamResult v, err := s.stream.CloseAndRecv() if err != nil { + if ctxErr := goagrpc.ContextError(s.ctx, err); ctxErr != nil { + return res, ctxErr + } return res, err } if err = ValidateGrpcClientStreamResponse(v); err != nil { @@ -328,6 +367,9 @@ func (s *GrpcBidiStreamClientStream) Recv() (*testhttpgrpc.GrpcBidiStreamResult, var res *testhttpgrpc.GrpcBidiStreamResult v, err := s.stream.Recv() if err != nil { + if ctxErr := goagrpc.ContextError(s.ctx, err); ctxErr != nil { + return res, ctxErr + } return res, err } if err = ValidateGrpcBidiStreamResponse(v); err != nil { @@ -367,6 +409,9 @@ func (s *MixedServerStreamClientStream) Recv() (*testhttpgrpc.MixedServerStreamR var res *testhttpgrpc.MixedServerStreamResult v, err := s.stream.Recv() if err != nil { + if ctxErr := goagrpc.ContextError(s.ctx, err); ctxErr != nil { + return res, ctxErr + } return res, err } if err = ValidateMixedServerStreamResponse(v); err != nil { @@ -389,6 +434,9 @@ func (s *MixedClientStreamWsGrpcClientStream) CloseAndRecv() (*testhttpgrpc.Mixe var res *testhttpgrpc.MixedClientStreamWsGrpcResult v, err := s.stream.CloseAndRecv() if err != nil { + if ctxErr := goagrpc.ContextError(s.ctx, err); ctxErr != nil { + return res, ctxErr + } return res, err } if err = ValidateMixedClientStreamWsGrpcResponse(v); err != nil { @@ -425,6 +473,9 @@ func (s *MixedBidiStreamWsGrpcClientStream) Recv() (*testhttpgrpc.MixedBidiStrea var res *testhttpgrpc.MixedBidiStreamWsGrpcResult v, err := s.stream.Recv() if err != nil { + if ctxErr := goagrpc.ContextError(s.ctx, err); ctxErr != nil { + return res, ctxErr + } return res, err } if err = ValidateMixedBidiStreamWsGrpcResponse(v); err != nil { diff --git a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go index 1d9a6d033..cb63d82cb 100644 --- a/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go +++ b/testing/examples/httpgrpc/gen/grpc/test_http_grpc/client/encode_decode.go @@ -123,6 +123,7 @@ func EncodeGrpcServerStreamRequest(ctx context.Context, v any, md *metadata.MD) func DecodeGrpcServerStreamResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { return &GrpcServerStreamClientStream{ stream: v.(test_http_grpcpb.TestHTTPGrpc_GrpcServerStreamClient), + ctx: ctx, }, nil } @@ -145,6 +146,7 @@ func BuildGrpcClientStreamFunc(grpccli test_http_grpcpb.TestHTTPGrpcClient, clio func DecodeGrpcClientStreamResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { return &GrpcClientStreamClientStream{ stream: v.(test_http_grpcpb.TestHTTPGrpc_GrpcClientStreamClient), + ctx: ctx, }, nil } @@ -167,6 +169,7 @@ func BuildGrpcBidiStreamFunc(grpccli test_http_grpcpb.TestHTTPGrpcClient, cliopt func DecodeGrpcBidiStreamResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { return &GrpcBidiStreamClientStream{ stream: v.(test_http_grpcpb.TestHTTPGrpc_GrpcBidiStreamClient), + ctx: ctx, }, nil } @@ -227,6 +230,7 @@ func BuildMixedServerStreamFunc(grpccli test_http_grpcpb.TestHTTPGrpcClient, cli func DecodeMixedServerStreamResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { return &MixedServerStreamClientStream{ stream: v.(test_http_grpcpb.TestHTTPGrpc_MixedServerStreamClient), + ctx: ctx, }, nil } @@ -249,6 +253,7 @@ func BuildMixedClientStreamWsGrpcFunc(grpccli test_http_grpcpb.TestHTTPGrpcClien func DecodeMixedClientStreamWsGrpcResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { return &MixedClientStreamWsGrpcClientStream{ stream: v.(test_http_grpcpb.TestHTTPGrpc_MixedClientStreamWsGrpcClient), + ctx: ctx, }, nil } @@ -271,5 +276,6 @@ func BuildMixedBidiStreamWsGrpcFunc(grpccli test_http_grpcpb.TestHTTPGrpcClient, func DecodeMixedBidiStreamWsGrpcResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { return &MixedBidiStreamWsGrpcClientStream{ stream: v.(test_http_grpcpb.TestHTTPGrpc_MixedBidiStreamWsGrpcClient), + ctx: ctx, }, nil } diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 24887cf96..bed148dba 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 05654f3a0..5b0064e8b 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,10 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/gen/goa.json b/testing/examples/jsonrpc/gen/goa.json index d279d6dad..924afff50 100644 --- a/testing/examples/jsonrpc/gen/goa.json +++ b/testing/examples/jsonrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index c65a5f103..e2cfc7bb6 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 05654f3a0..5b0064e8b 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,10 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054 h1:j+HyJr3A4zZ642ewmdO4lDfsJ6foFqj90XX8hQOxFJw= -goa.design/goa/v3 v3.30.1-0.20260826205939-36e92a56a054/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7 h1:z/GUg4DrWWQsH/UqH7QUmqdiUWSX8VWBr61dOs4UtOc= -goa.design/goa/v3 v3.30.1-0.20260826213606-c30c5e1423e7/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/tscap/example/gen/goa.json b/tscap/example/gen/goa.json index d279d6dad..924afff50 100644 --- a/tscap/example/gen/goa.json +++ b/tscap/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file diff --git a/zaplogger/examples/calc/gen/goa.json b/zaplogger/examples/calc/gen/goa.json index d279d6dad..924afff50 100644 --- a/zaplogger/examples/calc/gen/goa.json +++ b/zaplogger/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.30.0" + "goa_version": "v3.31.0-preview.1" } \ No newline at end of file From f5f325a04beeeb8c90946b59d1d78be47501bc5b Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Sun, 30 Aug 2026 11:09:30 -0700 Subject: [PATCH 26/30] Pin plugins to latest Goa preview --- cors/examples/calc/cmd/calc-cli/main.go | 4 ++-- docs/examples/calc/cmd/calc-cli/main.go | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- goakit/examples/calc/cmd/calc-cli/main.go | 4 ++-- goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go | 4 ++-- i18n/examples/calc/cmd/calc-cli/main.go | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- zaplogger/examples/calc/cmd/calc-cli/main.go | 4 ++-- 14 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cors/examples/calc/cmd/calc-cli/main.go b/cors/examples/calc/cmd/calc-cli/main.go index 3f4fb644a..88a807b36 100644 --- a/cors/examples/calc/cmd/calc-cli/main.go +++ b/cors/examples/calc/cmd/calc-cli/main.go @@ -68,7 +68,7 @@ func main() { case "http", "https": err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: - fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) + fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: http)\n", scheme) os.Exit(1) } } @@ -117,7 +117,7 @@ Usage: %s [-host HOST][-url URL][-timeout SECONDS][-verbose|-v] SERVICE ENDPOINT [flags] -host HOST: server host (localhost). valid values: localhost - -url URL: specify service URL overriding host URL (http://localhost:8080) + -url URL: specify service URL overriding host URL (http://localhost:80) -timeout: maximum number of seconds to wait for response (30) -verbose|-v: print request and response details (false) diff --git a/docs/examples/calc/cmd/calc-cli/main.go b/docs/examples/calc/cmd/calc-cli/main.go index 3f4fb644a..88a807b36 100644 --- a/docs/examples/calc/cmd/calc-cli/main.go +++ b/docs/examples/calc/cmd/calc-cli/main.go @@ -68,7 +68,7 @@ func main() { case "http", "https": err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: - fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) + fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: http)\n", scheme) os.Exit(1) } } @@ -117,7 +117,7 @@ Usage: %s [-host HOST][-url URL][-timeout SECONDS][-verbose|-v] SERVICE ENDPOINT [flags] -host HOST: server host (localhost). valid values: localhost - -url URL: specify service URL overriding host URL (http://localhost:8080) + -url URL: specify service URL overriding host URL (http://localhost:80) -timeout: maximum number of seconds to wait for response (30) -verbose|-v: print request and response details (false) diff --git a/go.mod b/go.mod index 30e689c64..2683ffa9b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 2d951f080..ad37944d4 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/cmd/calc-cli/main.go b/goakit/examples/calc/cmd/calc-cli/main.go index 072841dc2..6d4bf0dc8 100644 --- a/goakit/examples/calc/cmd/calc-cli/main.go +++ b/goakit/examples/calc/cmd/calc-cli/main.go @@ -68,7 +68,7 @@ func main() { case "http", "https": err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: - fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) + fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: http)\n", scheme) os.Exit(1) } } @@ -117,7 +117,7 @@ Usage: %s [-host HOST][-url URL][-timeout SECONDS][-verbose|-v] SERVICE ENDPOINT [flags] -host HOST: server host (localhost). valid values: localhost - -url URL: specify service URL overriding host URL (http://localhost:8080) + -url URL: specify service URL overriding host URL (http://localhost:80) -timeout: maximum number of seconds to wait for response (30) -verbose|-v: print request and response details (false) diff --git a/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go b/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go index dbf0fc6a2..b91c46331 100644 --- a/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go +++ b/goakit/examples/fetcher/archiver/cmd/archiver-cli/main.go @@ -68,7 +68,7 @@ func main() { case "http", "https": err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: - fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) + fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: http)\n", scheme) os.Exit(1) } } @@ -118,7 +118,7 @@ Usage: %s [-host HOST][-url URL][-timeout SECONDS][-verbose|-v] SERVICE ENDPOINT [flags] -host HOST: server host (localhost). valid values: localhost - -url URL: specify service URL overriding host URL (http://localhost:8080) + -url URL: specify service URL overriding host URL (http://localhost:80) -timeout: maximum number of seconds to wait for response (30) -verbose|-v: print request and response details (false) diff --git a/i18n/examples/calc/cmd/calc-cli/main.go b/i18n/examples/calc/cmd/calc-cli/main.go index 3f4fb644a..88a807b36 100644 --- a/i18n/examples/calc/cmd/calc-cli/main.go +++ b/i18n/examples/calc/cmd/calc-cli/main.go @@ -68,7 +68,7 @@ func main() { case "http", "https": err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: - fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|http)\n", scheme) + fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: http)\n", scheme) os.Exit(1) } } @@ -117,7 +117,7 @@ Usage: %s [-host HOST][-url URL][-timeout SECONDS][-verbose|-v] SERVICE ENDPOINT [flags] -host HOST: server host (localhost). valid values: localhost - -url URL: specify service URL overriding host URL (http://localhost:8080) + -url URL: specify service URL overriding host URL (http://localhost:80) -timeout: maximum number of seconds to wait for response (30) -verbose|-v: print request and response details (false) diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index b90832efd..ada9e1651 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 5b0064e8b..18875556e 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index bed148dba..5ce2e1a43 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 5b0064e8b..18875556e 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index e2cfc7bb6..5ae59f4cf 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 5b0064e8b..18875556e 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99 h1:R+nByHCAoWOMSqo3yJYs17RBTTgdwnf3aiszLqPmEzU= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830104908-eb6472c49e99/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/zaplogger/examples/calc/cmd/calc-cli/main.go b/zaplogger/examples/calc/cmd/calc-cli/main.go index 3cd8db60c..4c5eb8aa7 100644 --- a/zaplogger/examples/calc/cmd/calc-cli/main.go +++ b/zaplogger/examples/calc/cmd/calc-cli/main.go @@ -72,7 +72,7 @@ func main() { case "http", "https": err = doHTTP(context.Background(), scheme, host, timeout, debug, os.Stdout) default: - fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: grpc|grpcs|http|https)\n", scheme) + fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: http|https)\n", scheme) os.Exit(1) } } @@ -121,7 +121,7 @@ Usage: %s [-host HOST][-url URL][-timeout SECONDS][-verbose|-v][-version VERSION] SERVICE ENDPOINT [flags] -host HOST: server host (development). valid values: development, production - -url URL: specify service URL overriding host URL (http://localhost:8080) + -url URL: specify service URL overriding host URL (http://localhost:8000/calc) -timeout: maximum number of seconds to wait for response (30) -verbose|-v: print request and response details (false) -version: API version (v1) From 15a6cf586b3783b04403fd1eeea3fe8cb809d5f3 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Sun, 30 Aug 2026 12:34:16 -0700 Subject: [PATCH 27/30] chore: pin plugins to final Goa preview --- go.mod | 2 +- go.sum | 4 ++-- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2683ffa9b..16a8db65e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index ad37944d4..7444a2edf 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index ada9e1651..8cac6a0ce 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index 18875556e..c7a76c2cb 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 5ce2e1a43..545b141fc 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index 18875556e..c7a76c2cb 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 5ae59f4cf..8744fb913 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 + goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index 18875556e..c7a76c2cb 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260 h1:/xDlBPLY77lF13h194zLA3/5q8NU3y9UmF7VJCJbPB4= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830175414-d176af092260/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= +goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= From 75c642280eb180b25cc2e276259457d03d271676 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Sun, 30 Aug 2026 21:25:06 -0700 Subject: [PATCH 28/30] chore: use Goa preview 2 --- arnz/example/gen/goa.json | 2 +- cors/examples/calc/gen/goa.json | 2 +- docs/examples/calc/gen/goa.json | 2 +- go.mod | 2 +- go.sum | 4 ++-- goakit/examples/calc/gen/goa.json | 2 +- goakit/examples/fetcher/archiver/gen/goa.json | 2 +- goakit/examples/fetcher/fetcher/gen/goa.json | 2 +- i18n/examples/calc/gen/goa.json | 2 +- testing/examples/calculator/gen/goa.json | 2 +- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/gen/goa.json | 2 +- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/gen/goa.json | 2 +- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- tscap/example/gen/goa.json | 2 +- zaplogger/examples/calc/gen/goa.json | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/arnz/example/gen/goa.json b/arnz/example/gen/goa.json index 924afff50..789d4feec 100644 --- a/arnz/example/gen/goa.json +++ b/arnz/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/cors/examples/calc/gen/goa.json b/cors/examples/calc/gen/goa.json index 924afff50..789d4feec 100644 --- a/cors/examples/calc/gen/goa.json +++ b/cors/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/docs/examples/calc/gen/goa.json b/docs/examples/calc/gen/goa.json index 924afff50..789d4feec 100644 --- a/docs/examples/calc/gen/goa.json +++ b/docs/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/go.mod b/go.mod index 16a8db65e..6732f7b3b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 + goa.design/goa/v3 v3.31.0-preview.2 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 7444a2edf..fbc8c0a41 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= +goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/gen/goa.json b/goakit/examples/calc/gen/goa.json index 924afff50..789d4feec 100644 --- a/goakit/examples/calc/gen/goa.json +++ b/goakit/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/goakit/examples/fetcher/archiver/gen/goa.json b/goakit/examples/fetcher/archiver/gen/goa.json index 924afff50..789d4feec 100644 --- a/goakit/examples/fetcher/archiver/gen/goa.json +++ b/goakit/examples/fetcher/archiver/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/goakit/examples/fetcher/fetcher/gen/goa.json b/goakit/examples/fetcher/fetcher/gen/goa.json index 924afff50..789d4feec 100644 --- a/goakit/examples/fetcher/fetcher/gen/goa.json +++ b/goakit/examples/fetcher/fetcher/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/i18n/examples/calc/gen/goa.json b/i18n/examples/calc/gen/goa.json index 924afff50..789d4feec 100644 --- a/i18n/examples/calc/gen/goa.json +++ b/i18n/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/testing/examples/calculator/gen/goa.json b/testing/examples/calculator/gen/goa.json index 924afff50..789d4feec 100644 --- a/testing/examples/calculator/gen/goa.json +++ b/testing/examples/calculator/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 8cac6a0ce..9a370bae0 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 + goa.design/goa/v3 v3.31.0-preview.2 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index c7a76c2cb..b801712c3 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= +goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/gen/goa.json b/testing/examples/httpgrpc/gen/goa.json index 924afff50..789d4feec 100644 --- a/testing/examples/httpgrpc/gen/goa.json +++ b/testing/examples/httpgrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 545b141fc..97b953a61 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 + goa.design/goa/v3 v3.31.0-preview.2 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index c7a76c2cb..b801712c3 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= +goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/gen/goa.json b/testing/examples/jsonrpc/gen/goa.json index 924afff50..789d4feec 100644 --- a/testing/examples/jsonrpc/gen/goa.json +++ b/testing/examples/jsonrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 8744fb913..5528e863e 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 + goa.design/goa/v3 v3.31.0-preview.2 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index c7a76c2cb..b801712c3 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944 h1:1Ui2hEokREN21MBzSfcCsccb7Yq9z9IUFelMG3weKRQ= -goa.design/goa/v3 v3.31.0-preview.1.0.20260830192305-318c40614944/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= +goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/tscap/example/gen/goa.json b/tscap/example/gen/goa.json index 924afff50..789d4feec 100644 --- a/tscap/example/gen/goa.json +++ b/tscap/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file diff --git a/zaplogger/examples/calc/gen/goa.json b/zaplogger/examples/calc/gen/goa.json index 924afff50..789d4feec 100644 --- a/zaplogger/examples/calc/gen/goa.json +++ b/zaplogger/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.1" + "goa_version": "v3.31.0-preview.2" } \ No newline at end of file From b89aeccf8fde768513655d9047343ec9a1198b84 Mon Sep 17 00:00:00 2001 From: Raphael Simon Date: Sun, 30 Aug 2026 22:47:20 -0700 Subject: [PATCH 29/30] Pin Goa v3.31.0-preview.3 --- arnz/example/gen/goa.json | 2 +- cors/examples/calc/gen/goa.json | 2 +- docs/examples/calc/gen/goa.json | 2 +- go.mod | 2 +- go.sum | 4 ++-- goakit/examples/calc/gen/goa.json | 2 +- goakit/examples/fetcher/archiver/gen/goa.json | 2 +- goakit/examples/fetcher/fetcher/gen/goa.json | 2 +- i18n/examples/calc/gen/goa.json | 2 +- testing/examples/calculator/gen/goa.json | 2 +- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/gen/goa.json | 2 +- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/gen/goa.json | 2 +- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- tscap/example/gen/goa.json | 2 +- zaplogger/examples/calc/gen/goa.json | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/arnz/example/gen/goa.json b/arnz/example/gen/goa.json index 789d4feec..f3581f210 100644 --- a/arnz/example/gen/goa.json +++ b/arnz/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/cors/examples/calc/gen/goa.json b/cors/examples/calc/gen/goa.json index 789d4feec..f3581f210 100644 --- a/cors/examples/calc/gen/goa.json +++ b/cors/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/docs/examples/calc/gen/goa.json b/docs/examples/calc/gen/goa.json index 789d4feec..f3581f210 100644 --- a/docs/examples/calc/gen/goa.json +++ b/docs/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/go.mod b/go.mod index 6732f7b3b..ddf421635 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.2 + goa.design/goa/v3 v3.31.0-preview.3 goa.design/model v1.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index fbc8c0a41..3b597a5f0 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= -goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= +goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= goa.design/model v1.15.0 h1:MpcH1PmqBPrba3g9Mgq0AgnE8dzDyq+uZXHbzZ19TZ0= goa.design/model v1.15.0/go.mod h1:+T9CJxwS56wiy8AoPyE4l4CbvasozJwbI7c3BkqZvDc= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/gen/goa.json b/goakit/examples/calc/gen/goa.json index 789d4feec..f3581f210 100644 --- a/goakit/examples/calc/gen/goa.json +++ b/goakit/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/goakit/examples/fetcher/archiver/gen/goa.json b/goakit/examples/fetcher/archiver/gen/goa.json index 789d4feec..f3581f210 100644 --- a/goakit/examples/fetcher/archiver/gen/goa.json +++ b/goakit/examples/fetcher/archiver/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/goakit/examples/fetcher/fetcher/gen/goa.json b/goakit/examples/fetcher/fetcher/gen/goa.json index 789d4feec..f3581f210 100644 --- a/goakit/examples/fetcher/fetcher/gen/goa.json +++ b/goakit/examples/fetcher/fetcher/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/i18n/examples/calc/gen/goa.json b/i18n/examples/calc/gen/goa.json index 789d4feec..f3581f210 100644 --- a/i18n/examples/calc/gen/goa.json +++ b/i18n/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/testing/examples/calculator/gen/goa.json b/testing/examples/calculator/gen/goa.json index 789d4feec..f3581f210 100644 --- a/testing/examples/calculator/gen/goa.json +++ b/testing/examples/calculator/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index 9a370bae0..723b2b2d0 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.2 + goa.design/goa/v3 v3.31.0-preview.3 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index b801712c3..3d0baab17 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= -goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= +goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/gen/goa.json b/testing/examples/httpgrpc/gen/goa.json index 789d4feec..f3581f210 100644 --- a/testing/examples/httpgrpc/gen/goa.json +++ b/testing/examples/httpgrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index 97b953a61..03acfb1af 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.2 + goa.design/goa/v3 v3.31.0-preview.3 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.1 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index b801712c3..3d0baab17 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= -goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= +goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/gen/goa.json b/testing/examples/jsonrpc/gen/goa.json index 789d4feec..f3581f210 100644 --- a/testing/examples/jsonrpc/gen/goa.json +++ b/testing/examples/jsonrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 5528e863e..fd3e84697 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.2 + goa.design/goa/v3 v3.31.0-preview.3 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index b801712c3..3d0baab17 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.2 h1:yBJcqQO5sCrp0uQCIKe+Zo4/ByGAxn4fIPrL8wKeAnA= -goa.design/goa/v3 v3.31.0-preview.2/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= +goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/tscap/example/gen/goa.json b/tscap/example/gen/goa.json index 789d4feec..f3581f210 100644 --- a/tscap/example/gen/goa.json +++ b/tscap/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file diff --git a/zaplogger/examples/calc/gen/goa.json b/zaplogger/examples/calc/gen/goa.json index 789d4feec..f3581f210 100644 --- a/zaplogger/examples/calc/gen/goa.json +++ b/zaplogger/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.2" + "goa_version": "v3.31.0-preview.3" } \ No newline at end of file From da88124d899ec1b3f20dc25f6fd7616dcda4c2ee Mon Sep 17 00:00:00 2001 From: Raphael Simon Date: Wed, 16 Sep 2026 00:14:59 -0700 Subject: [PATCH 30/30] Sync plugins with the stable Goa generation candidate --- README.md | 15 +++++++++++++++ arnz/example/gen/goa.json | 2 +- cors/examples/calc/gen/goa.json | 2 +- docs/examples/calc/gen/goa.json | 2 +- go.mod | 2 +- go.sum | 4 ++-- goakit/examples/calc/gen/goa.json | 2 +- goakit/examples/fetcher/archiver/gen/goa.json | 2 +- goakit/examples/fetcher/fetcher/gen/goa.json | 2 +- i18n/examples/calc/gen/goa.json | 2 +- testing/examples/calculator/gen/goa.json | 2 +- .../gen/grpc/calculator/client/types.go | 3 --- .../gen/grpc/calculator/server/types.go | 3 --- testing/examples/calculator/go.mod | 2 +- testing/examples/calculator/go.sum | 4 ++-- testing/examples/httpgrpc/gen/goa.json | 2 +- testing/examples/httpgrpc/go.mod | 2 +- testing/examples/httpgrpc/go.sum | 4 ++-- testing/examples/jsonrpc/gen/goa.json | 2 +- testing/examples/jsonrpc/go.mod | 2 +- testing/examples/jsonrpc/go.sum | 4 ++-- tscap/example/gen/goa.json | 2 +- zaplogger/examples/calc/gen/goa.json | 2 +- 23 files changed, 39 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 96d395936..d0d5700ea 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,19 @@ This repository contains plugins for Each sub-directory README contains the information and usage for the given plugin. +Plugins v3.31.0 accompanies Goa v3.31.0 and requires Go 1.26 or later. +Upgrade both modules together: + +```shell +go get goa.design/goa/v3@v3.31.0 goa.design/plugins/v3@v3.31.0 +go install goa.design/goa/v3/cmd/goa@v3.31.0 +``` + +Regenerate the complete application after upgrading. This release adapts +declaration-producing plugins to Goa's generation plan and updates generated +commands, transport helpers, and test scenarios. Custom plugins that declare +names must plan those declarations before rendering. Read the +[Goa upgrade guide](https://github.com/goadesign/goa/blob/v3.31.0/UPGRADING.md) +for application changes and the generator API migration. + Goa v2 plugins can be found [here](https://github.com/goadesign/plugins/tree/v2). diff --git a/arnz/example/gen/goa.json b/arnz/example/gen/goa.json index f3581f210..8358dc795 100644 --- a/arnz/example/gen/goa.json +++ b/arnz/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/cors/examples/calc/gen/goa.json b/cors/examples/calc/gen/goa.json index f3581f210..8358dc795 100644 --- a/cors/examples/calc/gen/goa.json +++ b/cors/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/docs/examples/calc/gen/goa.json b/docs/examples/calc/gen/goa.json index f3581f210..8358dc795 100644 --- a/docs/examples/calc/gen/goa.json +++ b/docs/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/go.mod b/go.mod index 1f4c20efe..ddb9bff12 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/stretchr/testify v1.12.1 go.uber.org/zap v1.28.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.3 + goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 goa.design/model v1.16.10 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 5f1da84a9..3cb083d7c 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= -goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 h1:cPGzdpnxPKn1Xj46xCwJ6YOJkMmQ85a9bh2dS9NpC0g= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4/go.mod h1:d0bJ2ryjJh9pe3wWp6MD8szkKA48RspjKFPj0tDWpCs= goa.design/model v1.16.10 h1:6GssUUTJiY8VLBb8C18iSg9DjYaQc7vfOC+XAn90hjQ= goa.design/model v1.16.10/go.mod h1:3NogFOJDnZrQkbwr6FZCKwW82X8WWTq+YcYUy7kt8s8= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/goakit/examples/calc/gen/goa.json b/goakit/examples/calc/gen/goa.json index f3581f210..8358dc795 100644 --- a/goakit/examples/calc/gen/goa.json +++ b/goakit/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/goakit/examples/fetcher/archiver/gen/goa.json b/goakit/examples/fetcher/archiver/gen/goa.json index f3581f210..8358dc795 100644 --- a/goakit/examples/fetcher/archiver/gen/goa.json +++ b/goakit/examples/fetcher/archiver/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/goakit/examples/fetcher/fetcher/gen/goa.json b/goakit/examples/fetcher/fetcher/gen/goa.json index f3581f210..8358dc795 100644 --- a/goakit/examples/fetcher/fetcher/gen/goa.json +++ b/goakit/examples/fetcher/fetcher/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/i18n/examples/calc/gen/goa.json b/i18n/examples/calc/gen/goa.json index f3581f210..8358dc795 100644 --- a/i18n/examples/calc/gen/goa.json +++ b/i18n/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/testing/examples/calculator/gen/goa.json b/testing/examples/calculator/gen/goa.json index f3581f210..8358dc795 100644 --- a/testing/examples/calculator/gen/goa.json +++ b/testing/examples/calculator/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/testing/examples/calculator/gen/grpc/calculator/client/types.go b/testing/examples/calculator/gen/grpc/calculator/client/types.go index dddd58262..50a847798 100644 --- a/testing/examples/calculator/gen/grpc/calculator/client/types.go +++ b/testing/examples/calculator/gen/grpc/calculator/client/types.go @@ -197,9 +197,6 @@ func ValidateFactorialResponse(message *calculatorpb.FactorialResponse) (err err // ValidateStatisticsRequest runs the validations defined on StatisticsRequest. func ValidateStatisticsRequest(message *calculatorpb.StatisticsRequest) (err error) { - if message.Numbers == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("numbers", "message")) - } if len(message.Numbers) < 1 { err = goa.MergeErrors(err, goa.InvalidLengthError("message.numbers", message.Numbers, len(message.Numbers), 1, true)) } diff --git a/testing/examples/calculator/gen/grpc/calculator/server/types.go b/testing/examples/calculator/gen/grpc/calculator/server/types.go index dee4b3b24..d02b71534 100644 --- a/testing/examples/calculator/gen/grpc/calculator/server/types.go +++ b/testing/examples/calculator/gen/grpc/calculator/server/types.go @@ -164,9 +164,6 @@ func ValidateFactorialRequest(message *calculatorpb.FactorialRequest) (err error // ValidateStatisticsRequest runs the validations defined on StatisticsRequest. func ValidateStatisticsRequest(message *calculatorpb.StatisticsRequest) (err error) { - if message.Numbers == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("numbers", "message")) - } if len(message.Numbers) < 1 { err = goa.MergeErrors(err, goa.InvalidLengthError("message.numbers", message.Numbers, len(message.Numbers), 1, true)) } diff --git a/testing/examples/calculator/go.mod b/testing/examples/calculator/go.mod index ef5219ba4..d55d3b1c1 100644 --- a/testing/examples/calculator/go.mod +++ b/testing/examples/calculator/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.3 + goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 goa.design/plugins/v3 v3.21.5 google.golang.org/grpc v1.83.2 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/calculator/go.sum b/testing/examples/calculator/go.sum index babfa51a0..419b49387 100644 --- a/testing/examples/calculator/go.sum +++ b/testing/examples/calculator/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= -goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 h1:cPGzdpnxPKn1Xj46xCwJ6YOJkMmQ85a9bh2dS9NpC0g= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4/go.mod h1:d0bJ2ryjJh9pe3wWp6MD8szkKA48RspjKFPj0tDWpCs= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/httpgrpc/gen/goa.json b/testing/examples/httpgrpc/gen/goa.json index f3581f210..8358dc795 100644 --- a/testing/examples/httpgrpc/gen/goa.json +++ b/testing/examples/httpgrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/testing/examples/httpgrpc/go.mod b/testing/examples/httpgrpc/go.mod index ad5132fd4..c4d93f066 100644 --- a/testing/examples/httpgrpc/go.mod +++ b/testing/examples/httpgrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/gorilla/websocket v1.5.3 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.3 + goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 goa.design/plugins/v3 v3.0.0 google.golang.org/grpc v1.83.2 google.golang.org/protobuf v1.36.12 diff --git a/testing/examples/httpgrpc/go.sum b/testing/examples/httpgrpc/go.sum index babfa51a0..419b49387 100644 --- a/testing/examples/httpgrpc/go.sum +++ b/testing/examples/httpgrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= -goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 h1:cPGzdpnxPKn1Xj46xCwJ6YOJkMmQ85a9bh2dS9NpC0g= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4/go.mod h1:d0bJ2ryjJh9pe3wWp6MD8szkKA48RspjKFPj0tDWpCs= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/testing/examples/jsonrpc/gen/goa.json b/testing/examples/jsonrpc/gen/goa.json index f3581f210..8358dc795 100644 --- a/testing/examples/jsonrpc/gen/goa.json +++ b/testing/examples/jsonrpc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/testing/examples/jsonrpc/go.mod b/testing/examples/jsonrpc/go.mod index 539ecfc22..7a9ca5c67 100644 --- a/testing/examples/jsonrpc/go.mod +++ b/testing/examples/jsonrpc/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/google/uuid v1.6.0 goa.design/clue v1.2.6 - goa.design/goa/v3 v3.31.0-preview.3 + goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 goa.design/plugins/v3 v3.21.5 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/testing/examples/jsonrpc/go.sum b/testing/examples/jsonrpc/go.sum index babfa51a0..419b49387 100644 --- a/testing/examples/jsonrpc/go.sum +++ b/testing/examples/jsonrpc/go.sum @@ -64,8 +64,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= goa.design/clue v1.2.6 h1:ensyUkwlvEbNy8FuTGMldKuvaNouh8YoDY9QaE+EtYg= goa.design/clue v1.2.6/go.mod h1:Y4RS5o2k6MZiGk2PT3XbKyntjR2xkr8dYOKQ591dIDM= -goa.design/goa/v3 v3.31.0-preview.3 h1:E0vYOE/KbU7eogc+RJ7WXIDZGIbUkueC/i5KAzuhI/k= -goa.design/goa/v3 v3.31.0-preview.3/go.mod h1:XpqR5Ho75FrEGDZd6o/hIWnv7lN/TUHudl2QxfM1kFU= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4 h1:cPGzdpnxPKn1Xj46xCwJ6YOJkMmQ85a9bh2dS9NpC0g= +goa.design/goa/v3 v3.31.0-preview.5.0.20260916070337-f03a5d261cc4/go.mod h1:d0bJ2ryjJh9pe3wWp6MD8szkKA48RspjKFPj0tDWpCs= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/tscap/example/gen/goa.json b/tscap/example/gen/goa.json index f3581f210..8358dc795 100644 --- a/tscap/example/gen/goa.json +++ b/tscap/example/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file diff --git a/zaplogger/examples/calc/gen/goa.json b/zaplogger/examples/calc/gen/goa.json index f3581f210..8358dc795 100644 --- a/zaplogger/examples/calc/gen/goa.json +++ b/zaplogger/examples/calc/gen/goa.json @@ -1,3 +1,3 @@ { - "goa_version": "v3.31.0-preview.3" + "goa_version": "v3.31.0-preview.5" } \ No newline at end of file