Define reusable response bodies in .api with typed code mappings, per-route overrides and matching OpenAPI schemas. Add application and callback encoders, early response policy selection, and isolated remote-consumer release verification. Release v0.8.0-preview after full race, vet, build and local MySQL workflow acceptance.
177 lines
8.1 KiB
Go
177 lines
8.1 KiB
Go
package apigen
|
|
|
|
import "fmt"
|
|
|
|
var baseTemplates = map[string]string{
|
|
"cmd/server/main.go": `package main
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
omni "git.yuntuo.hk/k2safe/go-omni"
|
|
"git.yuntuo.hk/k2safe/go-omni/logx"
|
|
"MODULE/internal/handler"
|
|
"MODULE/internal/servicecontext"
|
|
"MODULE/internal/settings"
|
|
)
|
|
func main(){
|
|
path:=flag.String("config","etc/config.yaml","configuration file; empty uses defaults and environment")
|
|
flag.Parse()
|
|
if err:=run(*path);err!=nil{log.Fatal(err)}
|
|
}
|
|
func run(path string)(runErr error){
|
|
ctx,stop:=signal.NotifyContext(context.Background(),os.Interrupt,syscall.SIGTERM)
|
|
defer stop()
|
|
cfg,err:=settings.Load(path);if err!=nil{return err}
|
|
logger,err:=logx.New(cfg.Server.Log);if err!=nil{return err}
|
|
defer func(){runErr=errors.Join(runErr,logger.Close())}()
|
|
logx.SetDefault(logger)
|
|
deps,err:=servicecontext.New(ctx,logger,cfg);if err!=nil{return err}
|
|
defer func(){runErr=errors.Join(runErr,deps.Close())}()
|
|
app:=omni.New(omni.WithConfig(cfg.Server))
|
|
app.OnShutdown(logger.Close)
|
|
app.OnShutdown(deps.Close)
|
|
if err=handler.RegisterRoutes(app,deps);err!=nil{return err}
|
|
app.GET("/openapi.json",handler.OpenAPI)
|
|
app.GET("/health/live",deps.Health.LivenessHandler())
|
|
app.GET("/health/ready",deps.Health.ReadinessHandler())
|
|
logger.InfoContext(ctx,"service.starting","address",cfg.Server.Address)
|
|
return app.RunContext(ctx,cfg.Server.Address)
|
|
}
|
|
`,
|
|
".gitignore": "/server\n/var/\n.env\n.env.*\n!.env.example\n/.goOmni/api.lock\n",
|
|
}
|
|
|
|
func projectREADME(module, entry string) string {
|
|
return fmt.Sprintf(`# %s
|
|
|
|
This project is generated from an editable API contract. Business code uses
|
|
standard context.Context; generated HTTP handlers use Omni's transport-neutral httpx.
|
|
|
|
## Run
|
|
|
|
`+"```sh"+`
|
|
go mod tidy
|
|
go run ./cmd/server
|
|
`+"```"+`
|
|
|
|
GET /health/live and /health/ready check the running service. GET /openapi.json
|
|
serves the same document as docs/openapi.json. Bind address defaults to localhost.
|
|
Set GO_OMNI_TRANSPORT=native to select the native transport without changing services.
|
|
The greeting starter works immediately; other new operation stubs return 501 until
|
|
their business logic is implemented. Authentication hooks also fail closed with 501.
|
|
|
|
## Develop
|
|
|
|
1. Edit %s and its relative imports.
|
|
2. Run the same go-omni CLI version as the framework dependency:
|
|
|
|
`+"```sh"+`
|
|
go-omni api validate
|
|
go-omni api inspect
|
|
go-omni api generate --dry-run
|
|
go-omni api generate
|
|
go-omni api generate --check
|
|
go-omni api export > openapi.json
|
|
go test ./...
|
|
`+"```"+`
|
|
|
|
Commands inside a generated project discover the recorded API entry automatically.
|
|
--dry-run previews create/update/remove actions without writing files.
|
|
The --check command is read-only and suitable for CI. It fails on stale or missing
|
|
generated files and missing business scaffolds. go.mod, configuration, main,
|
|
Service, Repository, servicecontext, middleware hook bodies and API files are
|
|
created once and then preserved byte for byte. Gen.go files and OpenAPI are owned
|
|
by the generator; manual edits cause a conflict instead of being overwritten.
|
|
Commit .goOmni/apiManifest.json alongside the generated files. Do not edit it.
|
|
Avoid editing files while generation is running; api.lock serializes generators.
|
|
|
|
Routes and types that disappear are removed from generated output. Handwritten
|
|
business files remain: remove/adapt obsolete implementations explicitly if an
|
|
API change makes their old types or signatures fail to compile. Every regeneration
|
|
must be followed by go test ./... . Do not delete service files to change contracts.
|
|
|
|
## Layout
|
|
|
|
`+"```text"+`
|
|
api/ # editable contract and imported types
|
|
cmd/server/main.go # startup, lifecycle and route registration
|
|
etc/config.yaml # lowerCamelCase configuration
|
|
internal/settings/ # defaults -> file -> environment
|
|
internal/servicecontext/ # dependency initialization and cleanup
|
|
internal/types/typesGen.go # generated request/response types
|
|
internal/apierrors/errorsGen.go # generated fresh error constructors, when declared
|
|
internal/handler/<group>/ # generated binding and service adapters
|
|
internal/service/<group>/ # developer-owned business operations
|
|
internal/repository/<group>/ # developer-owned persistence contracts
|
|
internal/middleware/ # named hooks and generated registry
|
|
docs/openapi.json # generated OpenAPI 3.0.3
|
|
.goOmni/apiManifest.json # ownership and content hashes
|
|
`+"```"+`
|
|
|
|
The API response type describes business data. Declare response Name (...) and
|
|
select it with @server(response: Name) or @response Name to share a public body;
|
|
@response raw returns the business JSON directly. Without a template, the default
|
|
envelope includes code, message, data and requestId. Generated routes and OpenAPI
|
|
use the API contract, independently of the app's default response configuration.
|
|
Handlers reject nil successful responses. Body DTOs use application/json and have a 1 MiB limit by default.
|
|
Use contract.Binder.Bind's optional limit in a custom adapter for larger payloads.
|
|
|
|
Fields require exactly one source tag: json, path, form (URL query only), header.
|
|
All are required by presence unless optional, omitempty or default is specified.
|
|
Zero and false are valid present values; binding:"required" rejects scalar zero.
|
|
Use *T with optional when business logic needs to distinguish missing from zero.
|
|
Pointers may carry null; non-pointer null is rejected. Unknown/duplicate JSON
|
|
fields and duplicate scalar query/header values are rejected. Arrays in the query
|
|
use repeated keys. Enum/range/default and nested fields share the documentation
|
|
and binding compiler. Responses are expected to honor their declared constraints;
|
|
they are not validated on every response. Do not return nil slices/maps for a
|
|
non-nullable collection (initialize empty collections instead).
|
|
|
|
Declare public business errors in error (...) and reference them with @errors.
|
|
Use internal/apierrors constructors to share status, code and message with OpenAPI.
|
|
Auth declarations connect a bearer/header/cookie scheme to a real named middleware.
|
|
The selected auth runs first; @auth none removes inherited declarative auth.
|
|
Validation policy remains in the middleware implementation. Public messages must
|
|
not contain secrets; error causes may be included in server logs.
|
|
|
|
Named middleware is composed in declaration order (first is outermost). Implement
|
|
the generated hooks or replace entries in servicecontext; never bypass an auth
|
|
hook merely to make the server start. Middleware names do not imply a JWT policy.
|
|
|
|
This CLI supports a documented go-zero-style subset, not all goctl features.
|
|
Database tables and repositories cannot be inferred from an HTTP contract. Add
|
|
business repositories through servicecontext. Database and storage options are
|
|
composed at creation; the original profile is retained on regeneration.
|
|
`, module, entry)
|
|
}
|
|
|
|
func infrastructureREADME(o Options) string {
|
|
return fmt.Sprintf(`
|
|
## Infrastructure
|
|
|
|
Selected database: %s; storage: %s; logging: %s/%s.
|
|
MySQL uses database/sql through deps.DB, including InTx and a readiness probe.
|
|
Set GO_OMNI_DB_DSN; pool settings live in etc/config.yaml and GO_OMNI_DB_*.
|
|
No tables are invented or migrated automatically from request/response DTOs.
|
|
The none/memory selection opens no database; implement repositories per business.
|
|
|
|
With storage enabled, services receive deps.Files, Uploads, Downloads and
|
|
UploadLinks capability interfaces. Local signed GET/HEAD/PUT uses /storage/object;
|
|
set GO_OMNI_STORAGE_SIGNING_KEY (32+ bytes) and GO_OMNI_STORAGE_PUBLIC_URL.
|
|
Cloud credentials use GO_OMNI_STORAGE_ACCESS_KEY/SECRET_KEY; set bucket and endpoint
|
|
or R2 accountId as appropriate. Signed URLs grant temporary access: authenticate
|
|
and authorize before issuing them. No public upload-link route is added.
|
|
Storage failures during startup close an already-open database; Close is idempotent.
|
|
|
|
Database/storage/logging selection is recorded in .goOmni/apiManifest.json.
|
|
Changing selection through generation flags is rejected on an existing project;
|
|
edit your owned configuration and initialization explicitly, or generate a fresh
|
|
project for comparison. New routes still regenerate without repeating these flags.
|
|
`, o.Database, o.Storage, o.LogOutput, o.LogFormat)
|
|
}
|