Code Generation
Application schema and generated Go
Applications define content in Go; the native language does not know their fields:
schema := gogenconf.Schema{
Version: 1,
Sections: []gogenconf.SectionDefinition{{
Name: "service", GoName: "Service", GoType: "ServiceConfig",
Fields: []gogenconf.Field{
{Key: "endpoint", GoName: "Endpoint", GoType: "string",
Default: gogenconf.Literal{Value: "https://example.test"},
Documentation: []string{"Service endpoint."}},
{Key: "credential", GoName: "Credential", GoType: "[]byte",
Sensitive: true, Required: true,
Examples: []gogenconf.Expr{gogenconf.Call{
Name: "from_env", Args: []gogenconf.Expr{
gogenconf.Literal{Value: "CREDENTIAL"},
},
}}},
},
}},
}
An application-local go:generate driver writes the outputs of:
model, err := codegen.GenerateModel(schema, codegen.Options{Package: "appconfig"})
// Check err and write model to the application's model package.
binder, err := codegen.GenerateBinder(schema, codegen.Options{
Package: "configbinding", ConfigImport: "example.org/myapp/appconfig",
})
// Check err and write binder to the application's binding package.
ConfigImport names the application model package. The optional LibraryImport
defaults to github.com/arran4/gogenconf. Generated files carry the canonical
Code generated by gogenconf. DO NOT EDIT. header. The CLI is language tooling;
generation uses an application-local driver, not runtime schema/plugin loading.
For example, put //go:generate go run ./internal/generate in your application
package. That driver imports your Go schema and gogenconf/codegen, checks errors,
and writes model/binder files. The runnable driver
demonstrates this without installing a generator binary. Arbitrary Go schema
values cannot safely be dynamically discovered by a generic CLI.
The generated model is concrete application-owned Go, not aliases or AST fields:
type Config struct { Service ServiceConfig }
type ServiceConfig struct {
Endpoint string
Credential []byte
}
The generated binder’s Resolve(ctx, doc, registry, lookup) returns that Config
using explicit typed calls, with no reflection-based struct population. The
optional lookup supplies legacy environment fallbacks described by the schema.
Authored values take precedence over environment fallbacks, then schema defaults;
transient defaults inserted by enrichment retain that distinction until persisted.
Supported static mappings are deliberately narrow: string, []byte, []string
(the application registers its list semantics), and runtime-only bool fields.
Invalid/ambiguous mappings fail generation or compilation.
config file → Document / Expr → Schema + migrations + enrichment
↓
generated binder
↓
application-owned Go Config
This flow is one-way. Edit and persist the original Document; resolved values have lost provenance and cannot be converted back through a Config-to-Document API.
Generation architecture and dependency-free runtime
gogenconf is transitioning to a build-time-only generator where generated application code has zero runtime dependencies on gogenconf (issue #4).
Current generation boundary
codegen.GenerateModel: Emits application-owned concrete Go structs (Config, etc.).codegen.GenerateBinder: Emits typed resolution and binder helpers. In this initial phase, this legacy binder still relies on library-assisted runtime functions (gogenconf.BindingValue,gogenconf.ResolveField, etc.) and requiresgithub.com/arran4/gogenconfat runtime until #6 is complete.
New capability: application-owned native runtime (#5)
Applications can now generate a native configuration runtime directly into application-owned code using codegen.GenerateRuntime or codegen.PlanRuntime:
files, err := codegen.GenerateRuntime(codegen.RuntimeOptions{
Package: "myconfig",
})
if err != nil {
return err
}
if err := files.WriteToDir("internal/myconfig"); err != nil {
return err
}
This emits:
config_document_generated.go: Document, Section, Entry, Comment, Blank, Version, and document mutation methods.config_expr_generated.go: Expr, Literal, Call, and CloneExpr.config_parse_generated.go: native document parser and expression parser with v0/v1 comment ownership and version checking.config_format_generated.go: deterministic document and expression formatter.
The generated code depends solely on the Go standard library (bufio, fmt, io, strconv, strings, unicode). It contains no imports of, aliases to, or runtime forwarding back to github.com/arran4/gogenconf.
Production dogfooding
The shipped gogenconf syntax CLI now generates and checks in internal/nativeconfig through the repository-local internal/runtimegen driver. format, validate, expr format, and expr validate use that generated runtime in production. The root gogenconf parser and formatter remain the canonical public implementation and the differential-conformance oracle; they are not a normal fallback for the CLI syntax path. This demonstrates the dependency-free native parsing boundary from #5, while complete generated-binder dependency removal remains #6 and generator/product parity follow-up remains #12/#19.
Roadmap to build-time-only generation (#4)
- #5: Generate an application-owned native config runtime instead of importing gogenconf; the project CLI dogfoods it for syntax tooling.
- #6: Generate static resolver and declarer code for dependency-free binders.
- #7: Generate application-owned provider support and emit only schema-required runtime features.
- #8: Add a first-class
gogenconf generatecommand and canonical project generation contract. - #9: Generate a schema-specialized application config API.
- #10: Isolated consumer gate proving no gogenconf runtime dependency.