Generated Code
Running goninja generate writes one file per model into the -out
directory, named <modellowercase>_generated.go — for a Book model,
book_generated.go. The file is run through go/format before being
written, and every generated file starts with the same header:
// Code generated by goninja generate. DO NOT EDIT.Never hand-edit a generated file. Change the model’s struct tags or the
templates under internal/codegen/templates/*.tmpl instead, then
regenerate.
What one file contains
For book_generated.go, the top-level declarations appear in this order:
- Types:
BookList,BookRetrieve,BookCreate,BookUpdate,BookPatch,BookFilters - Var:
bookOrderableColumns - A const block
- Functions:
toBookList,toBookRetrieve - Type:
BookResource(embedsgoninja.BaseResource) - Type:
BookOption(afunc(*BookResource), applied byNewBookResource— build one withgoninja.Actionsto attach actions at construction) - Function:
NewBookResource(func(db *gorm.DB, opts ...BookOption) *BookResource— variadic, soNewBookResource(db)with no options still works unchanged) - Interface:
BookOps - Methods:
ops,patchOps,List,Retrieve,Create,Update,Patch,Delete - Function:
parseBookFilters - Methods:
listHandler,retrieveHandler,createHandler,updateHandler,deleteHandler,OpenAPI,Register - Internal OpenAPI helper functions and
resourceConfig
The four output types
Every model produces four distinct types, plus a filters type. None of them is the GORM model reused directly — a field only appears in a response or request body because it was explicitly tagged for that purpose. This is deliberate: reusing the model struct as a response type would leak any field added to it later, tagged or not.
Real output for the Book model from the Struct Tags example:
type BookList struct {
ID string `json:"id"`
Title string `json:"title"`
AuthorID string `json:"author_id"`
Price float64 `json:"price"`
Published bool `json:"published"`
CreatedAt time.Time `json:"created_at"`
}
type BookRetrieve struct {
ID string `json:"id"`
Title string `json:"title"`
AuthorID string `json:"author_id"`
Author AuthorRetrieve `json:"author"`
Price float64 `json:"price"`
Published bool `json:"published"`
CreatedAt time.Time `json:"created_at"`
}
type BookCreate struct {
Title string `json:"title" validate:"required,max=200"`
AuthorID string `json:"author_id" validate:"required,uuid4"`
Price float64 `json:"price" validate:"min=0"`
Published bool `json:"published"`
}
// BookUpdate has an identical shape to BookCreate.Note Published carries no validate tag on BookCreate, because the
model field itself had none — the tag is copied verbatim, not synthesized.
Note also that a relation field on Retrieve is nested as the related
model’s own Retrieve type (AuthorRetrieve) when it is not tagged
byid — see Struct Tags and Relations.
Extension points
Two things in the generated file exist specifically so you can extend a resource without editing generated code:
- The
<Model>Opsinterface (BookOpshere) is what makes per-method overriding possible — a wrapper type that embeds*BookResourceand redefines one ofList/Retrieve/Create/Update/Deletestill satisfies it. <Model>Resourceembedsgoninja.BaseResource, which suppliesSetSelf/Self()— the dispatch point hooks,Configurer, and method overrides all resolve through, since Go has no dynamic dispatch through embedding on its own.
See Hooks and Overrides for how these are used in practice.
OpenAPI type mapping
Every generated <Model>Resource also gets an OpenAPI() method built
from the same IR as the rest of the file — never hand-maintained JSON. Go
types map to JSON Schema type/format like this:
| Condition | OpenAPI type | OpenAPI format |
|---|---|---|
| Bool | boolean | (none) |
| String | string | (none) |
Float (float32/float64) | number | double |
time.Time | string | date-time |
Other numeric (int*/uint*) | integer | int64 |
| Anything else (relation) | object | (none) |
A relation field’s schema is always a $ref to the related model’s own
Retrieve schema, mirroring how toBookRetrieve nests it at runtime.
Example field anywhere in the generated schemas. If your docs UI
shows examples, they came from somewhere else.Commit generated code
Treat generated files as part of the repository, not as a build artifact
to .gitignore. Committing them means a reviewer sees the actual API
surface change in a pull request, and a fresh checkout builds without
running the generator first. Regenerate and re-commit whenever a model’s
tags change; see CLI for the exact command.