Files
go-omni/cmd/go-omni/apigen/templates.go

268 lines
16 KiB
Go

package apigen
import "fmt"
var baseTemplates = map[string]string{
"main.go": `package main
import (
"context"
"errors"
"flag"
"log"
"os"
"os/signal"
"syscall"
"git.yuntuo.hk/k2safe/go-omni/logx"
"git.yuntuo.hk/k2safe/go-omni/lifecycle"
"MODULE/internal/config"
"MODULE/internal/handler"
"MODULE/internal/svc"
)
func main(){
path:=flag.String("config","etc/config.yaml","configuration file; empty uses only Default()")
check:=flag.Bool("check-config",false,"validate configuration and exit without starting dependencies")
flag.Parse()
if *check {if _,err:=config.Load(*path);err!=nil{log.Fatal(err)};return}
ctx,stop:=signal.NotifyContext(context.Background(),os.Interrupt,syscall.SIGTERM)
defer stop()
if err:=run(ctx,*path);err!=nil{log.Fatal(err)}
}
func run(ctx context.Context,path string)(runErr error){
cfg,err:=config.Load(path);if err!=nil{return err}
logger,err:=logx.New(cfg.Server.Log);if err!=nil{return err}
// Unjoined background work may still log. The owning process exits on this
// error; do not close its borrowed logger before that work has finished.
defer func(){if !errors.Is(runErr,lifecycle.ErrResourcesRetained){runErr=errors.Join(runErr,logger.Close())}}()
deps,err:=svc.New(ctx,logger,cfg);if err!=nil{return err}
// RunContext drains HTTP requests before returning. This scope owns dependencies
// and closes them exactly once, before closing the borrowed logger.
defer func(){runErr=deps.Finish(runErr)}()
app,err:=handler.NewApp(deps);if err!=nil{return err}
defer func(){runErr=errors.Join(runErr,app.Close())}()
logger.InfoContext(ctx,"service.starting","address",app.Config().Address)
return app.RunContext(deps.Lifecycle.Context())
}
`,
".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.
main.go and HTTP composition are continuously generator-owned.
omni.gen.json selects repository: none (default) or group, and middleware:
factories for new projects. Named middleware factories run after svc.Init;
implement Dependencies accessors on ServiceContext and New<Name>(deps) once.
Unimplemented/nil factories fail startup and clean registered dependencies.
Existing projects retain middleware: handlers until explicitly migrated.
Ordinary Logic uses the generated Request/Response types directly. Where an
independent business type is needed, use ordinary Go or the optional copyx package.
Configuration loading is generator-managed in internal/config/loadGen.go. Extend fields/defaults/Validate in config.go; additional decoders belong in loader.go/NewLoader. JSON/YAML/XML use explicit ${NAME} / ${NAME:-fallback} references; fixed values are never overridden by inferred environment names. Use -check-config to validate without opening dependencies. Framework validation
identifies fields such as server.log.level and database.port. server.log.timeZone
optionally selects a per-logger IANA zone. See the framework CONFIG.md.
There are no mappings/mappingFiles settings or generated mapping files in v0.19.
See COPY.md in the framework repository for copy policies and migration steps,
and GENERATION.md for field composition and middleware factories.
## Run
`+"```sh"+`
go mod tidy
go run .
`+"```"+`
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.
Configure server.endpoints to disable or move each built-in route. Health
responseMode is app by default (project format), or raw for plain health data.
Probe HTTP statuses remain 200/503 even when business errors use HTTP 200.
OpenAPI remains an unwrapped document; disabled endpoints reserve no paths.
The default file explicitly references GO_OMNI_TRANSPORT; set it to native or edit server.transport.
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, Logic,
Repository, svc, middleware hook bodies and API files are created once and then
preserved byte for byte. main.go, 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.
Generation compares the last saved contract with the new one and reports affected
Logic files, including nested DTO field/tag changes and streaming/body changes.
REVIEW notices require a business review. ERROR diagnostics identify definite
Logic signature, syntax or duplicate-method problems. Generation preserves
business code and still writes valid generated output; fix those methods next.
--check fails on definite Logic errors even after generated files are current.
Import aliases, parameter names and moved methods within a group are supported.
Project type aliases, embedded methods or build tags may need compiler verification
and produce REVIEW notices. This source scan does not execute project code or
replace go test ./... . Snapshot-based REVIEW notices describe changes since the
last successful generation; they do not persist as an acknowledgement checklist.
## Contract tools
api format [--check] preserves comments and normalizes indentation. api upgrade
previews changed infrastructure; --apply only replaces recognized prior seeds.
Use @request policy overrides and @app(request: {...}) for query/JSON strictness.
Explicitly exported api/omni.errors.json documents the Go-owned error catalog;
the CLI never executes project Go. omni.imports.json permits named shared roots;
workspace format/generate/check processes explicitly listed independent services.
Full reference: https://git.yuntuo.hk/k2safe/go-omni/src/branch/main/DEVELOPMENT.md
## Layout
`+"```text"+`
api/ # editable contract and imported types
main.go # GENERATED startup, lifecycle and route registration
etc/config.yaml # lowerCamelCase configuration
internal/config/ # defaults -> file references -> typed binding -> validation
internal/svc/ # dependency initialization and cleanup
internal/types/*Gen.go # DTOs split by source API, one types package
internal/svc/runtimeGen.go # GENERATED lifecycle and infrastructure startup
internal/apierrors/errorsGen.go # generated fresh error constructors, when declared
internal/handler/<group>/ # generated binding and Logic adapters
internal/logic/<group>/ # developer-owned business operations
internal/repository/<group>/ # optional; repository: group creates editable seeds
internal/middleware/ # typed dependency interface, factories and registry
omni.gen.json # developer-owned repository/middleware options
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 the App default with @app(defaultResponse: Name) in the entry API.
@server(response: Name) and @response Name override it for groups/routes;
@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 same contract. 404/405 and ordinary App routes use the declared App default.
No template is selected by declaration/import order. Without @app, existing framework
response defaults apply. An explicit API App default takes precedence over response
options in svc, keeping generated routes and OpenAPI consistent.
Object responses reject nil; []DTO maps nil to []; *DTO permits null; returns(null) is a successful null value. No returns means 204. Body DTOs use application/json and have a 1 MiB limit by default.
Use @body(maxBytes: ...) for route limits. @body(mode: raw|multipart; maxBytes: ...)
adds a *httpx.Body argument to Logic, without handwritten handlers. Raw is the exact
limited request stream; Parts supports sequential multipart reading with no temp files.
@produces "application/octet-stream" (or another concrete media type) generates a
Logic result of *httpx.Stream; the handler closes Content and streams it unwrapped.
Signature verification and storage policy belong to Logic/middleware. A consumed
request stream cannot be reread automatically; use one reader owner or explicitly
bounded buffering. Stream reads respect configured server timeouts. Declare multipart/multipartJSON/file DTO fields for validated uploads; without those fields the existing streaming Body is passed. form tags remain URL query parameters. The optional manual
handler/custom binder hooks are preserved, and are needed only for custom protocols.
Fields require one source tag: json, path, form (URL query), header, multipart, multipartJSON or file. Multipart fields select full automatic binding before Logic; File/[]File are request-scoped disk-backed uploads. Use File.Open to stream them; generated cleanup owns their lifetime.
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).
Go-owned errors can be registered once through svc.ServiceContext.AppOptions with
omni.WithErrors(projecterrors.Catalog()). Generated routes inherit them unless they
declare an independent catalog. Do not repeat Go-owned errors in the API contract.
Alternatively, 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.
Generated svc.New receives Config and a borrowed Logger. Add dependencies in svc.Init,
then immediately register each cleanup with OnClose. A later initialization failure
runs registered cleanups in reverse order. Close is idempotent. GlobalMiddleware
provides global hooks; Middlewares supplies named API hooks. AppOptions is evaluated
once before route registration; logging is configured earlier through Config.
The generated main runs HTTP draining, all OnStop hooks, all OnDrain hooks, resource
OnClose callbacks, then logger close. Stop/drain hooks within a phase run concurrently;
combine components with ordering dependencies into one pair. Pair OnStop/OnDrain
with the same name. OnDrain returns nil only after final work and acknowledgements
have finished. Use WorkContext for accepted background work; Context is an early
shutdown notification. server.lifecycle configures stopTimeout (5s), drainTimeout
(10s), and cancelTimeout (5s grace per timed-out phase). Zero uses defaults.
Timeout/failure cancels WorkContext. If hooks cannot prove completion, resources
and the borrowed logger are retained and the generated process exits unsuccessfully.
Errors retain their causes. No automatic retry of close or force killing of goroutines.
Do not close borrowed resources or attach duplicate cleanup hooks to App. The startup
context cancels on signals; long-lived clients/workers that requests depend on need
their own lifetime, canceled by OnClose after HTTP draining.
svc.RequestShutdown(err) lets a dependency report a fatal service failure.
It returns immediately. Generated main stops accepting requests, drains them,
closes dependencies and logs, then exits nonzero with the original cause.
nil requests a normal stop. Concurrent calls keep the first non-nil failure;
notifications during initialization or draining are retained. Close must stop
and join project-owned monitors before the final shutdown result is returned.
The svc lifecycle context signals draining, not permission to close resources
still used by active requests. Liveness and readiness are different: shutdown
makes readiness 503; it does not turn a running process's liveness into failure.
Named middleware is composed in declaration order (first is outermost). Implement
the generated hooks or replace entries in svc; 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 svc. 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.
Use structured database/mysql configuration. The generated etc/config.yaml explicitly
references GO_OMNI_DB_PASSWORD and other required connection values; add fixed
values or explicit references for pool/TLS fields. No implicit environment overlay.
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;
the generated file references GO_OMNI_STORAGE_SIGNING_KEY (32+ bytes) and
GO_OMNI_STORAGE_PUBLIC_URL. File values may instead be fixed explicitly.
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)
}