Documentation
¶
Overview ¶
Package genutil holds what more than one Go generator needs.
It exists because rendering a field's type was written three times — once in the persistence generator, once in the API generator, once more when the model layer arrived — and the three had already begun to disagree about enums. A shared answer is not shared code for its own sake; it is the only way three generators can emit the same type for the same field.
Index ¶
- Constants
- func Artifact(path string, b *gobuf.Buf, mode gen.WriteMode) (gen.Artifact, error)
- func BodyRequired(fields []ir.Field) []string
- func BodyShapeName(res *ir.Resource, ep *ir.Endpoint) string
- func DefaultServer(servers []ir.Server) (ir.Server, bool)
- func Describe(comment, fallback string) string
- func ElemType(t string) string
- func ExpandLayout(namer *naming.Namer, res *ir.Resource, tmpl string) string
- func Exposed(doc *ir.Document) []*ir.Resource
- func FieldsTypeName(res *ir.Resource, ep *ir.Endpoint) string
- func FilterObjects(doc *ir.Document, reachable map[string]bool, res *ir.Resource) []*ir.Object
- func GoDuration(b *gobuf.Buf, d ir.Duration) string
- func GoType(b *gobuf.Buf, f ir.Field, modelPkg func() string) string
- func IdempotentWrite(ep *ir.Endpoint) bool
- func JSONTag(f ir.Field) string
- func ModelInputName(ep *ir.Endpoint) string
- func PatchType(patchPkg string, f ir.Field, elem string) string
- func PlainFields(fields []ir.ResourceField) []ir.Field
- func PointerTo(t string) string
- func QueryTypeName(res *ir.Resource, ep *ir.Endpoint) string
- func RevisionHeader(doc *ir.Document) string
- func RoutePath(pattern string) string
- func SearchFilterField(ep *ir.Endpoint) (ir.Field, bool)
- func Servers(doc *ir.Document) []ir.Server
- func StoredFields(res *ir.Resource) []ir.ResourceField
- func UnclaimedObjects(doc *ir.Document, reachable map[string]bool, claimed map[string]bool) []*ir.Object
- func UsesModelInput(ep *ir.Endpoint) bool
- func WritableFields(res *ir.Resource, op string) []ir.ResourceField
- type InputError
- type Walk
Constants ¶
const DefaultRevisionHeader = "API-Revision"
DefaultRevisionHeader carries the API revision when a document predates the setting.
It matches rigclient.DefaultRevisionHeader and rig.yaml's api.revision_header. A literal rather than the constant, because the compiler does not depend on the modules it generates code for, and every project that has the setting sends its own value through the document.
const RuntimeModule = "github.com/simonjanss/rig/runtime"
RuntimeModule is the import path generated code depends on.
Variables ¶
This section is empty.
Functions ¶
func BodyRequired ¶
BodyRequired names the fields of a request body a caller cannot leave out.
A field is required when the database cannot supply it: not nullable, not an array, and with no column default to fall back on. That is checkable rather than stylistic — omit one and the insert genuinely fails — which is what makes it safe for a specification to promise.
Never call it for an update body. A PATCH leaves an absent field alone, so every member of one is optional; that is the whole reason the update inputs are wrapped rather than plain pointers.
It is deliberately not the rule that decides `omitempty` on the Go client's struct tags, which ignores Default. The two answer different questions — may I leave this out of the JSON I send, versus must the server have received it — and a field with a column default answers them differently.
func BodyShapeName ¶
BodyShapeName is what an endpoint's request body is called, for every output that has to name it: the Go client's struct, the service layer's parameter shape, the OpenAPI component.
Empty when there is nothing to name — no body at all, or a body the document already names as an object, in which case that name is the one to use.
A create or update is named whether or not it turned out to have any fields. The model declares that input type from the operation rather than from the column list, so a table whose every column is server-managed still has a ProfileCreateInput; a caller that answered "nothing to name" here would leave the client emitting a parameter with no type.
It lives here because three generators name this shape and a fourth is arriving. A specification calling the create body ProfileCreateInput while the client declares ProfileCreateBody describes a type the SDK does not have, and the reader has no way to tell which of the two is the mistake.
func DefaultServer ¶ added in v0.3.0
DefaultServer is the deployment a caller who names none receives.
It takes the list rather than the document, because a client generator's list is not always the document's: a project still configuring the deprecated per-generator option has one nameless deployment that never reached the IR, and the question asked of both lists is the same one.
ok is false for a project that named no deployment, which is what tells a client generator to emit no constant and go on requiring a URL from its caller.
The project resolved which entry this is — an explicit marker, or the first when nobody claimed it — before the document was written, so this looks for a mark rather than reimplementing the rule.
func Describe ¶
Describe falls back to a generated sentence when there is no comment, so every exported type carries documentation even before anyone writes any.
func ExpandLayout ¶
ExpandLayout fills the placeholders in a stub directory template — {table}, {Table}, {tables} — from the resource a stub is being written for.
service-go's service stub and server-go's shape scopes both go into a project's own tree, from the same `layout` settings and — since the two now share a `stub_dir` — usually into the same directory, so the path one of them computes has to be the path the other would.
func Exposed ¶
Exposed is every resource with an API surface: endpoints to call, and not marked unexposed.
Both SDK generators ask this and they have to agree. A resource one of them emits a client for and the other does not is not a difference between two languages; it is one of them being wrong.
The OpenAPI generator asks a wider question — a shape route is its own read surface, so a resource can be unexposed and still belong in the specification — and answers it for itself.
func FieldsTypeName ¶
FieldsTypeName is the shape a validation failure on this endpoint's body arrives in, and is empty when there is no body to be wrong about.
Two kinds of body are left out. A body that is a named object is shared between endpoints, so its failure shape would have to be too, and nothing on the server emits one to fill in. And a generated endpoint whose body is its own — a search, a revert — is refused in some other shape than that body's: a search's filter is a question nothing validates, and a revert replays the version through the update path, so what comes back is the update's field errors and not one about a version identifier. A shape per call there would be the wrong shape, which decodes to an empty one rather than failing. Both fail as a plain client error, which is what everything did before any of this existed.
A create and an update are generated too and are not left out: their bodies are the model's inputs, and the validator that refuses one is generated beside them.
func FilterObjects ¶
FilterObjects are the search shapes belonging to a resource.
A filter is reached from the search body rather than from the entity, so a resource with no Search endpoint has filter shapes in the document and no use for them here — which is why reachable is a parameter rather than something this could work out from the resource alone.
func GoDuration ¶
GoDuration renders a duration as Go source a person can check against the configuration file.
30 * 24 * time.Hour rather than 2592000000000000: somebody reading the generated wiring has to be able to see that it says thirty days, because the whole point of configuring this in a file is that the number is reviewable.
func GoType ¶
GoType renders a field's Go type, importing whatever it needs.
The IR carries the type as source text — "*time.Time", "[]string" — so a package qualifier has to be recognized and registered rather than parsed out of a structured form.
modelPkg qualifies a type the model package declares, which is every enum. It is called only when there is something to qualify — importing on every field would leave an unused import in any file whose types are all builtin.
Pass nil inside the model package itself, where those names are local.
func IdempotentWrite ¶
IdempotentWrite reports whether an endpoint's work is worth recording against an Idempotency-Key.
The methods that can produce a second row if a client sends the same request twice, and only those. A GET has nothing to record. A DELETE is idempotent in what it leaves behind — its second answer is a 404 about a row that is genuinely gone — so buying a smoother answer for it would cost a transaction per delete to change an error nobody was wrong to receive.
A search is a QUERY here even where it is also mounted as a POST alias, so it is excluded by the same switch that excludes reads. The alias is a second route to one handler, and the handler is generated from the operation. This is why the test is ir.Endpoint.Method rather than whatever method a caller happens to be reading off a route.
A dedicated upload route never reaches this in the server generator: it takes a branch of its own, for reasons that branch explains.
It is here rather than in the server generator because the specification and the server have to agree. A document that advertises the header on a route the server does not guard is a document that costs somebody a duplicated write.
func JSONTag ¶
JSONTag renders a field's struct tag.
A nullable or empty field is omitted rather than sent as a wall of nulls, and a required one is always present so the receiver can rely on the key.
It is here because the model layer, the service layer and the Go client all tag the same field, and a field the client omits and the server requires is a request refused for a reason neither end can see. It is deliberately not the rule BodyRequired applies, which ignores Default: the two answer different questions — may I leave this out of the JSON I send, versus must the server have received it.
func ModelInputName ¶
ModelInputName is which of the model's input types an endpoint's body is, or empty when the body is a shape of its own.
Create and Update decode straight into the model's inputs: what a client sends and what the repository takes are the same fields, so a wire struct in between would be a third copy of the entity and a third place to forget a column.
It lives here because the API generator and the server generator both have to answer this, and they have to answer it the same way: one decodes into the type the other declared.
func PatchType ¶
PatchType picks the update wrapper a column's nullability calls for, around an element type the caller has already rendered.
Which wrapper a column gets is not a matter of taste: a client sending patch.Nullable where the server decodes patch.Optional is a null the server refuses, and the two ends have to agree. So the model generator and the client generator ask the same function rather than each deciding.
func PlainFields ¶
func PlainFields(fields []ir.ResourceField) []ir.Field
PlainFields drops the per-field operations from a resource's fields, for the emitters that only need the shape.
func PointerTo ¶
PointerTo makes a rendered Go type optional, unless it already is: a slice and a pointer both have a nil to mean absent, and a pointer to either would be a second one.
Note that internal/compile keeps a copy of this rule. That is not an oversight waiting to be tidied: the compiler must not import a generator package, and its copy also maps the empty type to the empty string, which is a contract for a field the IR has not typed yet rather than one this ever sees.
func QueryTypeName ¶
QueryTypeName is the shape an endpoint's query parameters arrive in.
One answer for both SDKs: the Go struct and the TypeScript interface are the same API described twice, and a caller reading one language's documentation to write the other has to find the same name there.
func RevisionHeader ¶
RevisionHeader is the header the API revision travels in, both ways.
One fallback rather than one per generator: the client that sends the header, the server that reads it and the specification that documents it have to name the same thing, and a document written before the setting existed says nothing at all.
func RoutePath ¶
RoutePath is the path half of a net/http pattern — "GET /v1/todos/{id}" becomes "/v1/todos/{id}".
A pattern with no method is returned whole. That is unreachable from a compiled document, where ir.Endpoint.Pattern always carries one, and returning the input rather than the empty string is what makes it unreachable harmlessly.
func SearchFilterField ¶
SearchFilterField is the member of a search body that carries the conditions.
Found rather than assumed: the body is one object field, and taking it from the document means a rename in the compiler does not silently produce a client that sends the wrong key.
func Servers ¶ added in v0.3.0
Servers are the deployments the project named, in the order it named them, or nil for a project that named none.
One accessor rather than three reads of the same field, for the reason RevisionHeader is one: the OpenAPI document, the Go client and the TypeScript client all have to say where the API is, and a document whose server list disagreed with the SDK beside it is the failure the `servers:` block exists to prevent.
func StoredFields ¶
func StoredFields(res *ir.Resource) []ir.ResourceField
StoredFields are the resource's fields that map to a column, in column order.
func UnclaimedObjects ¶
func UnclaimedObjects( doc *ir.Document, reachable map[string]bool, claimed map[string]bool, ) []*ir.Object
UnclaimedObjects are the reachable objects no resource's own file emits — whatever a project declared for itself and a custom endpoint returns. They have to land somewhere, or a method that returns one does not compile.
A resource's entity, its page shape and its filters are its files'. What is claimed beyond those differs by language and is the caller's to name: the Go client declares Error and Pagination in its base file, and the TypeScript client declares neither there. So claimed is a seed rather than a constant, and it is read rather than written — a caller may pass nil.
func UsesModelInput ¶
UsesModelInput reports whether an endpoint's body is one of the model's input types.
func WritableFields ¶
func WritableFields(res *ir.Resource, op string) []ir.ResourceField
WritableFields are the fields a client supplies for one operation.
Types ¶
type InputError ¶
type InputError struct {
// Name is the type to declare, LessonCreateInputError.
Name string
// Doc is the comment above it.
Doc string
// Subject begins the sentence Error builds — the table for a model input, so
// that a log line says which row was refused.
Subject string
// Entity is the comment on the Entity member, which is the one member that
// stands for no field.
Entity string
// Fields are the input's members, in the input's order.
Fields []ir.Field
}
InputError is one typed validation failure to emit: a struct that mirrors an input one member for one, plus the four methods that make it an error the HTTP layer can answer with.
It lives here because two generators emit one. The model layer emits one per create and update, and the service layer emits one per custom endpoint body — the same shape, in a different package, about a body no model owns. Two copies would eventually disagree about a wire name or an omitempty, and the client decoding them would find out at runtime.
The prose is per generator rather than shared: what the type is about is the one thing the two do not have in common, and a comment written to cover both would say nothing about either.
func (InputError) Emit ¶
func (e InputError) Emit(b *gobuf.Buf)
Emit writes the declaration and its methods.
type Walk ¶
type Walk struct {
// contains filtered or unexported fields
}
Walk collects every type reachable from the surface a generator decided to emit.
It exists because a compiled document describes more than any one output exposes. The authentication foundation's own tables have models, repositories and filter shapes — and no endpoints, deliberately — so emitting every object in the document would put a filter for the session table in the SDK of an application that cannot search sessions. What an output should declare is what its own surface mentions, and what that mentions in turn.
The recursion is here; the seeds are not. Three generators walk this graph and they genuinely start from different places: a client declares the shapes its methods mention, a specification the shapes its operations carry, and the TypeScript client reaches a streamed table's columns that no object mentions at all. So each caller writes its own seeding and shares the closure — which is the half that was copied three times, and the half where a divergence is a bug rather than a policy.
func (*Walk) Endpoint ¶
Endpoint follows everything one endpoint can carry, in either direction: its body, its parameters and its headers, and the same for every response.
Headers are walked even though no compiled document fills ir.EndpointRequest.Headers today — see openapigen's sharedHeaders. The first one to carry an enum should reach that enum in every output at once, rather than in whichever ones remembered to ask.
func (*Walk) Fields ¶
Fields follows whatever a list of fields names: an enum, an object, or a resource. A primitive names nothing.