Documentation
¶
Overview ¶
Package validation defines Arc findings, typed validator invocation and command severity policy, explicit graph registries and portable model annotations. Runtime method discovery and reflective invocation are not provided.
Index ¶
- Constants
- Variables
- func Register[T any](r *Registry, validator Validator[T]) error
- func RegisterConcept[T any](r *Registry, validator Validator[T]) error
- func RegisterScoped[T any](r *Registry, factory Factory[Validator[T]], keys ...di.Key) error
- func RegisterScopedConcept[T any](r *Registry, factory Factory[Validator[T]], keys ...di.Key) error
- func Reject(results ...Result) error
- type Factory
- type Failure
- type Graph
- type InvocationError
- type ModelValidator
- type Policy
- type Portable
- type Reason
- type Registry
- type Result
- type RuleDescriptor
- type Severity
- type SeverityOptions
- type Tags
- type Validator
- type ValidatorFunc
Examples ¶
Constants ¶
const AllowedSeverityHeader = "X-Allowed-Severity"
AllowedSeverityHeader is the command ingress allowance header.
Variables ¶
var ( // ErrRejected identifies an application rejection. ErrRejected = errors.New("validation rejected") // ErrValidatorFailed identifies an unexpected validator failure. ErrValidatorFailed = errors.New("validator failed") // ErrInvalidSeverity identifies unsupported numeric severities. ErrInvalidSeverity = errors.New("invalid validation severity") // ErrInvalidValidator identifies a nil callback or context. ErrInvalidValidator = errors.New("invalid validator") )
var ( // ErrDuplicate identifies multiple graph validators for one exact type. ErrDuplicate = errors.New("duplicate graph validator") // ErrFrozen identifies changes after a successful registry Build. ErrFrozen = errors.New("validation registry frozen") // ErrInvalidRegistration identifies invalid validator model/factory metadata. ErrInvalidRegistration = errors.New("invalid validation registration") // ErrGraphDepth identifies a graph exceeding the framework traversal budget. ErrGraphDepth = errors.New("validation graph depth exceeded") )
Functions ¶
func RegisterConcept ¶
RegisterConcept registers T as an opaque concept leaf. Findings attach to the owning member, not to a concept's private representation.
func RegisterScoped ¶
RegisterScoped registers a lazy validator factory. Optional exact dependency keys are copied and checked by pipeline Build without activating the factory. Omit keys for manually wired factories; invalid keys fail registration.
func RegisterScopedConcept ¶
RegisterScopedConcept registers a lazy concept validator factory with the same optional dependency manifest contract as RegisterScoped.
Types ¶
type Factory ¶
Factory creates a borrowed validator during validation, never during Build. The non-closing scope expires on return from this node's validation callback.
type Failure ¶
Failure is a business rejection or redacted invocation failure. Findings are returned as independent copies; their application State remains borrowed.
type Graph ¶
type Graph struct {
// contains filtered or unexported fields
}
Graph holds immutable registrations and is concurrent-safe when its validators are. Zero (and nil) enables model methods and tags without registered rules. Maps are opaque: register an explicit validator to inspect their entries.
func (*Graph) CheckDependencies ¶
CheckDependencies validates every registered scoped validator's declared keys against a borrowed catalog, without resolving dependencies or running rules. All registrations are checked because runtime interfaces may expose any model. Nil/zero graphs and graphs without keys need no catalog. A missing or typed-nil catalog fails when keys exist. Hidden closure dependencies cannot be checked.
func (*Graph) CheckType ¶
CheckType validates statically visible graph/tag shapes without invoking validators. Pipeline Build can use it for input types without registry rules. Interface-held runtime types remain subject to validation-time checks.
func (*Graph) Validate ¶
func (g *Graph) Validate(ctx context.Context, scope *execution.Scope, model any) (results []Result, err error)
Validate runs each nonnull node's exact registry validator, then its model method, before descending readable fields/collection elements. Pointer identity stops cycles/shared instances; equal distinct instances still run. Collections keep the owning path without indices. Concept registrations are opaque leaves. Root required tags run separately after the graph. Results/error semantics match Invoke: Failure findings remain on the returned error, not duplicated in results. Factory failures are infrastructure errors, never validator failures. Scope is optional for direct validators; scoped factories require a valid scope.
Example ¶
package main
import (
"context"
"fmt"
"github.com/cratis/arc.go/validation"
)
func main() {
type quantity int
var registry validation.Registry
err := validation.RegisterConcept(®istry, validation.ValidatorFunc[quantity](func(_ context.Context, value quantity) ([]validation.Result, error) {
if value > 0 {
return nil, nil
}
return []validation.Result{{Severity: validation.Error, Message: "Quantity must be positive.", Members: []string{"value"}}}, nil
}))
if err != nil {
panic(err)
}
graph, err := registry.Build()
if err != nil {
panic(err)
}
findings, err := graph.Validate(context.Background(), nil, struct {
Quantity quantity `json:"quantity"`
}{})
if err != nil {
panic(err)
}
fmt.Println(findings[0].Members, findings[0].Message)
}
Output: [quantity] Quantity must be positive.
type InvocationError ¶
type InvocationError struct {
// Cause is an inspectable unexpected callback error.
Cause error
// Panic is the recovered panic value, for local diagnostics only.
Panic any
}
InvocationError exposes local diagnostics, never callback details in its text. It implements Failure with one safe Error finding.
func (*InvocationError) Error ¶
func (e *InvocationError) Error() string
Error returns generic, client-safe text.
func (*InvocationError) Unwrap ¶
func (e *InvocationError) Unwrap() []error
Unwrap preserves the failure category and original cause.
func (*InvocationError) ValidationResults ¶
func (e *InvocationError) ValidationResults() []Result
ValidationResults returns a fresh, redacted finding with no State or members.
type ModelValidator ¶
ModelValidator is the optional model-bound Validate convention. Go method sets apply exactly: value input never synthesizes a pointer receiver.
type Policy ¶
type Policy struct {
// contains filtered or unexported fields
}
Policy is immutable and concurrent-safe. Zero retains only Error findings.
func NewPolicy ¶
func NewPolicy(options SeverityOptions) (Policy, error)
NewPolicy copies configuration, rejecting severities outside 0–3. An explicit floor cannot be weakened by Allowed, even a trusted Allowed=Error.
type Portable ¶
type Portable[T any] struct { // contains filtered or unexported fields }
Portable is an immutable concurrently callable validator built from field tags. Handwritten validators and concept inference remain separate server contracts.
func NewPortable ¶
NewPortable compiles the explicitly declared rules tags on T's wire fields. It does not run constructors, application validators or codecs. Nullable values skip non-presence rules, matching the pinned JavaScript rules.
type Reason ¶
type Reason string
Reason is an open wire string; unknown future reasons remain valid.
const ( // Rule identifies application-authored validation. Rule Reason = "rule" // ConcurrencyViolation identifies a concurrency rejection. ConcurrencyViolation Reason = "concurrencyViolation" // ConstraintViolation identifies an event-store constraint rejection. ConstraintViolation Reason = "constraintViolation" // ValidatorFailed identifies a validator that could not run. ValidatorFailed Reason = "validatorFailed" DependencyUnavailable Reason = "dependencyUnavailable" // MalformedRequest identifies invalid client input. MalformedRequest Reason = "malformedRequest" )
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a single-owner builder. Zero is usable. One validator (ordinary or concept) is allowed per exact type. Build freezes only after successful checks.
type Result ¶
type Result struct {
// Severity is the numeric importance of the finding.
Severity Severity `json:"severity"`
// Message is safe, client-visible feedback (never exception-redacted).
Message string `json:"message"`
// Members identifies affected wire members; nil means no member.
Members []string `json:"members"`
// State is optional application-owned data. Typed nil values are omitted.
State any `json:"state,omitempty"`
// Reason is the machine-readable open category; empty means Rule.
Reason Reason `json:"reason"`
// ReasonDetail optionally identifies a specific constraint or other cause.
ReasonDetail *string `json:"reasonDetail,omitempty"`
}
Result is one validation finding. Members use wire names, including indexed paths. State is borrowed application data; callers must not mutate it during encoding. A zero Reason marshals as "rule". Nil Members marshals as [], not null.
func Invoke ¶
func Invoke[T any](ctx context.Context, validator Validator[T], value T) (results []Result, err error)
Invoke copies findings (but borrows State), preserves wrapped Failure errors, and redacts unexpected errors/panics. Cancellation is not validation failure.
Example ¶
package main
import (
"context"
"fmt"
"github.com/cratis/arc.go/validation"
)
func main() {
validator := validation.ValidatorFunc[int](func(_ context.Context, value int) ([]validation.Result, error) {
if value < 0 {
return []validation.Result{{Severity: validation.Error, Message: "Must be positive."}}, nil
}
return nil, nil
})
findings, err := validation.Invoke(context.Background(), validator, -1)
fmt.Println(len(findings), err)
}
Output: 1 <nil>
func ValidateTags ¶
ValidateTags runs only the root's portable required annotations. It neither invokes model validators nor descends nested models. Nil roots are skipped.
func (Result) Clone ¶
Clone copies a finding's members and reason detail. Application State is borrowed.
func (Result) MarshalJSON ¶
MarshalJSON implements the Arc validation envelope.
type RuleDescriptor ¶
type RuleDescriptor struct {
Property string `json:"property,omitempty"`
Name string `json:"name"`
Arguments []json.RawMessage `json:"arguments,omitempty"`
Message string `json:"message,omitempty"`
Severity *Severity `json:"severity,omitempty"`
Source string `json:"source,omitempty"`
ServerOnly bool `json:"serverOnly,omitempty"`
Optional bool `json:"optional,omitempty"`
Concept bool `json:"concept,omitempty"`
}
RuleDescriptor is the portable, serializable server/client rule contract. Arguments are JSON string/number scalars, validated against Name before use. Nil Severity means Error. ServerOnly rules must supply their own server validator; neither the portable executor nor generator silently skips them.
func ParseRules ¶
func ParseRules(text, property, kind string) ([]RuleDescriptor, error)
ParseRules reads the rules field tag's JSON array without executing validators. kind is string, number, boolean, collection or object. Paths are wire names. Explicit messages are required so both projections preserve server messages. Unsupported rules/regex/severity are construction errors, not weaker checks.
type Severity ¶
type Severity int32
Severity is Arc's numeric wire severity. Every retained finding makes a result invalid, regardless of severity; filtering belongs to the later pipeline.
func AllowedFromHeader ¶
AllowedFromHeader accepts exactly one trimmed signed decimal int32 in 0–3. Untrusted Error allowance is capped at Warning. Invalid input means default policy, not permission to ignore all findings.
type SeverityOptions ¶
type SeverityOptions struct {
// Allowed retains only severities strictly greater than this value.
Allowed *Severity
// BlockOn is inclusive and always blocks Unknown when present.
BlockOn *Severity
}
SeverityOptions combines a caller allowance with an inclusive command floor.
type Tags ¶
type Tags struct {
// Required rejects nil or empty/whitespace strings in the top-level model.
// Numeric zero, false and nonnil empty collections are supplied values.
Required bool
// SkipConcept suppresses only the immediate child's registered concept rule.
SkipConcept bool
}
Tags is the portable validation annotation subset, separate from graph rules.
type Validator ¶
Validator validates caller-owned input synchronously. Implementations must honor cancellation and support concurrent calls if shared across operations.
type ValidatorFunc ¶
ValidatorFunc adapts a validation callback.