Skip to content

Commit b97d19e

Browse files
committed
Update code generation templates and configurations
- Added new templates for handling various code generation scenarios, including validation and transformation for different data types. - Refactored existing templates to improve readability and maintainability. - Updated the .golangci.yml configuration to include specific checks for static analysis. - Bumped dependencies in go.mod and updated go.sum accordingly. - Removed obsolete staticcheck configuration file. This commit enhances the code generation process and ensures better compliance with coding standards.
1 parent 30316d2 commit b97d19e

File tree

120 files changed

+4389
-905
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+4389
-905
lines changed

.golangci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,11 @@ linters:
44
- errorlint
55
- errcheck
66
- staticcheck
7+
- unparam # Detects unused parameters
8+
- unused # Detects unused constants, variables, functions and types
9+
- ineffassign # Detects ineffectual assignments
10+
settings:
11+
staticcheck:
12+
checks:
13+
- "-ST1001"
714

README.md

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Traditional API development suffers from:
9898
Goa solves these problems by:
9999
- Generating 30-50% of your codebase directly from your design
100100
- Ensuring perfect alignment between design, code, and documentation
101-
- Supporting multiple transports (HTTP and gRPC) from a single design
101+
- Supporting multiple transports (HTTP, gRPC, and JSON-RPC) from a single design
102102
- Maintaining a clean separation between business logic and transport details
103103

104104
## 🌟 Key Features
@@ -107,12 +107,13 @@ Goa solves these problems by:
107107
- **Comprehensive Code Generation**:
108108
- Type-safe server interfaces that enforce your design
109109
- Client packages with full error handling
110-
- Transport layer adapters (HTTP/gRPC) with routing and encoding
110+
- Transport layer adapters (HTTP/gRPC/JSON-RPC) with routing and encoding
111111
- OpenAPI/Swagger documentation that's always in sync
112112
- CLI tools for testing your services
113-
- **Multi-Protocol Support**: Generate HTTP REST and gRPC endpoints from a single design
113+
- **Multi-Protocol Support**: Generate HTTP REST, gRPC, and JSON-RPC endpoints from a single design
114114
- **Clean Architecture**: Business logic remains separate from transport concerns
115115
- **Enterprise Ready**: Supports authentication, authorization, CORS, logging, and more
116+
- **Comprehensive Testing**: Includes extensive unit and integration test suites ensuring quality and reliability
116117

117118
## 🔄 How It Works
118119

@@ -177,7 +178,32 @@ The example above:
177178
2. Generates server and client code
178179
3. Starts a server that logs requests server-side (without displaying any client output)
179180

180-
## 📚 Documentation
181+
### JSON-RPC Alternative
182+
183+
For a JSON-RPC service, simply add a `JSONRPC` expression to the method:
184+
185+
```go
186+
Method("say_hello", func() {
187+
Payload(func() {
188+
Field(1, "name", String)
189+
Required("name")
190+
})
191+
Result(String)
192+
193+
JSONRPC(func() {
194+
POST("/jsonrpc")
195+
})
196+
})
197+
```
198+
199+
Then test with:
200+
```bash
201+
curl -X POST http://localhost:8000/jsonrpc \
202+
-H "Content-Type: application/json" \
203+
-d '{"jsonrpc":"2.0","method":"hello.say_hello","params":{"name":"world"},"id":"1"}'
204+
```
205+
206+
## Documentation
181207

182208
Our completely redesigned documentation site at [goa.design](https://goa.design) provides comprehensive guides and references:
183209

@@ -188,7 +214,7 @@ Our completely redesigned documentation site at [goa.design](https://goa.design)
188214
- **[Real-World Guide](https://goa.design/docs/5-real-world/)**: Follow best practices for production services
189215
- **[Advanced Topics](https://goa.design/docs/6-advanced/)**: Explore advanced features and techniques
190216

191-
## 🛠️ Real-World Examples
217+
## Real-World Examples
192218

193219
The [examples repository](https://github.com/goadesign/examples) contains complete, working examples demonstrating:
194220

@@ -202,7 +228,7 @@ The [examples repository](https://github.com/goadesign/examples) contains comple
202228
- **Interceptors**: Request/response processing middleware
203229
- **Multipart**: Handling multipart form submissions
204230
- **Security**: Authentication and authorization examples
205-
- **Streaming**: Implementing streaming endpoints
231+
- **Streaming**: Implementing streaming endpoints (HTTP, WebSocket, JSON-RPC SSE)
206232
- **Tracing**: Integrating with observability tools
207233
- **TUS**: Resumable file uploads implementation
208234

@@ -220,12 +246,21 @@ The [examples repository](https://github.com/goadesign/examples) contains comple
220246
- Report issues on [GitHub](https://github.com/goadesign/goa/issues)
221247
- Find answers with the [Goa Guru](https://gurubase.io/g/goa) AI assistant
222248

223-
## 📣 What's New
249+
## What's New
250+
251+
**June 2025:** Goa now includes comprehensive **JSON-RPC 2.0 support** as a
252+
first-class transport alongside HTTP and gRPC! Generate complete JSON-RPC
253+
services with streaming support (WebSocket and SSE), client/server code, CLI
254+
tools, and full type safety - all from a single design.
224255

225-
**Jan 2024:** Goa's powerful design DSL is now accessible through the [Goa Design Wizard](https://chat.openai.com/g/g-mLuQDGyro-goa-design-wizard), a specialized AI trained on Goa. Generate service designs through natural language conversations!
256+
**February 2025:** The Goa website has been completely redesigned with extensive
257+
new documentation, tutorials, and guides to help you build better services.
226258

227-
**February 2025:** The Goa website has been completely redesigned with extensive new documentation, tutorials, and guides to help you build better services.
259+
**Jan 2024:** Goa's powerful design DSL is now accessible through the
260+
[Goa Design Wizard](https://chat.openai.com/g/g-mLuQDGyro-goa-design-wizard), a
261+
specialized AI trained on Goa. Generate service designs through natural language
262+
conversations!
228263

229-
## 📄 License
264+
## License
230265

231266
MIT License - see [LICENSE](LICENSE) for details.

codegen/cli/cli.go

Lines changed: 5 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func UsageCommands(data []*CommandData) *codegen.SectionTemplate {
262262
usages[i] = fmt.Sprintf("%s %s%s%s", cmd.Name, lp, strings.Join(subs, "|"), rp)
263263
}
264264

265-
return &codegen.SectionTemplate{Source: usageT, Data: usages}
265+
return &codegen.SectionTemplate{Source: cliTemplates.Read(usageCommandsT), Data: usages}
266266
}
267267

268268
// UsageExamples builds a section template that generates a help text showing
@@ -275,7 +275,7 @@ func UsageExamples(data []*CommandData) *codegen.SectionTemplate {
275275
}
276276
}
277277

278-
return &codegen.SectionTemplate{Source: exampleT, Data: examples}
278+
return &codegen.SectionTemplate{Source: cliTemplates.Read(usageExamplesT), Data: examples}
279279
}
280280

281281
// FlagsCode returns a string containing the code that parses the command-line
@@ -285,7 +285,7 @@ func UsageExamples(data []*CommandData) *codegen.SectionTemplate {
285285
func FlagsCode(data []*CommandData) string {
286286
section := codegen.SectionTemplate{
287287
Name: "parse-endpoint-flags",
288-
Source: parseFlagsT,
288+
Source: cliTemplates.Read(parseFlagsT),
289289
Data: data,
290290
FuncMap: map[string]any{"printDescription": printDescription},
291291
}
@@ -303,7 +303,7 @@ func FlagsCode(data []*CommandData) string {
303303
func CommandUsage(data *CommandData) *codegen.SectionTemplate {
304304
return &codegen.SectionTemplate{
305305
Name: "cli-command-usage",
306-
Source: commandUsageT,
306+
Source: cliTemplates.Read(commandUsageT),
307307
Data: data,
308308
FuncMap: map[string]any{"printDescription": printDescription},
309309
}
@@ -314,7 +314,7 @@ func CommandUsage(data *CommandData) *codegen.SectionTemplate {
314314
func PayloadBuilderSection(buildFunction *BuildFunctionData) *codegen.SectionTemplate {
315315
return &codegen.SectionTemplate{
316316
Name: "cli-build-payload",
317-
Source: buildPayloadT,
317+
Source: cliTemplates.Read(buildPayloadT),
318318
Data: buildFunction,
319319
FuncMap: map[string]any{
320320
"fieldCode": fieldCode,
@@ -606,166 +606,3 @@ func fieldCode(init *PayloadInitData) string {
606606
return c
607607
}
608608

609-
// input: []string
610-
const usageT = `// UsageCommands returns the set of commands and sub-commands using the format
611-
//
612-
// command (subcommand1|subcommand2|...)
613-
//
614-
func UsageCommands() string {
615-
return ` + "`" + `{{ range . }}{{ . }}
616-
{{ end }}` + "`" + `
617-
}
618-
`
619-
620-
// input: []string
621-
const exampleT = `// UsageExamples produces an example of a valid invocation of the CLI tool.
622-
func UsageExamples() string {
623-
return {{ range . }}os.Args[0] + ` + "`" + ` {{ . }}` + "`" + ` + "\n" +
624-
{{ end }}""
625-
}
626-
`
627-
628-
// input: []commandData
629-
const parseFlagsT = `var (
630-
{{- range . }}
631-
{{ .VarName }}Flags = flag.NewFlagSet("{{ .Name }}", flag.ContinueOnError)
632-
{{ range .Subcommands }}
633-
{{ .FullName }}Flags = flag.NewFlagSet("{{ .Name }}", flag.ExitOnError)
634-
{{- $sub := . }}
635-
{{- range .Flags }}
636-
{{ .FullName }}Flag = {{ $sub.FullName }}Flags.String("{{ .Name }}", "{{ if .Default }}{{ .Default }}{{ else if .Required }}REQUIRED{{ end }}", {{ printf "%q" .Description }})
637-
{{- end }}
638-
{{ end }}
639-
{{- end }}
640-
)
641-
{{ range . -}}
642-
{{ $cmd := . -}}
643-
{{ .VarName }}Flags.Usage = {{ .VarName }}Usage
644-
{{ range .Subcommands -}}
645-
{{ .FullName }}Flags.Usage = {{ .FullName }}Usage
646-
{{ end }}
647-
{{ end }}
648-
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
649-
return nil, nil, err
650-
}
651-
652-
if flag.NArg() < 2 { // two non flag args are required: SERVICE and ENDPOINT (aka COMMAND)
653-
return nil, nil, fmt.Errorf("not enough arguments")
654-
}
655-
656-
var (
657-
svcn string
658-
svcf *flag.FlagSet
659-
)
660-
{
661-
svcn = flag.Arg(0)
662-
switch svcn {
663-
{{- range . }}
664-
case "{{ .Name }}":
665-
svcf = {{ .VarName }}Flags
666-
{{- end }}
667-
default:
668-
return nil, nil, fmt.Errorf("unknown service %q", svcn)
669-
}
670-
}
671-
if err := svcf.Parse(flag.Args()[1:]); err != nil {
672-
return nil, nil, err
673-
}
674-
675-
var (
676-
epn string
677-
epf *flag.FlagSet
678-
)
679-
{
680-
epn = svcf.Arg(0)
681-
switch svcn {
682-
{{- range . }}
683-
case "{{ .Name }}":
684-
switch epn {
685-
{{- range .Subcommands }}
686-
case "{{ .Name }}":
687-
epf = {{ .FullName }}Flags
688-
{{ end }}
689-
}
690-
{{ end }}
691-
}
692-
}
693-
if epf == nil {
694-
return nil, nil, fmt.Errorf("unknown %q endpoint %q", svcn, epn)
695-
}
696-
697-
// Parse endpoint flags if any
698-
if svcf.NArg() > 1 {
699-
if err := epf.Parse(svcf.Args()[1:]); err != nil {
700-
return nil, nil, err
701-
}
702-
}
703-
`
704-
705-
// input: commandData
706-
const commandUsageT = `
707-
{{ printf "%sUsage displays the usage of the %s command and its subcommands." .VarName .Name | comment }}
708-
func {{ .VarName }}Usage() {
709-
fmt.Fprintf(os.Stderr, ` + "`" + `{{ printDescription .Description }}
710-
Usage:
711-
%[1]s [globalflags] {{ .Name }} COMMAND [flags]
712-
713-
COMMAND:
714-
{{- range .Subcommands }}
715-
{{ .Name }}: {{ printDescription .Description }}
716-
{{- end }}
717-
718-
Additional help:
719-
%[1]s {{ .Name }} COMMAND --help
720-
` + "`" + `, os.Args[0])
721-
}
722-
723-
{{- range .Subcommands }}
724-
func {{ .FullName }}Usage() {
725-
fmt.Fprintf(os.Stderr, ` + "`" + `%[1]s [flags] {{ $.Name }} {{ .Name }}{{range .Flags }} -{{ .Name }} {{ .Type }}{{ end }}
726-
727-
{{ printDescription .Description}}
728-
{{- range .Flags }}
729-
-{{ .Name }} {{ .Type }}: {{ .Description }}
730-
{{- end }}
731-
732-
Example:
733-
%[1]s {{ .Example }}
734-
` + "`" + `, os.Args[0])
735-
}
736-
{{ end }}
737-
`
738-
739-
// input: buildFunctionData
740-
const buildPayloadT = `{{ printf "%s builds the payload for the %s %s endpoint from CLI flags." .Name .ServiceName .MethodName | comment }}
741-
func {{ .Name }}({{ range .FormalParams }}{{ . }} string, {{ end }}) ({{ .ResultType }}, error) {
742-
{{- if .CheckErr }}
743-
var err error
744-
{{- end }}
745-
{{- range .Fields }}
746-
{{- if .VarName }}
747-
var {{ .VarName }} {{ .TypeRef }}
748-
{
749-
{{ .Init }}
750-
}
751-
{{- end }}
752-
{{- end }}
753-
{{- with .PayloadInit }}
754-
{{- if .Code }}
755-
{{ .Code }}
756-
{{- if .ReturnTypeAttribute }}
757-
res := &{{ .ReturnTypeName }}{
758-
{{ .ReturnTypeAttribute }}: {{ if .ReturnTypeAttributePointer }}&{{ end }}v,
759-
}
760-
{{- end }}
761-
{{- end }}
762-
{{- if .ReturnIsStruct }}
763-
{{- if not .Code }}
764-
{{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }} := &{{ .ReturnTypeName }}{}
765-
{{- end }}
766-
{{ fieldCode . }}
767-
{{- end }}
768-
return {{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }}, nil
769-
{{- end }}
770-
}
771-
`

codegen/cli/templates.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cli
2+
3+
import (
4+
"embed"
5+
6+
"goa.design/goa/v3/codegen/template"
7+
)
8+
9+
// Template constants
10+
const (
11+
usageCommandsT = "usage_commands"
12+
usageExamplesT = "usage_examples"
13+
parseFlagsT = "parse_flags"
14+
commandUsageT = "command_usage"
15+
buildPayloadT = "build_payload"
16+
)
17+
18+
//go:embed templates/*.go.tpl
19+
var templateFS embed.FS
20+
21+
// cliTemplates is the shared template reader for the cli package.
22+
var cliTemplates = &template.TemplateReader{FS: templateFS}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{{ printf "%s builds the payload for the %s %s endpoint from CLI flags." .Name .ServiceName .MethodName | comment }}
2+
func {{ .Name }}({{ range .FormalParams }}{{ . }} string, {{ end }}) ({{ .ResultType }}, error) {
3+
{{- if .CheckErr }}
4+
var err error
5+
{{- end }}
6+
{{- range .Fields }}
7+
{{- if .VarName }}
8+
var {{ .VarName }} {{ .TypeRef }}
9+
{
10+
{{ .Init }}
11+
}
12+
{{- end }}
13+
{{- end }}
14+
{{- with .PayloadInit }}
15+
{{- if .Code }}
16+
{{ .Code }}
17+
{{- if .ReturnTypeAttribute }}
18+
res := &{{ .ReturnTypeName }}{
19+
{{ .ReturnTypeAttribute }}: {{ if .ReturnTypeAttributePointer }}&{{ end }}v,
20+
}
21+
{{- end }}
22+
{{- end }}
23+
{{- if .ReturnIsStruct }}
24+
{{- if not .Code }}
25+
{{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }} := &{{ .ReturnTypeName }}{}
26+
{{- end }}
27+
{{ fieldCode . }}
28+
{{- end }}
29+
return {{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }}, nil
30+
{{- end }}
31+
}

0 commit comments

Comments
 (0)