Help us shape the future of Go! Take the 2026 Go Developer Survey

entity

package
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

Entity definitions and fields

kit/entity owns the shared Base, the Entity constraint and the field metadata derived from Go structs. It needs no application, database or web server. Its only dependency outside the standard library is google/uuid.

For a plain form or command input, derive fields directly from its type:

type Input struct {
	Title string `json:"title" validate:"required" doc:"A short title"`
	State string `json:"state" enum:"open,done" default:"open"`
}

fields := entity.FieldsOf(reflect.TypeFor[Input]())

Import reflect and github.com/septagon-oss/platformkit/kit/entity for this example. Use Fields[*YourEntity]() for a struct that embeds entity.Base and implements TableName() string. Each call returns its own fields, enum slices and reflection index paths, so presentation changes cannot alter another consumer's schema. FieldNamed looks up a field by its JSON name.

Existing crud.Base, crud.Entity, crud.Validator, crud.Schema and field types are aliases. Existing CRUD metadata helpers forward here. Applications can migrate those imports independently while keeping their storage calls.

display is the sibling that turns a field's value into words: Text for a control, Display for a person, Humanize and FieldLabel for a name. It depends on this package alone, so a renderer of schema values needs neither CRUD nor REST.

BaseOf lets a storage adapter reach the embedded metadata; it does not grant tenant access. CRUD retains tenant stamping, validation-error mapping and SQL operations. Tags still describe storage columns and presentation hints, but schema derivation neither writes a value nor executes those operations.

The widget vocabulary

ui:"widget:select" names the control a screen draws. Widgets is every name it may carry, and ValidWidget is the check; ui/forms renders one control per name, and widget_test.go there refuses a name in this list with no drawn control. What enforces the pair is rest.Spec.Mount, which panics at boot over a field naming anything else.

Before that, an unknown name drew a plain text input and said nothing, so widget:file was an upload control no schema could reach while the component behind it worked when built by hand in Go. datetime and checkbox are names the foundation's own entities, the catalog and a client already carry on fifteen fields, where the Go type chose the control and the name was ignored; both now mean the control, on a string holding an instant as much as on a time.Time. Run go test ./ui/forms ./kit/rest to see the vocabulary, the drawing and the refusal together. Neither proves a control is right for the field: entity-picker draws the identifier text box and says there is no picker yet.

Documentation

Overview

Package entity describes tenant-owned data and derives its field metadata. It does not open a database, authorize an operation or persist a value. Embed Base and implement TableName to describe a tenant-owned entity, or use FieldsOf for a plain input struct that needs no storage identity.

Index

Constants

This section is empty.

Variables

View Source
var Widgets = []string{
	"checkbox", "color", "date", "datetime", "email", "entity-picker", "file",
	"hidden", "month", "number", "password", "search", "select", "tel", "text",
	"textarea", "time", "url", "week",
}

Widgets is every name `ui:"widget:…"` may carry. It is the vocabulary, not a suggestion: the control each name draws lives in ui/forms, which cannot be imported from here, so what binds the two is a test there that names a rendered control for every entry here, and the mount-time refusal in kit/rest's Spec.check. A widget absent from this list is a compile-adjacent mistake rather than a runtime surprise: kit/entity owns the names because ui/forms owns the renderers, and a kernel below the presentation layer has no way to ask what a control is.

Two entries are aliases of what a Go type already picks — `datetime` for a time.Time and `checkbox` for a bool — kept because fifteen fields of the foundation's own entities, and of the catalog and a client, name them. They now mean the control instead of being ignored next to it, which is why a string holding a timestamp or a "true" draws the same control its time.Time and bool neighbours do.

Functions

func ValidWidget

func ValidWidget(widget string) bool

ValidWidget reports whether a screen can draw the named widget. The empty string is valid: it means "pick from Type", which is most fields.

Types

type Base

type Base struct {
	ID        uuid.UUID  `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id" required:"false" readOnly:"true"`
	TenantID  uuid.UUID  `gorm:"type:uuid;not null" json:"-"`
	CreatedAt time.Time  `json:"createdAt" required:"false" readOnly:"true"`
	UpdatedAt time.Time  `json:"updatedAt" required:"false" readOnly:"true"`
	DeletedAt *time.Time `gorm:"index" json:"-"`
}

Base is embedded by every tenant-owned entity. The kernel sets TenantID from the transaction's scope; a module never assigns it, and never sees it in JSON either, because a tenant that a caller could send is a tenant a caller could change.

The three fields a caller may read are marked read-only for OpenAPI and not required in a request body: the server sets all of them, so a create that had to send an id would be a create that could choose one.

func BaseOf

func BaseOf(e Entity) *Base

BaseOf returns the embedded identity and tenancy fields of a non-nil entity. Storage adapters use it to stamp a value from their transaction's tenant; it does not authorize access or establish a transaction by itself.

type Entity

type Entity interface {
	TableName() string
	// contains filtered or unexported methods
}

Entity is a tenant-owned row: a table name and an embedded Base.

type Field

type Field struct {
	// Name is the JSON name, which is the only name a caller ever uses.
	Name string `json:"name"`
	// Column is the storage column used by the CRUD adapter. It comes from
	// the struct, never from a request; deriving it executes no SQL.
	Column string    `json:"-"`
	Type   FieldType `json:"type"`
	// Elem is what a TypeList holds, and empty for everything else.
	Elem FieldType `json:"elem,omitempty"`
	// Widget overrides the control a screen would pick from Type: `ui:"widget:select"`.
	// It must be a name Widgets admits; a Spec whose entity carries any other
	// name refuses to mount, because a name no renderer knows used to draw a
	// plain text input in silence.
	Widget string `json:"widget,omitempty"`
	// Enum is the closed set of values, from `enum:"open,done"`.
	Enum []string `json:"enum,omitempty"`
	// Required comes from `validate:"required"`.
	Required bool `json:"required,omitempty"`
	// ReadOnly marks the fields Base contributes: a caller may read them and
	// may not write them, so they are skipped by the PATCH merge.
	ReadOnly bool `json:"readOnly,omitempty"`
	// HideList keeps a field off the list screen, from `ui:"hide:list"`.
	HideList bool `json:"hideList,omitempty"`
	// Default is the value the entity declares for a field a caller may leave
	// out, from `default:"open"` — the same tag huma reads, so the form and the
	// API document agree about what happens when nothing is sent. A form
	// preselects it, and a select that has one needs no "Choose a …" placeholder
	// because there is no unchosen state to name.
	Default string `json:"default,omitempty"`
	// Doc is what the field is for, from `doc:"Lifecycle state"` — again huma's
	// own tag, so the sentence in the OpenAPI document is the sentence under the
	// control. It is a description and not a label: the entities here write
	// "Short summary of the task", which reads under an input and not on it.
	Doc string `json:"doc,omitempty"`

	// Index locates the field in the struct. It is exported for one caller,
	// kit/rest's PATCH merge, which decodes a body into the field this names;
	// json:"-" because a screen has no use for it and a caller none at all.
	Index []int `json:"-"`
}

Field is one column, as the API and a screen see it.

func FieldNamed

func FieldNamed(fields []Field, name string) (Field, bool)

FieldNamed finds a field by its JSON name in the caller's metadata.

func Fields

func Fields[T Entity]() []Field

Fields derives the schema of a pointer entity type. Every call returns its own fields, enum values and index paths; callers may customize that metadata without changing another consumer's schema.

func FieldsOf

func FieldsOf(t reflect.Type) []Field

FieldsOf derives metadata for a struct or a pointer to one, including plain command inputs that do not embed Base. Nil and non-struct types have no fields. The returned fields, enum values and index paths belong to the caller.

type FieldType

type FieldType string

FieldType is the closed set of shapes a screen knows how to render and a query knows how to compare. A field of any other Go type is left out of the schema entirely, so it is neither rendered nor sortable nor filterable — it is still stored, and still in the JSON, because that is encoding/json's business and not this package's.

const (
	TypeString FieldType = "string"
	TypeText   FieldType = "text"
	TypeInt    FieldType = "int"
	TypeFloat  FieldType = "float"
	TypeBool   FieldType = "bool"
	TypeTime   FieldType = "time"
	TypeUUID   FieldType = "uuid"
	// TypeList is a slice of one of the above, which Field.Elem names. A user's
	// roles is the case that made it necessary: without it the field was in no
	// schema, so it rendered nowhere, no filter could refuse it and Immutable
	// could not name it — a PATCH could not reach it either, but only because
	// the field did not exist, which is the right answer for the wrong reason.
	TypeList FieldType = "list"
)

type Schema

type Schema struct {
	Module string  `json:"module"`
	Entity string  `json:"entity"`
	Path   string  `json:"path"`
	Fields []Field `json:"fields"`
}

Schema describes an entity for consumers that did not compile against it. An integration supplies its module, resource name and path.

type Validator

type Validator interface {
	Validate(ctx context.Context) error
}

Validator is the optional check an entity makes of itself before it is written. It takes a context and no transaction: storage adapters own database constraints and translate validation errors into their own error contract.

Directories

Path Synopsis
Package display is how a field's value reaches a person: the text a control carries, the word a screen shows, and the name a field is called by.
Package display is how a field's value reaches a person: the text a control carries, the word a screen shows, and the name a field is called by.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL