Skip to content

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

KindHeaderBehavior
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 point

Output 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_idUserID), 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:

  1. Decodes the request (JSON body, or binds @path / @query / @header / @cookie / @form fields with the right casts),
  2. Applies any @default pre-fill,
  3. Calls req.Validate(),
  4. Dispatches to your service logic,
  5. 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:

go
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.

Released under the MIT License.