schema

package
v0.96.0 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package schema is part of the GoFastr framework. See https://github.com/DonaldMurillo/gofastr for documentation.

Index

Constants

View Source
const DecimalColumnType = "DECIMAL(19,4)"

DecimalColumnType is the column a Decimal field gets when it sets no RawType: 19 significant digits, 4 of them after the point, so 15 before it. The migrators emit it and DecimalColumn reads it, so the write check and the DDL cannot drift.

View Source
const MaxDecimalExponent = 1000

MaxDecimalExponent is the largest exponent magnitude Postgres numeric_in reads (NUMERIC_MAX_PRECISION). Past it the literal is 22P02 even for an unconstrained NUMERIC, while SQLite stores it and strconv.ParseFloat reads "1e-1001" as 0 with no error. The write check and the filter coercion (framework/filter) share it.

View Source
const MaxVarcharWidth = 10485760

MaxVarcharWidth is the widest VARCHAR(n) every supported dialect accepts: Postgres refuses n past 10485760 at CREATE TABLE.

View Source
const SQLiteDecimalDigits = 15

SQLiteDecimalDigits is how many significant digits a SQLite column keeps of a decimal with a fraction. SQLite has no exact decimal type: a DECIMAL or NUMERIC column stores such a value as a 64-bit float, which holds any 15 significant digits exactly and not every 16.

View Source
const TimestampLayout = "2006-01-02T15:04:05.000000Z07:00"

TimestampLayout is the one spelling a Timestamp column holds: UTC, microseconds, trailing zeros kept. crud writes every Timestamp in it, a keyset cursor carries it, and every filter and typed query condition binds a Timestamp value in it. SQLite compares the column as text, so a value bound as the "...00Z" a caller sent matched no row stored as "...00.000000Z" for the same instant, and gt/lt compared spellings.

Variables

View Source
var (
	ReasonRequired         = i18n.Define("validation.required", "is required")
	ReasonInvalid          = i18n.Define("validation.invalid", "is invalid: {{detail}}")
	ReasonUnsupportedType  = i18n.Define("validation.unsupported_type", "has unsupported field type {{type}}")
	ReasonNotString        = i18n.Define("validation.not_string", "must be a string")
	ReasonInvalidText      = i18n.Define("validation.invalid_text", "must be valid UTF-8 without NUL bytes")
	ReasonMinLength        = i18n.Define("validation.min_length", "must be at least {{min}} characters")
	ReasonMaxLength        = i18n.Define("validation.max_length", "must be at most {{max}} characters")
	ReasonPatternInvalid   = i18n.Define("validation.pattern_invalid", "has an invalid pattern: {{detail}}")
	ReasonPattern          = i18n.Define("validation.pattern", "must match pattern {{pattern}}")
	ReasonNotInteger       = i18n.Define("validation.not_integer", "must be an integer")
	ReasonMin              = i18n.Define("validation.min", "must be at least {{min}}")
	ReasonMax              = i18n.Define("validation.max", "must be at most {{max}}")
	ReasonNotNumber        = i18n.Define("validation.not_number", "must be a number")
	ReasonNotFinite        = i18n.Define("validation.not_finite", "must be a finite number")
	ReasonNotDecimalString = i18n.Define("validation.not_decimal_string", "must be a decimal string")
	ReasonNotDecimal       = i18n.Define("validation.not_decimal", "must be a valid decimal number")
	ReasonNotFiniteDecimal = i18n.Define("validation.not_finite_decimal", "must be a finite decimal number")
	// ReasonDecimalExponent, ReasonDecimalIntegerDigits and
	// ReasonDecimalFractionDigits refuse a decimal its column cannot
	// store as sent (see [DecimalColumn]).
	ReasonDecimalExponent       = i18n.Define("validation.decimal_exponent", "must have an exponent between -{{max}} and {{max}}")
	ReasonDecimalIntegerDigits  = i18n.Define("validation.decimal_integer_digits", "must have at most {{max}} digits before the decimal point")
	ReasonDecimalFractionDigits = i18n.Define("validation.decimal_fraction_digits", "must have at most {{max}} digits after the decimal point")
	// ReasonDecimalSignificantDigits refuses, on SQLite, a decimal with a
	// fraction the column would keep only as a 64-bit float (see
	// [DecimalExactOnSQLite]).
	ReasonDecimalSignificantDigits = i18n.Define("validation.decimal_significant_digits", "must have at most {{max}} significant digits")
	ReasonNotTrueFalse             = i18n.Define("validation.not_true_false", "must be true or false")
	ReasonNotBoolean               = i18n.Define("validation.not_boolean", "must be a boolean")
	ReasonNotOneOf                 = i18n.Define("validation.not_one_of", "must be one of {{values}}")
	ReasonNotUUID                  = i18n.Define("validation.not_uuid", "must be a valid UUID")
	ReasonNotTimestamp             = i18n.Define("validation.not_timestamp", "must be a valid RFC 3339 timestamp")
	ReasonNotDate                  = i18n.Define("validation.not_date", "must be a valid date (YYYY-MM-DD)")
	ReasonNotJSON                  = i18n.Define("validation.not_json", "must be valid JSON")
	ReasonNotReference             = i18n.Define("validation.not_reference", "must be a string reference")
	ReasonEmptyReference           = i18n.Define("validation.empty_reference", "must be a non-empty reference")
	ReasonNotPath                  = i18n.Define("validation.not_path", "must be a string path or URL")
	ReasonEmptyPath                = i18n.Define("validation.empty_path", "must be a non-empty path or URL")
	ReasonUniqueTaken              = i18n.Define("validation.unique_taken", "is already in use")
)

The built-in validation phrases, under "validation.<code>".

Functions

func DecimalColumn added in v0.96.0

func DecimalColumn(f Field) (precision, scale int, bounded bool)

DecimalColumn reports the precision and scale of the column a Decimal field is stored in: DecimalColumnType when RawType is empty, or the field's own RawType when it is DECIMAL(p[,s]) or NUMERIC(p[,s]) (a missing scale is 0, as in SQL). bounded is false for any other RawType (a bare NUMERIC, TEXT, a domain) or a precision/scale pair outside 1 <= p <= 1000, 0 <= s <= p: the write check then bounds the exponent alone.

func DecimalExactOnSQLite added in v0.96.0

func DecimalExactOnSQLite(colType, s string) bool

DecimalExactOnSQLite reports whether a SQLite column of type colType (a Decimal field's RawType, or DecimalColumnType when it has none) stores the decimal literal s exactly. A column with TEXT or BLOB affinity stores the text as sent. Any other affinity stores a whole number that fits 64 bits as an integer, and anything else as a 64-bit float, which keeps SQLiteDecimalDigits significant digits. s is a literal the Decimal validator accepted.

func DecimalExponentFits added in v0.96.0

func DecimalExponentFits(s string) bool

DecimalExponentFits reports whether a decimal literal's exponent, if it has one, is within ±[MaxDecimalExponent]. It reads the exponent text alone, so it is safe ahead of big.Rat, whose SetString builds 10^|exp| in full.

func FieldTypeLabel added in v0.87.0

func FieldTypeLabel(t FieldType, wire bool) string

FieldTypeLabel returns the human-readable label for a field type. It is the one table behind both renderings that name field types in prose: the llm.md entity reference (framework/crud) and the SDK reference screens (framework/sdkdocs), which used to carry two copies that could drift. wire selects the audience: true appends the wire-format notes an SDK author needs (decimal arrives as a string, image and file fields arrive as URLs); false returns the bare name the llm.md table uses. An unknown FieldType labels as "string", matching what both tables previously answered.

func FormatTimestamp added in v0.96.0

func FormatTimestamp(t time.Time) string

FormatTimestamp is t in TimestampLayout.

func JSONSchema

func JSONSchema(fields []Field) map[string]any

JSONSchema generates a JSON Schema draft-07 object from a slice of Field definitions. The returned map can be marshaled directly to JSON.

func LengthBound added in v0.96.0

func LengthBound(v float64, round func(float64) float64) (n int, ok bool)

LengthBound converts a String/Text field's Min or Max to the integer a JSON Schema minLength/maxLength (or a column width) takes: rounded with round, then clamped to [0, math.MaxInt32]. ok is false for NaN, and the caller leaves the keyword out.

A bare int(*f.Max) is implementation-defined for a finite value past the int range (MinInt64 on amd64, MaxInt64 on arm64), so a huge Max published a negative or nonsense maxLength.

func Validate

func Validate(field Field, value any) error

Validate checks a single value against a Field definition. Returns nil on success or a descriptive error on failure.

func VarcharWidth added in v0.96.0

func VarcharWidth(max *float64) (int, bool)

VarcharWidth reports the VARCHAR width for a String field's Max, and false when the column should be TEXT instead: no Max, a Max under 1, or one wider than MaxVarcharWidth (validation still enforces the Max).

Types

type AutoGenerate

type AutoGenerate int

AutoGenerate controls how a field value is automatically produced server-side.

const (
	AutoNone      AutoGenerate = iota // not auto-generated
	AutoUUID                          // UUID v4
	AutoTimestamp                     // current RFC 3339 timestamp
	AutoIncrement                     // auto-incrementing integer
)

type Field

type Field struct {
	Name         string       // field name (must be unique within a Schema)
	Type         FieldType    // field type
	Required     bool         // value must be present and non-zero
	Unique       bool         // value must be unique across entities
	Default      any          // default value when omitted
	AutoGenerate AutoGenerate // server-side auto-generation strategy
	ReadOnly     bool         // excluded from create/update request bodies
	Hidden       bool         // excluded from API responses
	NoQuery      bool         // returned in responses, but never filterable/sortable/searchable
	Max          *float64     // upper bound (numeric max / string max-length)
	Min          *float64     // lower bound (numeric min / string min-length)
	Pattern      string       // regex pattern for string validation
	Values       []string     // allowed values for Enum
	To           string       // target entity name for Relation
	Many         bool         // has-many relation flag
	RawType      string       // explicit SQL column type, overrides Type→SQL mapping (e.g. "NUMERIC(10,2)", "INET")

	// WireName overrides the JSON key this field is exposed under. When
	// empty, the wire key is derived from Name via the entity's JSONCase
	// (camelCase → "authorId", snake_case → "author_id"). When set, it is
	// used verbatim as the JSON output key, the accepted input key, and the
	// filter parameter name. The DB column (Name) is never changed. This is
	// how an API version renames or aliases a field without altering the
	// underlying table: v2 exposes column "author_id" as "authorId" (the
	// JSONCase default) or as a custom "writer" (WireName), while v1 keeps
	// the bare column shape. Both read and write the same column.
	WireName string
}

Field defines a single field in an entity schema.

Hidden and NoQuery both restrict a field, at different layers. Hidden removes it from responses AND from every query surface, so its value and its existence stay server-side. NoQuery keeps the field in responses but bars it from filters, sorting, and search.

NoQuery exists for values the caller may see only in transformed form, a card masked to last-4 by an AfterGet/AfterList hook, say. Without it the stored value is still a live column in WHERE and ORDER BY, so a caller recovers it a character at a time (?card_like=4111) while every response reads "****1111". Mark such a field NoQuery and the query surface refuses it. See framework/crud/redaction_oracle_security_test.go.

Hidden implies NoQuery; setting both is redundant but harmless.

type FieldType

type FieldType int

FieldType enumerates all supported field types in a GoFastr entity schema.

const (
	String    FieldType = iota // short string
	Text                       // long text / textarea
	Int                        // integer
	Float                      // floating point
	Decimal                    // fixed-precision decimal (stored as string)
	Bool                       // boolean
	Enum                       // one of a fixed set of strings
	UUID                       // UUID v4 identifier
	Timestamp                  // RFC 3339 timestamp
	Date                       // calendar date (2006-01-02)
	JSON                       // arbitrary JSON blob
	Relation                   // reference to another entity
	Image                      // image URL or path
	File                       // file URL or path
)

type Schema

type Schema struct {
	Fields []Field
}

Schema is an ordered collection of Fields with convenience helpers.

func (Schema) AutoGeneratedFields

func (s Schema) AutoGeneratedFields() []Field

AutoGeneratedFields returns fields that are auto-generated server-side.

func (Schema) FieldByName

func (s Schema) FieldByName(name string) (Field, bool)

FieldByName returns the field with the given name and true, or the zero Field and false if not found.

func (Schema) Names

func (s Schema) Names() []string

Names returns the ordered list of field names.

func (Schema) RequiredFields

func (s Schema) RequiredFields() []Field

RequiredFields returns only the required fields.

func (Schema) VisibleFields

func (s Schema) VisibleFields() []Field

VisibleFields returns fields that are included in API responses (not hidden).

func (Schema) WritableFields

func (s Schema) WritableFields() []Field

WritableFields returns fields that can be set by the client (not auto-generated, not read-only).

type ValidationResult

type ValidationResult struct {
	Valid  bool
	Errors map[string][]Violation // field name → its violations
}

ValidationResult holds the outcome of validating a full map of values.

func ValidateAll

func ValidateAll(s Schema, values map[string]any) ValidationResult

ValidateAll validates a map of values against every Field in the Schema. Unknown keys are ignored; missing required fields produce errors.

func ValidatePartial

func ValidatePartial(s Schema, values map[string]any) ValidationResult

ValidatePartial validates only the fields that are present in `values`. Missing fields, required or not, are not reported. Use this for partial-update flows (PUT/PATCH with only the fields the caller wants to change) so a sparse update doesn't fail because some other required field happens not to be in the body.

type Violation added in v0.94.0

type Violation struct {
	Reason i18n.Phrase
	Params map[string]any
}

Violation is one field-level validation failure: the phrase that names it and the values its message interpolates. It is an error whose Error() is the English message; Violation.Text renders it in the caller's locale, and Violation.Code is the stable code an API response carries next to the message.

An app's own validators define their phrases the same way the built-in ones below are defined, so their messages translate too:

var vatInvalid = i18n.Define("validation.vat_invalid", "is not a valid VAT number")
... schema.Violation{Reason: vatInvalid}

func AsViolation added in v0.94.0

func AsViolation(err error) Violation

AsViolation returns err as a Violation. An error that is not one (a validator that returned a plain error) becomes ReasonInvalid with the error's text as its detail.

func (Violation) Code added in v0.94.0

func (v Violation) Code() string

Code is the stable code: "required", "max_length", ...

func (Violation) Error added in v0.94.0

func (v Violation) Error() string

Error is the English message.

func (Violation) Phrased added in v0.94.0

func (v Violation) Phrased() (i18n.Phrase, map[string]any)

Phrased implements i18n.Phrased.

func (Violation) Text added in v0.94.0

func (v Violation) Text(ctx context.Context) string

Text is the message in ctx's locale.

Jump to

Keyboard shortcuts

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