Documentation
¶
Index ¶
- Variables
- func Compare(op Operator, actual, expected interface{}) (bool, error)
- type AutoFixConfig
- type Condition
- type ConditionResolver
- type Enforcement
- type Field
- type FieldType
- type JoinFilter
- type JoinStep
- type Operator
- type PackageTable
- type Relation
- type Requires
- type Result
- type Rule
- type SchemaPackage
- type Severity
- type SystemContext
- type Table
- type Trigger
- type ValidationContext
- func (v *ValidationContext) GetFieldString(fieldName string) (string, bool)
- func (v *ValidationContext) GetFieldValue(fieldName string) (interface{}, bool)
- func (v *ValidationContext) GetRelatedRecords(foreignKey string) ([]map[string]interface{}, bool)
- func (v *ValidationContext) SetRelatedRecords(foreignKey string, records []map[string]interface{})
Constants ¶
This section is empty.
Variables ¶
var ( // ErrRuleNotFound indicates a rule with the given ID was not found. ErrRuleNotFound = errors.New("validation rule not found") // ErrInvalidRule indicates a rule has invalid configuration. ErrInvalidRule = errors.New("invalid validation rule") // ErrValidatorNotFound indicates a validator with the given type was not registered. ErrValidatorNotFound = errors.New("validator not found") // ErrRecordNotFound indicates the record to validate was not found. ErrRecordNotFound = errors.New("record not found") // ErrTableNotFound indicates the table does not exist. ErrTableNotFound = errors.New("table not found") // ErrFieldNotFound indicates the field does not exist in the record. ErrFieldNotFound = errors.New("field not found") // ErrAutoFixFailed indicates automatic fix attempt failed. ErrAutoFixFailed = errors.New("auto-fix failed") // ErrHardValidationFailed indicates a hard validation failed (should block save). ErrHardValidationFailed = errors.New("hard validation failed") // ErrInvalidParameters indicates rule parameters are invalid for the validator type. ErrInvalidParameters = errors.New("invalid validation parameters") // ErrSchemaMapperNotSet indicates no schema mapper was configured. ErrSchemaMapperNotSet = errors.New("schema mapper not set") // ErrRuleLoaderNotSet indicates no rule loader was configured. ErrRuleLoaderNotSet = errors.New("rule loader not set") )
Functions ¶
func Compare ¶
Compare applies op to actual, comparing against expected where applicable. Returns true when the condition holds, false when it doesn't. Unknown operators or type-incompatible arguments return an error rather than silently mis-matching.
Type handling is deliberately loose to match how rule parameters arrive: JSON numbers become float64, strings stay strings, arrays become []interface{}. Callers don't pre-normalize.
Types ¶
type AutoFixConfig ¶
type AutoFixConfig struct {
Strategy string `json:"strategy"` // copy_from_coordinate, set_default, capitalize, etc.
Params map[string]interface{} `json:"params"`
}
AutoFixConfig defines how to automatically fix a validation error.
func (*AutoFixConfig) IsEnabled ¶
func (a *AutoFixConfig) IsEnabled() bool
IsEnabled returns true if auto-fix is configured.
type Condition ¶
type Condition struct {
Field string `json:"field"`
Operator string `json:"operator"` // equals, not_equals, in, not_in, exists, not_exists, greater_than, less_than, contains, matches
Value interface{} `json:"value"`
Relation string `json:"relation,omitempty"` // optional named relation; empty = check the record's own field
}
Condition gates a rule's applicability. A rule with N conditions applies only when all N are satisfied. Empty condition list means the rule always applies (to every record of its target table).
A condition normally checks a field on the record being validated. When Relation is set, the condition instead checks a field on the record reached via that named relation from the bundle's Relations catalog. This lets a rule scoped on one table gate on fields belonging to a related table (e.g. a taxon-scoped rule that only fires when the owning name has a specific code).
func (*Condition) Eval ¶
func (c *Condition) Eval(vctx *ValidationContext) (bool, error)
Eval evaluates the condition against the current ValidationContext. When c.Relation is empty, reads the field from vctx.Record. Otherwise it resolves the relation via vctx.RelationResolver and reads the field from the related record. Missing resolver on a relation-scoped condition is an error; missing related row is treated as field-not-present.
c.Value may be a literal (string, number, list, …) or a same-record self-reference in the shape {"field": "other_col"}. Self-references always resolve against vctx.Record — even when c.Relation is set — so rules can compare a related row's field against a field on the current record (or against a fixed value).
type ConditionResolver ¶
type ConditionResolver interface {
LookupRelated(ctx context.Context, db *sql.DB, sourceTable, sourceID, relation string) (map[string]interface{}, error)
}
ConditionResolver looks up a related record by name. Supplied by the consumer (typically wrapping joins.RelationResolver) so condition.go stays free of any cross-table SQL specifics.
type Enforcement ¶
type Enforcement string
Enforcement expresses whether a rule violation blocks a write (hard) or is recorded as a warning that lets the write proceed (soft). It is a property of the rule, not the individual result.
const ( EnforcementHard Enforcement = "hard" EnforcementSoft Enforcement = "soft" )
func ParseEnforcement ¶
func ParseEnforcement(s string) Enforcement
ParseEnforcement normalises a string to an Enforcement value. Empty input or unrecognised values fall back to soft, so a rule missing the field is treated as non-blocking rather than silently blocking writes.
type Field ¶
type Field struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Type FieldType `json:"field_type"`
IsRequired bool `json:"is_required,omitempty"`
IsUnique bool `json:"is_unique,omitempty"`
IsSystem bool `json:"is_system,omitempty"`
DefaultValue string `json:"default_value,omitempty"`
ReferenceTableID string `json:"reference_table_id,omitempty"`
ReferenceDisplayField string `json:"reference_display_field,omitempty"`
Description string `json:"description,omitempty"`
SortOrder int `json:"sort_order,omitempty"`
}
Field is a column definition in a Table. Enough shape for gsvalidator to introspect a schema and reason about types / references; consumer-app UI hints (display styles, calculated field engines, etc.) are ignored by the bundle loader since they aren't in the JSON shape gsvalidator cares about.
type FieldType ¶
type FieldType string
FieldType classifies a Field's storage / semantics. Values match the shipping bundle format; type names are intentionally SQLite storage-class-aligned (text / integer / real / blob) plus higher-level flavors (number/date/reference) that consumers may treat specially.
const ( FieldTypeText FieldType = "text" FieldTypeNumber FieldType = "number" FieldTypeInteger FieldType = "integer" FieldTypeFloat FieldType = "float" FieldTypeBoolean FieldType = "boolean" FieldTypeDate FieldType = "date" FieldTypeDateTime FieldType = "datetime" FieldTypeReference FieldType = "reference" FieldTypeJSON FieldType = "json" )
type JoinFilter ¶
type JoinFilter struct {
Column string `json:"column"`
Equals interface{} `json:"equals"`
}
JoinFilter constrains a JoinStep to rows whose Column equals the given constant. Column names a field on the table that the step just joined in (identified by its alias if set, otherwise by its unaliased table name).
type JoinStep ¶
type JoinStep struct {
From string `json:"from"`
To string `json:"to"`
Alias string `json:"alias,omitempty"`
Where []JoinFilter `json:"where,omitempty"`
}
JoinStep is one edge of a relation's join chain. From/To are "<table_or_alias>.<column>" strings; Alias renames the joined table for later steps in the chain. Consumers walking a multi-step join thread aliases through the SQL they build.
Where narrows the joined rows to those satisfying additional equality predicates on the just-joined table (or its alias). Each JoinFilter contributes one `AND <alias>.<column> = ?` clause to the ON clause of the step, with the constant passed as a bound parameter. Only equality against a constant is supported today — the constrained shape keeps rule-supplied joins safe by construction.
type Operator ¶
type Operator string
Operator names the shared comparison vocabulary that validators share for value checks. Rule parameters carry an operator string (e.g. "equals", "ends_with"); the Compare helper below dispatches on it. Keeping the list centralized avoids per-validator drift where two mechanisms both accept "matches" but interpret it differently.
const ( OpEquals Operator = "equals" OpNotEquals Operator = "not_equals" OpGreaterThan Operator = "greater_than" OpLessThan Operator = "less_than" OpInSet Operator = "in_set" OpNotInSet Operator = "not_in_set" OpStartsWith Operator = "starts_with" OpEndsWith Operator = "ends_with" OpContains Operator = "contains" OpMatches Operator = "matches" // regex OpIsEmpty Operator = "is_empty" // unary; expected ignored OpIsPresent Operator = "is_present" // unary; expected ignored )
type PackageTable ¶
type PackageTable struct {
Table Table `json:"table"`
InitialData []map[string]interface{} `json:"initial_data,omitempty"`
}
PackageTable wraps a Table definition with optional initial-data rows (used to seed lookup tables at package install time).
type Relation ¶
type Relation struct {
TargetTable string `json:"target_table"`
TargetAlias string `json:"target_alias,omitempty"`
Cardinality string `json:"cardinality,omitempty"` // "one" (default) or "many"
Join []JoinStep `json:"join"`
Description string `json:"description,omitempty"`
}
Relation is a named cross-table join declared in a SchemaPackage's Relations block. Rules reference relations by name (via validator parameters like `related_field_equals`'s `relation` key); the consumer's SchemaMapper resolves the join against the live database using the declared join chain.
Constrained shape on purpose: each Join step is a bounded {from, to, alias?} tuple, no arbitrary WHERE clauses, no subqueries, no functions. This keeps rule-author-supplied relations bounded to safe compositions that a SQL translator can verify against a schema.
Cardinality signals whether a rule using this relation should expect exactly one target row (`one`) or many (`many`). A `related_field_equals` mechanism needs `one`; a `duplicate_detection` mechanism might use `many`.
TargetAlias is only needed when the target and source tables share a name (a self-join whose final step aliases the joined-in copy). When set, the SELECT projects the aliased row instead of the source. Blank in the common case where target and source are distinct tables.
type Requires ¶
type Requires struct {
Tables []string `json:"tables,omitempty"`
Columns map[string]string `json:"columns,omitempty"` // "table.column" -> "text|integer|real|blob|numeric"
Relations []string `json:"relations,omitempty"` // named relations that must resolve
}
Requires declares the schema features a bundle needs at load time — tables, columns (with expected type), and named relations. Consumers verify each item against the live database before activating the bundle's rules; missing items are per-item errors. Additive-compatible on the archive side: extra tables, columns, or relations don't fail the check.
Type checks on columns are loose (matched against SQLite storage classes). This avoids false-fails on unrelated schema tweaks (nullability, default value, CHECK constraints) that don't affect whether the ruleset works.
type Result ¶
type Result struct {
RuleID string `json:"rule_id"`
RuleName string `json:"rule_name"`
RecordID string `json:"record_id"`
TableName string `json:"table_name"`
FieldName string `json:"field_name"` // Required to support multiple issues per field
ValidatorType string `json:"validator_type"`
Enforcement Enforcement `json:"enforcement,omitempty"` // hard = blocked write; soft = advisory
Severity Severity `json:"severity,omitempty"` // error/warn/info/debug for display
ValidationType string `json:"validation_type,omitempty"` // legacy mirror of Severity for older readers
Passed bool `json:"passed"` // False for failures, true for passes
Message string `json:"message"`
ActualValue interface{} `json:"actual_value,omitempty"`
ExpectedValue interface{} `json:"expected_value,omitempty"`
AutoFixAvailable bool `json:"auto_fix_available"`
AutoFixStrategy string `json:"auto_fix_strategy,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
Result represents the outcome of a single validation rule execution. Multiple results can exist for the same field (multiple validation issues).
func NewResult ¶
func NewResult(ctx *ValidationContext, rule *Rule) *Result
NewResult builds a Result pre-populated with the fields every validator stamps identically: identifiers copied from the rule and context, plus the enforcement/severity/legacy validation_type triple derived from the rule. Individual validators still set Passed, Message, ActualValue, etc. on the returned pointer.
func (*Result) IsHardFailure ¶
IsHardFailure returns true when the result comes from a hard-enforcement rule that failed and should block the write.
type Rule ¶
type Rule struct {
ID string `json:"rule_id"`
Name string `json:"rule_name"`
Description string `json:"description"`
TableName string `json:"table_name"`
FieldName string `json:"field_name,omitempty"`
ValidatorType string `json:"validator_type"`
Enforcement Enforcement `json:"enforcement,omitempty"`
Severity Severity `json:"severity,omitempty"`
Trigger Trigger `json:"trigger,omitempty"` // default: on_write
RecheckDays int `json:"recheck_days,omitempty"` // for trigger=time_based
ValidationType string `json:"validation_type,omitempty"` // legacy: "hard"/"soft" or "error"/"warn"/"info"/"debug"
Conditions []Condition `json:"conditions,omitempty"`
Parameters map[string]interface{} `json:"parameters"`
ErrorMessage string `json:"error_message"`
WarningMessage string `json:"warning_message,omitempty"`
AutoFix *AutoFixConfig `json:"auto_fix,omitempty"`
IsActive bool `json:"is_active"`
Priority int `json:"priority"` // Execution order (lower = higher priority)
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
Rule defines a validation rule with conditions and parameters. This is a core domain entity with no external dependencies.
func (*Rule) EffectiveEnforcement ¶
func (r *Rule) EffectiveEnforcement() Enforcement
EffectiveEnforcement returns the rule's enforcement, falling back to legacy ValidationType parsing for rules loaded from older sources. "hard" and "error" both mean blocking; anything else is treated as soft.
func (*Rule) EffectiveSeverity ¶
EffectiveSeverity returns the severity a failing result of this rule should carry, falling back to legacy ValidationType parsing and finally to the default severity for the effective enforcement.
func (*Rule) EffectiveTrigger ¶
EffectiveTrigger returns TriggerOnWrite when the rule doesn't set one, matching the pre-Trigger behavior for existing rules.
func (*Rule) EvalConditions ¶
func (r *Rule) EvalConditions(vctx *ValidationContext) (bool, error)
EvalConditions checks whether the rule applies to the record named by vctx. Conditions may reference fields on the record itself or, when Relation is set, on a record reached via the named relation from the bundle's Relations catalog. Returns (false, nil) when the rule is inactive or any condition is unmet; (true, nil) when all conditions pass (or there are none). Returns an error when a relation-scoped condition can't be evaluated (missing resolver, SQL failure, etc.).
type SchemaPackage ¶
type SchemaPackage struct {
Name string `json:"name"`
Version string `json:"version"`
Domain string `json:"domain,omitempty"`
Description string `json:"description,omitempty"`
Author string `json:"author,omitempty"`
License string `json:"license,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
// SchemaFingerprint carries a SHA256 hash of the normalized
// schema for content-addressable discovery of matching
// packages (e.g. re-import of a plain SQLite that lost its
// metadata). Not used for load-time compatibility gating —
// Requires does that instead. See CompatSchema in this file.
SchemaFingerprint string `json:"schema_fingerprint,omitempty"`
// Tables is populated on schema-defining packages. Consumers
// apply the DDL to create tables + insert InitialData rows.
// Ruleset-only packages leave this empty.
Tables []PackageTable `json:"tables,omitempty"`
// Relations is the named-join catalog rules may reference.
// Constrained shape: each relation is a bounded join chain
// with no arbitrary SQL.
Relations map[string]Relation `json:"relations,omitempty"`
// Rules are the validation rules the bundle contributes. May
// be empty on a schema-only package.
Rules []*Rule `json:"rules,omitempty"`
// Requires declares the schema features (tables, columns,
// relations) this bundle needs at load time. Consumers walk
// the live database and verify each item resolves; missing
// items are per-item load-time errors. Additive on the archive
// side — extra tables/columns/relations are fine.
Requires *Requires `json:"requires,omitempty"`
}
SchemaPackage is a portable JSON bundle carrying (optionally) a schema definition, a named-relation catalog, and one or more validation rulesets.
Consumers load bundles at startup: a schema-defining package creates tables (via a consumer-provided DDL applier), a ruleset-only package layers rules on top of an already-present schema. The Requires block declares what the bundle needs at load time; consumers verify against the live database before activating rules.
Fields unknown to the loader are silently ignored, so a bundle can carry application-specific metadata that gsvalidator doesn't reason about.
func (*SchemaPackage) GetTableByName ¶
func (sp *SchemaPackage) GetTableByName(name string) *PackageTable
GetTableByName finds a table by name within the package. Returns nil when no table matches.
func (*SchemaPackage) Validate ¶
func (sp *SchemaPackage) Validate() error
Validate checks that the package has the minimum fields to be usable: name and version. Deeper checks (rule integrity, relation resolvability, requires satisfaction) happen at load time against a live database.
type Severity ¶
type Severity string
Severity classifies a violation for display and filtering. It is a property of the emitted result. A hard-enforcement rule always emits SeverityError when it fails; a soft-enforcement rule may emit warn, info, or debug.
func DefaultSeverityFor ¶
func DefaultSeverityFor(e Enforcement) Severity
DefaultSeverityFor returns the severity a violation of the given enforcement produces when the rule does not specify one explicitly.
func ParseSeverity ¶
ParseSeverity normalises a string to a Severity value. Empty or unknown input falls back to warn.
type SystemContext ¶
SystemContext provides system-level context for validation.
func NewSystemContext ¶
func NewSystemContext() SystemContext
NewSystemContext creates a new system context with current date/time.
type Table ¶
type Table struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Description string `json:"description,omitempty"`
IsSystem bool `json:"is_system,omitempty"`
IsLookup bool `json:"is_lookup,omitempty"`
Fields []Field `json:"fields"`
}
Table is a table definition in a SchemaPackage. Enough shape for gsvalidator to reason about a schema (name, columns, primary key) without carrying the app-specific metadata (timestamps, UI display hints, standards-body tags) that consumer applications track internally. JSON tags match the shipping bundle format; unknown fields on incoming JSON are ignored.
type Trigger ¶
type Trigger string
Trigger describes when a rule should be evaluated. Individual consumer applications decide how to honor each trigger — gsvalidator itself does not schedule re-evaluations; it just publishes the intent so callers can wire it in.
const ( // TriggerOnWrite (default) — evaluate immediately after any write // that touches a matching record. Same behavior existing consumers // already implemented; keeping it unnamed by leaving the field // empty is also treated as OnWrite for back-compat. TriggerOnWrite Trigger = "on_write" // TriggerTimeBased — re-evaluate periodically regardless of writes. // Suits rules whose truth changes with the passage of time (stale // data, freshness reminders, expiring references). RecheckDays on // the rule controls the cadence; a value of 0 falls back to a // consumer-defined default. TriggerTimeBased Trigger = "time_based" )
type ValidationContext ¶
type ValidationContext struct {
Ctx context.Context
DB *sql.DB
Record map[string]interface{}
TableName string
RecordID string
SystemContext SystemContext
RelatedRecords map[string][]map[string]interface{} // Cache of related records
CodeContext map[string]interface{} // Free-form per-record context supplied by the consumer
// RelationResolver resolves named relations from the bundle's
// Relations catalog. Populated by the caller (ValidateRecordUseCase
// sets it from an application-supplied resolver). Nil is
// acceptable for records / rules that don't use relations;
// relation-scoped conditions or validators error cleanly when
// this is nil.
RelationResolver ConditionResolver
}
ValidationContext provides access to the database and related records during validation. This is passed to validators to give them necessary context for complex validations.
func (*ValidationContext) GetFieldString ¶
func (v *ValidationContext) GetFieldString(fieldName string) (string, bool)
GetFieldString safely retrieves a field value as string from the record.
func (*ValidationContext) GetFieldValue ¶
func (v *ValidationContext) GetFieldValue(fieldName string) (interface{}, bool)
GetFieldValue safely retrieves a field value from the record.
func (*ValidationContext) GetRelatedRecords ¶
func (v *ValidationContext) GetRelatedRecords(foreignKey string) ([]map[string]interface{}, bool)
GetRelatedRecords retrieves cached related records for a given foreign key.
func (*ValidationContext) SetRelatedRecords ¶
func (v *ValidationContext) SetRelatedRecords(foreignKey string, records []map[string]interface{})
SetRelatedRecords caches related records for a given foreign key.