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 HTTP or 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} // Finish propagates incomplete HTTP draining: stop background work but retain // shared dependencies when any handler is still running. Otherwise close once. 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(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. Optional server.cors is loaded and installed automatically before routing/authentication; disabled by default. Configure allowedOrigins, allowedMethods and allowedHeaders in the selected file; no main/handler edits. See the framework CONFIG.md and cors/README.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// # generated binding and Logic adapters internal/logic// # developer-owned business operations internal/repository// # 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) }