229 lines
12 KiB
Go
229 lines
12 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"
|
|
"MODULE/internal/config"
|
|
"MODULE/internal/handler"
|
|
"MODULE/internal/svc"
|
|
)
|
|
func main(){
|
|
path:=flag.String("config","etc/config.yaml","configuration file; empty uses defaults and environment")
|
|
flag.Parse()
|
|
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}
|
|
defer func(){runErr=errors.Join(runErr,logger.Close())}()
|
|
logx.SetDefault(logger)
|
|
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=errors.Join(runErr,deps.Close())
|
|
if cause:=deps.Lifecycle.Err();cause!=nil && !errors.Is(runErr,cause){runErr=errors.Join(runErr,cause)}
|
|
}()
|
|
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.
|
|
|
|
## 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.
|
|
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, 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.
|
|
|
|
## 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 -> environment
|
|
internal/svc/ # 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 Logic adapters
|
|
internal/logic/<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 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.
|
|
Handlers reject nil successful responses. 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. Multipart parts
|
|
are not DTO form fields: form tags remain URL query parameters. The optional manual
|
|
handler/custom binder hooks are preserved, and are needed only for custom protocols.
|
|
|
|
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).
|
|
|
|
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.
|
|
|
|
svc.New receives the loaded Config and borrowed Logger. Add dependencies there,
|
|
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 before closing svc resources, then the logger.
|
|
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.
|
|
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)
|
|
}
|