Documentation
¶
Overview ¶
Package tableconf reads the per-table YAML that annotates an introspected schema.
The database says what exists; these files say what it means. A file can add documentation, rename what a column is called on the wire, forbid an update, exclude a column from the API, or declare an endpoint beyond CRUD. It can never contradict the database: there is no key for a column's type, nullability, or keys, because those are facts rather than choices.
Files are keyed by physical names — table, column, and enum label as they are spelled in Postgres — because API names are derived and can change under you.
Every diagnostic this package produces carries the exact line and column of the key that caused it. That is why the loader keeps the YAML syntax tree around instead of decoding straight into structs.
Descriptions live in `jsonschema_description` tags rather than inside the `jsonschema` tag, because that tag splits on commas and prose contains them.
Index ¶
Constants ¶
const SchemaID = "https://rig.dev/schema/table.v1.json"
SchemaID is the identifier the emitted schema publishes itself under.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Access ¶
type Access struct {
// Scope is "tenant" (every row the tenant owns, the default) or "own" (the
// rows the caller created).
//
// "own" needs a created-by audit column, which is what it filters on. It
// narrows reads only: an owner-scoped table refuses to change somebody else's
// row outright, with no parameter to widen that, because a write is a
// different kind of decision from a read and one flag for both would be a bad
// answer to two questions.
Scope string `` /* 177-byte string literal not displayed */
// Owner names the column "own" filters on. It defaults to
// created_by_account_id, the audit column every generated write stamps.
//
// Naming another one is for the table whose owner is not whoever created the
// row: an inbox line belongs to the person it is addressed to, and an
// assigned task belongs to its assignee. The column has to be a uuid
// referencing rig_account and it has to be NOT NULL — the nullability the
// audit column is allowed is exactly what a column like this must not have,
// because a null there is a row nobody can see and nothing reports.
Owner string `` /* 194-byte string literal not displayed */
}
Access is how wide a read of a table reaches by default.
Only the default. What a caller may ask for beyond it is a permission, and how far the answer actually reaches is in the response — this is the floor, not the ceiling.
type Column ¶
type Column struct {
// Comment documents the column. It wins over a Postgres COMMENT ON and over
// any comment rig would generate for a well-known column.
Comment string `yaml:"comment,omitempty" json:"comment,omitempty" jsonschema_description:"What this column holds."`
// Field overrides the derived API field name.
Field string `` /* 129-byte string literal not displayed */
// Format narrows a string for documentation and client validation.
Format string `` /* 239-byte string literal not displayed */
// GoType names a concrete Go type for a jsonb column, replacing the generic
// raw-message type.
GoType string `yaml:"go_type,omitempty" json:"go_type,omitempty" jsonschema_description:"Named Go type for a jsonb column."`
// Operations replaces the derived set of Create, Read, Update.
Operations []string `` /* 200-byte string literal not displayed */
// Immutable allows the column on create but never on update, so it is
// absent from the update input entirely.
Immutable bool `yaml:"immutable,omitempty" json:"immutable,omitempty" jsonschema_description:"Settable on create, never on update."`
// ReadOnly keeps the column out of every write path.
ReadOnly bool `yaml:"read_only,omitempty" json:"read_only,omitempty" jsonschema_description:"Never writable through the API."`
// Exclude removes the column from the API entirely.
Exclude bool `yaml:"exclude,omitempty" json:"exclude,omitempty" jsonschema_description:"Do not expose this column at all."`
// SnapshotIgnore keeps the live row's value when a snapshot is restored.
// The value is still copied into the snapshot itself.
SnapshotIgnore bool `` /* 137-byte string literal not displayed */
// Example is shown in generated documentation.
Example string `yaml:"example,omitempty" json:"example,omitempty" jsonschema_description:"Example value for documentation."`
}
Column configures one column's appearance in the API.
type Electric ¶
type Electric struct {
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty" jsonschema_description:"Expose a live-sync shape endpoint for this table."`
// Auth is the session kind required. Defaults to tenant.
Auth string `` /* 140-byte string literal not displayed */
// Params are extra query parameters, keyed by the query-string key.
Params map[string]ElectricParam `yaml:"params,omitempty" json:"params,omitempty" jsonschema_description:"Extra query parameters, keyed by query-string key."`
// Fallback is whether this shape answers from the database while the sync
// service is unreachable. Defaults to true.
//
// A pointer, so that "not mentioned" and "false" are different answers: the
// default is on, and there is no way to write the default down that could be
// mistaken for having asked for it.
Fallback *bool `` /* 166-byte string literal not displayed */
}
Electric configures a live-sync shape endpoint.
The tenant and lifecycle predicates are built by rig. Declared params are handed to the application's own scoping function, which can only narrow the shape further.
type ElectricParam ¶
type ElectricParam struct {
Type string `` /* 177-byte string literal not displayed */
Optional bool `yaml:"optional,omitempty" json:"optional,omitempty" jsonschema_description:"Whether the parameter may be omitted."`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema_description:"What this parameter narrows."`
}
ElectricParam is one declared query parameter of a live-sync endpoint.
type Endpoint ¶
type Endpoint struct {
Name string `yaml:"name" json:"name" jsonschema_description:"Method name on the generated service interface."`
Method string `` /* 140-byte string literal not displayed */
// Path is relative to the resource, for example "/{id}/_publish".
Path string `` /* 127-byte string literal not displayed */
Summary string `yaml:"summary,omitempty" json:"summary,omitempty" jsonschema_description:"One-line description."`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema_description:"Longer description."`
// Permission is the RBAC key required. Empty means a valid session is enough.
Permission string `` /* 132-byte string literal not displayed */
// Public answers without a credential. The table's `public:` list says the
// same thing from the other end; this is here so an endpoint declared in
// one block does not have half of itself in another.
Public bool `yaml:"public,omitempty" json:"public,omitempty" jsonschema_description:"Answer this endpoint without requiring a credential."`
Request *EndpointRequest `yaml:"request,omitempty" json:"request,omitempty" jsonschema_description:"What the client sends."`
Responses []EndpointResponse `yaml:"responses,omitempty" json:"responses,omitempty" jsonschema_description:"Every status this endpoint can return."`
}
Endpoint declares an operation beyond the generated CRUD set.
An endpoint named after a generated one replaces it. That is reported as a note, so the shadowing is visible rather than mysterious.
func (*Endpoint) Req ¶
func (e *Endpoint) Req() EndpointRequest
Req returns the endpoint's request, or an empty one when it declares none, so callers do not have to nil-check before reading parameter lists.
type EndpointRequest ¶
type EndpointRequest struct {
PathParams []Param `yaml:"path_params,omitempty" json:"path_params,omitempty" jsonschema_description:"Parameters interpolated into the path."`
QueryParams []Param `yaml:"query_params,omitempty" json:"query_params,omitempty" jsonschema_description:"Query-string parameters."`
Body []Param `yaml:"body,omitempty" json:"body,omitempty" jsonschema_description:"Body fields. Mutually exclusive with body_object."`
// BodyObject names a whole object as the body instead of listing fields.
BodyObject string `` /* 126-byte string literal not displayed */
// FileParts are the files the request may carry beside its body.
FileParts []FilePart `` /* 149-byte string literal not displayed */
}
EndpointRequest is what a client sends to a custom endpoint.
type EndpointResponse ¶
type EndpointResponse struct {
Status int `yaml:"status" json:"status" jsonschema:"minimum=100,maximum=599" jsonschema_description:"HTTP status code."`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema_description:"When this status is returned."`
// BodyObject names the object returned, for example Lesson or Error.
BodyObject string `yaml:"body_object,omitempty" json:"body_object,omitempty" jsonschema_description:"Name of the object returned."`
BodyFields []Param `` /* 139-byte string literal not displayed */
}
EndpointResponse is one outcome of a custom endpoint.
type Enum ¶
type Enum struct {
// Name overrides the derived Go and TypeScript type name.
Name string `` /* 135-byte string literal not displayed */
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema_description:"What this enumeration represents."`
// Values is keyed by the Postgres label, which stays the wire value.
Values map[string]EnumValue `yaml:"values,omitempty" json:"values,omitempty" jsonschema_description:"Per-label configuration keyed by the Postgres label."`
}
Enum configures a Postgres enum type's appearance in the API.
type EnumValue ¶
type EnumValue struct {
// Name overrides the derived identifier. The Postgres label remains the
// value on the wire regardless.
Name string `` /* 127-byte string literal not displayed */
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema_description:"What this value means."`
}
EnumValue configures one label.
type File ¶
type File struct {
// Table is the physical table name. It must match the file's location, so
// a renamed table is caught rather than silently ignored.
Table string `yaml:"table" json:"table" jsonschema_description:"The physical table name this file configures."`
// Resource overrides the derived API resource name, for example Lesson.
Resource string `` /* 137-byte string literal not displayed */
// Plural overrides the derived plural, for example Lessons.
Plural string `` /* 142-byte string literal not displayed */
// PathSegment overrides the derived URL segment, for example lessons.
PathSegment string `` /* 137-byte string literal not displayed */
// Comment documents what the table is for. It becomes the Go doc comment on
// the model and the description in the OpenAPI document.
Comment string `yaml:"comment,omitempty" json:"comment,omitempty" jsonschema_description:"What this table is for and why it exists."`
// Operations replaces the default operation set. It is a replacement rather
// than a merge: additive semantics could not express removing one.
Operations []string `` /* 217-byte string literal not displayed */
// Public names the operations that answer without a credential.
//
// Both generated operations and custom endpoints, by name, because they
// share one namespace: a custom endpoint called Get replaces the generated
// one. A custom endpoint can also say so in its own block, which is where
// everything else about it is declared.
Public []string `` /* 131-byte string literal not displayed */
// Expose controls whether the table appears in the API at all. Nil means
// yes. False keeps the model and the repository and generates no endpoints,
// which is what a table like rig_account_token wants: the data layer is worth
// generating and a REST interface for it is not.
Expose *bool `` /* 173-byte string literal not displayed */
// OrderBy is the default ordering, most significant first. A leading minus
// sorts descending, for example "-starts_at".
OrderBy []string `` /* 139-byte string literal not displayed */
// RestoreWindowDays bounds how long a soft-deleted row stays restorable.
// Required for a soft-deletable table and rejected on any other.
RestoreWindowDays *int `` /* 180-byte string literal not displayed */
// Access is how wide a read reaches by default.
Access *Access `yaml:"access,omitempty" json:"access,omitempty" jsonschema_description:"How wide a read of this table reaches by default."`
// Cache holds this table's Get in memory, withdrawn on the writes rig makes
// to it. It needs the project's `cache:` block, and it is a promise as much
// as a setting: every write to this table has to go through the generated
// repository, because that is the only place rig can publish the withdrawal
// from. A write through Store.Pool, or raw SQL from inside a dbhook, serves a
// stale row until the entry expires.
Cache *bool `` /* 201-byte string literal not displayed */
// OnDelete states the order this table's children hear about a delete.
OnDelete *OnDelete `` /* 141-byte string literal not displayed */
Columns map[string]Column `` /* 130-byte string literal not displayed */
Enums map[string]Enum `` /* 127-byte string literal not displayed */
Relations map[string]Relation `` /* 138-byte string literal not displayed */
Electric *Electric `yaml:"electric,omitempty" json:"electric,omitempty" jsonschema_description:"Live-sync shape endpoint for this table."`
Endpoints []Endpoint `yaml:"endpoints,omitempty" json:"endpoints,omitempty" jsonschema_description:"Endpoints beyond the generated CRUD set."`
}
File is one table's configuration.
type FilePart ¶ added in v0.6.3
type FilePart struct {
Name string `yaml:"name" json:"name" jsonschema_description:"Part name in PascalCase."`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema_description:"What this file is."`
Optional bool `yaml:"optional,omitempty" json:"optional,omitempty" jsonschema_description:"Whether the part may be omitted."`
Array bool `` /* 138-byte string literal not displayed */
}
FilePart is one file a request carries.
Declaring one makes the endpoint accept `multipart/form-data` as well as JSON: the body travels in a part named `json` and each file in a part of its own, and the service method receives the uploads beside the decoded body. It is the same shape a create on a table with a file column already takes, said for an endpoint that has no file column to derive it from — which is why Name is the whole of it and there is no column, role or segment here to name.
What the service does with the bytes is the service's. rig stores them and hands over the pending uploads; nothing here says which row they end up on, because an endpoint that needed that would be a create.
type Loaded ¶
type Loaded struct {
Path string
File *File
Index *yamlconf.Index
// Builtin says rig supplied this configuration rather than a project, so
// [Loaded.Path] names no file anybody can open.
//
// It is the difference between "nobody has said what this column is for"
// and "rig has, in prose it keeps in the migration that created it". The
// `unmentioned_column` rule needs that difference: rig's own configurations
// list no columns on purpose, so without this the rule would tell every
// project to run `rig sync` against a file `rig sync` will not write.
//
// A project that writes its own file for one of rig's tables loses the
// exemption with it, which is the intended reading: `rig sync` does keep
// that file up to date, so the advice has somewhere to land.
Builtin bool
}
Loaded is one configuration file, its syntax tree index, and where it came from. The index is what lets later stages report a problem at the exact key that caused it, long after the YAML has been decoded into structs.
func Parse ¶
Parse is Load on bytes already in hand, so tests and the sync command can validate content that is not yet on disk.
type OnDelete ¶
type OnDelete struct {
// Order names child tables, by their physical names, in the order they
// should hear about a delete. Anything left out runs after, in the derived
// order.
Order []string `` /* 156-byte string literal not displayed */
}
OnDelete states the order the tables referencing this one are told that a row is going.
Note what is and is not here. The parent states the *sequence*, which is a fact about the relationships between its children and belongs to no single one of them — and the parent is the only place that can see all of them at once. It does not state the *action*: that is a function on the child, and the parent has no opinion about it.
rig derives an order already — referencing tables before referenced ones — and that default is right for the case that recurs. This is for when it is wrong.
type Param ¶
type Param struct {
Name string `yaml:"name" json:"name" jsonschema_description:"Field name in PascalCase."`
Type string `yaml:"type" json:"type" jsonschema_description:"Primitive type, or the name of an enum or object."`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema_description:"What this parameter does."`
Optional bool `yaml:"optional,omitempty" json:"optional,omitempty" jsonschema_description:"Whether the parameter may be omitted."`
Array bool `yaml:"array,omitempty" json:"array,omitempty" jsonschema_description:"Whether the parameter is a list."`
Default string `yaml:"default,omitempty" json:"default,omitempty" jsonschema_description:"Default value when omitted."`
}
Param is one request parameter.
type Relation ¶
type Relation struct {
// Name overrides the derived accessor name.
Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema_description:"Name of the relation on the resource."`
// Embed includes the related rows in read responses.
Embed bool `yaml:"embed,omitempty" json:"embed,omitempty" jsonschema_description:"Include the related rows in read responses."`
}
Relation configures a link derived from a foreign key or a join table.
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
Set is every table configuration in a project, keyed by table name.
func LoadDir ¶
LoadDir reads every configuration file at the given paths.
A file whose declared table does not match its filename is an error: the filename is how `rig sync` finds the file to update, so a mismatch means one of the two names is silently ignored from then on.
func (*Set) Failed ¶
Failed reports whether a table's configuration exists but could not be read.
Such a table is not unconfigured — its intent is simply unknown, and that is a different thing. Rules that ask what the configuration says must stay quiet about it, or one mistyped key buries itself under a diagnostic for every column in the table.
func (*Set) MarkFailed ¶
MarkFailed records that a table's configuration could not be read.