Codegen Output
What craftgo gen writes to disk, file by file. Output is deterministic — the same DSL always produces byte-identical files — and every Go file is run through go/format, so generated code is always gofmt-clean.
Two kinds of file
| Kind | Header | Behavior |
|---|---|---|
| Regenerated | // Code generated by craftgo. DO NOT EDIT. | Overwritten on every craftgo gen. Never edit these. |
| Gen-once | // Scaffold generated by craftgo. Safe to edit; will not be overwritten. | Written only if absent. Your edits survive regeneration. |
The split is the core of the workflow: regenerated files are the mechanical translation of your DSL; gen-once files are where you live. The CLI checks for existence before writing a gen-once file, so it never clobbers your business logic.
Directory layout
For a project whose design declares package design with a UserService:
internal/
├── types/<pkg>/ REGEN — one folder per DSL package
│ ├── types.go structs for every type
│ ├── validate.go Validate() method per type
│ ├── enums.go enum constants + String()
│ └── errors.go typed error values
├── transport/<svc>/ REGEN — one folder per service
│ ├── <method>.go http.HandlerFunc per method
│ └── errors.go writeError helper for this service
├── service/<svc>/ GEN-ONCE — your business logic
│ └── <method>.go the stub you fill in
├── routes/
│ ├── routes.go REGEN — umbrella RegisterRoutes
│ └── <svc>/routes.go REGEN — per-service registration
└── middleware/
└── <name>-middleware.go GEN-ONCE — one per declared middleware
svccontext/
├── svccontext.go GEN-ONCE — your dependency container
└── middlewares.go REGEN — typed middleware fields
config/ GEN-ONCE — runtime config loader
├── config.go
├── config.yaml
└── example.config.yaml
docs/openapi.yaml REGEN — OpenAPI 3.1 spec
main.go GEN-ONCE — wired entry pointOutput directories are configurable in craftgo.design.yaml — see Configuration.
What each file contains
types/<pkg>/types.go (regen)
A Go struct per type, with field names mapped to Go conventions (user_id → UserID), JSON tags from the DSL field names, pointers for optional (?) fields, and Go-generic syntax for generics (Page<User> → Page[User]). Cross-package field types resolve to the right import.
types/<pkg>/validate.go (regen)
A Validate() error method per type. Every validator decorator becomes a plain if statement; there is no reflection and no runtime struct-tag parsing. Regexes (@pattern, regex-backed @format) compile once into package-level vars. Validate() is fail-fast — it returns the first violation. Nested struct fields, generic instances, map values/keys, and embedded mixins all dispatch recursively.
types/<pkg>/enums.go + errors.go (regen)
Enum constants (StatusActive, …) plus a String() method; typed error values with their HTTP category, code, and any custom body fields.
transport/<svc>/<method>.go (regen)
One func <Method>(svcCtx) http.HandlerFunc per method. The handler:
- Decodes the request (JSON body, or binds
@path/@query/@header/@cookie/@formfields with the right casts), - Applies any
@defaultpre-fill, - Calls
req.Validate(), - Dispatches to your
servicelogic, - Encodes the response (and writes any
@status, response headers, or cookies).
@passthrough methods skip 1–5 and hand you the raw http.ResponseWriter + *http.Request.
service/<svc>/<method>.go (gen-once)
The stub you implement:
type GetUserService struct{ ctx context.Context; svcCtx *svccontext.ServiceContext }
func NewGetUserService(ctx, svcCtx) *GetUserService { ... }
func (l *GetUserService) GetUser(req *types.GetUserReq) (*types.User, error) {
// TODO: your logic
}This is the only place you write code. Everything above and below it is regenerated.
routes/ (regen)
routes/<svc>/routes.go registers each method on the mux via srv.Handle("VERB /path", transport.X(svcCtx), mws...), applying declared middleware. routes/routes.go is the umbrella that calls every per-service RegisterRoutes.
svccontext/ (gen-once + regen)
svccontext.go is your dependency container — add DB handles, clients, config here. middlewares.go (regen) declares the typed middleware fields so @middlewares(Auth) has a svcCtx.Auth to resolve against.
docs/openapi.yaml (regen)
The OpenAPI 3.1 document — paths, component schemas, parameters, request bodies, responses, security schemes. See OpenAPI.
main.go (gen-once)
Wires the ServiceContext, the server.Server, route registration, middleware, logging/metrics/otel, and Start. Yours to customize — add a flag, change the listen address, register an extra middleware.
Drift safety
Because regenerated files are deterministic and headed with the // Code generated marker, you can commit them and let CI re-run craftgo gen + git diff --exit-code as a drift gate: if the committed output doesn't match what the current DSL produces, the build fails. That guarantees the checked-in code always matches the design.