├── .deepsource.toml ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── dependency-review.yml │ ├── report-coverage.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .golint_exclude ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── RELEASE.md ├── cmd └── goa │ ├── gen.go │ ├── main.go │ └── main_test.go ├── codegen ├── cli │ └── cli.go ├── codegentest │ └── codegentest.go ├── doc.go ├── example │ ├── docs.go │ ├── example_client.go │ ├── example_client_test.go │ ├── example_server.go │ ├── example_server_test.go │ ├── server_data.go │ ├── templates.go │ ├── templates │ │ ├── client_end.go.tpl │ │ ├── client_endpoint_init.go.tpl │ │ ├── client_start.go.tpl │ │ ├── client_usage.go.tpl │ │ ├── client_var_init.go.tpl │ │ ├── server_end.go.tpl │ │ ├── server_endpoints.go.tpl │ │ ├── server_handler.go.tpl │ │ ├── server_interceptors.go.tpl │ │ ├── server_interrupts.go.tpl │ │ ├── server_logger.go.tpl │ │ ├── server_services.go.tpl │ │ └── server_start.go.tpl │ └── testdata │ │ ├── client-no-server.golden │ │ ├── client-single-server-multiple-hosts-with-variables.golden │ │ ├── client-single-server-multiple-hosts.golden │ │ ├── client-single-server-single-host-with-variables.golden │ │ ├── client-single-server-single-host.golden │ │ ├── dsls.go │ │ ├── server-no-server.golden │ │ ├── server-same-api-service-name.golden │ │ ├── server-sercice-for-only-grpc.golden │ │ ├── server-server-hosting-multiple-services.golden │ │ ├── server-server-hosting-service-subset.golden │ │ ├── server-server-hosting-service-with-file-server.golden │ │ ├── server-service-for-http-and-part-of-grpc.golden │ │ ├── server-service-for-only-http.golden │ │ ├── server-service-name-with-spaces.golden │ │ ├── server-single-server-multiple-hosts-with-variables.golden │ │ ├── server-single-server-multiple-hosts.golden │ │ ├── server-single-server-single-host-with-variables.golden │ │ └── server-single-server-single-host.golden ├── file.go ├── funcs.go ├── funcs_test.go ├── generator │ ├── docs.go │ ├── example.go │ ├── generate.go │ ├── generators.go │ ├── openapi.go │ ├── service.go │ └── transport.go ├── go_transform.go ├── go_transform_helpers_test.go ├── go_transform_test.go ├── go_transform_union_test.go ├── goify.go ├── goify_test.go ├── header.go ├── import.go ├── import_test.go ├── plugin.go ├── plugin_test.go ├── scope.go ├── scope_test.go ├── sections_test.go ├── service │ ├── client.go │ ├── client_test.go │ ├── convert.go │ ├── convert_test.go │ ├── docs.go │ ├── endpoint.go │ ├── endpoint_test.go │ ├── example_interceptors.go │ ├── example_interceptors_test.go │ ├── example_svc.go │ ├── example_svc_test.go │ ├── interceptors.go │ ├── interceptors.md │ ├── interceptors_test.go │ ├── security_test.go │ ├── service.go │ ├── service_data.go │ ├── service_test.go │ ├── templates.go │ ├── templates │ │ ├── client_interceptor_stream_wrapper_types.go.tpl │ │ ├── client_interceptor_stream_wrappers.go.tpl │ │ ├── client_interceptor_wrappers.go.tpl │ │ ├── client_interceptors.go.tpl │ │ ├── client_wrappers.go.tpl │ │ ├── convert.go.tpl │ │ ├── create.go.tpl │ │ ├── endpoint.go.tpl │ │ ├── endpoint_wrappers.go.tpl │ │ ├── error.go.tpl │ │ ├── error_init.go.tpl │ │ ├── example_client_interceptor.go.tpl │ │ ├── example_security_authfuncs.go.tpl │ │ ├── example_server_interceptor.go.tpl │ │ ├── example_service_init.go.tpl │ │ ├── example_service_struct.go.tpl │ │ ├── interceptors.go.tpl │ │ ├── interceptors_types.go.tpl │ │ ├── payload.go.tpl │ │ ├── result.go.tpl │ │ ├── return_type_init.go.tpl │ │ ├── server_interceptor_stream_wrapper_types.go.tpl │ │ ├── server_interceptor_stream_wrappers.go.tpl │ │ ├── server_interceptor_wrappers.go.tpl │ │ ├── server_interceptors.go.tpl │ │ ├── service.go.tpl │ │ ├── service_client.go.tpl │ │ ├── service_client_init.go.tpl │ │ ├── service_client_method.go.tpl │ │ ├── service_endpoint_method.go.tpl │ │ ├── service_endpoint_stream_struct.go.tpl │ │ ├── service_endpoints.go.tpl │ │ ├── service_endpoints_init.go.tpl │ │ ├── service_endpoints_use.go.tpl │ │ ├── service_request_body_struct.go.tpl │ │ ├── service_response_body_struct.go.tpl │ │ ├── streaming_payload.go.tpl │ │ ├── transform_helper.go.tpl │ │ ├── type_init.go.tpl │ │ ├── type_validate.go.tpl │ │ ├── union_value_method.go.tpl │ │ ├── user_type.go.tpl │ │ ├── validate.go.tpl │ │ └── viewed_type_map.go.tpl │ ├── testdata │ │ ├── alias-external │ │ │ └── alias.go │ │ ├── client_code.go │ │ ├── convert_dsls.go │ │ ├── convert_functions.go │ │ ├── create_dsls.go │ │ ├── create_functions.go │ │ ├── endpoint_code.go │ │ ├── endpoint_dsls.go │ │ ├── example_interceptors │ │ │ ├── api_interceptor_service_client.golden │ │ │ ├── api_interceptor_service_server.golden │ │ │ ├── chained_interceptor_service_client.golden │ │ │ ├── chained_interceptor_service_server.golden │ │ │ ├── client_interceptor_service_client.golden │ │ │ ├── multiple_interceptors_service_client.golden │ │ │ ├── multiple_interceptors_service_server.golden │ │ │ ├── multiple_services_interceptors_service2_client.golden │ │ │ ├── multiple_services_interceptors_service2_server.golden │ │ │ ├── multiple_services_interceptors_service_client.golden │ │ │ ├── multiple_services_interceptors_service_server.golden │ │ │ ├── server_interceptor_by_name_service_server.golden │ │ │ └── server_interceptor_service_server.golden │ │ ├── example_interceptors_dsls.go │ │ ├── example_svc_dsls.go │ │ ├── external │ │ │ └── external.go │ │ ├── interceptors │ │ │ ├── interceptor-with-read-payload_client_interceptors.go.golden │ │ │ ├── interceptor-with-read-payload_interceptor_wrappers.go.golden │ │ │ ├── interceptor-with-read-payload_service_interceptors.go.golden │ │ │ ├── interceptor-with-read-result_client_interceptors.go.golden │ │ │ ├── interceptor-with-read-result_interceptor_wrappers.go.golden │ │ │ ├── interceptor-with-read-result_service_interceptors.go.golden │ │ │ ├── interceptor-with-read-write-payload_client_interceptors.go.golden │ │ │ ├── interceptor-with-read-write-payload_interceptor_wrappers.go.golden │ │ │ ├── interceptor-with-read-write-payload_service_interceptors.go.golden │ │ │ ├── interceptor-with-read-write-result_client_interceptors.go.golden │ │ │ ├── interceptor-with-read-write-result_interceptor_wrappers.go.golden │ │ │ ├── interceptor-with-read-write-result_service_interceptors.go.golden │ │ │ ├── interceptor-with-write-payload_client_interceptors.go.golden │ │ │ ├── interceptor-with-write-payload_interceptor_wrappers.go.golden │ │ │ ├── interceptor-with-write-payload_service_interceptors.go.golden │ │ │ ├── interceptor-with-write-result_client_interceptors.go.golden │ │ │ ├── interceptor-with-write-result_interceptor_wrappers.go.golden │ │ │ ├── interceptor-with-write-result_service_interceptors.go.golden │ │ │ ├── multiple-interceptors_client_interceptors.go.golden │ │ │ ├── multiple-interceptors_interceptor_wrappers.go.golden │ │ │ ├── multiple-interceptors_service_interceptors.go.golden │ │ │ ├── single-api-server-interceptor_interceptor_wrappers.go.golden │ │ │ ├── single-api-server-interceptor_service_interceptors.go.golden │ │ │ ├── single-client-interceptor_client_interceptors.go.golden │ │ │ ├── single-client-interceptor_interceptor_wrappers.go.golden │ │ │ ├── single-method-server-interceptor_interceptor_wrappers.go.golden │ │ │ ├── single-method-server-interceptor_service_interceptors.go.golden │ │ │ ├── single-service-server-interceptor_interceptor_wrappers.go.golden │ │ │ ├── single-service-server-interceptor_service_interceptors.go.golden │ │ │ ├── streaming-interceptors-with-read-payload-and-read-streaming-payload_client_interceptors.go.golden │ │ │ ├── streaming-interceptors-with-read-payload-and-read-streaming-payload_interceptor_wrappers.go.golden │ │ │ ├── streaming-interceptors-with-read-payload-and-read-streaming-payload_service_interceptors.go.golden │ │ │ ├── streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden │ │ │ ├── streaming-interceptors-with-read-payload_service_interceptors.go.golden │ │ │ ├── streaming-interceptors-with-read-result_interceptor_wrappers.go.golden │ │ │ ├── streaming-interceptors-with-read-result_service_interceptors.go.golden │ │ │ ├── streaming-interceptors-with-read-streaming-result_client_interceptors.go.golden │ │ │ ├── streaming-interceptors-with-read-streaming-result_interceptor_wrappers.go.golden │ │ │ ├── streaming-interceptors-with-read-streaming-result_service_interceptors.go.golden │ │ │ ├── streaming-interceptors_client_interceptors.go.golden │ │ │ ├── streaming-interceptors_interceptor_wrappers.go.golden │ │ │ └── streaming-interceptors_service_interceptors.go.golden │ │ ├── interceptors_dsls.go │ │ ├── security_dsls.go │ │ ├── security_functions.go │ │ ├── service_code.go │ │ ├── service_dsls.go │ │ ├── types.go │ │ ├── views_code.go │ │ └── views_dsls.go │ ├── testing.go │ ├── views.go │ └── views_test.go ├── testdata │ ├── types_dsl.go │ ├── union_dsls.go │ ├── validation_code.go │ └── validation_types_dsl.go ├── testing.go ├── transformer.go ├── transformer_test.go ├── types.go ├── types_test.go ├── validation.go ├── validation_test.go └── walk.go ├── docs ├── Wizard.png ├── incidentio.png ├── moved.md ├── speakeasy.png ├── zuplo-dark.png └── zuplo.png ├── dsl ├── _spec │ ├── dsl_spec_test.go │ └── type_spec_test.go ├── api.go ├── attribute.go ├── convert.go ├── description.go ├── description_test.go ├── doc.go ├── error.go ├── grpc.go ├── headers.go ├── http.go ├── http_file_server.go ├── http_redirect.go ├── interceptor.go ├── interceptor_test.go ├── meta.go ├── meta_test.go ├── method.go ├── method_test.go ├── payload.go ├── response.go ├── result.go ├── result_type.go ├── result_type_test.go ├── security.go ├── server.go ├── service.go ├── sse.go ├── types.go ├── user_type.go ├── validation.go ├── validation_test.go └── value.go ├── eval ├── context.go ├── doc.go ├── error.go ├── eval.go ├── eval_test.go ├── expression.go └── expression_test.go ├── expr ├── api.go ├── api_test.go ├── attribute.go ├── attribute_test.go ├── docs.go ├── dup.go ├── equal_test.go ├── example.go ├── example_test.go ├── grpc.go ├── grpc_endpoint.go ├── grpc_endpoint_test.go ├── grpc_error.go ├── grpc_response.go ├── grpc_service.go ├── hasher.go ├── hasher_test.go ├── helpers.go ├── http.go ├── http_body_types.go ├── http_cookie_test.go ├── http_endpoint.go ├── http_endpoint_test.go ├── http_error.go ├── http_error_test.go ├── http_file_server.go ├── http_file_server_test.go ├── http_redirect.go ├── http_redirect_test.go ├── http_response.go ├── http_response_test.go ├── http_service.go ├── http_sse.go ├── http_sse_test.go ├── init.go ├── interceptor.go ├── interceptor_test.go ├── mapped_attribute.go ├── method.go ├── method_test.go ├── project_test.go ├── random.go ├── result_type.go ├── result_type_test.go ├── result_types_root.go ├── root.go ├── root_test.go ├── security.go ├── security_test.go ├── server.go ├── server_test.go ├── service.go ├── service_test.go ├── testdata │ ├── cookie_dsls.go │ ├── endpoint_dsls.go │ ├── example_test_dsls.go │ ├── http_body_types.go │ ├── http_file_server.go │ ├── interceptors_validate_dsls.go │ ├── method_validate_dsls.go │ └── service_validate_dsls.go ├── testing.go ├── types.go ├── types_test.go ├── user_type.go └── user_type_test.go ├── go.mod ├── go.sum ├── grpc ├── client.go ├── codegen │ ├── client.go │ ├── client_cli.go │ ├── client_cli_test.go │ ├── client_test.go │ ├── client_types.go │ ├── client_types_test.go │ ├── doc.go │ ├── example_cli.go │ ├── example_cli_test.go │ ├── example_server.go │ ├── example_server_test.go │ ├── funcs.go │ ├── parse_endpoint_test.go │ ├── proto.go │ ├── proto_test.go │ ├── protobuf.go │ ├── protobuf_test.go │ ├── protobuf_transform.go │ ├── protobuf_transform_test.go │ ├── server.go │ ├── server_test.go │ ├── server_types.go │ ├── server_types_test.go │ ├── service_data.go │ ├── streaming_test.go │ ├── templates.go │ ├── templates │ │ ├── client_endpoint_init.go.tpl │ │ ├── client_init.go.tpl │ │ ├── client_struct.go.tpl │ │ ├── do_grpc_cli.go.tpl │ │ ├── grpc_handler_init.go.tpl │ │ ├── grpc_message.go.tpl │ │ ├── grpc_service.go.tpl │ │ ├── parse_endpoint.go.tpl │ │ ├── partial │ │ │ ├── convert_string_to_type.go.tpl │ │ │ └── convert_type_to_string.go.tpl │ │ ├── proto_header.go.tpl │ │ ├── proto_start.go.tpl │ │ ├── remote_method_builder.go.tpl │ │ ├── request_decoder.go.tpl │ │ ├── request_encoder.go.tpl │ │ ├── response_decoder.go.tpl │ │ ├── response_encoder.go.tpl │ │ ├── server_grpc_end.go.tpl │ │ ├── server_grpc_init.go.tpl │ │ ├── server_grpc_interface.go.tpl │ │ ├── server_grpc_register.go.tpl │ │ ├── server_grpc_start.go.tpl │ │ ├── server_init.go.tpl │ │ ├── server_struct_type.go.tpl │ │ ├── stream_close.go.tpl │ │ ├── stream_recv.go.tpl │ │ ├── stream_send.go.tpl │ │ ├── stream_set_view.go.tpl │ │ ├── stream_struct_type.go.tpl │ │ ├── transform_go_array.go.tpl │ │ ├── transform_go_map.go.tpl │ │ ├── transform_go_union_from_proto.go.tpl │ │ ├── transform_go_union_to_proto.go.tpl │ │ ├── transform_helper.go.tpl │ │ ├── type_init.go.tpl │ │ └── validate.go.tpl │ ├── testdata │ │ ├── client-interceptors.golden │ │ ├── client-no-server-pkgpath.golden │ │ ├── client-no-server.golden │ │ ├── client-server-hosting-multiple-services-pkgpath.golden │ │ ├── client-server-hosting-multiple-services.golden │ │ ├── client-server-hosting-service-subset-pkgpath.golden │ │ ├── client-server-hosting-service-subset.golden │ │ ├── client_cli_code.go │ │ ├── client_endpoint_init_code.go │ │ ├── client_type_code.go │ │ ├── dsls.go │ │ ├── endpoint-endpoint-with-interceptors.golden │ │ ├── proto_code.go │ │ ├── protoc │ │ │ └── main.go │ │ ├── request_decoder_code.go │ │ ├── request_encoder_code.go │ │ ├── response_decoder_code.go │ │ ├── response_encoder_code.go │ │ ├── server-no-server.golden │ │ ├── server-server-hosting-multiple-services.golden │ │ ├── server-server-hosting-service-subset.golden │ │ ├── server_handler_init_code.go │ │ ├── server_interface_code.go │ │ ├── server_type_code.go │ │ └── streaming_code.go │ └── testing.go ├── doc.go ├── docs │ └── FAQ.md ├── encoding.go ├── error.go ├── handler.go ├── middleware │ ├── canceler.go │ ├── canceler_test.go │ ├── doc.go │ ├── helpers.go │ ├── log.go │ ├── requestid.go │ ├── requestid_test.go │ ├── trace.go │ ├── trace_test.go │ └── xray │ │ ├── doc.go │ │ ├── middleware.go │ │ ├── middleware_test.go │ │ ├── segment.go │ │ └── segment_test.go └── pb │ ├── doc.go │ ├── goadesign_goa_error.pb.go │ └── goadesign_goa_error.proto ├── http ├── client.go ├── client_test.go ├── codegen │ ├── client.go │ ├── client_body_types_test.go │ ├── client_cli.go │ ├── client_cli_test.go │ ├── client_decode_test.go │ ├── client_encode_test.go │ ├── client_init_test.go │ ├── client_types.go │ ├── doc.go │ ├── example_cli.go │ ├── example_cli_test.go │ ├── example_server.go │ ├── example_server_test.go │ ├── funcs.go │ ├── funcs_test.go │ ├── handler_test.go │ ├── multipart_test.go │ ├── openapi.go │ ├── openapi │ │ ├── doc.go │ │ ├── docs.go │ │ ├── extensions.go │ │ ├── json_schema.go │ │ ├── marshal.go │ │ ├── merge.go │ │ ├── tags.go │ │ ├── v2 │ │ │ ├── builder.go │ │ │ ├── builder_test.go │ │ │ ├── doc.go │ │ │ ├── files.go │ │ │ ├── files_test.go │ │ │ ├── openapi.go │ │ │ └── testdata │ │ │ │ ├── TestExtensions │ │ │ │ ├── endpoint_file0.golden │ │ │ │ └── endpoint_file1.golden │ │ │ │ ├── TestSections │ │ │ │ ├── additional-properties-embedded-payload-result_file0.golden │ │ │ │ ├── additional-properties-embedded-payload-result_file1.golden │ │ │ │ ├── additional-properties-payload-result_file0.golden │ │ │ │ ├── additional-properties-payload-result_file1.golden │ │ │ │ ├── additional-properties-type_file0.golden │ │ │ │ ├── additional-properties-type_file1.golden │ │ │ │ ├── empty_file0.golden │ │ │ │ ├── empty_file1.golden │ │ │ │ ├── explicit-view_file0.golden │ │ │ │ ├── explicit-view_file1.golden │ │ │ │ ├── file-service_file0.golden │ │ │ │ ├── file-service_file1.golden │ │ │ │ ├── headers_file0.golden │ │ │ │ ├── headers_file1.golden │ │ │ │ ├── json-indent_file0.golden │ │ │ │ ├── json-indent_file1.golden │ │ │ │ ├── json-prefix-indent_file0.golden │ │ │ │ ├── json-prefix-indent_file1.golden │ │ │ │ ├── json-prefix_file0.golden │ │ │ │ ├── json-prefix_file1.golden │ │ │ │ ├── multiple-services_file0.golden │ │ │ │ ├── multiple-services_file1.golden │ │ │ │ ├── multiple-views_file0.golden │ │ │ │ ├── multiple-views_file1.golden │ │ │ │ ├── not-generate-attribute_file0.golden │ │ │ │ ├── not-generate-attribute_file1.golden │ │ │ │ ├── not-generate-host_file0.golden │ │ │ │ ├── not-generate-host_file1.golden │ │ │ │ ├── not-generate-server_file0.golden │ │ │ │ ├── not-generate-server_file1.golden │ │ │ │ ├── path-with-multiple-explicit-wildcards_file0.golden │ │ │ │ ├── path-with-multiple-explicit-wildcards_file1.golden │ │ │ │ ├── path-with-multiple-wildcards_file0.golden │ │ │ │ ├── path-with-multiple-wildcards_file1.golden │ │ │ │ ├── path-with-wildcards_file0.golden │ │ │ │ ├── path-with-wildcards_file1.golden │ │ │ │ ├── security_file0.golden │ │ │ │ ├── security_file1.golden │ │ │ │ ├── server-host-with-variables_file0.golden │ │ │ │ ├── server-host-with-variables_file1.golden │ │ │ │ ├── typename_file0.golden │ │ │ │ ├── typename_file1.golden │ │ │ │ ├── valid_file0.golden │ │ │ │ ├── valid_file1.golden │ │ │ │ ├── with-any_file0.golden │ │ │ │ ├── with-any_file1.golden │ │ │ │ ├── with-map_file0.golden │ │ │ │ ├── with-map_file1.golden │ │ │ │ ├── with-spaces_file0.golden │ │ │ │ └── with-spaces_file1.golden │ │ │ │ └── TestValidations │ │ │ │ ├── array_file0.golden │ │ │ │ ├── array_file1.golden │ │ │ │ ├── integer_file0.golden │ │ │ │ ├── integer_file1.golden │ │ │ │ ├── string_file0.golden │ │ │ │ └── string_file1.golden │ │ └── v3 │ │ │ ├── builder.go │ │ │ ├── builder_test.go │ │ │ ├── doc.go │ │ │ ├── example.go │ │ │ ├── files.go │ │ │ ├── files_test.go │ │ │ ├── openapi.go │ │ │ ├── parameters.go │ │ │ ├── ref.go │ │ │ ├── response.go │ │ │ ├── testdata │ │ │ ├── dsls │ │ │ │ ├── operations.go │ │ │ │ └── types.go │ │ │ └── golden │ │ │ │ ├── array_file0.golden │ │ │ │ ├── array_file1.golden │ │ │ │ ├── empty_file0.golden │ │ │ │ ├── empty_file1.golden │ │ │ │ ├── endpoint_file0.golden │ │ │ │ ├── endpoint_file1.golden │ │ │ │ ├── error-examples_file0.golden │ │ │ │ ├── error-examples_file1.golden │ │ │ │ ├── explicit-view_file0.golden │ │ │ │ ├── explicit-view_file1.golden │ │ │ │ ├── file-service_file0.golden │ │ │ │ ├── file-service_file1.golden │ │ │ │ ├── headers_file0.golden │ │ │ │ ├── headers_file1.golden │ │ │ │ ├── integer_file0.golden │ │ │ │ ├── integer_file1.golden │ │ │ │ ├── json-indent_file0.golden │ │ │ │ ├── json-indent_file1.golden │ │ │ │ ├── json-prefix-indent_file0.golden │ │ │ │ ├── json-prefix-indent_file1.golden │ │ │ │ ├── json-prefix_file0.golden │ │ │ │ ├── json-prefix_file1.golden │ │ │ │ ├── multiple-services_file0.golden │ │ │ │ ├── multiple-services_file1.golden │ │ │ │ ├── multiple-views_file0.golden │ │ │ │ ├── multiple-views_file1.golden │ │ │ │ ├── not-generate-attribute_file0.golden │ │ │ │ ├── not-generate-attribute_file1.golden │ │ │ │ ├── not-generate-host_file0.golden │ │ │ │ ├── not-generate-host_file1.golden │ │ │ │ ├── not-generate-server_file0.golden │ │ │ │ ├── not-generate-server_file1.golden │ │ │ │ ├── path-with-multiple-explicit-wildcards_file0.golden │ │ │ │ ├── path-with-multiple-explicit-wildcards_file1.golden │ │ │ │ ├── path-with-multiple-wildcards_file0.golden │ │ │ │ ├── path-with-multiple-wildcards_file1.golden │ │ │ │ ├── path-with-wildcards_file0.golden │ │ │ │ ├── path-with-wildcards_file1.golden │ │ │ │ ├── security_file0.golden │ │ │ │ ├── security_file1.golden │ │ │ │ ├── server-host-with-variables_file0.golden │ │ │ │ ├── server-host-with-variables_file1.golden │ │ │ │ ├── skip-response-body-encode-decode_file0.golden │ │ │ │ ├── skip-response-body-encode-decode_file1.golden │ │ │ │ ├── string_file0.golden │ │ │ │ ├── string_file1.golden │ │ │ │ ├── typename_file0.golden │ │ │ │ ├── typename_file1.golden │ │ │ │ ├── valid_file0.golden │ │ │ │ ├── valid_file1.golden │ │ │ │ ├── with-any_file0.golden │ │ │ │ ├── with-any_file1.golden │ │ │ │ ├── with-map_file0.golden │ │ │ │ ├── with-map_file1.golden │ │ │ │ ├── with-spaces_file0.golden │ │ │ │ ├── with-spaces_file1.golden │ │ │ │ ├── with-tags_file0.golden │ │ │ │ └── with-tags_file1.golden │ │ │ ├── types.go │ │ │ └── types_test.go │ ├── openapi_test.go │ ├── paths.go │ ├── paths_test.go │ ├── server.go │ ├── server_decode_test.go │ ├── server_encode_test.go │ ├── server_error_encoder_test.go │ ├── server_handler_test.go │ ├── server_init_test.go │ ├── server_mount_test.go │ ├── server_payload_types_test.go │ ├── server_types.go │ ├── server_types_test.go │ ├── service_data.go │ ├── sse.go │ ├── sse_client.go │ ├── sse_server_test.go │ ├── streaming_test.go │ ├── templates.go │ ├── templates │ │ ├── append_fs.go.tpl │ │ ├── build_stream_request.go.tpl │ │ ├── cli_end.go.tpl │ │ ├── cli_start.go.tpl │ │ ├── cli_streaming.go.tpl │ │ ├── cli_usage.go.tpl │ │ ├── client_body_init.go.tpl │ │ ├── client_init.go.tpl │ │ ├── client_sse.go.tpl │ │ ├── client_struct.go.tpl │ │ ├── client_type_init.go.tpl │ │ ├── dummy_multipart_request_decoder.go.tpl │ │ ├── dummy_multipart_request_encoder.go.tpl │ │ ├── endpoint_init.go.tpl │ │ ├── error_encoder.go.tpl │ │ ├── file_server.go.tpl │ │ ├── mount_point_struct.go.tpl │ │ ├── multipart_request_decoder.go.tpl │ │ ├── multipart_request_decoder_type.go.tpl │ │ ├── multipart_request_encoder.go.tpl │ │ ├── multipart_request_encoder_type.go.tpl │ │ ├── parse_endpoint.go.tpl │ │ ├── partial │ │ │ ├── client_map_conversion.go.tpl │ │ │ ├── client_type_conversion.go.tpl │ │ │ ├── element_slice_conversion.go.tpl │ │ │ ├── header_conversion.go.tpl │ │ │ ├── path_conversion.go.tpl │ │ │ ├── query_map_conversion.go.tpl │ │ │ ├── query_slice_conversion.go.tpl │ │ │ ├── query_type_conversion.go.tpl │ │ │ ├── request_elements.go.tpl │ │ │ ├── response.go.tpl │ │ │ ├── single_response.go.tpl │ │ │ ├── slice_item_conversion.go.tpl │ │ │ ├── sse_format.go.tpl │ │ │ ├── sse_parse.go.tpl │ │ │ └── websocket_upgrade.go.tpl │ │ ├── path.go.tpl │ │ ├── path_init.go.tpl │ │ ├── request_builder.go.tpl │ │ ├── request_decoder.go.tpl │ │ ├── request_encoder.go.tpl │ │ ├── request_init.go.tpl │ │ ├── response_decoder.go.tpl │ │ ├── response_encoder.go.tpl │ │ ├── server_body_init.go.tpl │ │ ├── server_configure.go.tpl │ │ ├── server_encoding.go.tpl │ │ ├── server_end.go.tpl │ │ ├── server_error_handler.go.tpl │ │ ├── server_handler.go.tpl │ │ ├── server_handler_init.go.tpl │ │ ├── server_init.go.tpl │ │ ├── server_method_names.go.tpl │ │ ├── server_middleware.go.tpl │ │ ├── server_mount.go.tpl │ │ ├── server_mux.go.tpl │ │ ├── server_service.go.tpl │ │ ├── server_sse.go.tpl │ │ ├── server_start.go.tpl │ │ ├── server_struct.go.tpl │ │ ├── server_type_init.go.tpl │ │ ├── server_use.go.tpl │ │ ├── transform_helper.go.tpl │ │ ├── type_decl.go.tpl │ │ ├── validate.go.tpl │ │ ├── websocket_close.go.tpl │ │ ├── websocket_conn_configurer_struct.go.tpl │ │ ├── websocket_conn_configurer_struct_init.go.tpl │ │ ├── websocket_recv.go.tpl │ │ ├── websocket_send.go.tpl │ │ ├── websocket_set_view.go.tpl │ │ └── websocket_struct_type.go.tpl │ ├── testdata │ │ ├── client_init_functions.go │ │ ├── client_request_build_functions.go │ │ ├── error_encoder_code.go │ │ ├── error_response_dsls.go │ │ ├── golden │ │ │ ├── client-no-server.golden │ │ │ ├── client-server-hosting-multiple-services.golden │ │ │ ├── client-server-hosting-service-subset.golden │ │ │ ├── client-streaming-multiple-services.golden │ │ │ ├── client-streaming.golden │ │ │ ├── server-no-server.golden │ │ │ ├── server-server-hosting-multiple-services.golden │ │ │ ├── server-server-hosting-service-subset.golden │ │ │ ├── server-server-hosting-service-with-file-server.golden │ │ │ ├── server-streaming.golden │ │ │ ├── sse-all-fields.golden │ │ │ ├── sse-bool.golden │ │ │ ├── sse-data-field.golden │ │ │ ├── sse-data-id-field.golden │ │ │ ├── sse-int.golden │ │ │ ├── sse-object.golden │ │ │ ├── sse-request-id.golden │ │ │ └── sse-string.golden │ │ ├── handler_init_functions.go │ │ ├── multi_endpoint_dsls.go │ │ ├── multipart_code.go │ │ ├── openapi_dsls.go │ │ ├── parse_endpoint_functions.go │ │ ├── path_dsls.go │ │ ├── path_functions.go │ │ ├── payload_constructor_functions.go │ │ ├── payload_decode_functions.go │ │ ├── payload_dsls.go │ │ ├── payload_encode_functions.go │ │ ├── result_decode_functions.go │ │ ├── result_dsls.go │ │ ├── result_encode_functions.go │ │ ├── result_encode_types.go │ │ ├── server_dsls.go │ │ ├── server_init_functions.go │ │ ├── sse_dsls.go │ │ ├── streaming_aliased_array_dsls.go │ │ ├── streaming_code.go │ │ ├── streaming_dsls.go │ │ └── transform_helper_functions.go │ ├── testing.go │ ├── transform_helper_test.go │ ├── typedef.go │ ├── typedef_test.go │ └── websocket.go ├── doc.go ├── encoding.go ├── encoding_test.go ├── error.go ├── middleware │ ├── capture.go │ ├── chi.go │ ├── chi_test.go │ ├── context.go │ ├── ctxkeys.go │ ├── debug.go │ ├── doc.go │ ├── log.go │ ├── request.go │ ├── requestid.go │ ├── requestid_test.go │ ├── trace.go │ ├── trace_test.go │ └── xray │ │ ├── doc.go │ │ ├── middleware.go │ │ ├── middleware_test.go │ │ ├── segment.go │ │ ├── segment_test.go │ │ ├── wrap_doer.go │ │ ├── wrap_doer_test.go │ │ ├── wrap_transport.go │ │ └── wrap_transport_test.go ├── mux.go ├── mux_test.go ├── server.go └── websocket.go ├── middleware ├── ctxkeys.go ├── doc.go ├── log.go ├── requestid.go ├── sampler.go ├── sampler_test.go ├── trace.go ├── trace_test.go └── xray │ ├── segment.go │ ├── segment_test.go │ ├── xray.go │ ├── xray_test.go │ └── xraytest │ └── testing.go ├── pkg ├── doc.go ├── endpoint.go ├── error.go ├── error_test.go ├── interceptor.go ├── skip_response_writer.go ├── skip_response_writer_test.go ├── validation.go ├── validation_test.go ├── version.go └── version_test.go ├── security └── scheme.go └── staticcheck.conf /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | [[analyzers]] 3 | name = "test-coverage" 4 | enabled = true 5 | [[analyzers]] 6 | name = "go" 7 | 8 | [analyzers.meta] 9 | import_root = "goa.design/goa/v3" -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: goadesign 4 | github: [raphael, tchssk] 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v4 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v4 21 | -------------------------------------------------------------------------------- /.github/workflows/report-coverage.yml: -------------------------------------------------------------------------------- 1 | name: Report Test Coverage 2 | 3 | on: 4 | workflow_run: 5 | workflows: 6 | - Run Static Checks and Tests 7 | types: [completed] 8 | 9 | jobs: 10 | report: 11 | runs-on: ubuntu-latest 12 | if: github.event.workflow_run.conclusion == 'success' 13 | 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.event.workflow_run.head_sha }} 19 | 20 | - name: Download test coverage 21 | uses: dawidd6/action-download-artifact@v9 22 | with: 23 | workflow: test.yml 24 | name: coverage 25 | 26 | - name: Report analysis to DeepSource 27 | run: | 28 | curl https://deepsource.io/cli | sh 29 | ./bin/deepsource report --analyzer test-coverage --key go --value-file ./cover.out 30 | env: 31 | DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Static Checks and Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - v3 7 | pull_request: 8 | branches: 9 | - v3 10 | 11 | jobs: 12 | ci: 13 | strategy: 14 | fail-fast: true 15 | matrix: 16 | go: ['1.23', '1.24'] 17 | os: ['ubuntu-latest', 'windows-latest'] 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - name: Check out code 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up Go ${{ matrix.go }} 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: ${{ matrix.go }} 28 | id: go 29 | 30 | - name: Build 31 | run: make ci 32 | 33 | - name: Upload test coverage for deep source 34 | if: matrix.go == '1.23' && matrix.os == 'ubuntu-latest' 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: coverage 38 | path: cover.out 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Golang tools artifacts 2 | **/*.test 3 | coverage.out 4 | test-report.json 5 | 6 | # Executables and test outputs 7 | cmd/goa/goa 8 | 9 | # Editor / IDEs cruft 10 | .idea/ 11 | *.iml 12 | .vscode/ 13 | *~ 14 | *.orig 15 | *.swp 16 | 17 | # DeepSource cruft 18 | cover.out 19 | 20 | # MacOS cruft 21 | **/.DS_Store 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - errorlint 5 | - errcheck 6 | - staticcheck 7 | 8 | -------------------------------------------------------------------------------- /.golint_exclude: -------------------------------------------------------------------------------- 1 | dsl[/\]http.go 2 | expr[/\]http_response.go 3 | codegen[/\]service[/\]testing[/\].* 4 | http[/\]codegen[/\]codegen[/\].openapi[/\]v3[/\]ref.go 5 | http[/\]codegen[/\]testing[/\].* 6 | http[/\]middleware[/\]trace.go 7 | http[/\]middleware[/\]xray[/\]middleware.go 8 | http[/\]middleware[/\]xray[/\]wrap_doer.go 9 | http[/\]middleware[/\]xray[/\]wrap_doer_test.go 10 | grpc[/\]pb[/\].* 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | dist: xenial 3 | go: 4 | - 1.14.x 5 | if: branch = v2 6 | install: 7 | - export PATH=${PATH}:${HOME}/gopath/bin 8 | script: 9 | - export PATH=${PATH}:${HOME}/bin 10 | - cd ../../.. && mkdir goa.design && cp -r github.com/goadesign/goa goa.design/goa 11 | - cd goa.design/goa 12 | # https://graysonkoonce.com/getting-the-current-branch-name-during-a-pull-request-in-travis-ci/ 13 | - export GOA_BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) && echo $GOA_BRANCH 14 | - make travis 15 | notifications: 16 | slack: 17 | secure: bMYXaoSEGoNdqR0t1VnMAv/4V9PSOhEWyekdJM7p9WmKjJi2yKy0k77uRmwf+5Mrz5GLs3CkZnDha/8cSFld3KEN9SC6QYmIBF/1Pd/5mKHFQOI81i7sTlhrdMv897+6sofEtbBNq1jffhVGVttbMrMWwCTNZu0NrCGBVsDmb44= 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Raphael Simon and goa Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing Goa 2 | 3 | This document is intended to help Goa maintainers release new versions of Goa. 4 | 5 | ## Using `make release` 6 | 7 | 1. Update `MAJOR`, `MINOR` and `BUILD` as needed in `Makefile`. 8 | 2. Run `make release` 9 | 10 | ## Manual release procedure 11 | 12 | 1. Update `MAJOR`, `MINOR` and `BUILD` as needed in `Makefile`. 13 | 2. Update `pkg/version.go` and `README.md` to reflect the new version. 14 | 3. Commit and push to v3. 15 | 4. Create and push release git tag. 16 | 5. Update `go.mod` in the examples repo `master` branch. 17 | 6. Run `make` in the examples repo. 18 | 7. Push the examples repo `master` branch. 19 | 8. Create and push release git tag. 20 | 9. Update `go.mod` in the plugins repo `v3` branch. 21 | 10. Run `make` in the plugins repo. 22 | 11. Create and push release git tag. 23 | -------------------------------------------------------------------------------- /codegen/codegentest/codegentest.go: -------------------------------------------------------------------------------- 1 | // Package codegentest provides utilities to assist writing unit test for 2 | // codegen packages. 3 | package codegentest 4 | 5 | import ( 6 | "strings" 7 | 8 | "goa.design/goa/v3/codegen" 9 | ) 10 | 11 | // Sections can be used to extract the code sections that match a path suffix 12 | // and a section name. 13 | func Sections(files []*codegen.File, pathSuffix string, sectionName string) []*codegen.SectionTemplate { 14 | var result []*codegen.SectionTemplate 15 | for _, file := range files { 16 | if !strings.HasSuffix(file.Path, pathSuffix) { 17 | continue 18 | } 19 | 20 | for _, section := range file.SectionTemplates { 21 | if section.Name == sectionName { 22 | result = append(result, section) 23 | } 24 | } 25 | } 26 | return result 27 | } 28 | -------------------------------------------------------------------------------- /codegen/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package codegen contains data structures and algorithms used by the Goa code 3 | generation tool. 4 | 5 | In particular package codegen defines the data structure that represents a 6 | generated file (see File) which is composed of sections, each corresponding to a 7 | Go text template and accompanying data used to render the final code. 8 | 9 | The package also includes functions that generate code to transform a 10 | given type into another (see GoTransform). 11 | */ 12 | package codegen 13 | -------------------------------------------------------------------------------- /codegen/example/docs.go: -------------------------------------------------------------------------------- 1 | // Package example contains code generation algorithms to produce an example 2 | // server and client implementation for the transports defined in the design. 3 | package example 4 | -------------------------------------------------------------------------------- /codegen/example/templates.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "embed" 5 | "path" 6 | ) 7 | 8 | //go:embed templates/* 9 | var tmplFS embed.FS 10 | 11 | // readTemplate returns the example template with the given name. 12 | func readTemplate(name string) string { 13 | content, err := tmplFS.ReadFile(path.Join("templates", name) + ".go.tpl") 14 | if err != nil { 15 | panic("failed to load template " + name + ": " + err.Error()) // Should never happen, bug if it does 16 | } 17 | return string(content) 18 | } 19 | -------------------------------------------------------------------------------- /codegen/example/templates/client_end.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | data, err := endpoint(context.Background(), payload) 4 | if err != nil { 5 | fmt.Fprintln(os.Stderr, err.Error()) 6 | os.Exit(1) 7 | } 8 | 9 | if data != nil { 10 | m, _ := json.MarshalIndent(data, "", " ") 11 | fmt.Println(string(m)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /codegen/example/templates/client_endpoint_init.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | var( 3 | endpoint goa.Endpoint 4 | payload any 5 | err error 6 | ) 7 | { 8 | switch scheme { 9 | {{- range $t := .Server.Transports }} 10 | case "{{ $t.Type }}", "{{ $t.Type }}s": 11 | endpoint, payload, err = do{{ toUpper $t.Name }}(scheme, host, timeout, debug) 12 | {{- end }} 13 | default: 14 | fmt.Fprintf(os.Stderr, "invalid scheme: %q (valid schemes: {{ join .Server.Schemes "|" }})\n", scheme) 15 | os.Exit(1) 16 | } 17 | } 18 | if err != nil { 19 | if err == flag.ErrHelp { 20 | os.Exit(0) 21 | } 22 | fmt.Fprintln(os.Stderr, err.Error()) 23 | fmt.Fprintln(os.Stderr, "run '"+os.Args[0]+" --help' for detailed usage.") 24 | os.Exit(1) 25 | } 26 | -------------------------------------------------------------------------------- /codegen/example/templates/client_start.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | func main() { 3 | var ( 4 | hostF = flag.String("host", {{ printf "%q" .Server.DefaultHost.Name }}, "Server host (valid values: {{ (join .Server.AvailableHosts ", ") }})") 5 | addrF = flag.String("url", "", "URL to service host") 6 | {{ range .Server.Variables }} 7 | {{ .VarName }}F = flag.String({{ printf "%q" .Name }}, {{ printf "%q" .DefaultValue }}, {{ printf "%q" .Description }}) 8 | {{- end }} 9 | verboseF = flag.Bool("verbose", false, "Print request and response details") 10 | vF = flag.Bool("v", false, "Print request and response details") 11 | timeoutF = flag.Int("timeout", 30, "Maximum number of seconds to wait for response") 12 | ) 13 | flag.Usage = usage 14 | flag.Parse() 15 | -------------------------------------------------------------------------------- /codegen/example/templates/client_usage.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | func usage() { 4 | fmt.Fprintf(os.Stderr, `%s is a command line client for the {{ .APIName }} API. 5 | 6 | Usage: 7 | %s [-host HOST][-url URL][-timeout SECONDS][-verbose|-v]{{ range .Server.Variables }}[-{{ .Name }} {{ toUpper .Name }}]{{ end }} SERVICE ENDPOINT [flags] 8 | 9 | -host HOST: server host ({{ .Server.DefaultHost.Name }}). valid values: {{ (join .Server.AvailableHosts ", ") }} 10 | -url URL: specify service URL overriding host URL (http://localhost:8080) 11 | -timeout: maximum number of seconds to wait for response (30) 12 | -verbose|-v: print request and response details (false) 13 | {{- range .Server.Variables }} 14 | -{{ .Name }}: {{ .Description }} ({{ .DefaultValue }}) 15 | {{- end }} 16 | 17 | Commands: 18 | %s 19 | Additional help: 20 | %s SERVICE [ENDPOINT] --help 21 | 22 | Example: 23 | %s 24 | `, os.Args[0], os.Args[0], indent({{ .Server.DefaultTransport.Type }}UsageCommands()), os.Args[0], indent({{ .Server.DefaultTransport.Type }}UsageExamples())) 25 | } 26 | 27 | func indent(s string) string { 28 | if s == "" { 29 | return "" 30 | } 31 | return " " + strings.ReplaceAll(s, "\n", "\n ") 32 | } 33 | -------------------------------------------------------------------------------- /codegen/example/templates/server_end.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ comment "Wait for signal." }} 4 | log.Printf(ctx, "exiting (%v)", <-errc) 5 | 6 | {{ comment "Send cancellation signal to the goroutines." }} 7 | cancel() 8 | 9 | wg.Wait() 10 | log.Printf(ctx, "exited") 11 | } 12 | -------------------------------------------------------------------------------- /codegen/example/templates/server_endpoints.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if mustInitServices .Services }} 2 | 3 | {{ comment "Wrap the services in endpoints that can be invoked from other services potentially running in different processes." }} 4 | var ( 5 | {{- range .Services }} 6 | {{- if .Methods }} 7 | {{ .VarName }}Endpoints *{{ .PkgName }}.Endpoints 8 | {{- end }} 9 | {{- end }} 10 | ) 11 | { 12 | {{- range .Services }} 13 | {{- if .Methods }} 14 | {{ .VarName }}Endpoints = {{ .PkgName }}.NewEndpoints({{ .VarName }}Svc{{ if .ServerInterceptors }}, {{ .VarName }}Interceptors{{ end }}) 15 | {{ .VarName }}Endpoints.Use(debug.LogPayloads()) 16 | {{ .VarName }}Endpoints.Use(log.Endpoint) 17 | {{- end }} 18 | {{- end }} 19 | } 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /codegen/example/templates/server_interceptors.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if mustInitServices .Services }} 2 | {{- if .HasInterceptors }} 3 | {{ comment "Initialize the interceptors." }} 4 | var ( 5 | {{- range .Services }} 6 | {{- if and .Methods .ServerInterceptors }} 7 | {{ .VarName }}Interceptors {{ .PkgName }}.ServerInterceptors 8 | {{- end }} 9 | {{- end }} 10 | ) 11 | { 12 | {{- range .Services }} 13 | {{- if and .Methods .ServerInterceptors }} 14 | {{ .VarName }}Interceptors = {{ $.InterPkg }}.New{{ .StructName }}ServerInterceptors() 15 | {{- end }} 16 | {{- end }} 17 | } 18 | {{- end }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /codegen/example/templates/server_interrupts.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Create channel used by both the signal handler and server goroutines 4 | // to notify the main goroutine when to stop the server. 5 | errc := make(chan error) 6 | 7 | // Setup interrupt handler. This optional step configures the process so 8 | // that SIGINT and SIGTERM signals cause the services to stop gracefully. 9 | go func() { 10 | c := make(chan os.Signal, 1) 11 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 12 | errc <- fmt.Errorf("%s", <-c) 13 | }() 14 | 15 | var wg sync.WaitGroup 16 | ctx, cancel := context.WithCancel(ctx) 17 | -------------------------------------------------------------------------------- /codegen/example/templates/server_logger.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ comment "Setup logger. Replace logger with your own log package of choice." }} 4 | format := log.FormatJSON 5 | if log.IsTerminal() { 6 | format = log.FormatTerminal 7 | } 8 | ctx := log.Context(context.Background(), log.WithFormat(format)) 9 | if *dbgF { 10 | ctx = log.Context(ctx, log.WithDebug()) 11 | log.Debugf(ctx, "debug logs enabled") 12 | } 13 | log.Print(ctx, log.KV{K: "http-port", V: *httpPortF}) 14 | -------------------------------------------------------------------------------- /codegen/example/templates/server_services.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if mustInitServices .Services }} 2 | 3 | {{ comment "Initialize the services." }} 4 | var ( 5 | {{- range .Services }} 6 | {{- if .Methods }} 7 | {{ .VarName }}Svc {{ .PkgName }}.Service 8 | {{- end }} 9 | {{- end }} 10 | ) 11 | { 12 | {{- range .Services }} 13 | {{- if .Methods }} 14 | {{ .VarName }}Svc = {{ $.APIPkg }}.New{{ .StructName }}() 15 | {{- end }} 16 | {{- end }} 17 | } 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /codegen/example/templates/server_start.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | func main() { 3 | {{ comment "Define command line flags, add any other flag required to configure the service." }} 4 | var( 5 | hostF = flag.String("host", {{ printf "%q" .Server.DefaultHost.Name }}, "Server host (valid values: {{ (join .Server.AvailableHosts ", ") }})") 6 | domainF = flag.String("domain", "", "Host domain name (overrides host domain specified in service design)") 7 | {{- range .Server.Transports }} 8 | {{ .Type }}PortF = flag.String("{{ .Type }}-port", "", "{{ .Name }} port (overrides host {{ .Name }} port specified in service design)") 9 | {{- end }} 10 | {{- range .Server.Variables }} 11 | {{ .VarName }}F = flag.String({{ printf "%q" .Name }}, {{ printf "%q" .DefaultValue }}, "{{ .Description }}{{ if .Values }} (valid values: {{ join .Values ", " }}){{ end }}") 12 | {{- end }} 13 | secureF = flag.Bool("secure", false, "Use secure scheme (https or grpcs)") 14 | dbgF = flag.Bool("debug", false, "Log request and response bodies") 15 | ) 16 | flag.Parse() 17 | -------------------------------------------------------------------------------- /codegen/generator/docs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package generator contains the code generation algorithms for a service server, 3 | client, and OpenAPI specification. 4 | 5 | # Server and Client 6 | 7 | The code generated for the service server and client includes: 8 | 9 | - A `service` package that contains the declarations for the service 10 | interfaces and endpoints which wrap the service methods. 11 | - A `views` package that contains code to render a result type using a view. 12 | - transport specific packages for each of the transports defined in the 13 | design. 14 | - An example implementation of the client, server, and the service. 15 | 16 | # OpenAPI 17 | 18 | The OpenAPI generator generates a OpenAPI v2 specification for the service 19 | REST endpoints. This generator requires the design to define the HTTP transport. 20 | */ 21 | package generator 22 | -------------------------------------------------------------------------------- /codegen/generator/generators.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | 6 | "goa.design/goa/v3/codegen" 7 | "goa.design/goa/v3/eval" 8 | ) 9 | 10 | // Genfunc is the type of the functions invoked to generate code. 11 | type Genfunc func(genpkg string, roots []eval.Root) ([]*codegen.File, error) 12 | 13 | // Generators returns the qualified paths (including the package name) to the 14 | // code generator functions for the given command, an error if the command is 15 | // not supported. Generators is a public variable so that external code (e.g. 16 | // plugins) may override the default generators. 17 | var Generators = generators 18 | 19 | // generators returns the generator functions exposed by the generator package 20 | // for the given command. 21 | func generators(cmd string) ([]Genfunc, error) { 22 | switch cmd { 23 | case "gen": 24 | return []Genfunc{Service, Transport, OpenAPI}, nil 25 | case "example": 26 | return []Genfunc{Example}, nil 27 | default: 28 | return nil, fmt.Errorf("unknown command %q", cmd) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /codegen/generator/openapi.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "goa.design/goa/v3/codegen" 5 | "goa.design/goa/v3/eval" 6 | "goa.design/goa/v3/expr" 7 | httpcodegen "goa.design/goa/v3/http/codegen" 8 | ) 9 | 10 | // OpenAPI iterates through the roots and returns the files needed to render 11 | // the service OpenAPI spec. It produces OpenAPI specifications only if the 12 | // roots define a HTTP service. 13 | func OpenAPI(_ string, roots []eval.Root) ([]*codegen.File, error) { 14 | for _, root := range roots { 15 | if r, ok := root.(*expr.RootExpr); ok { 16 | return httpcodegen.OpenAPIFiles(r) 17 | } 18 | } 19 | return nil, nil 20 | } 21 | -------------------------------------------------------------------------------- /codegen/goify_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFixReservedGo(t *testing.T) { 10 | cases := map[string]struct { 11 | w string 12 | want string 13 | }{ 14 | "predeclared type": {w: "bool", want: "bool_"}, 15 | "predeclared constant": {w: "true", want: "true_"}, 16 | "predeclared zero value": {w: "nil", want: "nil_"}, 17 | "predeclared function": {w: "append", want: "append_"}, 18 | "non predeclared identifier": {w: "foo", want: "foo"}, 19 | "package": {w: "fmt", want: "fmt_"}, 20 | } 21 | for k, tc := range cases { 22 | t.Run(k, func(t *testing.T) { 23 | assert.Equal(t, tc.want, fixReservedGo(tc.w)) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /codegen/service/docs.go: -------------------------------------------------------------------------------- 1 | // Package service contains the code generation algorithms to produce code for 2 | // the service and views packages and dummy implementation for the services 3 | // defined in the design. 4 | package service 5 | -------------------------------------------------------------------------------- /codegen/service/templates.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "embed" 5 | "path" 6 | ) 7 | 8 | //go:embed templates/* 9 | var tmplFS embed.FS 10 | 11 | // readTemplate returns the service template with the given name. 12 | func readTemplate(name string) string { 13 | content, err := tmplFS.ReadFile(path.Join("templates", name) + ".go.tpl") 14 | if err != nil { 15 | panic("failed to load template " + name + ": " + err.Error()) // Should never happen, bug if it does 16 | } 17 | return string(content) 18 | } 19 | -------------------------------------------------------------------------------- /codegen/service/templates/client_interceptor_stream_wrapper_types.go.tpl: -------------------------------------------------------------------------------- 1 | {{- range .WrappedClientStreams }} 2 | 3 | {{ comment (printf "wrapped%s is a client interceptor wrapper for the %s stream." .Interface .Interface) }} 4 | type wrapped{{ .Interface }} struct { 5 | ctx context.Context 6 | {{- if ne .SendTypeRef "" }} 7 | sendWithContext func(context.Context, {{ .SendTypeRef }}) error 8 | {{- end }} 9 | {{- if ne .RecvTypeRef "" }} 10 | recvWithContext func(context.Context) ({{ .RecvTypeRef }}, error) 11 | {{- end }} 12 | stream {{ .Interface }} 13 | } 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /codegen/service/templates/client_interceptors.go.tpl: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | {{- range .ClientInterceptors }} 7 | {{- if .Description }} 8 | {{ comment .Description }} 9 | {{- end }} 10 | {{ .Name }}(ctx context.Context, info *{{ .Name }}Info, next goa.Endpoint) (any, error) 11 | {{- end }} 12 | } 13 | -------------------------------------------------------------------------------- /codegen/service/templates/client_wrappers.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{ comment (printf "Wrap%sClientEndpoint wraps the %s endpoint with the client interceptors defined in the design." .MethodVarName .Method) }} 3 | func Wrap{{ .MethodVarName }}ClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 4 | {{- range .Interceptors }} 5 | endpoint = wrapClient{{ $.MethodVarName }}{{ . }}(endpoint, i) 6 | {{- end }} 7 | return endpoint 8 | } 9 | -------------------------------------------------------------------------------- /codegen/service/templates/convert.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s creates an instance of %s initialized from t." .Name .TypeName | comment }} 2 | func (t {{ .ReceiverTypeRef }}) {{ .Name }}() {{ .TypeRef }} { 3 | {{ .Code }} 4 | return v 5 | } 6 | -------------------------------------------------------------------------------- /codegen/service/templates/create.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s initializes t from the fields of v" .Name | comment }} 2 | func (t {{ .ReceiverTypeRef }}) {{ .Name }}(v {{ .TypeRef }}) { 3 | {{ .Code }} 4 | *t = *temp 5 | } 6 | -------------------------------------------------------------------------------- /codegen/service/templates/endpoint_wrappers.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment (printf "Wrap%sEndpoint wraps the %s endpoint with the server-side interceptors defined in the design." .MethodVarName .Method) }} 2 | func Wrap{{ .MethodVarName }}Endpoint(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 3 | {{- range .Interceptors }} 4 | endpoint = wrap{{ $.MethodVarName }}{{ . }}(endpoint, i) 5 | {{- end }} 6 | return endpoint 7 | } 8 | -------------------------------------------------------------------------------- /codegen/service/templates/error.go.tpl: -------------------------------------------------------------------------------- 1 | // Error returns an error description. 2 | func (e {{ .Ref }}) Error() string { 3 | return {{ printf "%q" .Description }} 4 | } 5 | 6 | // ErrorName returns {{ printf "%q" .Name }}. 7 | // 8 | // Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 9 | func (e {{ .Ref }}) ErrorName() string { 10 | return e.GoaErrorName() 11 | } 12 | 13 | // GoaErrorName returns {{ printf "%q" .Name }}. 14 | func (e {{ .Ref }}) GoaErrorName() string { 15 | return {{ errorName . }} 16 | } 17 | -------------------------------------------------------------------------------- /codegen/service/templates/error_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s builds a %s from an error." .Name .TypeName | comment }} 2 | func {{ .Name }}(err error) {{ .TypeRef }} { 3 | return goa.NewServiceError(err, {{ printf "%q" .ErrName }}, {{ printf "%v" .Timeout }}, {{ printf "%v" .Temporary}}, {{ printf "%v" .Fault}}) 4 | } 5 | -------------------------------------------------------------------------------- /codegen/service/templates/example_client_interceptor.go.tpl: -------------------------------------------------------------------------------- 1 | // {{ .StructName }}ClientInterceptors implements the client interceptors for the {{ .ServiceName }} service. 2 | type {{ .StructName }}ClientInterceptors struct { 3 | } 4 | 5 | // New{{ .StructName }}ClientInterceptors creates a new client interceptor for the {{ .ServiceName }} service. 6 | func New{{ .StructName }}ClientInterceptors() *{{ .StructName }}ClientInterceptors { 7 | return &{{ .StructName }}ClientInterceptors{} 8 | } 9 | 10 | {{- range .ClientInterceptors }} 11 | {{- if .Description }} 12 | {{ comment .Description }} 13 | {{- end }} 14 | func (i *{{ $.StructName }}ClientInterceptors) {{ .Name }}(ctx context.Context, info *{{ $.PkgName }}.{{ .Name }}Info, next goa.Endpoint) (any, error) { 15 | log.Printf(ctx, "[{{ .Name }}] Sending request: %v", info.RawPayload()) 16 | resp, err := next(ctx, info.RawPayload()) 17 | if err != nil { 18 | log.Printf(ctx, "[{{ .Name }}] Error: %v", err) 19 | return nil, err 20 | } 21 | log.Printf(ctx, "[{{ .Name }}] Received response: %v", resp) 22 | return resp, nil 23 | } 24 | {{- end }} 25 | -------------------------------------------------------------------------------- /codegen/service/templates/example_security_authfuncs.go.tpl: -------------------------------------------------------------------------------- 1 | {{ range .Schemes }} 2 | {{ printf "%sAuth implements the authorization logic for service %q for the %q security scheme." .Type $.Name .SchemeName | comment }} 3 | func (s *{{ $.VarName }}srvc) {{ .Type }}Auth(ctx context.Context, {{ if eq .Type "Basic" }}user, pass{{ else if eq .Type "APIKey" }}key{{ else }}token{{ end }} string, scheme *security.{{ .Type }}Scheme) (context.Context, error) { 4 | // 5 | // TBD: add authorization logic. 6 | // 7 | // In case of authorization failure this function should return 8 | // one of the generated error structs, e.g.: 9 | // 10 | // return ctx, myservice.MakeUnauthorizedError("invalid token") 11 | // 12 | // Alternatively this function may return an instance of 13 | // goa.ServiceError with a Name field value that matches one of 14 | // the design error names, e.g: 15 | // 16 | // return ctx, goa.PermanentError("unauthorized", "invalid token") 17 | // 18 | return ctx, fmt.Errorf("not implemented") 19 | } 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /codegen/service/templates/example_server_interceptor.go.tpl: -------------------------------------------------------------------------------- 1 | // {{ .StructName }}ServerInterceptors implements the server interceptor for the {{ .ServiceName }} service. 2 | type {{ .StructName }}ServerInterceptors struct { 3 | } 4 | 5 | // New{{ .StructName }}ServerInterceptors creates a new server interceptor for the {{ .ServiceName }} service. 6 | func New{{ .StructName }}ServerInterceptors() *{{ .StructName }}ServerInterceptors { 7 | return &{{ .StructName }}ServerInterceptors{} 8 | } 9 | 10 | {{- range .ServerInterceptors }} 11 | {{- if .Description }} 12 | {{ comment .Description }} 13 | {{- end }} 14 | func (i *{{ $.StructName }}ServerInterceptors) {{ .Name }}(ctx context.Context, info *{{ $.PkgName }}.{{ .Name }}Info, next goa.Endpoint) (any, error) { 15 | log.Printf(ctx, "[{{ .Name }}] Processing request: %v", info.RawPayload()) 16 | resp, err := next(ctx, info.RawPayload()) 17 | if err != nil { 18 | log.Printf(ctx, "[{{ .Name }}] Error: %v", err) 19 | return nil, err 20 | } 21 | log.Printf(ctx, "[{{ .Name }}] Response: %v", resp) 22 | return resp, nil 23 | } 24 | {{- end }} 25 | -------------------------------------------------------------------------------- /codegen/service/templates/example_service_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "New%s returns the %s service implementation." .StructName .Name | comment }} 2 | func New{{ .StructName }}() {{ .PkgName }}.Service { 3 | return &{{ .VarName }}srvc{} 4 | } 5 | -------------------------------------------------------------------------------- /codegen/service/templates/example_service_struct.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s service example implementation.\nThe example methods log the requests and return zero values." .Name | comment }} 2 | type {{ .VarName }}srvc struct {} 3 | -------------------------------------------------------------------------------- /codegen/service/templates/payload.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .PayloadDesc }} 2 | type {{ .Payload }} {{ .PayloadDef }} 3 | -------------------------------------------------------------------------------- /codegen/service/templates/result.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .ResultDesc }} 2 | type {{ .Result }} {{ .ResultDef }} 3 | -------------------------------------------------------------------------------- /codegen/service/templates/server_interceptor_stream_wrapper_types.go.tpl: -------------------------------------------------------------------------------- 1 | {{- range .WrappedServerStreams }} 2 | 3 | {{ comment (printf "wrapped%s is a server interceptor wrapper for the %s stream." .Interface .Interface) }} 4 | type wrapped{{ .Interface }} struct { 5 | ctx context.Context 6 | {{- if ne .SendTypeRef "" }} 7 | sendWithContext func(context.Context, {{ .SendTypeRef }}) error 8 | {{- end }} 9 | {{- if ne .RecvTypeRef "" }} 10 | recvWithContext func(context.Context) ({{ .RecvTypeRef }}, error) 11 | {{- end }} 12 | stream {{ .Interface }} 13 | } 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /codegen/service/templates/server_interceptors.go.tpl: -------------------------------------------------------------------------------- 1 | // ServerInterceptors defines the interface for all server-side interceptors. 2 | // Server interceptors execute after the request is decoded and before the 3 | // payload is sent to the service. The implementation is responsible for calling 4 | // next to complete the request. 5 | type ServerInterceptors interface { 6 | {{- range .ServerInterceptors }} 7 | {{- if .Description }} 8 | {{ comment .Description }} 9 | {{- end }} 10 | {{ .Name }}(ctx context.Context, info *{{ .Name }}Info, next goa.Endpoint) (any, error) 11 | {{- end }} 12 | } 13 | -------------------------------------------------------------------------------- /codegen/service/templates/service_client.go.tpl: -------------------------------------------------------------------------------- 1 | // {{ .ClientVarName }} is the {{ printf "%q" .Name }} service client. 2 | type {{ .ClientVarName }} struct { 3 | {{- range .Methods}} 4 | {{ .VarName }}Endpoint goa.Endpoint 5 | {{- end }} 6 | } 7 | -------------------------------------------------------------------------------- /codegen/service/templates/service_client_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "New%s initializes a %q service client given the endpoints." .ClientVarName .Name | comment }} 2 | func New{{ .ClientVarName }}({{ .ClientInitArgs }} goa.Endpoint{{ if .HasClientInterceptors }}, ci ClientInterceptors{{ end }}) *{{ .ClientVarName }} { 3 | return &{{ .ClientVarName }}{ 4 | {{- range .Methods }} 5 | {{ .VarName }}Endpoint: {{ if .ClientInterceptors }}Wrap{{ .VarName }}ClientEndpoint({{ end }}{{ .ArgName }}{{ if .ClientInterceptors }}, ci){{ end }}, 6 | {{- end }} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /codegen/service/templates/service_endpoint_stream_struct.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ printf "%s holds both the payload and the server stream of the %q method." .ServerStream.EndpointStruct .Name | comment }} 4 | type {{ .ServerStream.EndpointStruct }} struct { 5 | {{- if .PayloadRef }} 6 | {{ comment "Payload is the method payload." }} 7 | Payload {{ .PayloadRef }} 8 | {{- end }} 9 | {{ printf "Stream is the server stream used by the %q method to send data." .Name | comment }} 10 | Stream {{ .ServerStream.Interface }} 11 | } 12 | -------------------------------------------------------------------------------- /codegen/service/templates/service_endpoints.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | type {{ .VarName }} struct { 3 | {{- range .Methods}} 4 | {{ .VarName }} goa.Endpoint 5 | {{- end }} 6 | } 7 | -------------------------------------------------------------------------------- /codegen/service/templates/service_endpoints_init.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{ printf "New%s wraps the methods of the %q service with endpoints." .VarName .Name | comment }} 3 | func New{{ .VarName }}(s {{ .ServiceVarName }}{{ if .HasServerInterceptors }}, si ServerInterceptors{{ end }}) *{{ .VarName }} { 4 | {{- if .Schemes }} 5 | // Casting service to Auther interface 6 | a := s.(Auther) 7 | {{- end }} 8 | {{- if .HasServerInterceptors }} 9 | endpoints := &{{ .VarName }}{ 10 | {{- else }} 11 | return &{{ .VarName }}{ 12 | {{- end }} 13 | {{- range .Methods }} 14 | {{ .VarName }}: New{{ .VarName }}Endpoint(s{{ range .Schemes.DedupeByType }}, a.{{ .Type }}Auth{{ end }}), 15 | {{- end }} 16 | } 17 | {{- if .HasServerInterceptors }} 18 | {{- range .Methods }} 19 | {{- if .ServerInterceptors }} 20 | endpoints.{{ .VarName }} = Wrap{{ .VarName }}Endpoint(endpoints.{{ .VarName }}, si) 21 | {{- end }} 22 | {{- end }} 23 | return endpoints 24 | {{- end }} 25 | } 26 | -------------------------------------------------------------------------------- /codegen/service/templates/service_endpoints_use.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ printf "Use applies the given middleware to all the %q service endpoints." .Name | comment }} 4 | func (e *{{ .VarName }}) Use(m func(goa.Endpoint) goa.Endpoint) { 5 | {{- range .Methods }} 6 | e.{{ .VarName }} = m(e.{{ .VarName }}) 7 | {{- end }} 8 | } 9 | -------------------------------------------------------------------------------- /codegen/service/templates/service_request_body_struct.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ printf "%s holds both the payload and the HTTP request body reader of the %q method." .RequestStruct .Name | comment }} 4 | type {{ .RequestStruct }} struct { 5 | {{- if .PayloadRef }} 6 | {{ comment "Payload is the method payload." }} 7 | Payload {{ .PayloadRef }} 8 | {{- end }} 9 | {{ comment "Body streams the HTTP request body." }} 10 | Body io.ReadCloser 11 | } 12 | -------------------------------------------------------------------------------- /codegen/service/templates/service_response_body_struct.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s holds both the result and the HTTP response body reader of the %q method." .ResponseStruct .Name | comment }} 2 | type {{ .ResponseStruct }} struct { 3 | {{- if .ResultRef }} 4 | {{ comment "Result is the method result." }} 5 | Result {{ .ResultRef }} 6 | {{- end }} 7 | {{ comment "Body streams the HTTP response body." }} 8 | Body io.ReadCloser 9 | } 10 | -------------------------------------------------------------------------------- /codegen/service/templates/streaming_payload.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .StreamingPayloadDesc }} 2 | type {{ .StreamingPayload }} {{ .StreamingPayloadDef }} 3 | -------------------------------------------------------------------------------- /codegen/service/templates/transform_helper.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s builds a value of type %s from a value of type %s." .Name .ResultTypeRef .ParamTypeRef | comment }} 2 | func {{ .Name }}(v {{ .ParamTypeRef }}) {{ .ResultTypeRef }} { 3 | {{ .Code }} 4 | return res 5 | } 6 | -------------------------------------------------------------------------------- /codegen/service/templates/type_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | func {{ .Name }}({{ range .Args }}{{ .Name }} {{ .Ref }}, {{ end }}) {{ .ReturnTypeRef }} { 3 | {{ .Code }} 4 | } 5 | -------------------------------------------------------------------------------- /codegen/service/templates/type_validate.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if .IsViewed -}} 2 | switch {{ .ArgVar }}.View { 3 | {{- range .Views }} 4 | case {{ printf "%q" .Name }}{{ if eq .Name "default" }}, ""{{ end }}: 5 | err = Validate{{ $.Projected }}{{ if ne .Name "default" }}{{ goify .Name true }}{{ end }}({{ $.ArgVar }}.Projected) 6 | {{- end }} 7 | default: 8 | err = goa.InvalidEnumValueError("view", {{ .Source }}.View, []any{ {{ range .Views }}{{ printf "%q" .Name }}, {{ end }} }) 9 | } 10 | {{- else -}} 11 | {{- if .IsCollection -}} 12 | for _, {{ $.Source }} := range {{ $.ArgVar }} { 13 | if err2 := {{ .ValidateVar }}({{ $.Source }}); err2 != nil { 14 | err = goa.MergeErrors(err, err2) 15 | } 16 | } 17 | {{- else -}} 18 | {{ .Validate }} 19 | {{- range .Fields -}} 20 | {{- if .IsRequired -}} 21 | if {{ $.Source }}.{{ goify .Name true }} == nil { 22 | err = goa.MergeErrors(err, goa.MissingFieldError({{ printf "%q" .Name }}, {{ printf "%q" $.Source }})) 23 | } 24 | {{- end }} 25 | if {{ $.Source }}.{{ goify .Name true }} != nil { 26 | if err2 := {{ .ValidateVar }}({{ $.Source }}.{{ goify .Name true }}); err2 != nil { 27 | err = goa.MergeErrors(err, err2) 28 | } 29 | } 30 | {{- end -}} 31 | {{- end -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /codegen/service/templates/union_value_method.go.tpl: -------------------------------------------------------------------------------- 1 | func ({{ .TypeRef }}) {{ .Name }}() {} 2 | -------------------------------------------------------------------------------- /codegen/service/templates/user_type.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | type {{ .VarName }} {{ .Def }} 3 | -------------------------------------------------------------------------------- /codegen/service/templates/validate.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | func {{ .Name }}(result {{ .Ref }}) (err error) { 3 | {{ .Validate }} 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /codegen/service/templates/viewed_type_map.go.tpl: -------------------------------------------------------------------------------- 1 | var ( 2 | {{- range .ViewedTypes }} 3 | {{ printf "%sMap is a map indexing the attribute names of %s by view name." .Name .Name | comment }} 4 | {{ .Name }}Map = map[string][]string{ 5 | {{- range .Views }} 6 | "{{ .Name }}": { 7 | {{- range $n := .Attributes }} 8 | "{{ $n }}", 9 | {{- end }} 10 | }, 11 | {{- end }} 12 | } 13 | {{- end }} 14 | ) 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/alias-external/alias.go: -------------------------------------------------------------------------------- 1 | package aliasd 2 | 3 | type ConvertModel struct { 4 | Bar string 5 | } 6 | -------------------------------------------------------------------------------- /codegen/service/testdata/example_svc_dsls.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | . "goa.design/goa/v3/dsl" 5 | ) 6 | 7 | var ConflictWithAPINameAndServiceNameDSL = func() { 8 | var _ = API("aloha", func() { 9 | Title("conflict with API name and service names") 10 | }) 11 | var _ = Service("aloha", func() {}) // same as API name 12 | var _ = Service("alohaapi", func() {}) // API name + 'api' suffix 13 | var _ = Service("alohaapi1", func() {}) // API name + 'api' suffix + sequential no. 14 | } 15 | 16 | var ConflictWithGoifiedAPINameAndServiceNamesDSL = func() { 17 | var _ = API("good-by", func() { 18 | Title("conflict with goified API name and goified service names") 19 | }) 20 | var _ = Service("good-by-", func() {}) // Goify name is same as API name 21 | var _ = Service("good-by-api", func() {}) // API name + 'api' suffix 22 | var _ = Service("good-by-api-1", func() {}) // API name + 'api' suffix + sequential no. 23 | } 24 | -------------------------------------------------------------------------------- /codegen/service/testdata/external/external.go: -------------------------------------------------------------------------------- 1 | package external 2 | 3 | type ConvertModel struct { 4 | Foo string 5 | } 6 | 7 | type MixedCaseModel struct { 8 | LowerCamelID string 9 | UpperCamelID string 10 | SnakeID string 11 | } 12 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-payload_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodvalidation(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-payload_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapValidationMethod applies the validation server interceptor to endpoints. 4 | func wrapMethodValidation(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &ValidationInfo{ 7 | service: "InterceptorWithReadPayload", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Validation(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapClientValidationMethod applies the validation client interceptor to 17 | // endpoints. 18 | func wrapClientMethodValidation(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 19 | return func(ctx context.Context, req any) (any, error) { 20 | info := &ValidationInfo{ 21 | service: "InterceptorWithReadPayload", 22 | method: "Method", 23 | callType: goa.InterceptorUnary, 24 | rawPayload: req, 25 | } 26 | return i.Validation(ctx, info, endpoint) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-result_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodcaching(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-result_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapCachingMethod applies the caching server interceptor to endpoints. 4 | func wrapMethodCaching(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &CachingInfo{ 7 | service: "InterceptorWithReadResult", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Caching(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapClientCachingMethod applies the caching client interceptor to endpoints. 17 | func wrapClientMethodCaching(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 18 | return func(ctx context.Context, req any) (any, error) { 19 | info := &CachingInfo{ 20 | service: "InterceptorWithReadResult", 21 | method: "Method", 22 | callType: goa.InterceptorUnary, 23 | rawPayload: req, 24 | } 25 | return i.Caching(ctx, info, endpoint) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-write-payload_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodvalidation(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-write-payload_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapValidationMethod applies the validation server interceptor to endpoints. 4 | func wrapMethodValidation(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &ValidationInfo{ 7 | service: "InterceptorWithReadWritePayload", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Validation(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapClientValidationMethod applies the validation client interceptor to 17 | // endpoints. 18 | func wrapClientMethodValidation(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 19 | return func(ctx context.Context, req any) (any, error) { 20 | info := &ValidationInfo{ 21 | service: "InterceptorWithReadWritePayload", 22 | method: "Method", 23 | callType: goa.InterceptorUnary, 24 | rawPayload: req, 25 | } 26 | return i.Validation(ctx, info, endpoint) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-write-result_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodcaching(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-read-write-result_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapCachingMethod applies the caching server interceptor to endpoints. 4 | func wrapMethodCaching(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &CachingInfo{ 7 | service: "InterceptorWithReadWriteResult", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Caching(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapClientCachingMethod applies the caching client interceptor to endpoints. 17 | func wrapClientMethodCaching(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 18 | return func(ctx context.Context, req any) (any, error) { 19 | info := &CachingInfo{ 20 | service: "InterceptorWithReadWriteResult", 21 | method: "Method", 22 | callType: goa.InterceptorUnary, 23 | rawPayload: req, 24 | } 25 | return i.Caching(ctx, info, endpoint) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-write-payload_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Validation(ctx context.Context, info *ValidationInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodvalidation(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-write-payload_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapValidationMethod applies the validation server interceptor to endpoints. 4 | func wrapMethodValidation(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &ValidationInfo{ 7 | service: "InterceptorWithWritePayload", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Validation(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapClientValidationMethod applies the validation client interceptor to 17 | // endpoints. 18 | func wrapClientMethodValidation(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 19 | return func(ctx context.Context, req any) (any, error) { 20 | info := &ValidationInfo{ 21 | service: "InterceptorWithWritePayload", 22 | method: "Method", 23 | callType: goa.InterceptorUnary, 24 | rawPayload: req, 25 | } 26 | return i.Validation(ctx, info, endpoint) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-write-result_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Caching(ctx context.Context, info *CachingInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodcaching(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/interceptor-with-write-result_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapCachingMethod applies the caching server interceptor to endpoints. 4 | func wrapMethodCaching(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &CachingInfo{ 7 | service: "InterceptorWithWriteResult", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Caching(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapClientCachingMethod applies the caching client interceptor to endpoints. 17 | func wrapClientMethodCaching(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 18 | return func(ctx context.Context, req any) (any, error) { 19 | info := &CachingInfo{ 20 | service: "InterceptorWithWriteResult", 21 | method: "Method", 22 | callType: goa.InterceptorUnary, 23 | rawPayload: req, 24 | } 25 | return i.Caching(ctx, info, endpoint) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/single-api-server-interceptor_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapLoggingMethod applies the logging server interceptor to endpoints. 4 | func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &LoggingInfo{ 7 | service: "SingleAPIServerInterceptor", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Logging(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapLoggingMethod2 applies the logging server interceptor to endpoints. 17 | func wrapMethod2Logging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 18 | return func(ctx context.Context, req any) (any, error) { 19 | info := &LoggingInfo{ 20 | service: "SingleAPIServerInterceptor", 21 | method: "Method2", 22 | callType: goa.InterceptorUnary, 23 | rawPayload: req, 24 | } 25 | return i.Logging(ctx, info, endpoint) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/single-client-interceptor_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapClientTracingMethod applies the tracing client interceptor to endpoints. 4 | func wrapClientMethodTracing(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &TracingInfo{ 7 | service: "SingleClientInterceptor", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Tracing(ctx, info, endpoint) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/single-method-server-interceptor_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapLoggingMethod applies the logging server interceptor to endpoints. 4 | func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &LoggingInfo{ 7 | service: "SingleMethodServerInterceptor", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Logging(ctx, info, endpoint) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/single-service-server-interceptor_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapLoggingMethod applies the logging server interceptor to endpoints. 4 | func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &LoggingInfo{ 7 | service: "SingleServerInterceptor", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Logging(ctx, info, endpoint) 13 | } 14 | } 15 | 16 | // wrapLoggingMethod2 applies the logging server interceptor to endpoints. 17 | func wrapMethod2Logging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 18 | return func(ctx context.Context, req any) (any, error) { 19 | info := &LoggingInfo{ 20 | service: "SingleServerInterceptor", 21 | method: "Method2", 22 | callType: goa.InterceptorUnary, 23 | rawPayload: req, 24 | } 25 | return i.Logging(ctx, info, endpoint) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload-and-read-streaming-payload_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodlogging(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapLoggingMethod applies the logging server interceptor to endpoints. 4 | func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &LoggingInfo{ 7 | service: "StreamingInterceptorsWithReadPayload", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Logging(ctx, info, endpoint) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/streaming-interceptors-with-read-result_interceptor_wrappers.go.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | // wrapLoggingMethod applies the logging server interceptor to endpoints. 4 | func wrapMethodLogging(endpoint goa.Endpoint, i ServerInterceptors) goa.Endpoint { 5 | return func(ctx context.Context, req any) (any, error) { 6 | info := &LoggingInfo{ 7 | service: "StreamingInterceptorsWithReadResult", 8 | method: "Method", 9 | callType: goa.InterceptorUnary, 10 | rawPayload: req, 11 | } 12 | return i.Logging(ctx, info, endpoint) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/streaming-interceptors-with-read-streaming-result_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodlogging(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/interceptors/streaming-interceptors_client_interceptors.go.golden: -------------------------------------------------------------------------------- 1 | // ClientInterceptors defines the interface for all client-side interceptors. 2 | // Client interceptors execute after the payload is encoded and before the request 3 | // is sent to the server. The implementation is responsible for calling next to 4 | // complete the request. 5 | type ClientInterceptors interface { 6 | Logging(ctx context.Context, info *LoggingInfo, next goa.Endpoint) (any, error) 7 | } 8 | 9 | // WrapMethodClientEndpoint wraps the Method endpoint with the client 10 | // interceptors defined in the design. 11 | func WrapMethodClientEndpoint(endpoint goa.Endpoint, i ClientInterceptors) goa.Endpoint { 12 | endpoint = wrapClientMethodlogging(endpoint, i) 13 | return endpoint 14 | } 15 | -------------------------------------------------------------------------------- /codegen/service/testdata/types.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import "time" 4 | 5 | type StringT struct { 6 | String string 7 | } 8 | 9 | type StringPointerT struct { 10 | String *string 11 | } 12 | 13 | type ExternalNameT struct { 14 | String string 15 | } 16 | 17 | type ExternalNamePointerT struct { 18 | String *string 19 | } 20 | 21 | type ApiNameT struct { 22 | String string 23 | } 24 | 25 | type ArrayStringT struct { 26 | ArrayString []string 27 | } 28 | 29 | type ObjectT struct { 30 | Object *ObjectFieldT 31 | } 32 | 33 | type ObjectExtraT struct { 34 | Object *ObjectFieldT 35 | t *time.Time 36 | } 37 | 38 | type ObjectFieldT struct { 39 | Bool bool 40 | Int int 41 | Int32 int32 42 | Int64 int64 43 | UInt uint 44 | UInt32 uint32 45 | UInt64 uint64 46 | Float32 float32 47 | Float64 float64 48 | Bytes []byte 49 | String string 50 | Array []bool 51 | Map map[string]bool 52 | } 53 | -------------------------------------------------------------------------------- /docs/Wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goadesign/goa/1aa2d145306bd028bb94bd2628561b38f270db2f/docs/Wizard.png -------------------------------------------------------------------------------- /docs/incidentio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goadesign/goa/1aa2d145306bd028bb94bd2628561b38f270db2f/docs/incidentio.png -------------------------------------------------------------------------------- /docs/moved.md: -------------------------------------------------------------------------------- 1 | # Moved! 2 | 3 | The docs are now hosted on [goa.design](https://goa.design). -------------------------------------------------------------------------------- /docs/speakeasy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goadesign/goa/1aa2d145306bd028bb94bd2628561b38f270db2f/docs/speakeasy.png -------------------------------------------------------------------------------- /docs/zuplo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goadesign/goa/1aa2d145306bd028bb94bd2628561b38f270db2f/docs/zuplo-dark.png -------------------------------------------------------------------------------- /docs/zuplo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goadesign/goa/1aa2d145306bd028bb94bd2628561b38f270db2f/docs/zuplo.png -------------------------------------------------------------------------------- /dsl/types.go: -------------------------------------------------------------------------------- 1 | package dsl 2 | 3 | import "goa.design/goa/v3/expr" 4 | 5 | const ( 6 | // Boolean is the type for a JSON boolean. 7 | Boolean = expr.Boolean 8 | 9 | // Int is the type for a signed integer. 10 | Int = expr.Int 11 | 12 | // Int32 is the type for a signed 32-bit integer. 13 | Int32 = expr.Int32 14 | 15 | // Int64 is the type for a signed 64-bit integer. 16 | Int64 = expr.Int64 17 | 18 | // UInt is the type for an unsigned integer. 19 | UInt = expr.UInt 20 | 21 | // UInt32 is the type for an unsigned 32-bit integer. 22 | UInt32 = expr.UInt32 23 | 24 | // UInt64 is the type for an unsigned 64-bit integer. 25 | UInt64 = expr.UInt64 26 | 27 | // Float32 is the type for a 32-bit floating number. 28 | Float32 = expr.Float32 29 | 30 | // Float64 is the type for a 64-bit floating number. 31 | Float64 = expr.Float64 32 | 33 | // String is the type for a JSON string. 34 | String = expr.String 35 | 36 | // Bytes is the type for binary data. 37 | Bytes = expr.Bytes 38 | 39 | // Any is the type for an arbitrary JSON value (any in Go). 40 | Any = expr.Any 41 | ) 42 | 43 | // Empty represents empty values. 44 | var Empty = expr.Empty 45 | -------------------------------------------------------------------------------- /dsl/value.go: -------------------------------------------------------------------------------- 1 | package dsl 2 | 3 | import ( 4 | "goa.design/goa/v3/eval" 5 | "goa.design/goa/v3/expr" 6 | ) 7 | 8 | // Val is an alias for expr.Val. 9 | type Val expr.Val 10 | 11 | // Value sets the example value. 12 | // 13 | // Value must appear in Example. 14 | // 15 | // Value takes one argument: the example value. 16 | // 17 | // Example: 18 | // 19 | // Example("A simple bottle", func() { 20 | // Description("This bottle has an ID set to 1") 21 | // Value(Val{"ID": 1}) 22 | // }) 23 | func Value(val any) { 24 | switch e := eval.Current().(type) { 25 | case *expr.ExampleExpr: 26 | if v, ok := val.(expr.Val); ok { 27 | val = map[string]any(v) 28 | } 29 | e.Value = val 30 | default: 31 | eval.IncompatibleDSL() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /eval/expression_test.go: -------------------------------------------------------------------------------- 1 | package eval 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type Expr int 8 | 9 | func (Expr) EvalName() string { return "test expression" } 10 | 11 | func TestToExpressionSet(t *testing.T) { 12 | cases := []struct { 13 | Name string 14 | Slice []any 15 | ExpectPanic bool 16 | }{ 17 | {"simple", []any{Expr(42)}, false}, 18 | {"nil", nil, false}, 19 | {"invalid", []any{42}, true}, 20 | } 21 | for _, c := range cases { 22 | t.Run(c.Name, func(t *testing.T) { 23 | defer func() { 24 | if r := recover(); r == nil && c.ExpectPanic { 25 | t.Errorf("test did not panic") 26 | } 27 | }() 28 | set := ToExpressionSet(c.Slice) 29 | if len(set) != len(c.Slice) { 30 | t.Errorf("got set of length %d, expected %d.", len(set), len(c.Slice)) 31 | } else { 32 | for i, e := range set { 33 | if e != c.Slice[i] { 34 | t.Errorf("got value %v at index %d, expected %v", e, i, c.Slice[i]) 35 | } 36 | } 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /expr/docs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package expr defines expressions and data types used by the DSL and the code 3 | generators. The expressions implement the Preparer, Validator, and Finalizer 4 | interfaces. The code generators use the finalized expressions to generate the 5 | final output. 6 | 7 | The data types defined in the expr package are primitive types corresponding 8 | to scalar values (bool, string, integers, and numbers), array types which 9 | represent a collection of items, map types which represent maps of key/value 10 | pairs, and object types describing data structures with fields. The package 11 | also defines user types which can also be a result types. A result type is a 12 | user type used to describe response messages rendered using a view. 13 | */ 14 | package expr 15 | -------------------------------------------------------------------------------- /expr/grpc.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | type ( 4 | // GRPCExpr contains the API level gRPC specific expressions. 5 | GRPCExpr struct { 6 | // Services contains the gRPC services created by the DSL. 7 | Services []*GRPCServiceExpr 8 | // Errors lists the error gRPC error responses defined globally. 9 | Errors []*GRPCErrorExpr 10 | } 11 | ) 12 | 13 | // Service returns the service with the given name if any. 14 | func (g *GRPCExpr) Service(name string) *GRPCServiceExpr { 15 | for _, res := range g.Services { 16 | if res.Name() == name { 17 | return res 18 | } 19 | } 20 | return nil 21 | } 22 | 23 | // ServiceFor creates a new or returns the existing service definition for 24 | // the given service. 25 | func (g *GRPCExpr) ServiceFor(s *ServiceExpr) *GRPCServiceExpr { 26 | if res := g.Service(s.Name); res != nil { 27 | return res 28 | } 29 | res := &GRPCServiceExpr{ 30 | ServiceExpr: s, 31 | } 32 | g.Services = append(g.Services, res) 33 | return res 34 | } 35 | 36 | // EvalName returns the name printed in case of evaluation error. 37 | func (*GRPCExpr) EvalName() string { 38 | return "API GRPC" 39 | } 40 | -------------------------------------------------------------------------------- /expr/http_file_server_test.go: -------------------------------------------------------------------------------- 1 | package expr_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "goa.design/goa/v3/expr" 8 | "goa.design/goa/v3/expr/testdata" 9 | ) 10 | 11 | func TestFilesDSL(t *testing.T) { 12 | cases := []struct { 13 | Name string 14 | DSL func() 15 | Error string 16 | }{ 17 | {Name: "valid", DSL: testdata.FilesValidDSL}, 18 | {Name: "incompatible", DSL: testdata.FilesIncompatibleDSL, Error: "invalid use of Files in API files-incompatile"}, 19 | {Name: "too many arg error", DSL: testdata.FilesTooManyArgErrorDSL, Error: "too many arguments given to Files in API files-too-many-arg-error"}, 20 | } 21 | for _, c := range cases { 22 | t.Run(c.Name, func(t *testing.T) { 23 | if c.Error == "" { 24 | expr.RunDSL(t, c.DSL) 25 | } else { 26 | err := expr.RunInvalidDSL(t, c.DSL) 27 | if !strings.HasSuffix(err.Error(), c.Error) { 28 | t.Errorf("got error %q, expected has suffix %q", err.Error(), c.Error) 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /expr/http_redirect.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "fmt" 5 | 6 | "goa.design/goa/v3/eval" 7 | ) 8 | 9 | type ( 10 | // HTTPRedirectExpr defines an endpoint that replies to the request with a redirect. 11 | HTTPRedirectExpr struct { 12 | // URL is the URL that is being redirected to. 13 | URL string 14 | // StatusCode is the HTTP status code. 15 | StatusCode int 16 | // Parent expression, one of HTTPEndpointExpr or HTTPFileServerExpr. 17 | Parent eval.Expression 18 | } 19 | ) 20 | 21 | // EvalName returns the generic definition name used in error messages. 22 | func (r *HTTPRedirectExpr) EvalName() string { 23 | suffix := fmt.Sprintf("redirect to %s with status code %d", r.URL, r.StatusCode) 24 | var prefix string 25 | if r.Parent != nil { 26 | prefix = r.Parent.EvalName() + " " 27 | } 28 | return prefix + suffix 29 | } 30 | -------------------------------------------------------------------------------- /expr/init.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import ( 4 | "goa.design/goa/v3/eval" 5 | ) 6 | 7 | // Register DSL roots. 8 | func init() { 9 | if err := eval.Register(Root); err != nil { 10 | panic(err) // bug 11 | } 12 | if err := eval.Register(GeneratedResultTypes); err != nil { 13 | panic(err) // bug 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /expr/testdata/http_body_types.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import "goa.design/goa/v3/expr" 4 | 5 | var FinalizeEndpointBodyAsExtendedType = &expr.UserTypeExpr{ 6 | AttributeExpr: &expr.AttributeExpr{ 7 | Type: &expr.Object{ 8 | {"id", &expr.AttributeExpr{Type: expr.String}}, 9 | {"name", &expr.AttributeExpr{Type: expr.String}}, 10 | }, 11 | }, 12 | TypeName: "FinalizeEndpointBodyAsExtendedType", 13 | } 14 | 15 | var FinalizeEndpointBodyAsPropWithExtendedType = &expr.UserTypeExpr{ 16 | AttributeExpr: &expr.AttributeExpr{ 17 | Type: &expr.Object{ 18 | {"id", &expr.AttributeExpr{Type: expr.String}}, 19 | {"name", &expr.AttributeExpr{Type: expr.String}}, 20 | }, 21 | }, 22 | TypeName: "FinalizeEndpointBodyAsPropWithExtendedTypeDSL", 23 | } 24 | -------------------------------------------------------------------------------- /expr/testdata/http_file_server.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | . "goa.design/goa/v3/dsl" 5 | ) 6 | 7 | var FilesValidDSL = func() { 8 | Service("files-dsl", func() { 9 | Files("path", "filename") 10 | }) 11 | } 12 | 13 | var FilesIncompatibleDSL = func() { 14 | API("files-incompatile", func() { 15 | Files("path", "filename") 16 | }) 17 | } 18 | 19 | var FilesTooManyArgErrorDSL = func() { 20 | API("files-too-many-arg-error", func() { 21 | Files("path", "filename", func() {}, func() {}) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /grpc/codegen/client_cli_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | 10 | "goa.design/goa/v3/codegen" 11 | "goa.design/goa/v3/expr" 12 | "goa.design/goa/v3/grpc/codegen/testdata" 13 | ) 14 | 15 | func TestClientCLIFiles(t *testing.T) { 16 | 17 | cases := []struct { 18 | Name string 19 | DSL func() 20 | Code string 21 | }{ 22 | {"payload-with-validations", testdata.PayloadWithValidationsDSL, testdata.PayloadWithValidationsBuildCode}, 23 | } 24 | 25 | for _, c := range cases { 26 | t.Run(c.Name, func(t *testing.T) { 27 | RunGRPCDSL(t, c.DSL) 28 | fs := ClientCLIFiles("", expr.Root) 29 | require.Greater(t, len(fs), 1, "expected at least 2 files") 30 | require.NotEmpty(t, fs[1].SectionTemplates) 31 | var buf bytes.Buffer 32 | for _, s := range fs[1].SectionTemplates { 33 | require.NoError(t, s.Write(&buf)) 34 | } 35 | code := codegen.FormatTestCode(t, buf.String()) 36 | assert.Equal(t, c.Code, code) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /grpc/codegen/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package codegen contains the code generation logic to generate gRPC service 3 | definitions (.proto files) from the design DSLs and the corresponding server 4 | and client code that wraps the goa-generated endpoints with the protocol buffer 5 | compiler (protoc) generated clients and servers. 6 | 7 | The code generator uses "proto3" syntax for generating the proto files. 8 | 9 | The code generator compiles the proto files using the protocol buffer compiler 10 | (protoc) with the gRPC in Go plugin. It hooks up the generated protocol buffer 11 | types to the goa generated types as follows: 12 | 13 | - It generates a server that implements the protoc-generated gRPC server interface. 14 | - It generates a client that invokes the protoc-generated gRPC client. 15 | - It generates encoders and decoders that transforms the protocol buffer types and gRPC metadata into goa types and vice versa. 16 | - It generates validations to validate the protocol buffer message types and gRPC metadata fields with the validations set in the design. 17 | */ 18 | package codegen 19 | -------------------------------------------------------------------------------- /grpc/codegen/parse_endpoint_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | "goa.design/goa/v3/codegen" 10 | "goa.design/goa/v3/expr" 11 | "goa.design/goa/v3/grpc/codegen/testdata" 12 | ) 13 | 14 | func TestParseEndpointWithInterceptors(t *testing.T) { 15 | cases := []struct { 16 | Name string 17 | DSL func() 18 | }{ 19 | { 20 | Name: "endpoint-with-interceptors", 21 | DSL: testdata.InterceptorsDSL, 22 | }, 23 | } 24 | 25 | for _, c := range cases { 26 | t.Run(c.Name, func(t *testing.T) { 27 | RunGRPCDSL(t, c.DSL) 28 | fs := ClientCLIFiles("", expr.Root) 29 | require.Greater(t, len(fs), 1, "expected at least 2 files") 30 | require.NotEmpty(t, fs[0].SectionTemplates) 31 | var buf bytes.Buffer 32 | for _, s := range fs[0].SectionTemplates { 33 | require.NoError(t, s.Write(&buf)) 34 | } 35 | code := codegen.FormatTestCode(t, buf.String()) 36 | golden := filepath.Join("testdata", "endpoint-"+c.Name+".golden") 37 | compareOrUpdateGolden(t, code, golden) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /grpc/codegen/templates.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "embed" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | //go:embed templates/* 10 | var tmplFS embed.FS 11 | 12 | // readTemplate returns the service template with the given name. 13 | func readTemplate(name string, partials ...string) string { 14 | var tmpl strings.Builder 15 | { 16 | for _, partial := range partials { 17 | data, err := tmplFS.ReadFile(path.Join("templates", "partial", partial+".go.tpl")) 18 | if err != nil { 19 | panic("failed to read partial template " + partial + ": " + err.Error()) // Should never happen, bug if it does 20 | } 21 | tmpl.Write(data) 22 | tmpl.WriteByte('\n') 23 | } 24 | } 25 | data, err := tmplFS.ReadFile(path.Join("templates", name) + ".go.tpl") 26 | if err != nil { 27 | panic("failed to load template " + name + ": " + err.Error()) // Should never happen, bug if it does 28 | } 29 | tmpl.Write(data) 30 | return tmpl.String() 31 | } 32 | -------------------------------------------------------------------------------- /grpc/codegen/templates/client_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "New%s instantiates gRPC client for all the %s service servers." .ClientStruct .Service.Name | comment }} 2 | func New{{ .ClientStruct }}(cc *grpc.ClientConn, opts ...grpc.CallOption) *{{ .ClientStruct }} { 3 | return &{{ .ClientStruct }}{ 4 | grpccli: {{ .ClientInterfaceInit }}(cc), 5 | opts: opts, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /grpc/codegen/templates/client_struct.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s lists the service endpoint gRPC clients." .ClientStruct | comment }} 2 | type {{ .ClientStruct }} struct { 3 | grpccli {{ .PkgName }}.{{ .ClientInterface }} 4 | opts []grpc.CallOption 5 | } 6 | -------------------------------------------------------------------------------- /grpc/codegen/templates/do_grpc_cli.go.tpl: -------------------------------------------------------------------------------- 1 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 2 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 3 | if err != nil { 4 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 5 | } 6 | {{- range .Services }} 7 | {{- if .Service.ClientInterceptors }} 8 | {{ .Service.VarName }}Interceptors := {{ $.InterceptorsPkg }}.New{{ .Service.StructName }}ClientInterceptors() 9 | {{- end }} 10 | {{- end }} 11 | return cli.ParseEndpoint( 12 | conn, 13 | {{- range .Services }} 14 | {{- if .Service.ClientInterceptors }} 15 | {{ .Service.VarName }}Interceptors, 16 | {{- end }} 17 | {{- end }} 18 | ) 19 | } 20 | 21 | {{ if eq .DefaultTransport.Type "grpc" }} 22 | func grpcUsageCommands() string { 23 | return cli.UsageCommands() 24 | } 25 | 26 | func grpcUsageExamples() string { 27 | return cli.UsageExamples() 28 | } 29 | {{- end }} 30 | -------------------------------------------------------------------------------- /grpc/codegen/templates/grpc_handler_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "New%sHandler creates a gRPC handler which serves the %q service %q endpoint." .Method.VarName .ServiceName .Method.Name | comment }} 2 | func New{{ .Method.VarName }}Handler(endpoint goa.Endpoint, h goagrpc.{{ if .ServerStream }}Stream{{ else }}Unary{{ end }}Handler) goagrpc.{{ if .ServerStream }}Stream{{ else }}Unary{{ end }}Handler { 3 | if h == nil { 4 | h = goagrpc.New{{ if .ServerStream }}Stream{{ else }}Unary{{ end }}Handler(endpoint, {{ if .Method.Payload }}Decode{{ .Method.VarName }}Request{{ else }}nil{{ end }}{{ if not .ServerStream }}, Encode{{ .Method.VarName }}Response{{ end }}) 5 | } 6 | return h 7 | } 8 | -------------------------------------------------------------------------------- /grpc/codegen/templates/grpc_message.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | message {{ .VarName }}{{ .Def }} 3 | -------------------------------------------------------------------------------- /grpc/codegen/templates/grpc_service.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{ .Description | comment }} 3 | service {{ .Name }} { 4 | {{- range .Endpoints }} 5 | {{ if .Method.Description }}{{ .Method.Description | comment }}{{ end }} 6 | {{- $serverStream := or (eq .Method.StreamKind 3) (eq .Method.StreamKind 4) }} 7 | {{- $clientStream := or (eq .Method.StreamKind 2) (eq .Method.StreamKind 4) }} 8 | rpc {{ .Method.VarName }} ({{ if $clientStream }}stream {{ end }}{{ .Request.Message.VarName }}) returns ({{ if $serverStream }}stream {{ end }}{{ .Response.Message.VarName }}); 9 | {{- end }} 10 | } 11 | -------------------------------------------------------------------------------- /grpc/codegen/templates/proto_header.go.tpl: -------------------------------------------------------------------------------- 1 | {{ if .Title -}} 2 | // Code generated with goa {{ .ToolVersion }}, DO NOT EDIT. 3 | // 4 | // {{ .Title }} 5 | // 6 | // Command: 7 | {{ comment commandLine }} 8 | {{- end }} 9 | -------------------------------------------------------------------------------- /grpc/codegen/templates/proto_start.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | syntax = {{ printf "%q" .ProtoVersion }}; 3 | 4 | package {{ .Pkg }}; 5 | 6 | option go_package = "/{{ .Pkg }}pb"; 7 | {{- range .Imports }} 8 | import "{{ . }}"; 9 | {{- end }} 10 | -------------------------------------------------------------------------------- /grpc/codegen/templates/remote_method_builder.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "Build%sFunc builds the remote method to invoke for %q service %q endpoint." .Method.VarName .ServiceName .Method.Name | comment }} 2 | func Build{{ .Method.VarName }}Func(grpccli {{ .PkgName }}.{{ .ClientInterface }}, cliopts ...grpc.CallOption) goagrpc.RemoteFunc { 3 | return func(ctx context.Context, reqpb any, opts ...grpc.CallOption) (any, error) { 4 | for _, opt := range cliopts { 5 | opts = append(opts, opt) 6 | } 7 | if reqpb != nil { 8 | return grpccli.{{ .ClientMethodName }}(ctx{{ if not .Method.StreamingPayload }}, reqpb.({{ .Request.ClientConvert.TgtRef }}){{ end }}, opts...) 9 | } 10 | return grpccli.{{ .ClientMethodName }}(ctx{{ if not .Method.StreamingPayload }}, &{{ .Request.ClientConvert.TgtName }}{}{{ end }}, opts...) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /grpc/codegen/templates/server_grpc_end.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | (*wg).Add(1) 3 | go func() { 4 | defer (*wg).Done() 5 | 6 | {{ comment "Start gRPC server in a separate goroutine." }} 7 | go func() { 8 | lis, err := net.Listen("tcp", u.Host) 9 | if err != nil { 10 | errc <- err 11 | } 12 | if lis == nil { 13 | errc <- fmt.Errorf("failed to listen on %q", u.Host) 14 | } 15 | log.Printf(ctx, "gRPC server listening on %q", u.Host) 16 | errc <- srv.Serve(lis) 17 | }() 18 | 19 | <-ctx.Done() 20 | log.Printf(ctx, "shutting down gRPC server at %q", u.Host) 21 | srv.Stop() 22 | }() 23 | } 24 | -------------------------------------------------------------------------------- /grpc/codegen/templates/server_grpc_init.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | // Wrap the endpoints with the transport specific layers. The generated 3 | // server packages contains code generated from the design which maps 4 | // the service input and output data structures to gRPC requests and 5 | // responses. 6 | var ( 7 | {{- range .Services }} 8 | {{ .Service.VarName }}Server *{{.Service.PkgName}}svr.Server 9 | {{- end }} 10 | ) 11 | { 12 | {{- range .Services }} 13 | {{- if .Endpoints }} 14 | {{ .Service.VarName }}Server = {{ .Service.PkgName }}svr.New({{ .Service.VarName }}Endpoints{{ if .HasUnaryEndpoint }}, nil{{ end }}{{ if .HasStreamingEndpoint }}, nil{{ end }}) 15 | {{- else }} 16 | {{ .Service.VarName }}Server = {{ .Service.PkgName }}svr.New(nil{{ if .HasUnaryEndpoint }}, nil{{ end }}{{ if .HasStreamingEndpoint }}, nil{{ end }}) 17 | {{- end }} 18 | {{- end }} 19 | } 20 | -------------------------------------------------------------------------------- /grpc/codegen/templates/server_grpc_start.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment "handleGRPCServer starts configures and starts a gRPC server on the given URL. It shuts down the server if any error is received in the error channel." }} 2 | func handleGRPCServer(ctx context.Context, u *url.URL{{ range $.Services }}{{ if .Service.Methods }}, {{ .Service.VarName }}Endpoints *{{ .Service.PkgName }}.Endpoints{{ end }}{{ end }}, wg *sync.WaitGroup, errc chan error, dbg bool) { 3 | -------------------------------------------------------------------------------- /grpc/codegen/templates/server_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s instantiates the server struct with the %s service endpoints." .ServerInit .Service.Name | comment }} 2 | func {{ .ServerInit }}(e *{{ .Service.PkgName }}.Endpoints{{ if .HasUnaryEndpoint }}, uh goagrpc.UnaryHandler{{ end }}{{ if .HasStreamingEndpoint }}, sh goagrpc.StreamHandler{{ end }}) *{{ .ServerStruct }} { 3 | return &{{ .ServerStruct }}{ 4 | {{- range .Endpoints }} 5 | {{ .Method.VarName }}H: New{{ .Method.VarName }}Handler(e.{{ .Method.VarName }}{{ if .ServerStream }}, sh{{ else }}, uh{{ end }}), 6 | {{- end }} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /grpc/codegen/templates/server_struct_type.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s implements the %s.%s interface." .ServerStruct .PkgName .ServerInterface | comment }} 2 | type {{ .ServerStruct }} struct { 3 | {{- range .Endpoints }} 4 | {{ .Method.VarName }}H {{ if .ServerStream }}goagrpc.StreamHandler{{ else }}goagrpc.UnaryHandler{{ end }} 5 | {{- end }} 6 | {{ .PkgName }}.Unimplemented{{ .ServerInterface }} 7 | } 8 | -------------------------------------------------------------------------------- /grpc/codegen/templates/stream_close.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | func (s *{{ .VarName }}) Close() error { 3 | {{- if eq .Type "client" }} 4 | {{- if .Endpoint.Method.Result }} 5 | {{ comment "Close the send direction of the stream" }} 6 | return s.stream.CloseSend() 7 | {{- else }} 8 | {{ comment "synchronize and report any server error" }} 9 | _, err := s.stream.CloseAndRecv() 10 | return err 11 | {{- end }} 12 | {{- else }} 13 | {{- if .Endpoint.Method.Result }} 14 | {{ comment "nothing to do here" }} 15 | return nil 16 | {{- else }} 17 | {{ comment "synchronize stream" }} 18 | return s.stream.SendAndClose(&{{ .Endpoint.Response.ServerConvert.TgtName }}{}) 19 | {{- end }} 20 | {{- end }} 21 | } 22 | -------------------------------------------------------------------------------- /grpc/codegen/templates/stream_send.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .SendDesc }} 2 | func (s *{{ .VarName }}) {{ .SendName }}(res {{ .SendRef }}) error { 3 | {{- if and .Endpoint.Method.ViewedResult (eq .Type "server") }} 4 | {{- if .Endpoint.Method.ViewedResult.ViewName }} 5 | vres := {{ .Endpoint.ServicePkgName }}.{{ .Endpoint.Method.ViewedResult.Init.Name }}(res, {{ printf "%q" .Endpoint.Method.ViewedResult.ViewName }}) 6 | {{- else }} 7 | vres := {{ .Endpoint.ServicePkgName }}.{{ .Endpoint.Method.ViewedResult.Init.Name }}(res, s.view) 8 | {{- end }} 9 | {{- end }} 10 | v := {{ .SendConvert.Init.Name }}({{ if and .Endpoint.Method.ViewedResult (eq .Type "server") }}vres.Projected{{ else }}res{{ end }}) 11 | return s.stream.{{ .SendName }}(v) 12 | } 13 | 14 | {{ comment .SendWithContextDesc }} 15 | func (s *{{ .VarName }}) {{ .SendWithContextName }}(ctx context.Context, res {{ .SendRef }}) error { 16 | return s.{{ .SendName }}(res) 17 | } 18 | -------------------------------------------------------------------------------- /grpc/codegen/templates/stream_set_view.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "SetView sets the view." | comment }} 2 | func (s *{{ .VarName }}) SetView(view string) { 3 | s.view = view 4 | } 5 | -------------------------------------------------------------------------------- /grpc/codegen/templates/stream_struct_type.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s implements the %s interface." .VarName .ServiceInterface | comment }} 2 | type {{ .VarName }} struct { 3 | stream {{ .Interface }} 4 | {{- if .Endpoint.Method.ViewedResult }} 5 | view string 6 | {{- end }} 7 | } 8 | -------------------------------------------------------------------------------- /grpc/codegen/templates/transform_go_array.go.tpl: -------------------------------------------------------------------------------- 1 | {{ .TargetVar }} {{ if .NewVar }}:={{ else }}={{ end }} make([]{{ .ElemTypeRef }}, len({{ .SourceVar }})) 2 | for {{ .LoopVar }}{{ if .ValVar }}, {{ .ValVar }}{{ end }} := range {{ .SourceVar }} { 3 | {{ transformAttribute .SourceElem .TargetElem "val" (printf "%s[%s]" .TargetVar .LoopVar) false .TransformAttrs -}} 4 | } 5 | -------------------------------------------------------------------------------- /grpc/codegen/templates/transform_go_map.go.tpl: -------------------------------------------------------------------------------- 1 | {{ .TargetVar }} {{ if .NewVar }}:={{ else }}={{ end }} make(map[{{ .KeyTypeRef }}]{{ .ElemTypeRef }}, len({{ .SourceVar }})) 2 | for key, val := range {{ .SourceVar }} { 3 | {{ transformAttribute .SourceKey .TargetKey "key" "tk" true .TransformAttrs -}} 4 | {{ transformAttribute .SourceElem .TargetElem "val" (printf "tv%s" .LoopVar) true .TransformAttrs -}} 5 | {{ .TargetVar }}[tk] = {{ printf "tv%s" .LoopVar }} 6 | } 7 | -------------------------------------------------------------------------------- /grpc/codegen/templates/transform_go_union_from_proto.go.tpl: -------------------------------------------------------------------------------- 1 | switch val := {{ .SourceVar }}.(type) { 2 | {{- range $i, $ref := .SourceValueTypeRefs }} 3 | case {{ . }}: 4 | {{- $field := (print "val." (index $.SourceFieldNames $i)) }} 5 | {{ $.TargetVar }} = {{ convertType (index $.SourceValues $i).Attribute (index $.TargetValues $i).Attribute false false $field $.TransformAttrs }} 6 | {{- end }} 7 | } 8 | -------------------------------------------------------------------------------- /grpc/codegen/templates/transform_go_union_to_proto.go.tpl: -------------------------------------------------------------------------------- 1 | switch src := {{ .SourceVar }}.(type) { 2 | {{- range $i, $ref := .SourceValueTypeRefs }} 3 | case {{ . }}: 4 | {{- $val := (convertType (index $.SourceValues $i).Attribute (index $.TargetValues $i).Attribute false false "src" $.TransformAttrs) }} 5 | {{ $.TargetVar }} = &{{ index $.TargetValueTypeNames $i }}{ {{ (index $.TargetFieldNames $i) }}: {{ $val }} } 6 | {{- end }} 7 | } 8 | -------------------------------------------------------------------------------- /grpc/codegen/templates/transform_helper.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s builds a value of type %s from a value of type %s." .Name .ResultTypeRef .ParamTypeRef | comment }} 2 | func {{ .Name }}(v {{ .ParamTypeRef }}) {{ .ResultTypeRef }} { 3 | {{ .Code }} 4 | return res 5 | } 6 | -------------------------------------------------------------------------------- /grpc/codegen/templates/type_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | func {{ .Name }}({{ range .Args }}{{ .Name }} {{ .TypeRef }}, {{ end }}) {{ .ReturnTypeRef }} { 3 | {{ .Code }} 4 | {{- if .ReturnIsStruct }} 5 | {{- range .Args }} 6 | {{- if .FieldName }} 7 | {{ $.ReturnVarName }}.{{ .FieldName }} = {{ if isAlias .FieldType }}{{ fullName .FieldType }}({{ end }}{{ .Name }}{{ if isAlias .FieldType }}){{ end }} 8 | {{- end }} 9 | {{- end }} 10 | {{- end }} 11 | return {{ .ReturnVarName }} 12 | } 13 | -------------------------------------------------------------------------------- /grpc/codegen/templates/validate.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s runs the validations defined on %s." .Name .SrcName | comment }} 2 | func {{ .Name }}({{ .ArgName }} {{ .SrcRef }}) (err error) { 3 | {{ .Def }} 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/client-interceptors.golden: -------------------------------------------------------------------------------- 1 | import ( 2 | "fmt" 3 | cli "grpc/cli/test" 4 | "os" 5 | 6 | "./interceptors" 7 | goa "goa.design/goa/v3/pkg" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/credentials/insecure" 10 | ) 11 | 12 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 13 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 14 | if err != nil { 15 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 16 | } 17 | serviceWithInterceptorsInterceptors := interceptors.NewServiceWithInterceptorsClientInterceptors() 18 | return cli.ParseEndpoint( 19 | conn, 20 | serviceWithInterceptorsInterceptors, 21 | ) 22 | } 23 | 24 | func grpcUsageCommands() string { 25 | return cli.UsageCommands() 26 | } 27 | 28 | func grpcUsageExamples() string { 29 | return cli.UsageExamples() 30 | } 31 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/client-no-server-pkgpath.golden: -------------------------------------------------------------------------------- 1 | import ( 2 | "fmt" 3 | cli "my/pkg/path/grpc/cli/test_api" 4 | "os" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials/insecure" 9 | ) 10 | 11 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 12 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 13 | if err != nil { 14 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 15 | } 16 | return cli.ParseEndpoint( 17 | conn, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/client-no-server.golden: -------------------------------------------------------------------------------- 1 | import ( 2 | "fmt" 3 | cli "grpc/cli/test_api" 4 | "os" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials/insecure" 9 | ) 10 | 11 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 12 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 13 | if err != nil { 14 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 15 | } 16 | return cli.ParseEndpoint( 17 | conn, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/client-server-hosting-multiple-services-pkgpath.golden: -------------------------------------------------------------------------------- 1 | import ( 2 | "fmt" 3 | cli "my/pkg/path/grpc/cli/single_host" 4 | "os" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials/insecure" 9 | ) 10 | 11 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 12 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 13 | if err != nil { 14 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 15 | } 16 | return cli.ParseEndpoint( 17 | conn, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/client-server-hosting-multiple-services.golden: -------------------------------------------------------------------------------- 1 | import ( 2 | "fmt" 3 | cli "grpc/cli/single_host" 4 | "os" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials/insecure" 9 | ) 10 | 11 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 12 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 13 | if err != nil { 14 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 15 | } 16 | return cli.ParseEndpoint( 17 | conn, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/client-server-hosting-service-subset-pkgpath.golden: -------------------------------------------------------------------------------- 1 | import ( 2 | "fmt" 3 | cli "my/pkg/path/grpc/cli/single_host" 4 | "os" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials/insecure" 9 | ) 10 | 11 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 12 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 13 | if err != nil { 14 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 15 | } 16 | return cli.ParseEndpoint( 17 | conn, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/client-server-hosting-service-subset.golden: -------------------------------------------------------------------------------- 1 | import ( 2 | "fmt" 3 | cli "grpc/cli/single_host" 4 | "os" 5 | 6 | goa "goa.design/goa/v3/pkg" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials/insecure" 9 | ) 10 | 11 | func doGRPC(_, host string, _ int, _ bool) (goa.Endpoint, any, error) { 12 | conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 13 | if err != nil { 14 | fmt.Fprintf(os.Stderr, "could not connect to gRPC server at %s: %v\n", host, err) 15 | } 16 | return cli.ParseEndpoint( 17 | conn, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /grpc/codegen/testdata/protoc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | func main() { 10 | if len(os.Args) < 2 { 11 | fmt.Println("must pass a prefix arg to be ignored, to test it being passed") 12 | os.Exit(1) 13 | } 14 | cmd := exec.Command("protoc", os.Args[2:]...) 15 | fmt.Println(cmd) 16 | cmd.Stdin = os.Stdin 17 | cmd.Stdout = os.Stdout 18 | cmd.Stderr = os.Stderr 19 | cmd.Run() 20 | os.Exit(cmd.ProcessState.ExitCode()) 21 | } 22 | -------------------------------------------------------------------------------- /grpc/codegen/testing.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "goa.design/goa/v3/codegen" 8 | "goa.design/goa/v3/codegen/service" 9 | "goa.design/goa/v3/expr" 10 | ) 11 | 12 | // RunGRPCDSL returns the GRPC DSL root resulting from running the given DSL. 13 | // It is used only in tests. 14 | func RunGRPCDSL(t *testing.T, dsl func()) *expr.RootExpr { 15 | // reset all roots and codegen data structures 16 | service.Services = make(service.ServicesData) 17 | GRPCServices = make(ServicesData) 18 | return expr.RunDSL(t, dsl) 19 | } 20 | 21 | func sectionCode(t *testing.T, section ...*codegen.SectionTemplate) string { 22 | t.Helper() 23 | var code bytes.Buffer 24 | for _, s := range section { 25 | if err := s.Write(&code); err != nil { 26 | t.Fatal(err) 27 | } 28 | } 29 | return code.String() 30 | } 31 | -------------------------------------------------------------------------------- /grpc/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package grpc contains code generation logic to produce a server that serves gRPC 3 | requests and a client that encode requests to and decode responses from a gRPC 4 | server. It produces gRPC service definitions (.proto files) from Goa expressions 5 | that were created by executing a design DSL. It then compiles the definition 6 | using the protocol buffer compiler (protoc) using the Go gRPC plugin, and 7 | generates code that hooks up the compiled protocol buffer types and gRPC code 8 | with the types and code generated by Goa. It uses the "proto3" syntax to 9 | generate gRPC service and protocol buffer message definitions. 10 | 11 | In addition to the code generation logic, the grpc package contains: 12 | 13 | - A customizable server and client handler interface to handle unary and streaming RPCs. 14 | - Encoder and decoder interfaces to convert a protocol buffer type to a Goa type and vice versa. 15 | - Error handlers to encode and decode error responses. 16 | - Interceptors (a.k.a middlewares) to wrap additional functionality around unary and streaming RPCs. 17 | */ 18 | package grpc 19 | -------------------------------------------------------------------------------- /grpc/encoding.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/metadata" 7 | ) 8 | 9 | type ( 10 | // RequestDecoder is used by the server to decode gRPC request message type 11 | // and any incoming metadata to goa type. 12 | RequestDecoder func(ctx context.Context, pb any, md metadata.MD) (v any, err error) 13 | 14 | // RequestEncoder is used by the client to encode goa type to gRPC message 15 | // type and sets the outgoing metadata. 16 | RequestEncoder func(ctx context.Context, v any, md *metadata.MD) (pb any, err error) 17 | 18 | // ResponseDecoder is used by the client to decode gRPC response message 19 | // type and any incoming metadata (headers and trailers) to goa type. 20 | ResponseDecoder func(ctx context.Context, pb any, hdr, trlr metadata.MD) (v any, err error) 21 | 22 | // ResponseEncoder is used by the server to encode goa type to gRPC response 23 | // message type and sets the response headers and trailers. 24 | ResponseEncoder func(ctx context.Context, v any, hdr, trlr *metadata.MD) (pb any, err error) 25 | ) 26 | -------------------------------------------------------------------------------- /grpc/middleware/doc.go: -------------------------------------------------------------------------------- 1 | // Package middleware contains gRPC server and client interceptors that wraps 2 | // unary and streaming RPCs to provide additional functionality. 3 | // 4 | // This package contains the following middlewares: 5 | // 6 | // - Logging server middleware for unary and streaming endpoints. 7 | // - Request ID server middleware for unary and streaming endpoints. 8 | // - Stream Canceler server middleware for canceling streaming requests. 9 | // - Tracing middleware for unary and streaming server and client. 10 | // - AWS X-Ray middleware for producing X-Ray segments for unary and streaming 11 | // client and server. 12 | // 13 | // Example to use the server middleware: 14 | // 15 | // srv := grpc.NewServer(middleware.UnaryRequestID()) 16 | // 17 | // Example to use the client middleware: 18 | // 19 | // conn, err := grpc.Dial(host, 20 | // grpc.WithUnaryInterceptor(middleware.UnaryClientTrace()), 21 | // grpc.WithStreamInterceptor(middleware.StreamClientTrace()), 22 | // ) 23 | package middleware 24 | -------------------------------------------------------------------------------- /grpc/middleware/helpers.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | "google.golang.org/grpc/metadata" 8 | ) 9 | 10 | type ( 11 | // WrappedServerStream overrides the Context() method of the 12 | // grpc.ServerStream interface. 13 | WrappedServerStream struct { 14 | grpc.ServerStream 15 | ctx context.Context 16 | } 17 | ) 18 | 19 | // NewWrappedServerStream returns a new wrapped grpc ServerStream. 20 | func NewWrappedServerStream(ctx context.Context, ss grpc.ServerStream) *WrappedServerStream { 21 | return &WrappedServerStream{ 22 | ctx: ctx, 23 | ServerStream: ss, 24 | } 25 | } 26 | 27 | // Context returns the context for the server stream. 28 | func (w *WrappedServerStream) Context() context.Context { 29 | return w.ctx 30 | } 31 | 32 | // MetadataValue returns the first value for the given metadata key if 33 | // key exists, else returns an empty string. 34 | func MetadataValue(md metadata.MD, key string) string { 35 | if vals := md.Get(key); len(vals) > 0 { 36 | return vals[0] 37 | } 38 | return "" 39 | } 40 | -------------------------------------------------------------------------------- /grpc/middleware/xray/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package xray contains unary and streaming server and client interceptors that 3 | create AWS X-Ray segments from the gRPC requests and responses and send the 4 | segments to an AWS X-ray daemon. 5 | 6 | The server interceptor works by extracting the tracing information setup by the 7 | tracing server middleware. The tracing server middleware must be chained before 8 | adding this middleware. It creates a new segment and stores the segment in the 9 | RPC's context. User code can further configure the segment for example to set a 10 | service version or record an error. 11 | 12 | The client interceptor works by extracing the segment from the RPC's context 13 | and creates a new sub-segment. It updates the RPC context with the latest trace 14 | information. 15 | */ 16 | package xray 17 | -------------------------------------------------------------------------------- /grpc/pb/doc.go: -------------------------------------------------------------------------------- 1 | // Package goapb contains protocol buffer message types used by the code 2 | // generation logic. 3 | package goapb 4 | -------------------------------------------------------------------------------- /grpc/pb/goadesign_goa_error.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // NOTE: If there are any changes to this file, run the protoc command as 4 | // below from the pb directory to generate the goadesign_goa_error.pb.go file. 5 | // 6 | // $ protoc --proto_path=. --go_out=. --go_opt=paths=source_relative goadesign_goa_error.proto 7 | 8 | package goapb; 9 | 10 | option go_package = "/goapb"; 11 | 12 | // ErrorResponse message defines the error encoded in the gRPC response that 13 | // correspond to the errors created by the generated code. This is mainly 14 | // intended for the clients to decode error responses. 15 | message ErrorResponse { 16 | // name is the name for that class of errors. 17 | string name = 1; 18 | // id is the unique error instance identifier. 19 | string id = 2; 20 | // msg describes the specific error occurrence. 21 | string msg = 3; 22 | // temporary indicates whether the error is temporary. 23 | bool temporary = 4; 24 | // timeout indicates whether the error is a timeout. 25 | bool timeout = 5; 26 | // fault indicates whether the error is a server-side fault. 27 | bool fault = 6; 28 | } 29 | -------------------------------------------------------------------------------- /http/codegen/client_init_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | 9 | "goa.design/goa/v3/codegen" 10 | "goa.design/goa/v3/expr" 11 | "goa.design/goa/v3/http/codegen/testdata" 12 | ) 13 | 14 | func TestClientInit(t *testing.T) { 15 | cases := []struct { 16 | Name string 17 | DSL func() 18 | Code string 19 | FileCount int 20 | SectionNum int 21 | }{ 22 | {"multiple endpoints", testdata.ServerMultiEndpointsDSL, testdata.MultipleEndpointsClientInitCode, 2, 2}, 23 | {"streaming", testdata.StreamingResultDSL, testdata.StreamingClientInitCode, 3, 2}, 24 | } 25 | for _, c := range cases { 26 | t.Run(c.Name, func(t *testing.T) { 27 | RunHTTPDSL(t, c.DSL) 28 | fs := ClientFiles("", expr.Root) 29 | require.Len(t, fs, c.FileCount) 30 | sections := fs[0].SectionTemplates 31 | require.Greater(t, len(sections), c.SectionNum) 32 | code := codegen.SectionCode(t, sections[c.SectionNum]) 33 | assert.Equal(t, c.Code, code) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /http/codegen/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package codegen contains code generation algorithms that produce HTTP servers 3 | and clients meant to call - or wrap - the Goa endpoints generated by the service 4 | package. 5 | */ 6 | package codegen 7 | -------------------------------------------------------------------------------- /http/codegen/funcs_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestStatusCodeToHttpConst(t *testing.T) { 10 | cases := map[string]struct { 11 | Code int 12 | Expected string 13 | }{ 14 | "know-status-code": {Code: 200, Expected: "http.StatusOK"}, 15 | "unknow-status-code": {Code: 700, Expected: "700"}, 16 | } 17 | for k, tc := range cases { 18 | actual := statusCodeToHTTPConst(tc.Code) 19 | assert.Equal(t, tc.Expected, actual, k) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /http/codegen/openapi.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "goa.design/goa/v3/codegen" 5 | "goa.design/goa/v3/expr" 6 | openapiv2 "goa.design/goa/v3/http/codegen/openapi/v2" 7 | openapiv3 "goa.design/goa/v3/http/codegen/openapi/v3" 8 | ) 9 | 10 | // OpenAPIFiles returns the files for the OpenAPIFile spec of the given HTTP API. 11 | func OpenAPIFiles(root *expr.RootExpr) ([]*codegen.File, error) { 12 | // Only create a OpenAPI specification if there are HTTP services. 13 | if len(root.API.HTTP.Services) == 0 { 14 | return nil, nil 15 | } 16 | 17 | var files []*codegen.File 18 | { 19 | // OpenAPI v2 20 | fs, err := openapiv2.Files(root) 21 | if err != nil { 22 | return nil, err 23 | } 24 | files = append(files, fs...) 25 | 26 | // OpenAPI v3 27 | fs, err = openapiv3.Files(root) 28 | if err != nil { 29 | return nil, err 30 | } 31 | files = append(files, fs...) 32 | } 33 | return files, nil 34 | } 35 | -------------------------------------------------------------------------------- /http/codegen/openapi/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package openapi provides common algorithms and data structures used to 3 | generate both OpenAPI v2 and v3 specifications from Goa designs. 4 | */ 5 | package openapi 6 | -------------------------------------------------------------------------------- /http/codegen/openapi/docs.go: -------------------------------------------------------------------------------- 1 | package openapi 2 | 3 | import "goa.design/goa/v3/expr" 4 | 5 | // ExternalDocs represents an OpenAPI External Documentation object as defined in 6 | // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#externalDocumentationObject 7 | type ExternalDocs struct { 8 | Description string `json:"description,omitempty"` 9 | URL string `json:"url,omitempty"` 10 | Extensions map[string]any `json:"-" yaml:"-"` 11 | } 12 | 13 | // DocsFromExpr builds a ExternalDocs from the Goa docs expression. 14 | func DocsFromExpr(docs *expr.DocsExpr, meta expr.MetaExpr) *ExternalDocs { 15 | if docs == nil { 16 | return nil 17 | } 18 | return &ExternalDocs{ 19 | Description: docs.Description, 20 | URL: docs.URL, 21 | Extensions: ExtensionsFromExpr(meta), 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package openapiv2 contains the algorithms and data structures used to 3 | generate OpenAPI v2 specifications from Goa designs. 4 | */ 5 | package openapiv2 6 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestExtensions/endpoint_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1","x-test-api":"API"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"post":{"operationId":"testService#testEndpoint","parameters":[{"in":"body","name":"TestEndpointRequestBody","required":true,"schema":{"$ref":"#/definitions/Payload"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result"}}},"schemes":["https"],"summary":"testEndpoint testService","tags":["testService"],"x-test-operation":"Operation"},"x-test-foo":"bar"}},"definitions":{"Payload":{"title":"Payload","type":"object","properties":{"string":{"example":"","type":"string","x-test-schema":"Payload"}},"example":{"string":""}},"Result":{"title":"Result","type":"object","properties":{"string":{"example":"","type":"string","x-test-schema":"Result"}},"example":{"string":""}}},"tags":[{"description":"Description of Backend","externalDocs":{"description":"See more docs here","url":"http://example.com"},"name":"Backend","x-data":{"foo":"bar"}}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/additional-properties-embedded-payload-result_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/Payload"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result"}}},"schemes":["https"]}}},"definitions":{"Payload":{"title":"Payload","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""},"additionalProperties":false},"Result":{"title":"Result","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""},"additionalProperties":false}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/additional-properties-payload-result_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/Payload"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result"}}},"schemes":["https"]}}},"definitions":{"Payload":{"title":"Payload","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""},"additionalProperties":false},"Result":{"title":"Result","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""},"additionalProperties":false}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/additional-properties-type_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/Payload"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result"}}},"schemes":["https"]}}},"definitions":{"Payload":{"title":"Payload","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""},"additionalProperties":false},"Result":{"title":"Result","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""},"additionalProperties":false}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/empty_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/empty_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: goa.design 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: {} 15 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/file-service_file0.golden: -------------------------------------------------------------------------------- 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":{"/path1":{"get":{"tags":["service-name"],"summary":"Download filename","operationId":"service-name#/path1","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}},"/path2":{"get":{"tags":["user-tag"],"summary":"Download filename","operationId":"service-name#/path2","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/file-service_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: localhost:80 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /path1: 16 | get: 17 | tags: 18 | - service-name 19 | summary: Download filename 20 | operationId: service-name#/path1 21 | responses: 22 | "200": 23 | description: File downloaded 24 | schema: 25 | type: file 26 | schemes: 27 | - http 28 | /path2: 29 | get: 30 | tags: 31 | - user-tag 32 | summary: Download filename 33 | operationId: service-name#/path2 34 | responses: 35 | "200": 36 | description: File downloaded 37 | schema: 38 | type: file 39 | schemes: 40 | - http 41 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/headers_file0.golden: -------------------------------------------------------------------------------- 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":{"/":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"foo","in":"header","required":false,"type":"integer"},{"name":"bar","in":"header","required":false,"type":"integer"}],"responses":{"204":{"description":"No Content response."}},"schemes":["http"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/headers_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: localhost:80 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /: 16 | post: 17 | tags: 18 | - test service 19 | summary: test endpoint test service 20 | operationId: test service#test endpoint 21 | parameters: 22 | - name: foo 23 | in: header 24 | required: false 25 | type: integer 26 | - name: bar 27 | in: header 28 | required: false 29 | type: integer 30 | responses: 31 | "204": 32 | description: No Content response. 33 | schemes: 34 | - http 35 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/json-indent_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: goa.design 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /path1: 16 | get: 17 | tags: 18 | - service-name 19 | summary: Download filename 20 | operationId: service-name#/path1 21 | responses: 22 | "200": 23 | description: File downloaded 24 | schema: 25 | type: file 26 | schemes: 27 | - https 28 | /path2: 29 | get: 30 | tags: 31 | - user-tag 32 | summary: Download filename 33 | operationId: service-name#/path2 34 | responses: 35 | "200": 36 | description: File downloaded 37 | schema: 38 | type: file 39 | schemes: 40 | - https 41 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/json-prefix-indent_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: goa.design 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /path1: 16 | get: 17 | tags: 18 | - service-name 19 | summary: Download filename 20 | operationId: service-name#/path1 21 | responses: 22 | "200": 23 | description: File downloaded 24 | schema: 25 | type: file 26 | schemes: 27 | - https 28 | /path2: 29 | get: 30 | tags: 31 | - user-tag 32 | summary: Download filename 33 | operationId: service-name#/path2 34 | responses: 35 | "200": 36 | description: File downloaded 37 | schema: 38 | type: file 39 | schemes: 40 | - https 41 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/json-prefix_file0.golden: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "", 5 | "version": "0.0.1" 6 | }, 7 | "host": "goa.design", 8 | "consumes": [ 9 | "application/json", 10 | "application/xml", 11 | "application/gob" 12 | ], 13 | "produces": [ 14 | "application/json", 15 | "application/xml", 16 | "application/gob" 17 | ], 18 | "paths": { 19 | "/path1": { 20 | "get": { 21 | "tags": [ 22 | "service-name" 23 | ], 24 | "summary": "Download filename", 25 | "operationId": "service-name#/path1", 26 | "responses": { 27 | "200": { 28 | "description": "File downloaded", 29 | "schema": { 30 | "type": "file" 31 | } 32 | } 33 | }, 34 | "schemes": [ 35 | "https" 36 | ] 37 | } 38 | }, 39 | "/path2": { 40 | "get": { 41 | "tags": [ 42 | "user-tag" 43 | ], 44 | "summary": "Download filename", 45 | "operationId": "service-name#/path2", 46 | "responses": { 47 | "200": { 48 | "description": "File downloaded", 49 | "schema": { 50 | "type": "file" 51 | } 52 | } 53 | }, 54 | "schemes": [ 55 | "https" 56 | ] 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/json-prefix_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: goa.design 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /path1: 16 | get: 17 | tags: 18 | - service-name 19 | summary: Download filename 20 | operationId: service-name#/path1 21 | responses: 22 | "200": 23 | description: File downloaded 24 | schema: 25 | type: file 26 | schemes: 27 | - https 28 | /path2: 29 | get: 30 | tags: 31 | - user-tag 32 | summary: Download filename 33 | operationId: service-name#/path2 34 | responses: 35 | "200": 36 | description: File downloaded 37 | schema: 38 | type: file 39 | schemes: 40 | - https 41 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/multiple-services_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/Payload"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result"}}},"schemes":["https"]},"post":{"tags":["anotherTestService"],"summary":"testEndpoint anotherTestService","operationId":"anotherTestService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/Payload"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result"}}},"schemes":["https"]}}},"definitions":{"Payload":{"title":"Payload","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}},"Result":{"title":"Result","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/not-generate-attribute_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/Payload","required":["required_string"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result","required":["required_int"]}}},"schemes":["https"]}}},"definitions":{"Payload":{"title":"Payload","type":"object","properties":{"required_string":{"type":"string","example":""},"string":{"type":"string","example":""}},"example":{"required_string":"","string":""},"required":["required_string"]},"Result":{"title":"Result","type":"object","properties":{"int":{"type":"integer","example":0,"format":"int64"},"required_int":{"type":"integer","example":0,"format":"int64"}},"example":{"int":0,"required_int":0},"required":["required_int"]}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/not-generate-host_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["https"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/not-generate-host_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | consumes: 6 | - application/json 7 | - application/xml 8 | - application/gob 9 | produces: 10 | - application/json 11 | - application/xml 12 | - application/gob 13 | paths: 14 | /: 15 | get: 16 | tags: 17 | - testService 18 | summary: testEndpoint testService 19 | operationId: testService#testEndpoint 20 | responses: 21 | "200": 22 | description: OK response. 23 | schema: 24 | type: string 25 | schemes: 26 | - https 27 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/not-generate-server_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","responses":{"200":{"description":"OK response.","schema":{"type":"string"}}},"schemes":["https"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/not-generate-server_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | consumes: 6 | - application/json 7 | - application/xml 8 | - application/gob 9 | produces: 10 | - application/json 11 | - application/xml 12 | - application/gob 13 | paths: 14 | /: 15 | get: 16 | tags: 17 | - testService 18 | summary: testEndpoint testService 19 | operationId: testService#testEndpoint 20 | responses: 21 | "200": 22 | description: OK response. 23 | schema: 24 | type: string 25 | schemes: 26 | - https 27 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file0.golden: -------------------------------------------------------------------------------- 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":{"/{foo}/{bar}":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"foo","in":"path","required":true,"type":"integer"},{"name":"bar","in":"path","required":true,"type":"integer"}],"responses":{"204":{"description":"No Content response."}},"schemes":["http"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-explicit-wildcards_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: localhost:80 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /{foo}/{bar}: 16 | post: 17 | tags: 18 | - test service 19 | summary: test endpoint test service 20 | operationId: test service#test endpoint 21 | parameters: 22 | - name: foo 23 | in: path 24 | required: true 25 | type: integer 26 | - name: bar 27 | in: path 28 | required: true 29 | type: integer 30 | responses: 31 | "204": 32 | description: No Content response. 33 | schemes: 34 | - http 35 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file0.golden: -------------------------------------------------------------------------------- 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":{"/{foo}/{bar}":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"foo","in":"path","required":true,"type":"integer"},{"name":"bar","in":"path","required":true,"type":"integer"}],"responses":{"204":{"description":"No Content response."}},"schemes":["http"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/path-with-multiple-wildcards_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: localhost:80 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /{foo}/{bar}: 16 | post: 17 | tags: 18 | - test service 19 | summary: test endpoint test service 20 | operationId: test service#test endpoint 21 | parameters: 22 | - name: foo 23 | in: path 24 | required: true 25 | type: integer 26 | - name: bar 27 | in: path 28 | required: true 29 | type: integer 30 | responses: 31 | "204": 32 | description: No Content response. 33 | schemes: 34 | - http 35 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file0.golden: -------------------------------------------------------------------------------- 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":{"/{int_map}":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"int_map","in":"path","required":true,"type":"integer"}],"responses":{"204":{"description":"No Content response."}},"schemes":["http"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/path-with-wildcards_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: localhost:80 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /{int_map}: 16 | post: 17 | tags: 18 | - test service 19 | summary: test endpoint test service 20 | operationId: test service#test endpoint 21 | parameters: 22 | - name: int_map 23 | in: path 24 | required: true 25 | type: integer 26 | responses: 27 | "204": 28 | description: No Content response. 29 | schemes: 30 | - http 31 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/server-host-with-variables_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"v1.goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","responses":{"204":{"description":"No Content response."}},"schemes":["https"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/server-host-with-variables_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: v1.goa.design 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /: 16 | post: 17 | tags: 18 | - testService 19 | summary: testEndpoint testService 20 | operationId: testService#testEndpoint 21 | responses: 22 | "204": 23 | description: No Content response. 24 | schemes: 25 | - https 26 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestSections/valid_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"TestEndpointRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/Payload"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Result"}}},"schemes":["https"]}}},"definitions":{"Payload":{"title":"Payload","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}},"Result":{"title":"Result","type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestValidations/array_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"array","in":"body","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/Foobar"}}}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","minLength":0,"maxLength":42}}},"schemes":["https"]}}},"definitions":{"Bar":{"title":"Bar","type":"object","properties":{"string":{"type":"string","example":"","minLength":0,"maxLength":42}},"example":{"string":""}},"Foobar":{"title":"Foobar","type":"object","properties":{"bar":{"type":"array","items":{"$ref":"#/definitions/Bar"},"example":[{"string":""},{"string":""}],"minItems":0,"maxItems":42},"foo":{"type":"array","items":{"type":"string","example":"Beatae non id consequatur."},"example":[],"minItems":0,"maxItems":42}},"example":{"bar":[{"string":""},{"string":""}],"foo":["Repudiandae sit.","Asperiores fuga qui rem qui earum eos."]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestValidations/integer_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"int","in":"body","required":true,"schema":{"type":"integer","format":"int64","minimum":0,"maximum":42}}],"responses":{"200":{"description":"OK response.","schema":{"type":"integer","format":"int64","minimum":0,"maximum":42}}},"schemes":["https"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestValidations/integer_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: goa.design 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /: 16 | post: 17 | tags: 18 | - testService 19 | summary: testEndpoint testService 20 | operationId: testService#testEndpoint 21 | parameters: 22 | - name: int 23 | in: body 24 | required: true 25 | schema: 26 | type: integer 27 | format: int64 28 | minimum: 0 29 | maximum: 42 30 | responses: 31 | "200": 32 | description: OK response. 33 | schema: 34 | type: integer 35 | format: int64 36 | minimum: 0 37 | maximum: 42 38 | schemes: 39 | - https 40 | -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestValidations/string_file0.golden: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"title":"","version":"0.0.1"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","parameters":[{"name":"string","in":"body","required":true,"schema":{"type":"string","minLength":0,"maxLength":42}}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","minLength":0,"maxLength":42}}},"schemes":["https"]}}}} -------------------------------------------------------------------------------- /http/codegen/openapi/v2/testdata/TestValidations/string_file1.golden: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "" 4 | version: 0.0.1 5 | host: goa.design 6 | consumes: 7 | - application/json 8 | - application/xml 9 | - application/gob 10 | produces: 11 | - application/json 12 | - application/xml 13 | - application/gob 14 | paths: 15 | /: 16 | post: 17 | tags: 18 | - testService 19 | summary: testEndpoint testService 20 | operationId: testService#testEndpoint 21 | parameters: 22 | - name: string 23 | in: body 24 | required: true 25 | schema: 26 | type: string 27 | minLength: 0 28 | maxLength: 42 29 | responses: 30 | "200": 31 | description: OK response. 32 | schema: 33 | type: string 34 | minLength: 0 35 | maxLength: 42 36 | schemes: 37 | - https 38 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package openapiv3 contains the algorithms and data structures used to 3 | generate OpenAPI v3 specifications from Goa designs. 4 | */ 5 | package openapiv3 6 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/example.go: -------------------------------------------------------------------------------- 1 | package openapiv3 2 | 3 | import ( 4 | "goa.design/goa/v3/expr" 5 | ) 6 | 7 | type ( 8 | // exampler is the interface used to initialize the example of an 9 | // OpenAPI object. 10 | exampler interface { 11 | setExample(any) 12 | setExamples(map[string]*ExampleRef) 13 | } 14 | ) 15 | 16 | // initExample sets the example or examples of the given object. 17 | func initExamples(obj exampler, attr *expr.AttributeExpr, r *expr.ExampleGenerator) { 18 | examples := attr.ExtractUserExamples() 19 | switch { 20 | case len(examples) > 1: 21 | refs := make(map[string]*ExampleRef, len(examples)) 22 | for _, ex := range examples { 23 | example := &Example{ 24 | Summary: ex.Summary, 25 | Description: ex.Description, 26 | Value: ex.Value, 27 | } 28 | refs[ex.Summary] = &ExampleRef{Value: example} 29 | } 30 | obj.setExamples(refs) 31 | return 32 | case len(examples) > 0: 33 | obj.setExample(examples[0].Value) 34 | default: 35 | obj.setExample(attr.Example(r)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/empty_file0.golden: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/empty_file1.golden: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/endpoint_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1","x-test-api":"API"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"post":{"operationId":"testService#testEndpoint","requestBody":{"content":{"application/json":{"example":{"string":""},"schema":{"$ref":"#/components/schemas/Payload"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"string":""},"schema":{"$ref":"#/components/schemas/Result"}}},"description":"OK response."}},"summary":"testEndpoint testService","tags":["testService"],"x-test-operation":"Operation"},"x-test-foo":"bar"}},"components":{"schemas":{"Payload":{"type":"object","properties":{"string":{"example":"","type":"string","x-test-schema":"Payload"}},"example":{"string":""}},"Result":{"type":"object","properties":{"string":{"example":"","type":"string","x-test-schema":"Result"}},"example":{"string":""}}}},"tags":[{"description":"Description of Backend","externalDocs":{"description":"See more docs here","url":"http://example.com"},"name":"Backend","x-data":{"foo":"bar"}}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/file-service_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/path1":{"get":{"tags":["service-name"],"summary":"Download filename","operationId":"service-name#/path1","responses":{"200":{"description":"File downloaded"}}}},"/path2":{"get":{"tags":["user-tag"],"summary":"Download filename","operationId":"service-name#/path2","responses":{"200":{"description":"File downloaded"}}}}},"components":{},"tags":[{"name":"service-name"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/file-service_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | servers: 6 | - url: http://localhost:80 7 | description: Default server for test api 8 | paths: 9 | /path1: 10 | get: 11 | tags: 12 | - service-name 13 | summary: Download filename 14 | operationId: service-name#/path1 15 | responses: 16 | "200": 17 | description: File downloaded 18 | /path2: 19 | get: 20 | tags: 21 | - user-tag 22 | summary: Download filename 23 | operationId: service-name#/path2 24 | responses: 25 | "200": 26 | description: File downloaded 27 | components: {} 28 | tags: 29 | - name: service-name 30 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/headers_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"foo","in":"header","allowEmptyValue":true,"schema":{"type":"integer","example":9176544974339886224,"format":"int64"},"example":1933576090881074823},{"name":"bar","in":"header","allowEmptyValue":true,"schema":{"type":"integer","example":2166276375441812184,"format":"int64"},"example":7595816812588075382}],"responses":{"204":{"description":"No Content response."}}}}},"components":{},"tags":[{"name":"test service"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/integer_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"integer","example":1,"format":"int64","minimum":0,"maximum":42},"example":1}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"integer","example":1,"format":"int64","minimum":0,"maximum":42},"example":1}}}}}}},"components":{},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/json-indent_file0.golden: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Goa API", 5 | "version": "0.0.1" 6 | }, 7 | "servers": [ 8 | { 9 | "url": "https://goa.design" 10 | } 11 | ], 12 | "paths": { 13 | "/path1": { 14 | "get": { 15 | "tags": [ 16 | "service-name" 17 | ], 18 | "summary": "Download filename", 19 | "operationId": "service-name#/path1", 20 | "responses": { 21 | "200": { 22 | "description": "File downloaded" 23 | } 24 | } 25 | } 26 | }, 27 | "/path2": { 28 | "get": { 29 | "tags": [ 30 | "user-tag" 31 | ], 32 | "summary": "Download filename", 33 | "operationId": "service-name#/path2", 34 | "responses": { 35 | "200": { 36 | "description": "File downloaded" 37 | } 38 | } 39 | } 40 | } 41 | }, 42 | "components": {}, 43 | "tags": [ 44 | { 45 | "name": "service-name" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/json-indent_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | servers: 6 | - url: https://goa.design 7 | paths: 8 | /path1: 9 | get: 10 | tags: 11 | - service-name 12 | summary: Download filename 13 | operationId: service-name#/path1 14 | responses: 15 | "200": 16 | description: File downloaded 17 | /path2: 18 | get: 19 | tags: 20 | - user-tag 21 | summary: Download filename 22 | operationId: service-name#/path2 23 | responses: 24 | "200": 25 | description: File downloaded 26 | components: {} 27 | tags: 28 | - name: service-name 29 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/json-prefix-indent_file0.golden: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Goa API", 5 | "version": "0.0.1" 6 | }, 7 | "servers": [ 8 | { 9 | "url": "https://goa.design" 10 | } 11 | ], 12 | "paths": { 13 | "/path1": { 14 | "get": { 15 | "tags": [ 16 | "service-name" 17 | ], 18 | "summary": "Download filename", 19 | "operationId": "service-name#/path1", 20 | "responses": { 21 | "200": { 22 | "description": "File downloaded" 23 | } 24 | } 25 | } 26 | }, 27 | "/path2": { 28 | "get": { 29 | "tags": [ 30 | "user-tag" 31 | ], 32 | "summary": "Download filename", 33 | "operationId": "service-name#/path2", 34 | "responses": { 35 | "200": { 36 | "description": "File downloaded" 37 | } 38 | } 39 | } 40 | } 41 | }, 42 | "components": {}, 43 | "tags": [ 44 | { 45 | "name": "service-name" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/json-prefix-indent_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | servers: 6 | - url: https://goa.design 7 | paths: 8 | /path1: 9 | get: 10 | tags: 11 | - service-name 12 | summary: Download filename 13 | operationId: service-name#/path1 14 | responses: 15 | "200": 16 | description: File downloaded 17 | /path2: 18 | get: 19 | tags: 20 | - user-tag 21 | summary: Download filename 22 | operationId: service-name#/path2 23 | responses: 24 | "200": 25 | description: File downloaded 26 | components: {} 27 | tags: 28 | - name: service-name 29 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/json-prefix_file0.golden: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Goa API", 5 | "version": "0.0.1" 6 | }, 7 | "servers": [ 8 | { 9 | "url": "https://goa.design" 10 | } 11 | ], 12 | "paths": { 13 | "/path1": { 14 | "get": { 15 | "tags": [ 16 | "service-name" 17 | ], 18 | "summary": "Download filename", 19 | "operationId": "service-name#/path1", 20 | "responses": { 21 | "200": { 22 | "description": "File downloaded" 23 | } 24 | } 25 | } 26 | }, 27 | "/path2": { 28 | "get": { 29 | "tags": [ 30 | "user-tag" 31 | ], 32 | "summary": "Download filename", 33 | "operationId": "service-name#/path2", 34 | "responses": { 35 | "200": { 36 | "description": "File downloaded" 37 | } 38 | } 39 | } 40 | } 41 | }, 42 | "components": {}, 43 | "tags": [ 44 | { 45 | "name": "service-name" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/json-prefix_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | servers: 6 | - url: https://goa.design 7 | paths: 8 | /path1: 9 | get: 10 | tags: 11 | - service-name 12 | summary: Download filename 13 | operationId: service-name#/path1 14 | responses: 15 | "200": 16 | description: File downloaded 17 | /path2: 18 | get: 19 | tags: 20 | - user-tag 21 | summary: Download filename 22 | operationId: service-name#/path2 23 | responses: 24 | "200": 25 | description: File downloaded 26 | components: {} 27 | tags: 28 | - name: service-name 29 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/multiple-views_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpointDefault testService","operationId":"testService#testEndpointDefault","responses":{"200":{"description":"OK response.","content":{"application/custom+json":{"schema":{"$ref":"#/components/schemas/JSON"},"example":{"int":1,"string":""}}}}}}},"/tiny":{"get":{"tags":["testService"],"summary":"testEndpointTiny testService","operationId":"testService#testEndpointTiny","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEndpointTinyResponseBodyTiny"},"example":{"string":""}}}}}}}},"components":{"schemas":{"JSON":{"type":"object","properties":{"int":{"type":"integer","example":1,"format":"int64"},"string":{"type":"string","example":""}},"example":{"int":1,"string":""}},"TestEndpointTinyResponseBodyTiny":{"type":"object","properties":{"string":{"type":"string","example":""}},"description":"TestEndpointTinyResponseBody result type (tiny view)","example":{"string":""}}}},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/not-generate-attribute_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payload"},"example":{"required_string":"","string":""}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"},"example":{"int":0,"required_int":0}}}}}}}},"components":{"schemas":{"Payload":{"type":"object","properties":{"required_string":{"type":"string","example":""},"string":{"type":"string","example":""}},"example":{"required_string":"","string":""},"required":["required_string"]},"Result":{"type":"object","properties":{"int":{"type":"integer","example":0,"format":"int64"},"required_int":{"type":"integer","example":0,"format":"int64"}},"example":{"int":0,"required_int":0},"required":["required_int"]}}},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/not-generate-host_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Beatae non id consequatur."},"example":"Aut sed ducimus repudiandae sit explicabo asperiores."}}}}}}},"components":{},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/not-generate-host_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | paths: 6 | /: 7 | get: 8 | tags: 9 | - testService 10 | summary: testEndpoint testService 11 | operationId: testService#testEndpoint 12 | responses: 13 | "200": 14 | description: OK response. 15 | content: 16 | application/json: 17 | schema: 18 | type: string 19 | example: Beatae non id consequatur. 20 | example: Aut sed ducimus repudiandae sit explicabo asperiores. 21 | components: {} 22 | tags: 23 | - name: testService 24 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/not-generate-server_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Beatae non id consequatur."},"example":"Aut sed ducimus repudiandae sit explicabo asperiores."}}}}}}},"components":{},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/not-generate-server_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | paths: 6 | /: 7 | get: 8 | tags: 9 | - testService 10 | summary: testEndpoint testService 11 | operationId: testService#testEndpoint 12 | responses: 13 | "200": 14 | description: OK response. 15 | content: 16 | application/json: 17 | schema: 18 | type: string 19 | example: Beatae non id consequatur. 20 | example: Aut sed ducimus repudiandae sit explicabo asperiores. 21 | components: {} 22 | tags: 23 | - name: testService 24 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/path-with-multiple-explicit-wildcards_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/{foo}/{bar}":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"foo","in":"path","required":true,"schema":{"type":"integer","example":9176544974339886224,"format":"int64"},"example":1933576090881074823},{"name":"bar","in":"path","required":true,"schema":{"type":"integer","example":2166276375441812184,"format":"int64"},"example":7595816812588075382}],"responses":{"204":{"description":"No Content response."}}}}},"components":{},"tags":[{"name":"test service"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/path-with-multiple-wildcards_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/{foo}/{bar}":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"foo","in":"path","required":true,"schema":{"type":"integer","example":9176544974339886224,"format":"int64"},"example":1933576090881074823},{"name":"bar","in":"path","required":true,"schema":{"type":"integer","example":2166276375441812184,"format":"int64"},"example":7595816812588075382}],"responses":{"204":{"description":"No Content response."}}}}},"components":{},"tags":[{"name":"test service"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/path-with-wildcards_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/{int_map}":{"post":{"tags":["test service"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"int_map","in":"path","required":true,"schema":{"type":"integer","example":9176544974339886224,"format":"int64"},"example":1933576090881074823}],"responses":{"204":{"description":"No Content response."}}}}},"components":{},"tags":[{"name":"test service"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/path-with-wildcards_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | servers: 6 | - url: http://localhost:80 7 | description: Default server for test api 8 | paths: 9 | /{int_map}: 10 | post: 11 | tags: 12 | - test service 13 | summary: test endpoint test service 14 | operationId: test service#test endpoint 15 | parameters: 16 | - name: int_map 17 | in: path 18 | required: true 19 | schema: 20 | type: integer 21 | example: 9176544974339886224 22 | format: int64 23 | example: 1933576090881074823 24 | responses: 25 | "204": 26 | description: No Content response. 27 | components: {} 28 | tags: 29 | - name: test service 30 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/server-host-with-variables_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"https://{version}.goa.design","variables":{"version":{"default":"v1"}}}],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","responses":{"204":{"description":"No Content response."}}}}},"components":{},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/server-host-with-variables_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | servers: 6 | - url: https://{version}.goa.design 7 | variables: 8 | version: 9 | default: v1 10 | paths: 11 | /: 12 | post: 13 | tags: 14 | - testService 15 | summary: testEndpoint testService 16 | operationId: testService#testEndpoint 17 | responses: 18 | "204": 19 | description: No Content response. 20 | components: {} 21 | tags: 22 | - name: testService 23 | -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/skip-response-body-encode-decode_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/binary":{"get":{"tags":["testService"],"summary":"binary testService","operationId":"testService#binary","responses":{"200":{"description":"OK response.","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}}}}},"/empty":{"get":{"tags":["testService"],"summary":"empty testService","operationId":"testService#empty","responses":{"204":{"description":"No Content response."}}}},"/empty/ok":{"get":{"tags":["testService"],"summary":"empty_ok testService","operationId":"testService#empty_ok","responses":{"200":{"description":"OK response."}}}}},"components":{},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/string_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"string","example":"","minLength":0,"maxLength":42},"example":""}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"","minLength":0,"maxLength":42},"example":""}}}}}}},"components":{},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/valid_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"get":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payload"},"example":{"string":""}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"},"example":{"string":""}}}}}}}},"components":{"schemas":{"Payload":{"type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}},"Result":{"type":"object","properties":{"string":{"type":"string","example":""}},"example":{"string":""}}}},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/with-any_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEndpointRequestBody"},"example":{"any":"","any_array":["","","",""],"any_map":{"":""}}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEndpointRequestBody"},"example":{"any":"","any_array":["","",""],"any_map":{"":""}}}}}}}}},"components":{"schemas":{"TestEndpointRequestBody":{"type":"object","properties":{"any":{"example":""},"any_array":{"type":"array","items":{"example":""},"example":["","","",""]},"any_map":{"type":"object","example":{"":""},"additionalProperties":true}},"example":{"any":"","any_array":["",""],"any_map":{"":""}}}}},"tags":[{"name":"testService"}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/with-tags_file0.golden: -------------------------------------------------------------------------------- 1 | {"openapi":"3.0.3","info":{"title":"Goa API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/{int_map}":{"post":{"tags":["SomeTag"],"summary":"test endpoint test service","operationId":"test service#test endpoint","parameters":[{"name":"int_map","in":"path","required":true,"schema":{"type":"integer","example":9176544974339886224,"format":"int64"},"example":1933576090881074823}],"responses":{"204":{"description":"No Content response."}}}}},"components":{},"tags":[{"name":"AnotherTag","description":"Endpoint description","externalDocs":{"url":"Endpoint URL"}},{"name":"SomeTag","description":"Endpoint description","externalDocs":{"url":"Endpoint URL"}}]} -------------------------------------------------------------------------------- /http/codegen/openapi/v3/testdata/golden/with-tags_file1.golden: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Goa API 4 | version: 0.0.1 5 | servers: 6 | - url: http://localhost:80 7 | description: Default server for test api 8 | paths: 9 | /{int_map}: 10 | post: 11 | tags: 12 | - SomeTag 13 | summary: test endpoint test service 14 | operationId: test service#test endpoint 15 | parameters: 16 | - name: int_map 17 | in: path 18 | required: true 19 | schema: 20 | type: integer 21 | example: 9176544974339886224 22 | format: int64 23 | example: 1933576090881074823 24 | responses: 25 | "204": 26 | description: No Content response. 27 | components: {} 28 | tags: 29 | - name: AnotherTag 30 | description: Endpoint description 31 | externalDocs: 32 | description: "" 33 | url: Endpoint URL 34 | - name: SomeTag 35 | description: Endpoint description 36 | externalDocs: 37 | description: "" 38 | url: Endpoint URL 39 | -------------------------------------------------------------------------------- /http/codegen/sse_server_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "goa.design/goa/v3/codegen" 10 | "goa.design/goa/v3/expr" 11 | "goa.design/goa/v3/http/codegen/testdata" 12 | ) 13 | 14 | func TestSSE(t *testing.T) { 15 | cases := []struct { 16 | Name string 17 | DSL func() 18 | }{ 19 | {"string", testdata.SSEStringDSL}, 20 | {"int", testdata.SSEIntDSL}, 21 | {"bool", testdata.SSEBoolDSL}, 22 | {"object", testdata.SSEObjectDSL}, 23 | {"data-field", testdata.SSEDataFieldDSL}, 24 | {"data-id-field", testdata.SSEDataIDFieldDSL}, 25 | {"request-id", testdata.SSERequestIDDSL}, 26 | {"all-fields", testdata.SSEAllFieldsDSL}, 27 | } 28 | 29 | for _, c := range cases { 30 | t.Run(c.Name, func(t *testing.T) { 31 | RunHTTPDSL(t, c.DSL) 32 | fs := ServerFiles("", expr.Root) 33 | require.Len(t, fs, 3) 34 | sections := fs[1].SectionTemplates 35 | require.Greater(t, len(sections), 1) 36 | code := codegen.SectionCode(t, sections[1]) 37 | golden := filepath.Join("testdata", "golden", "sse-"+c.Name+".golden") 38 | compareOrUpdateGolden(t, code, golden) 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /http/codegen/templates.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "path" 7 | "strings" 8 | ) 9 | 10 | //go:embed templates/* 11 | var tmplFS embed.FS 12 | 13 | // readTemplate returns the service template with the given name. 14 | func readTemplate(name string, partials ...string) string { 15 | var prefix string 16 | { 17 | var partialDefs []string 18 | for _, partial := range partials { 19 | tmpl, err := tmplFS.ReadFile(path.Join("templates", "partial", partial+".go.tpl")) 20 | if err != nil { 21 | panic("failed to read partial template " + partial + ": " + err.Error()) // Should never happen, bug if it does 22 | } 23 | partialDefs = append(partialDefs, 24 | fmt.Sprintf("{{- define \"partial_%s\" }}\n%s{{- end }}", partial, string(tmpl))) 25 | } 26 | prefix = strings.Join(partialDefs, "\n") 27 | } 28 | content, err := tmplFS.ReadFile(path.Join("templates", name) + ".go.tpl") 29 | if err != nil { 30 | panic("failed to load template " + name + ": " + err.Error()) // Should never happen, bug if it does 31 | } 32 | return prefix + "\n" + string(content) 33 | } 34 | -------------------------------------------------------------------------------- /http/codegen/templates/append_fs.go.tpl: -------------------------------------------------------------------------------- 1 | // appendFS is a custom implementation of fs.FS that appends a specified prefix 2 | // to the file paths before delegating the Open call to the underlying fs.FS. 3 | type appendFS struct { 4 | prefix string 5 | fs http.FileSystem 6 | } 7 | 8 | // Open opens the named file, appending the prefix to the file path before 9 | // passing it to the underlying fs.FS. 10 | func (s appendFS) Open(name string) (http.File, error) { 11 | switch name { 12 | {{- range $requested, $embedded := . }} 13 | case {{ printf "%q" $requested }}: 14 | name = {{ printf "%q" $embedded }} 15 | {{- end }} 16 | } 17 | return s.fs.Open(path.Join(s.prefix, name)) 18 | } 19 | 20 | // appendPrefix returns a new fs.FS that appends the specified prefix to file paths 21 | // before delegating to the provided embed.FS. 22 | func appendPrefix(fsys http.FileSystem, prefix string) http.FileSystem { 23 | return appendFS{prefix: prefix, fs: fsys} 24 | } 25 | -------------------------------------------------------------------------------- /http/codegen/templates/build_stream_request.go.tpl: -------------------------------------------------------------------------------- 1 | // {{ printf "%s creates a streaming endpoint request payload from the method payload and the path to the file to be streamed" .BuildStreamPayload | comment }} 2 | func {{ .BuildStreamPayload }}({{ if .Payload.Ref }}payload any, {{ end }}fpath string) (*{{ requestStructPkg .Method .ServicePkgName }}.{{ .Method.RequestStruct }}, error) { 3 | f, err := os.Open(fpath) 4 | if err != nil { 5 | return nil, err 6 | } 7 | return &{{ requestStructPkg .Method .ServicePkgName }}.{{ .Method.RequestStruct }}{ 8 | {{- if .Payload.Ref }} 9 | Payload: payload.({{ .Payload.Ref }}), 10 | {{- end }} 11 | Body: f, 12 | }, nil 13 | } 14 | -------------------------------------------------------------------------------- /http/codegen/templates/cli_end.go.tpl: -------------------------------------------------------------------------------- 1 | return cli.ParseEndpoint( 2 | scheme, 3 | host, 4 | doer, 5 | goahttp.RequestEncoder, 6 | goahttp.ResponseDecoder, 7 | debug, 8 | {{- if needDialer .Services }} 9 | dialer, 10 | {{- range $svc := .Services }} 11 | {{- if hasWebSocket $svc }} 12 | nil, 13 | {{- end }} 14 | {{- end }} 15 | {{- end }} 16 | {{- range .Services }} 17 | {{- range .Endpoints }} 18 | {{- if .MultipartRequestDecoder }} 19 | {{ $.APIPkg }}.{{ .MultipartRequestEncoder.FuncName }}, 20 | {{- end }} 21 | {{- end }} 22 | {{- end }} 23 | {{- range .Services }} 24 | {{- if .Service.ClientInterceptors }} 25 | {{ .Service.VarName }}Interceptors, 26 | {{- end }} 27 | {{- end }} 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /http/codegen/templates/cli_start.go.tpl: -------------------------------------------------------------------------------- 1 | func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { 2 | var ( 3 | doer goahttp.Doer 4 | {{- range .Services }} 5 | {{- if .Service.ClientInterceptors }} 6 | {{ .Service.VarName }}Interceptors {{ .Service.PkgName }}.ClientInterceptors 7 | {{- end }} 8 | {{- end }} 9 | ) 10 | { 11 | doer = &http.Client{Timeout: time.Duration(timeout) * time.Second} 12 | if debug { 13 | doer = goahttp.NewDebugDoer(doer) 14 | } 15 | {{- range .Services }} 16 | {{- if .Service.ClientInterceptors }} 17 | {{ .Service.VarName }}Interceptors = {{ $.InterceptorsPkg }}.New{{ .Service.StructName }}ClientInterceptors() 18 | {{- end }} 19 | {{- end }} 20 | } 21 | -------------------------------------------------------------------------------- /http/codegen/templates/cli_streaming.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if needDialer .Services }} 2 | var ( 3 | dialer *websocket.Dialer 4 | ) 5 | { 6 | dialer = websocket.DefaultDialer 7 | } 8 | {{ end }} 9 | -------------------------------------------------------------------------------- /http/codegen/templates/cli_usage.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | func httpUsageCommands() string { 3 | return cli.UsageCommands() 4 | } 5 | 6 | func httpUsageExamples() string { 7 | return cli.UsageExamples() 8 | } 9 | -------------------------------------------------------------------------------- /http/codegen/templates/client_body_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | func {{ .Name }}({{ range .ClientArgs }}{{ .VarName }} {{.TypeRef }}, {{ end }}) {{ .ReturnTypeRef }} { 3 | {{ .ClientCode }} 4 | return body 5 | } 6 | -------------------------------------------------------------------------------- /http/codegen/templates/client_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "New%s instantiates HTTP clients for all the %s service servers." .ClientStruct .Service.Name | comment }} 2 | func New{{ .ClientStruct }}( 3 | scheme string, 4 | host string, 5 | doer goahttp.Doer, 6 | enc func(*http.Request) goahttp.Encoder, 7 | dec func(*http.Response) goahttp.Decoder, 8 | restoreBody bool, 9 | {{- if hasWebSocket . }} 10 | dialer goahttp.Dialer, 11 | cfn *ConnConfigurer, 12 | {{- end }} 13 | ) *{{ .ClientStruct }} { 14 | {{- if hasWebSocket . }} 15 | if cfn == nil { 16 | cfn = &ConnConfigurer{} 17 | } 18 | {{- end }} 19 | return &{{ .ClientStruct }}{ 20 | {{- range .Endpoints }} 21 | {{ .Method.VarName }}Doer: doer, 22 | {{- end }} 23 | RestoreResponseBody: restoreBody, 24 | scheme: scheme, 25 | host: host, 26 | decoder: dec, 27 | encoder: enc, 28 | {{- if hasWebSocket . }} 29 | dialer: dialer, 30 | configurer: cfn, 31 | {{- end }} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /http/codegen/templates/client_struct.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s lists the %s service endpoint HTTP clients." .ClientStruct .Service.Name | comment }} 2 | type {{ .ClientStruct }} struct { 3 | {{- range .Endpoints }} 4 | {{ printf "%s Doer is the HTTP client used to make requests to the %s endpoint." .Method.VarName .Method.Name | comment }} 5 | {{ .Method.VarName }}Doer goahttp.Doer 6 | {{ end }} 7 | // RestoreResponseBody controls whether the response bodies are reset after 8 | // decoding so they can be read again. 9 | RestoreResponseBody bool 10 | 11 | scheme string 12 | host string 13 | encoder func(*http.Request) goahttp.Encoder 14 | decoder func(*http.Response) goahttp.Decoder 15 | {{- if hasWebSocket . }} 16 | dialer goahttp.Dialer 17 | configurer *ConnConfigurer 18 | {{- end }} 19 | } 20 | -------------------------------------------------------------------------------- /http/codegen/templates/client_type_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | func {{ .Name }}({{- range .ClientArgs }}{{ .VarName }} {{ .TypeRef }}, {{ end }}) {{ .ReturnTypeRef }} { 3 | {{- if .ClientCode }} 4 | {{ .ClientCode }} 5 | {{- if .ReturnTypeAttribute }} 6 | res := &{{ .ReturnTypeName }}{ 7 | {{ .ReturnTypeAttribute }}: {{ if .ReturnIsPrimitivePointer }}&{{ end }}v, 8 | } 9 | {{- end }} 10 | {{- end }} 11 | {{- if .ReturnIsStruct }} 12 | {{- if not .ClientCode }} 13 | {{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }} := &{{ .ReturnTypeName }}{} 14 | {{- end }} 15 | {{- end }} 16 | {{ fieldCode . "client" }} 17 | return {{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }} 18 | } 19 | -------------------------------------------------------------------------------- /http/codegen/templates/dummy_multipart_request_decoder.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s implements the multipart decoder for service %q endpoint %q. The decoder must populate the argument p after encoding." .FuncName .ServiceName .MethodName | comment }} 2 | func {{ .FuncName }}(mr *multipart.Reader, p *{{ .Payload.Ref }}) error { 3 | // Add multipart request decoder logic here 4 | return nil 5 | } 6 | -------------------------------------------------------------------------------- /http/codegen/templates/dummy_multipart_request_encoder.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s implements the multipart encoder for service %q endpoint %q." .FuncName .ServiceName .MethodName | comment }} 2 | func {{ .FuncName }}(mw *multipart.Writer, p {{ .Payload.Ref }}) error { 3 | // Add multipart request encoder logic here 4 | return nil 5 | } 6 | -------------------------------------------------------------------------------- /http/codegen/templates/file_server.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s configures the mux to serve GET request made to %q." .MountHandler (join .RequestPaths ", ") | comment }} 2 | func {{ .MountHandler }}(mux goahttp.Muxer, h http.Handler) { 3 | {{- if .IsDir }} 4 | {{- range .RequestPaths }} 5 | mux.Handle("GET", "{{ . }}{{if ne . "/"}}/{{end}}", h.ServeHTTP) 6 | mux.Handle("GET", "{{ . }}{{if ne . "/"}}/{{end}}{*{{ $.PathParam }}}", h.ServeHTTP) 7 | {{- end }} 8 | {{- else }} 9 | {{- range .RequestPaths }} 10 | mux.Handle("GET", "{{ . }}", h.ServeHTTP) 11 | {{- end }} 12 | {{- end }} 13 | } 14 | -------------------------------------------------------------------------------- /http/codegen/templates/mount_point_struct.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s holds information about the mounted endpoints." .MountPointStruct | comment }} 2 | type {{ .MountPointStruct }} struct { 3 | {{ printf "Method is the name of the service method served by the mounted HTTP handler." | comment }} 4 | Method string 5 | {{ printf "Verb is the HTTP method used to match requests to the mounted handler." | comment }} 6 | Verb string 7 | {{ printf "Pattern is the HTTP request path pattern used to match requests to the mounted handler." | comment }} 8 | Pattern string 9 | } 10 | -------------------------------------------------------------------------------- /http/codegen/templates/multipart_request_decoder.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s returns a decoder to decode the multipart request for the %q service %q endpoint." .InitName .ServiceName .MethodName | comment }} 2 | func {{ .InitName }}(mux goahttp.Muxer, {{ .VarName }} {{ .FuncName }}) func(r *http.Request) goahttp.Decoder { 3 | return func(r *http.Request) goahttp.Decoder { 4 | return goahttp.EncodingFunc(func(v any) error { 5 | mr, merr := r.MultipartReader() 6 | if merr != nil { 7 | return merr 8 | } 9 | p := v.(*{{ .Payload.Ref }}) 10 | if err := {{ .VarName }}(mr, p); err != nil { 11 | return err 12 | } 13 | {{- template "partial_request_elements" .Payload.Request }} 14 | {{- if .Payload.Request.MustValidate }} 15 | if err != nil { 16 | return err 17 | } 18 | {{- end }} 19 | {{- if .Payload.Request.PayloadInit }} 20 | {{- range .Payload.Request.PayloadInit.ServerArgs }} 21 | {{- if .FieldName }} 22 | (*p).{{ .FieldName }} = {{ if and (not .Pointer) .FieldPointer }}&{{ end }}{{ .VarName }} 23 | {{- end }} 24 | {{- end }} 25 | {{- end }} 26 | return nil 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /http/codegen/templates/multipart_request_decoder_type.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s is the type to decode multipart request for the %q service %q endpoint." .FuncName .ServiceName .MethodName | comment }} 2 | type {{ .FuncName }} func(*multipart.Reader, *{{ .Payload.Ref }}) error 3 | -------------------------------------------------------------------------------- /http/codegen/templates/multipart_request_encoder.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s returns an encoder to encode the multipart request for the %q service %q endpoint." .InitName .ServiceName .MethodName | comment }} 2 | func {{ .InitName }}(encoderFn {{ .FuncName }}) func(r *http.Request) goahttp.Encoder { 3 | return func(r *http.Request) goahttp.Encoder { 4 | body := &bytes.Buffer{} 5 | mw := multipart.NewWriter(body) 6 | return goahttp.EncodingFunc(func(v any) error { 7 | p := v.({{ .Payload.Ref }}) 8 | if err := encoderFn(mw, p); err != nil { 9 | return err 10 | } 11 | r.Body = io.NopCloser(body) 12 | r.Header.Set("Content-Type", mw.FormDataContentType()) 13 | return mw.Close() 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /http/codegen/templates/multipart_request_encoder_type.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s is the type to encode multipart request for the %q service %q endpoint." .FuncName .ServiceName .MethodName | comment }} 2 | type {{ .FuncName }} func(*multipart.Writer, {{ .Payload.Ref }}) error 3 | -------------------------------------------------------------------------------- /http/codegen/templates/partial/element_slice_conversion.go.tpl: -------------------------------------------------------------------------------- 1 | {{ .VarName }} = make({{ goTypeRef .Type }}, len({{ .VarName }}Raw)) 2 | for i, rv := range {{ .VarName }}Raw { 3 | {{- template "partial_slice_item_conversion" . }} 4 | } 5 | -------------------------------------------------------------------------------- /http/codegen/templates/partial/path_conversion.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if eq .Type.Name "array" }} 2 | {{ .VarName }}RawSlice := strings.Split({{ .VarName }}Raw, ",") 3 | {{ .VarName }} = make({{ goTypeRef .Type }}, len({{ .VarName }}RawSlice)) 4 | for i, rv := range {{ .VarName }}RawSlice { 5 | {{- template "partial_slice_item_conversion" . }} 6 | } 7 | {{- else }} 8 | {{- template "partial_query_type_conversion" . }} 9 | {{- end }} 10 | -------------------------------------------------------------------------------- /http/codegen/templates/partial/query_slice_conversion.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if eq . "string" }} url.QueryEscape(v) 2 | {{- else if eq . "int" "int32" }} strconv.FormatInt(int64(v), 10) 3 | {{- else if eq . "int64" }} strconv.FormatInt(v, 10) 4 | {{- else if eq . "uint" "uint32" }} strconv.FormatUint(uint64(v), 10) 5 | {{- else if eq . "uint64" }} strconv.FormatUint(v, 10) 6 | {{- else if eq . "float32" }} strconv.FormatFloat(float64(v), 'f', -1, 32) 7 | {{- else if eq . "float64" }} strconv.FormatFloat(v, 'f', -1, 64) 8 | {{- else if eq . "boolean" }} strconv.FormatBool(v) 9 | {{- else if eq . "bytes" }} url.QueryEscape(string(v)) 10 | {{- else }} url.QueryEscape(fmt.Sprintf("%v", v)) 11 | {{- end }} 12 | -------------------------------------------------------------------------------- /http/codegen/templates/partial/sse_format.go.tpl: -------------------------------------------------------------------------------- 1 | {{- if eq .TypeRef "string" }} 2 | data = {{ .VarName }} 3 | {{- else if eq .TypeRef "boolean" }} 4 | if {{ .VarName }} { 5 | data = "true" 6 | } else { 7 | data = "false" 8 | } 9 | {{- else if eq .TypeRef "bytes" }} 10 | data = string({{ .VarName }}) 11 | {{- else if or (eq .TypeRef "int") (eq .TypeRef "int32") (eq .TypeRef "int64") (eq .TypeRef "uint") (eq .TypeRef "uint32") (eq .TypeRef "uint64") }} 12 | data = fmt.Sprintf("%d", {{ .VarName }}) 13 | {{- else if or (eq .TypeRef "float32") (eq .TypeRef "float64") }} 14 | data = fmt.Sprintf("%g", {{ .VarName }}) 15 | {{- else }} 16 | byts, err := json.Marshal({{ .VarName }}) 17 | if err != nil { 18 | return err 19 | } 20 | data = string(byts) 21 | {{- end }} -------------------------------------------------------------------------------- /http/codegen/templates/path.go.tpl: -------------------------------------------------------------------------------- 1 | {{ range .Routes }}// {{ .PathInit.Description }} 2 | func {{ .PathInit.Name }}({{ range .PathInit.ServerArgs }}{{ .VarName }} {{ .TypeRef }}, {{ end }}) {{ .PathInit.ReturnTypeRef }} { 3 | {{- .PathInit.ServerCode }} 4 | } 5 | {{ end }} 6 | -------------------------------------------------------------------------------- /http/codegen/templates/path_init.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{- if .Args }} 3 | {{- range $i, $arg := .Args }} 4 | {{- $typ := (index $.PathParams $i).Attribute.Type }} 5 | {{- if eq $typ.Name "array" }} 6 | {{ .VarName }}Slice := make([]string, len({{ .VarName }})) 7 | for i, v := range {{ .VarName }} { 8 | {{ .VarName }}Slice[i] = {{ template "partial_query_slice_conversion" $typ.ElemType.Type.Name }} 9 | } 10 | {{- end }} 11 | {{- end }} 12 | return fmt.Sprintf("{{ .PathFormat }}", {{ range $i, $arg := .Args }} 13 | {{- if eq (index $.PathParams $i).Attribute.Type.Name "array" }}strings.Join({{ .VarName }}Slice, ",") 14 | {{- else }}{{ .VarName }} 15 | {{- end }}, {{ end }}) 16 | {{- else }} 17 | return "{{ .PathFormat }}" 18 | {{- end -}} 19 | -------------------------------------------------------------------------------- /http/codegen/templates/request_builder.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .RequestInit.Description }} 2 | func (c *{{ .ClientStruct }}) {{ .RequestInit.Name }}(ctx context.Context, {{ range .RequestInit.ClientArgs }}{{ .VarName }} {{ .TypeRef }},{{ end }}) (*http.Request, error) { 3 | {{- .RequestInit.ClientCode }} 4 | } 5 | -------------------------------------------------------------------------------- /http/codegen/templates/server_body_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | func {{ .Name }}({{ range .ServerArgs }}{{ .VarName }} {{.TypeRef }}, {{ end }}) {{ .ReturnTypeRef }} { 3 | {{ .ServerCode }} 4 | return body 5 | } 6 | -------------------------------------------------------------------------------- /http/codegen/templates/server_encoding.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | // Provide the transport specific request decoder and response encoder. 3 | // The goa http package has built-in support for JSON, XML and gob. 4 | // Other encodings can be used by providing the corresponding functions, 5 | // see goa.design/implement/encoding. 6 | var ( 7 | dec = goahttp.RequestDecoder 8 | enc = goahttp.ResponseEncoder 9 | ) 10 | -------------------------------------------------------------------------------- /http/codegen/templates/server_end.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | // Start HTTP server using default configuration, change the code to 3 | // configure the server as required by your service. 4 | srv := &http.Server{Addr: u.Host, Handler: handler, ReadHeaderTimeout: time.Second * 60} 5 | 6 | {{- range .Services }} 7 | for _, m := range {{ .Service.VarName }}Server.Mounts { 8 | log.Printf(ctx, "HTTP %q mounted on %s %s", m.Method, m.Verb, m.Pattern) 9 | } 10 | {{- end }} 11 | 12 | (*wg).Add(1) 13 | go func() { 14 | defer (*wg).Done() 15 | 16 | {{ comment "Start HTTP server in a separate goroutine." }} 17 | go func() { 18 | log.Printf(ctx, "HTTP server listening on %q", u.Host) 19 | errc <- srv.ListenAndServe() 20 | }() 21 | 22 | <-ctx.Done() 23 | log.Printf(ctx, "shutting down HTTP server at %q", u.Host) 24 | 25 | {{ comment "Shutdown gracefully with a 30s timeout." }} 26 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 27 | defer cancel() 28 | 29 | err := srv.Shutdown(ctx) 30 | if err != nil { 31 | log.Printf(ctx, "failed to shutdown: %v", err) 32 | } 33 | }() 34 | } 35 | -------------------------------------------------------------------------------- /http/codegen/templates/server_error_handler.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | // errorHandler returns a function that writes and logs the given error. 3 | // The function also writes and logs the error unique ID so that it's possible 4 | // to correlate. 5 | func errorHandler(logCtx context.Context) func(context.Context, http.ResponseWriter, error) { 6 | return func(ctx context.Context, w http.ResponseWriter, err error) { 7 | log.Printf(logCtx, "ERROR: %s", err.Error()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /http/codegen/templates/server_handler.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s configures the mux to serve the %q service %q endpoint." .MountHandler .ServiceName .Method.Name | comment }} 2 | func {{ .MountHandler }}(mux goahttp.Muxer, h http.Handler) { 3 | f, ok := h.(http.HandlerFunc) 4 | if !ok { 5 | f = func(w http.ResponseWriter, r *http.Request) { 6 | h.ServeHTTP(w, r) 7 | } 8 | } 9 | {{- range .Routes }} 10 | mux.Handle("{{ .Verb }}", "{{ .Path }}", f) 11 | {{- end }} 12 | } 13 | -------------------------------------------------------------------------------- /http/codegen/templates/server_method_names.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "MethodNames returns the methods served." | comment }} 2 | func (s *{{ .ServerStruct }}) MethodNames() []string { return {{ .Service.PkgName }}.MethodNames[:] } 3 | -------------------------------------------------------------------------------- /http/codegen/templates/server_middleware.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | var handler http.Handler = mux 3 | if dbg { 4 | // Log query and response bodies if debug logs are enabled. 5 | handler = debug.HTTP()(handler) 6 | } 7 | handler = log.HTTP(ctx)(handler) 8 | -------------------------------------------------------------------------------- /http/codegen/templates/server_mux.go.tpl: -------------------------------------------------------------------------------- 1 | 2 | // Build the service HTTP request multiplexer and mount debug and profiler 3 | // endpoints in debug mode. 4 | var mux goahttp.Muxer 5 | { 6 | mux = goahttp.NewMuxer() 7 | if dbg { 8 | // Mount pprof handlers for memory profiling under /debug/pprof. 9 | debug.MountPprofHandlers(debug.Adapt(mux)) 10 | // Mount /debug endpoint to enable or disable debug logs at runtime. 11 | debug.MountDebugLogEnabler(debug.Adapt(mux)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /http/codegen/templates/server_service.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s returns the name of the service served." .ServerService | comment }} 2 | func (s *{{ .ServerStruct }}) {{ .ServerService }}() string { return "{{ .Service.Name }}" } 3 | -------------------------------------------------------------------------------- /http/codegen/templates/server_start.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment "handleHTTPServer starts configures and starts a HTTP server on the given URL. It shuts down the server if any error is received in the error channel." }} 2 | func handleHTTPServer(ctx context.Context, u *url.URL{{ range $.Services }}{{ if .Service.Methods }}, {{ .Service.VarName }}Endpoints *{{ .Service.PkgName }}.Endpoints{{ end }}{{ end }}, wg *sync.WaitGroup, errc chan error, dbg bool) { 3 | -------------------------------------------------------------------------------- /http/codegen/templates/server_struct.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s lists the %s service endpoint HTTP handlers." .ServerStruct .Service.Name | comment }} 2 | type {{ .ServerStruct }} struct { 3 | Mounts []*{{ .MountPointStruct }} 4 | {{- range .Endpoints }} 5 | {{ .Method.VarName }} http.Handler 6 | {{- end }} 7 | {{- range .FileServers }} 8 | {{ .VarName }} http.Handler 9 | {{- end }} 10 | } 11 | -------------------------------------------------------------------------------- /http/codegen/templates/server_type_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | func {{ .Name }}({{- range .ServerArgs }}{{ .VarName }} {{ .TypeRef }}, {{ end }}) {{ .ReturnTypeRef }} { 3 | {{- if .ServerCode }} 4 | {{ .ServerCode }} 5 | {{- if .ReturnTypeAttribute }} 6 | res := &{{ .ReturnTypeName }}{ 7 | {{ .ReturnTypeAttribute }}: {{ if .ReturnIsPrimitivePointer }}&{{ end }}v, 8 | } 9 | {{- end }} 10 | {{- end }} 11 | {{- if .ReturnIsStruct }} 12 | {{- if not .ServerCode }} 13 | {{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }} := &{{ .ReturnTypeName }}{} 14 | {{- end }} 15 | {{ fieldCode . "server" }} 16 | {{- end }} 17 | return {{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }} 18 | } 19 | -------------------------------------------------------------------------------- /http/codegen/templates/server_use.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "Use wraps the server handlers with the given middleware." | comment }} 2 | func (s *{{ .ServerStruct }}) Use(m func(http.Handler) http.Handler) { 3 | {{- range .Endpoints }} 4 | s.{{ .Method.VarName }} = m(s.{{ .Method.VarName }}) 5 | {{- end }} 6 | } 7 | -------------------------------------------------------------------------------- /http/codegen/templates/transform_helper.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s builds a value of type %s from a value of type %s." .Name .ResultTypeRef .ParamTypeRef | comment }} 2 | func {{ .Name }}(v {{ .ParamTypeRef }}) {{ .ResultTypeRef }} { 3 | {{ .Code }} 4 | return res 5 | } 6 | -------------------------------------------------------------------------------- /http/codegen/templates/type_decl.go.tpl: -------------------------------------------------------------------------------- 1 | {{ comment .Description }} 2 | type {{ .VarName }} {{ .Def }} 3 | -------------------------------------------------------------------------------- /http/codegen/templates/validate.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "Validate%s runs the validations defined on %s" .VarName .Name | comment }} 2 | func Validate{{ .VarName }}(body {{ .Ref }}) (err error) { 3 | {{ .ValidateDef }} 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /http/codegen/templates/websocket_close.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "Close closes the %q endpoint websocket connection." .Endpoint.Method.Name | comment }} 2 | func (s *{{ .VarName }}) Close() error { 3 | var err error 4 | {{- if eq .Type "server" }} 5 | if s.conn == nil { 6 | return nil 7 | } 8 | if err = s.conn.WriteControl( 9 | websocket.CloseMessage, 10 | websocket.FormatCloseMessage(websocket.CloseNormalClosure, "server closing connection"), 11 | time.Now().Add(time.Second), 12 | ); err != nil { 13 | return err 14 | } 15 | {{- else }} {{/* client side code */}} 16 | {{ comment "Send a nil payload to the server implying client closing connection." }} 17 | if err = s.conn.WriteJSON(nil); err != nil { 18 | return err 19 | } 20 | {{- end }} 21 | return s.conn.Close() 22 | } 23 | -------------------------------------------------------------------------------- /http/codegen/templates/websocket_conn_configurer_struct.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "ConnConfigurer holds the websocket connection configurer functions for the streaming endpoints in %q service." .Service.Name | comment }} 2 | type ConnConfigurer struct { 3 | {{- range .Endpoints }} 4 | {{- if isWebSocketEndpoint . }} 5 | {{ .Method.VarName }}Fn goahttp.ConnConfigureFunc 6 | {{- end }} 7 | {{- end }} 8 | } 9 | -------------------------------------------------------------------------------- /http/codegen/templates/websocket_conn_configurer_struct_init.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "NewConnConfigurer initializes the websocket connection configurer function with fn for all the streaming endpoints in %q service." .Service.Name | comment }} 2 | func NewConnConfigurer(fn goahttp.ConnConfigureFunc) *ConnConfigurer { 3 | return &ConnConfigurer{ 4 | {{- range .Endpoints }} 5 | {{- if isWebSocketEndpoint . }} 6 | {{ .Method.VarName}}Fn: fn, 7 | {{- end }} 8 | {{- end }} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /http/codegen/templates/websocket_set_view.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "SetView sets the view to render the %s type before sending to the %q endpoint websocket connection." .SendTypeName .Endpoint.Method.Name | comment }} 2 | func (s *{{ .VarName }}) SetView(view string) { 3 | s.view = view 4 | } 5 | -------------------------------------------------------------------------------- /http/codegen/templates/websocket_struct_type.go.tpl: -------------------------------------------------------------------------------- 1 | {{ printf "%s implements the %s interface." .VarName .Interface | comment }} 2 | type {{ .VarName }} struct { 3 | {{- if eq .Type "server" }} 4 | once sync.Once 5 | {{ comment "upgrader is the websocket connection upgrader." }} 6 | upgrader goahttp.Upgrader 7 | {{ comment "configurer is the websocket connection configurer." }} 8 | configurer goahttp.ConnConfigureFunc 9 | {{ comment "cancel is the context cancellation function which cancels the request context when invoked." }} 10 | cancel context.CancelFunc 11 | {{ comment "w is the HTTP response writer used in upgrading the connection." }} 12 | w http.ResponseWriter 13 | {{ comment "r is the HTTP request." }} 14 | r *http.Request 15 | {{- end }} 16 | {{ comment "conn is the underlying websocket connection." }} 17 | conn *websocket.Conn 18 | {{- if .Endpoint.Method.ViewedResult }} 19 | {{- if not .Endpoint.Method.ViewedResult.ViewName }} 20 | {{ printf "view is the view to render %s result type before sending to the websocket connection." .SendTypeName | comment }} 21 | view string 22 | {{- end }} 23 | {{- end }} 24 | } 25 | -------------------------------------------------------------------------------- /http/codegen/testdata/golden/client-no-server.golden: -------------------------------------------------------------------------------- 1 | func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { 2 | var ( 3 | doer goahttp.Doer 4 | ) 5 | { 6 | doer = &http.Client{Timeout: time.Duration(timeout) * time.Second} 7 | if debug { 8 | doer = goahttp.NewDebugDoer(doer) 9 | } 10 | } 11 | 12 | return cli.ParseEndpoint( 13 | scheme, 14 | host, 15 | doer, 16 | goahttp.RequestEncoder, 17 | goahttp.ResponseDecoder, 18 | debug, 19 | ) 20 | } 21 | 22 | func httpUsageCommands() string { 23 | return cli.UsageCommands() 24 | } 25 | 26 | func httpUsageExamples() string { 27 | return cli.UsageExamples() 28 | } 29 | -------------------------------------------------------------------------------- /http/codegen/testdata/golden/client-server-hosting-multiple-services.golden: -------------------------------------------------------------------------------- 1 | func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { 2 | var ( 3 | doer goahttp.Doer 4 | ) 5 | { 6 | doer = &http.Client{Timeout: time.Duration(timeout) * time.Second} 7 | if debug { 8 | doer = goahttp.NewDebugDoer(doer) 9 | } 10 | } 11 | 12 | return cli.ParseEndpoint( 13 | scheme, 14 | host, 15 | doer, 16 | goahttp.RequestEncoder, 17 | goahttp.ResponseDecoder, 18 | debug, 19 | ) 20 | } 21 | 22 | func httpUsageCommands() string { 23 | return cli.UsageCommands() 24 | } 25 | 26 | func httpUsageExamples() string { 27 | return cli.UsageExamples() 28 | } 29 | -------------------------------------------------------------------------------- /http/codegen/testdata/golden/client-server-hosting-service-subset.golden: -------------------------------------------------------------------------------- 1 | func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { 2 | var ( 3 | doer goahttp.Doer 4 | ) 5 | { 6 | doer = &http.Client{Timeout: time.Duration(timeout) * time.Second} 7 | if debug { 8 | doer = goahttp.NewDebugDoer(doer) 9 | } 10 | } 11 | 12 | return cli.ParseEndpoint( 13 | scheme, 14 | host, 15 | doer, 16 | goahttp.RequestEncoder, 17 | goahttp.ResponseDecoder, 18 | debug, 19 | ) 20 | } 21 | 22 | func httpUsageCommands() string { 23 | return cli.UsageCommands() 24 | } 25 | 26 | func httpUsageExamples() string { 27 | return cli.UsageExamples() 28 | } 29 | -------------------------------------------------------------------------------- /http/codegen/testdata/golden/client-streaming-multiple-services.golden: -------------------------------------------------------------------------------- 1 | func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { 2 | var ( 3 | doer goahttp.Doer 4 | ) 5 | { 6 | doer = &http.Client{Timeout: time.Duration(timeout) * time.Second} 7 | if debug { 8 | doer = goahttp.NewDebugDoer(doer) 9 | } 10 | } 11 | 12 | var ( 13 | dialer *websocket.Dialer 14 | ) 15 | { 16 | dialer = websocket.DefaultDialer 17 | } 18 | 19 | return cli.ParseEndpoint( 20 | scheme, 21 | host, 22 | doer, 23 | goahttp.RequestEncoder, 24 | goahttp.ResponseDecoder, 25 | debug, 26 | dialer, 27 | nil, 28 | nil, 29 | ) 30 | } 31 | 32 | func httpUsageCommands() string { 33 | return cli.UsageCommands() 34 | } 35 | 36 | func httpUsageExamples() string { 37 | return cli.UsageExamples() 38 | } 39 | -------------------------------------------------------------------------------- /http/codegen/testdata/golden/client-streaming.golden: -------------------------------------------------------------------------------- 1 | func doHTTP(scheme, host string, timeout int, debug bool) (goa.Endpoint, any, error) { 2 | var ( 3 | doer goahttp.Doer 4 | ) 5 | { 6 | doer = &http.Client{Timeout: time.Duration(timeout) * time.Second} 7 | if debug { 8 | doer = goahttp.NewDebugDoer(doer) 9 | } 10 | } 11 | 12 | var ( 13 | dialer *websocket.Dialer 14 | ) 15 | { 16 | dialer = websocket.DefaultDialer 17 | } 18 | 19 | return cli.ParseEndpoint( 20 | scheme, 21 | host, 22 | doer, 23 | goahttp.RequestEncoder, 24 | goahttp.ResponseDecoder, 25 | debug, 26 | dialer, 27 | nil, 28 | ) 29 | } 30 | 31 | func httpUsageCommands() string { 32 | return cli.UsageCommands() 33 | } 34 | 35 | func httpUsageExamples() string { 36 | return cli.UsageExamples() 37 | } 38 | -------------------------------------------------------------------------------- /http/codegen/testdata/streaming_aliased_array_dsls.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | . "goa.design/goa/v3/dsl" 5 | ) 6 | 7 | // StreamingAliasedArrayDSL defines a service with a streaming endpoint that uses 8 | // an array of aliased types with a custom package path. 9 | var StreamingAliasedArrayDSL = func() { 10 | // Define a type in a custom package 11 | var CustomInt = Type("CustomInt", Int, func() { 12 | Meta("struct:pkg:path", "github.com/example/custompkg") 13 | }) 14 | 15 | var PayloadType = Type("PayloadType", func() { 16 | Attribute("values", ArrayOf(CustomInt)) 17 | }) 18 | 19 | Service("StreamingAliasedArray", func() { 20 | Method("Stream", func() { 21 | // Use an array of the custom type as streaming payload 22 | StreamingPayload(PayloadType) 23 | HTTP(func() { 24 | GET("/stream") 25 | }) 26 | }) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /http/codegen/testing.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "goa.design/goa/v3/codegen/service" 8 | "goa.design/goa/v3/expr" 9 | ) 10 | 11 | // RunHTTPDSL returns the HTTP DSL root resulting from running the given DSL. 12 | func RunHTTPDSL(t *testing.T, dsl func()) *expr.RootExpr { 13 | // reset all roots and codegen data structures 14 | service.Services = make(service.ServicesData) 15 | HTTPServices = make(ServicesData) 16 | return expr.RunDSL(t, dsl) 17 | } 18 | 19 | // makeGolden returns a file object used to write test expectations. If 20 | // makeGolden returns nil then the test should not generate test 21 | // expectations. 22 | func makeGolden(t *testing.T, p string) *os.File { 23 | t.Helper() 24 | if os.Getenv("GOLDEN") == "" { 25 | return nil 26 | } 27 | f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY, 0600) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | return f 32 | } 33 | -------------------------------------------------------------------------------- /http/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package http contains HTTP specific constructs that complement the code 3 | generated by Goa. The constructs include a composable HTTP client, default 4 | encodings, a mux and a websocket implementation that relies on the Gorilla 5 | websocket package. 6 | */ 7 | package http 8 | -------------------------------------------------------------------------------- /http/middleware/context.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // RequestContext returns a middleware which initializes the request context. 9 | func RequestContext(ctx context.Context) func(http.Handler) http.Handler { 10 | return func(h http.Handler) http.Handler { 11 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | h.ServeHTTP(w, r.WithContext(ctx)) 13 | }) 14 | } 15 | } 16 | 17 | // RequestContextKeyVals returns a middleware which adds the given key/value pairs to the 18 | // request context. 19 | func RequestContextKeyVals(keyvals ...any) func(http.Handler) http.Handler { 20 | if len(keyvals)%2 != 0 { 21 | panic("initctx: invalid number of key/value elements, must be an even number") 22 | } 23 | return func(h http.Handler) http.Handler { 24 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 | for i := 0; i < len(keyvals); i += 2 { 26 | r = r.WithContext(context.WithValue(r.Context(), keyvals[i], keyvals[i+1])) 27 | } 28 | h.ServeHTTP(w, r) 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /http/middleware/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package middleware contains HTTP middlewares that wrap a HTTP handler to provide 3 | ancilliary functionality such as capturing HTTP details into the request 4 | context or printing debug information on incoming requests. 5 | */ 6 | package middleware 7 | -------------------------------------------------------------------------------- /http/middleware/xray/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package xray contains middleware that creates AWS X-Ray segments from the 3 | HTTP requests and responses and send the segments to an AWS X-ray daemon. 4 | 5 | The server middleware works by extracting the trace information from the 6 | context using the tracing middleware package. The tracing middleware must be 7 | mounted first on the service. It stores the request segment in the context. 8 | User code can further configure the segment for example to set a service 9 | version or record an error. 10 | 11 | The client middleware wraps the client Doer and works by extracing the 12 | segment from the request context. It creates a new sub-segment and updates 13 | the request context with the latest segment before making the request. 14 | 15 | Deprecated: use OpenTelemetry instead, see for example 16 | https://github.com/goadesign/clue. This package will be removed in a future 17 | version of Goa. 18 | */ 19 | package xray 20 | -------------------------------------------------------------------------------- /http/server.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type ( 8 | // Server is the HTTP server interface used to wrap the server handlers 9 | // with the given middleware. 10 | Server interface { 11 | Use(func(http.Handler) http.Handler) 12 | } 13 | 14 | // Mounter is the interface for servers that allow mounting their endpoints 15 | // into a muxer. 16 | Mounter interface { 17 | Mount(Muxer) 18 | } 19 | 20 | // Servers is a list of servers. 21 | Servers []Server 22 | ) 23 | 24 | // Use wraps the servers with the given middleware. 25 | func (s Servers) Use(m func(http.Handler) http.Handler) { 26 | for _, v := range s { 27 | v.Use(m) 28 | } 29 | } 30 | 31 | // Mount will go through all the servers and mount them into the Muxer. It will 32 | // panic unless all servers satisfy the Mounter interface. 33 | func (s Servers) Mount(mux Muxer) { 34 | for _, v := range s { 35 | m := v.(Mounter) 36 | m.Mount(mux) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /http/websocket.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | type ( 11 | // Upgrader is an HTTP connection that is able to upgrade to websocket. 12 | Upgrader interface { 13 | // Upgrade upgrades the HTTP connection to the websocket protocol. 14 | Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error) 15 | } 16 | 17 | // Dialer creates a websocket connection to a given URL. 18 | Dialer interface { 19 | // DialContext creates a client connection to the websocket server. 20 | DialContext(ctx context.Context, url string, h http.Header) (*websocket.Conn, *http.Response, error) 21 | } 22 | 23 | // ConnConfigureFunc is used to configure a websocket connection with 24 | // custom handlers. The cancel function cancels the request context when 25 | // invoked in the configure function. 26 | ConnConfigureFunc func(conn *websocket.Conn, cancel context.CancelFunc) *websocket.Conn 27 | ) 28 | -------------------------------------------------------------------------------- /middleware/ctxkeys.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | var ( 4 | // RequestIDKey is the request context key used to store the request ID 5 | // created by the RequestID middleware. 6 | RequestIDKey = "goa-request-id" 7 | 8 | // TraceIDKey is the request context key used to store the current Trace 9 | // ID if any. 10 | TraceIDKey = "goa-trace-id" 11 | 12 | // TraceSpanIDKey is the request context key used to store the current 13 | // trace span ID if any. 14 | TraceSpanIDKey = "goa-trace-span-id" 15 | 16 | // TraceParentSpanIDKey is the request context key used to store the current 17 | // trace parent span ID if any. 18 | TraceParentSpanIDKey = "goa-trace-parent-span-id" 19 | ) 20 | -------------------------------------------------------------------------------- /middleware/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package middleware contains transport independent middlewares. A middleware is a 3 | function that accepts a endpoint function and returns another endpoint function. 4 | The middleware should invoke the endpoint function given as argument and may 5 | apply additional transformations prior to and after calling the original. The 6 | middlewares included in this package include a logger middleware to log incoming 7 | requests, a request ID middleware that makes sure every request as a unique ID 8 | stored in the context and a couple of middlewares used to implement tracing. 9 | */ 10 | package middleware 11 | -------------------------------------------------------------------------------- /middleware/log.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | type ( 11 | // Logger is the logging interface used by the middleware to produce 12 | // log entries. 13 | Logger interface { 14 | // Log creates a log entry using a sequence of alternating keys 15 | // and values. 16 | Log(keyvals ...any) error 17 | } 18 | 19 | // adapter is a thin wrapper around the stdlib logger that adapts it to 20 | // the Logger interface. 21 | adapter struct { 22 | *log.Logger 23 | } 24 | ) 25 | 26 | // NewLogger creates a Logger backed by a stdlib logger. 27 | func NewLogger(l *log.Logger) Logger { 28 | return &adapter{l} 29 | } 30 | 31 | func (a *adapter) Log(keyvals ...any) error { 32 | n := (len(keyvals) + 1) / 2 33 | if len(keyvals)%2 != 0 { 34 | keyvals = append(keyvals, "MISSING") 35 | } 36 | var fm bytes.Buffer 37 | vals := make([]any, n) 38 | for i := 0; i < len(keyvals); i += 2 { 39 | k := keyvals[i] 40 | v := keyvals[i+1] 41 | vals[i/2] = v 42 | fm.WriteString(fmt.Sprintf(" %s=%%+v", k)) 43 | } 44 | a.Printf(strings.TrimSpace(fm.String()), vals...) 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package goa implements a Go framework for writing microservices that promotes 3 | best practice by providing a single source of truth from which server code, 4 | client code, and documentation is derived. The code generated by goa follows 5 | the clean architecture pattern where composable modules are generated for the 6 | transport, endpoint, and business logic layers. The goa package contains 7 | middleware, plugins, and other complementary functionality that can be 8 | leveraged in tandem with the generated code to implement complete 9 | microservices in an efficient manner. By using Goa for developing 10 | microservices, implementers don’t have to worry with the documentation 11 | getting out of sync from the implementation as goa takes care of generating 12 | OpenAPI specifications for HTTP based services and gRPC protocol buffer files 13 | for gRPC based services (or both if the service supports both transports). 14 | Reviewers can also be assured that the implementation follows the 15 | documentation as the code is generated from the same source. 16 | 17 | Visit https://goa.design for more information. 18 | */ 19 | package goa 20 | -------------------------------------------------------------------------------- /pkg/endpoint.go: -------------------------------------------------------------------------------- 1 | package goa 2 | 3 | import "context" 4 | 5 | const ( 6 | // MethodKey is the request context key used to store the name of the 7 | // method as defined in the design. The generated transport code 8 | // initializes the corresponding value prior to invoking the endpoint. 9 | MethodKey contextKey = iota + 1 10 | 11 | // ServiceKey is the request context key used to store the name of the 12 | // service as defined in the design. The generated transport code 13 | // initializes the corresponding value prior to invoking the endpoint. 14 | ServiceKey 15 | ) 16 | 17 | type ( 18 | // private type used to define context keys. 19 | contextKey int 20 | ) 21 | 22 | // Endpoint exposes service methods to remote clients independently of the 23 | // underlying transport. 24 | type Endpoint func(ctx context.Context, request any) (response any, err error) 25 | -------------------------------------------------------------------------------- /staticcheck.conf: -------------------------------------------------------------------------------- 1 | checks = ["all", "-SA1029"] --------------------------------------------------------------------------------