instance

package
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package instance provides instance validation for yammm schemas.

This package transforms raw, unvalidated instance data into typed, validated ValidInstance objects suitable for graph ingestion. It is the primary entry point for validating data against compiled schemas.

Overview

The validation pipeline takes RawInstance objects and produces either ValidInstance (on success) or ValidationFailure (on error). The Validator orchestrates this process using a compiled schema.Schema.

validator := instance.NewValidator(compiledSchema)
valid, failures, err := validator.Validate(ctx, "Person", raws)

Instance Types

RawInstance represents unvalidated input data with optional provenance information for error reporting. ValidInstance is the immutable output containing typed properties, validated edges, and the extracted primary key.

Validation Semantics

The validator performs:

  • Type resolution (qualified and unqualified type names)
  • Property type validation against schema constraints
  • Required property enforcement
  • Primary key extraction and validation
  • Edge object validation (associations and compositions)
  • Invariant expression evaluation

Thread Safety

Validator is stateless and safe for concurrent use. Multiple goroutines may call Validator.Validate simultaneously with different inputs.

Subpackages

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrTypeNotFound indicates the type name was not found in the schema.
	ErrTypeNotFound = diag.E_INSTANCE_TYPE_NOT_FOUND

	// ErrAbstractType indicates an attempt to instantiate an abstract type.
	ErrAbstractType = diag.E_ABSTRACT_TYPE

	// ErrPartTypeDirect indicates a part type was instantiated outside composition.
	ErrPartTypeDirect = diag.E_PART_TYPE_DIRECT

	// ErrMissingRequired indicates a required property was absent.
	ErrMissingRequired = diag.E_MISSING_REQUIRED

	// ErrUnknownField indicates an undeclared property was present.
	ErrUnknownField = diag.E_UNKNOWN_FIELD

	// ErrTypeMismatch indicates a property value failed type validation.
	ErrTypeMismatch = diag.E_TYPE_MISMATCH

	// ErrConstraintFail indicates a property constraint was not satisfied.
	ErrConstraintFail = diag.E_CONSTRAINT_FAIL

	// ErrInvariantFail indicates a type invariant expression failed.
	ErrInvariantFail = diag.E_INVARIANT_FAIL

	// ErrMissingPrimaryKey indicates a required primary key property was absent.
	ErrMissingPrimaryKey = diag.E_MISSING_PRIMARY_KEY

	// ErrEdgeShapeMismatch indicates an edge object has an invalid shape.
	ErrEdgeShapeMismatch = diag.E_EDGE_SHAPE_MISMATCH

	// ErrMissingFKTarget indicates a _target_* field is missing from an edge.
	ErrMissingFKTarget = diag.E_MISSING_FK_TARGET

	// ErrPartialCompositeFK indicates an incomplete composite foreign key.
	ErrPartialCompositeFK = diag.E_PARTIAL_COMPOSITE_FK

	// ErrUnknownEdgeField indicates an unknown field in an edge object.
	ErrUnknownEdgeField = diag.E_UNKNOWN_EDGE_FIELD

	// ErrUnresolvedRequiredComposition indicates a required composition is absent/empty.
	ErrUnresolvedRequiredComposition = diag.E_UNRESOLVED_REQUIRED_COMPOSITION

	// ErrCompositionNotFound indicates a composition relation was not found on the parent type.
	ErrCompositionNotFound = diag.E_COMPOSITION_NOT_FOUND

	// ErrDuplicateComposedPK indicates duplicate primary keys in composed children.
	ErrDuplicateComposedPK = diag.E_DUPLICATE_COMPOSED_PK

	// ErrEvalError indicates an error during expression evaluation.
	ErrEvalError = diag.E_EVAL_ERROR

	// ErrCaseFoldCollision indicates multiple input fields fold to the same schema property.
	// This occurs when non-strict mode is enabled and the input contains multiple
	// field names that differ only in case (e.g., "Name" and "name").
	ErrCaseFoldCollision = diag.E_CASE_FOLD_COLLISION
)

Error codes for validation failures. These are aliases to the canonical codes in the diag package.

View Source
var (
	// ErrInternalFailure is the parent sentinel for all internal failures.
	// Use errors.Is(err, ErrInternalFailure) to detect any internal error.
	ErrInternalFailure = errors.New("internal validation failure")

	// ErrNilValidator is returned when a Validate method is called on a nil receiver.
	ErrNilValidator = fmt.Errorf("%w: nil validator receiver", ErrInternalFailure)

	// ErrCorruptedSchema is returned when the schema is in an invalid state.
	ErrCorruptedSchema = fmt.Errorf("%w: corrupted schema state", ErrInternalFailure)
)

Internal error sentinels for programmatic detection via errors.Is(). These represent system-level failures, not validation failures.

Functions

This section is empty.

Types

type EdgeBuilder

type EdgeBuilder struct {
	// contains filtered or unexported fields
}

EdgeBuilder constructs edge objects for associations. Use NewEdge() for building edges to pass to InstanceBuilder.Edges(), or use InstanceBuilder.Edge() which returns an EdgeBuilder for inline chaining.

func NewEdge

func NewEdge() *EdgeBuilder

NewEdge creates a standalone EdgeBuilder for use with InstanceBuilder.Edges().

func (*EdgeBuilder) Build

func (e *EdgeBuilder) Build() map[string]any

Build returns the edge object as a map. This copies the internal data to ensure immutability.

func (*EdgeBuilder) Done

func (e *EdgeBuilder) Done() *InstanceBuilder

Done returns to the parent InstanceBuilder (for inline chaining). Panics if called on a standalone EdgeBuilder (created via NewEdge()).

func (*EdgeBuilder) Prop

func (e *EdgeBuilder) Prop(name string, value any) *EdgeBuilder

Prop sets an edge property value.

func (*EdgeBuilder) Target

func (e *EdgeBuilder) Target(id any) *EdgeBuilder

Target sets the _target_id field for simple (single-field) primary keys.

func (*EdgeBuilder) TargetField

func (e *EdgeBuilder) TargetField(fieldName string, value any) *EdgeBuilder

TargetField sets a specific _target_<fieldName> for composite primary keys. Call multiple times for each PK component. The fieldName must match the schema PK property name exactly (case-sensitive).

type InstanceBuilder

type InstanceBuilder struct {
	// contains filtered or unexported fields
}

InstanceBuilder constructs RawInstance values for testing and programmatic use. It provides a fluent API for building property maps with correct edge object and composition structure.

InstanceBuilder is NOT thread-safe; use separate builders per goroutine. The builder can be reused after Build(), but modifications will not affect previously built instances (properties are copied on build).

func NewInstance

func NewInstance() *InstanceBuilder

NewInstance starts building a RawInstance.

func (*InstanceBuilder) Build

func (b *InstanceBuilder) Build() RawInstance

Build returns the constructed RawInstance. The builder can be reused after Build() but modifications will not affect previously built instances (the properties map is copied).

func (*InstanceBuilder) Composed

func (b *InstanceBuilder) Composed(relationName string, child *InstanceBuilder) *InstanceBuilder

Composed embeds a single composed child instance for a one/optional-one composition.

func (*InstanceBuilder) ComposedMany

func (b *InstanceBuilder) ComposedMany(relationName string, children ...*InstanceBuilder) *InstanceBuilder

ComposedMany embeds multiple composed children for a many-composition.

func (*InstanceBuilder) Edge

func (b *InstanceBuilder) Edge(relationName string) *EdgeBuilder

Edge starts building an edge object for a one/optional-one association. Returns an EdgeBuilder for fluent configuration; call Done() to return to this builder.

func (*InstanceBuilder) Edges

func (b *InstanceBuilder) Edges(relationName string, edges ...*EdgeBuilder) *InstanceBuilder

Edges sets a many-association as an array of edge objects. Each EdgeBuilder should be built via NewEdge() rather than InstanceBuilder.Edge().

func (*InstanceBuilder) Prop

func (b *InstanceBuilder) Prop(name string, value any) *InstanceBuilder

Prop sets a property value.

func (*InstanceBuilder) WithFullProvenance

func (b *InstanceBuilder) WithFullProvenance(p *Provenance) *InstanceBuilder

WithFullProvenance sets complete provenance including span information.

func (*InstanceBuilder) WithProvenance

func (b *InstanceBuilder) WithProvenance(sourceName, pathStr string) *InstanceBuilder

WithProvenance sets source location metadata for diagnostics. The path follows instance/path canonical syntax (e.g., "$.Person[0]"). If the path cannot be parsed, it falls back to the root path.

type InternalError

type InternalError struct {
	Kind  InternalErrorKind
	Cause error
	Stack string // Stack trace from panic recovery, empty otherwise
}

InternalError wraps internal failures with context for debugging. Use errors.AsType[*InternalError](err) to extract debugging context.

func (*InternalError) Error

func (e *InternalError) Error() string

func (*InternalError) Is

func (e *InternalError) Is(target error) bool

Is reports whether the error matches target. InternalError always matches ErrInternalFailure, enabling errors.Is(err, ErrInternalFailure) to work for all internal errors including panic-derived ones.

func (*InternalError) Unwrap

func (e *InternalError) Unwrap() error

type InternalErrorKind

type InternalErrorKind int

InternalErrorKind classifies internal errors for programmatic handling.

const (
	// KindNilValidator indicates a nil validator receiver.
	KindNilValidator InternalErrorKind = iota
	// KindCorruptedSchema indicates schema invariants were violated.
	KindCorruptedSchema
	// KindInvariantPanic indicates a panic during invariant evaluation.
	KindInvariantPanic
	// KindConstraintPanic indicates a panic during constraint checking.
	KindConstraintPanic
)

func (InternalErrorKind) String

func (k InternalErrorKind) String() string

String returns a human-readable name for the error kind.

type Provenance

type Provenance struct {
	// contains filtered or unexported fields
}

Provenance captures source location metadata for error reporting.

Provenance links validation errors back to their source location in the input document. This enables helpful error messages that point to the exact position of invalid data.

Nil Receiver Behavior

All Provenance methods are safe to call on nil receivers. Navigation methods (WithPath, AtKey, AtIndex) convert nil to a new Provenance with the specified path but empty source information:

var prov *Provenance = nil
derived := prov.AtKey("foo").AtIndex(0)  // Safe, returns valid Provenance

After any navigation operation on nil, sourceName and span will be zero values. This is intentional: it preserves path navigation for diagnostics while indicating no source location is available. Accessor methods (SourceName, Path, Span) return zero values when called on nil.

func NewProvenance

func NewProvenance(sourceName string, path path.Builder, span location.Span) *Provenance

NewProvenance creates a new Provenance with the given source information.

func (*Provenance) AtIndex

func (p *Provenance) AtIndex(index int) *Provenance

AtIndex returns a new Provenance with the path extended by an index.

func (*Provenance) AtKey

func (p *Provenance) AtKey(key string) *Provenance

AtKey returns a new Provenance with the path extended by a key.

func (*Provenance) Path

func (p *Provenance) Path() path.Builder

Path returns the JSONPath-like path to this instance within the source.

func (*Provenance) SourceName

func (p *Provenance) SourceName() string

SourceName returns the name of the source (e.g., filename).

func (*Provenance) Span

func (p *Provenance) Span() location.Span

Span returns the source location span.

func (*Provenance) WithPath

func (p *Provenance) WithPath(newPath path.Builder) *Provenance

WithPath returns a new Provenance with a different path.

type RawInstance

type RawInstance struct {
	// Properties contains the raw property values keyed by property name.
	// Property names may use any casing; the validator normalizes them.
	Properties map[string]any

	// Provenance optionally captures source location metadata.
	// If nil, error messages will not include source locations.
	Provenance *Provenance
}

RawInstance represents unvalidated instance data.

RawInstance is the input to the validation pipeline. It contains the raw property values from the input source (typically JSON) and optional provenance information for error reporting.

type ValidEdgeData

type ValidEdgeData struct {
	// contains filtered or unexported fields
}

ValidEdgeData represents validated association edge data.

ValidEdgeData contains the targets for an association relation, each with its foreign key and optional edge properties.

func NewValidEdgeData

func NewValidEdgeData(targets []ValidEdgeTarget) *ValidEdgeData

NewValidEdgeData creates a new ValidEdgeData.

func (*ValidEdgeData) IsEmpty

func (e *ValidEdgeData) IsEmpty() bool

IsEmpty returns true if there are no edge targets.

func (*ValidEdgeData) TargetCount

func (e *ValidEdgeData) TargetCount() int

TargetCount returns the number of edge targets.

func (*ValidEdgeData) Targets

func (e *ValidEdgeData) Targets() []ValidEdgeTarget

Targets returns a defensive copy of all edge targets. Callers may modify the returned slice without affecting the original data.

func (*ValidEdgeData) TargetsIter

func (e *ValidEdgeData) TargetsIter() iter.Seq[ValidEdgeTarget]

TargetsIter returns an iterator over edge targets for efficient read-only traversal. Unlike Targets(), this does not allocate a new slice.

Example:

for target := range edge.TargetsIter() {
    fmt.Println(target.Key())
}

type ValidEdgeTarget

type ValidEdgeTarget struct {
	// contains filtered or unexported fields
}

ValidEdgeTarget represents a single edge target.

ValidEdgeTarget contains the foreign key referencing the target instance and any validated edge properties.

func NewValidEdgeTarget

func NewValidEdgeTarget(targetKey immutable.Key, props immutable.Properties) ValidEdgeTarget

NewValidEdgeTarget creates a new ValidEdgeTarget.

func (*ValidEdgeTarget) HasProperties

func (t *ValidEdgeTarget) HasProperties() bool

HasProperties reports whether this edge target has any properties.

func (*ValidEdgeTarget) Properties

func (t *ValidEdgeTarget) Properties() immutable.Properties

Properties returns the edge properties.

func (*ValidEdgeTarget) Property

func (t *ValidEdgeTarget) Property(name string) (immutable.Value, bool)

Property returns a single edge property by name.

func (*ValidEdgeTarget) TargetKey

func (t *ValidEdgeTarget) TargetKey() immutable.Key

TargetKey returns the foreign key referencing the target instance.

type ValidInstance

type ValidInstance struct {
	// contains filtered or unexported fields
}

ValidInstance is the immutable output of successful validation.

ValidInstance contains the validated properties, extracted primary key, and validated edge data. All values are immutable and safe to share.

func NewValidInstance

func NewValidInstance(
	typeName string,
	typeID schema.TypeID,
	pk immutable.Key,
	props immutable.Properties,
	edges map[string]*ValidEdgeData,
	composed map[string]immutable.Value,
	provenance *Provenance,
) *ValidInstance

NewValidInstance creates a new ValidInstance. This is an internal constructor; use Validator to create instances.

func (*ValidInstance) Composed

func (v *ValidInstance) Composed(relationName string) (immutable.Value, bool)

Composed returns the validated composed children for a composition relation. Returns (zero, false) if the relation has no composed children.

func (*ValidInstance) Compositions

func (v *ValidInstance) Compositions() iter.Seq2[string, immutable.Value]

Compositions returns an iterator over all compositions. The iteration order is not guaranteed to be deterministic.

func (*ValidInstance) Edge

func (v *ValidInstance) Edge(relationName string) (*ValidEdgeData, bool)

Edge returns the validated edge data for a relation. Returns (nil, false) if the relation has no edges.

func (*ValidInstance) Edges

func (v *ValidInstance) Edges() iter.Seq2[string, *ValidEdgeData]

Edges returns an iterator over all edges. The iteration order is not guaranteed to be deterministic.

func (*ValidInstance) HasProvenance

func (v *ValidInstance) HasProvenance() bool

HasProvenance reports whether provenance is available.

func (*ValidInstance) PrimaryKey

func (v *ValidInstance) PrimaryKey() immutable.Key

PrimaryKey returns the extracted primary key.

func (*ValidInstance) Properties

func (v *ValidInstance) Properties() immutable.Properties

Properties returns all validated properties.

func (*ValidInstance) Property

func (v *ValidInstance) Property(name string) (immutable.Value, bool)

Property returns the value of a property by name. Returns (zero, false) if the property is not set.

func (*ValidInstance) Provenance

func (v *ValidInstance) Provenance() *Provenance

Provenance returns the source location metadata. Returns nil if no provenance was provided.

func (*ValidInstance) TypeID

func (v *ValidInstance) TypeID() schema.TypeID

TypeID returns the schema type ID.

func (*ValidInstance) TypeName

func (v *ValidInstance) TypeName() string

TypeName returns the name of the validated type.

type ValidationError

type ValidationError struct {
	Code    diag.Code
	Message string
}

ValidationError represents a system-level validation error.

func (*ValidationError) Error

func (e *ValidationError) Error() string

type ValidationFailure

type ValidationFailure struct {
	// Raw is the raw instance that failed validation.
	Raw RawInstance

	// Result contains the diagnostic issues explaining the failure.
	Result diag.Result
}

ValidationFailure represents a data validation failure.

ValidationFailure pairs the raw instance that failed validation with the diagnostic result explaining why validation failed. Multiple issues may be collected in a single failure.

func NewValidationFailure

func NewValidationFailure(raw RawInstance, result diag.Result) ValidationFailure

NewValidationFailure creates a new ValidationFailure.

func (ValidationFailure) Error

func (f ValidationFailure) Error() string

Error returns the first error message, or empty string if none.

func (ValidationFailure) HasErrors

func (f ValidationFailure) HasErrors() bool

HasErrors returns true if there are any error-level issues.

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator validates raw instances against a schema.

Validator is stateless and safe for concurrent use. Multiple goroutines may call Validate simultaneously with different inputs.

All validation methods (Validate, ValidateOne, ValidateForComposition) require a non-nil context and will panic if passed nil. Use context.Background() for non-cancellable operations.

func NewValidator

func NewValidator(s *schema.Schema, opts ...ValidatorOption) *Validator

NewValidator creates a new Validator for the given schema. Panics if schema is nil.

func (*Validator) Validate

func (v *Validator) Validate(ctx context.Context, typeName string, raws []RawInstance) ([]*ValidInstance, []ValidationFailure, error)

Validate validates a batch of raw instances of the given type.

Returns:

  • valid: Successfully validated instances
  • failures: Instances that failed validation with diagnostics
  • err: System error (not validation errors)

The triple-return semantics allow callers to:

  • Process successful instances immediately
  • Report failures with detailed diagnostics
  • Handle system errors separately from validation errors
Example
package main

import (
	"context"
	"fmt"

	"github.com/simon-lentz/yammm/instance"
	"github.com/simon-lentz/yammm/location"
	"github.com/simon-lentz/yammm/schema"
	"github.com/simon-lentz/yammm/schema/build"
)

func main() {
	// Build schema
	s, _ := build.NewBuilder().
		WithName("example").
		WithSourceID(location.MustNewSourceID("test://example.yammm")).
		AddType("Product").
		WithPrimaryKey("sku", schema.StringConstraint{}).
		WithProperty("name", schema.StringConstraint{}).
		Done().
		Build()

	validator := instance.NewValidator(s)
	ctx := context.Background()

	// Validate multiple instances at once
	raws := []instance.RawInstance{
		{Properties: map[string]any{"sku": "PROD-001", "name": "Widget"}},
		{Properties: map[string]any{"sku": "PROD-002", "name": "Gadget"}},
		{Properties: map[string]any{"sku": "PROD-003", "name": "Sprocket"}},
	}

	valid, failures, err := validator.Validate(ctx, "Product", raws)
	if err != nil {
		fmt.Println("Internal error:", err)
		return
	}

	fmt.Printf("Valid: %d, Failures: %d\n", len(valid), len(failures))

}
Output:
Valid: 3, Failures: 0

func (*Validator) ValidateForComposition

func (v *Validator) ValidateForComposition(ctx context.Context, parentType, relationName string, raws []RawInstance) ([]*ValidInstance, []ValidationFailure, error)

ValidateForComposition validates instances as composed children.

This is used when validating part types within a composition context. Unlike direct validation, part types are allowed here.

func (*Validator) ValidateOne

func (v *Validator) ValidateOne(ctx context.Context, typeName string, raw RawInstance) (*ValidInstance, *ValidationFailure, error)

ValidateOne validates a single raw instance.

Returns exactly one of:

  • (valid, nil, nil) on success
  • (nil, failure, nil) on validation failure
  • (nil, nil, err) on system error
Example
package main

import (
	"context"
	"fmt"

	"github.com/simon-lentz/yammm/instance"
	"github.com/simon-lentz/yammm/location"
	"github.com/simon-lentz/yammm/schema"
	"github.com/simon-lentz/yammm/schema/build"
)

func main() {
	// Build a simple schema
	s, result := build.NewBuilder().
		WithName("example").
		WithSourceID(location.MustNewSourceID("test://example.yammm")).
		AddType("Person").
		WithPrimaryKey("id", schema.StringConstraint{}).
		WithProperty("name", schema.StringConstraint{}).
		WithOptionalProperty("age", schema.IntegerConstraint{}).
		Done().
		Build()

	if result.HasErrors() {
		fmt.Println("Schema build failed")
		return
	}

	// Create a validator
	validator := instance.NewValidator(s)
	ctx := context.Background()

	// Validate a single instance
	raw := instance.RawInstance{
		Properties: map[string]any{
			"id":   "alice",
			"name": "Alice Smith",
			"age":  int64(30),
		},
	}

	valid, failure, err := validator.ValidateOne(ctx, "Person", raw)
	if err != nil {
		fmt.Println("Internal error:", err)
		return
	}
	if failure != nil {
		fmt.Println("Validation failed")
		return
	}

	fmt.Println("Type:", valid.TypeName())
	fmt.Println("Key:", valid.PrimaryKey().String())

}
Output:
Type: Person
Key: ["alice"]

type ValidatorOption

type ValidatorOption func(*validatorConfig)

ValidatorOption configures the Validator.

func RecommendedValidatorOptions

func RecommendedValidatorOptions() []ValidatorOption

RecommendedValidatorOptions returns the recommended default options for new projects. These options prioritize correctness and early error detection over permissiveness.

Includes:

  • WithStrictPropertyNames(true): Require exact case matching for property names

Use this as a starting point and relax specific options as needed for your use case.

func WithAllowUnknownFields

func WithAllowUnknownFields(allow bool) ValidatorOption

WithAllowUnknownFields controls handling of unknown fields.

When true, unknown fields in the input are silently ignored. When false (default), unknown fields produce a diagnostic error.

func WithLogger

func WithLogger(logger *slog.Logger) ValidatorOption

WithLogger sets the logger for debug output during validation. If not set, no logging is performed.

func WithMaxIssuesPerInstance

func WithMaxIssuesPerInstance(max int) ValidatorOption

WithMaxIssuesPerInstance sets the maximum number of issues to collect per instance before stopping validation of that instance. Default is 100.

func WithStrictPropertyNames

func WithStrictPropertyNames(strict bool) ValidatorOption

WithStrictPropertyNames controls property name matching.

When true, property names must match exactly (case-sensitive). When false (default), property names are matched case-insensitively.

func WithValueRegistry

func WithValueRegistry(reg value.Registry) ValidatorOption

WithValueRegistry sets a custom value registry for type classification. This enables recognition of custom Go types (e.g., `type MyInt int64`) during constraint checking.

Note: The registry affects type classification in CheckValue, allowing custom types to pass type checks. However, CoerceValue converts values to canonical Go types (int64, float64, etc.), so the original custom type information is not preserved in validated instances.

A zero-value Registry uses built-in type detection only.

Directories

Path Synopsis
Package eval provides expression evaluation for instance validation.
Package eval provides expression evaluation for instance validation.
Package path provides canonical JSONPath-like syntax for identifying positions within validated instance data.
Package path provides canonical JSONPath-like syntax for identifying positions within validated instance data.

Jump to

Keyboard shortcuts

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