resolver

package
v0.38.0 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

Resolver Package

The resolver package provides a powerful, dependency-aware configuration resolution system for scafctl. It enables declarative value resolution through providers, with support for type coercion, conditional execution, transformations, validation, and concurrent execution.

Table of Contents

Overview

The resolver package enables dynamic configuration resolution through a declarative YAML-based system. Resolvers can:

  • Fetch values from various sources (environment, HTTP, files, parameters)
  • Transform values through CEL expressions or Go templates
  • Validate values against rules (regex, CEL expressions)
  • Execute concurrently when dependencies allow
  • Handle errors gracefully with configurable behavior

Features

  • Dependency-based execution: Automatic DAG construction and phase-based execution
  • Type coercion: Automatic conversion between types (string, int, float, bool, array, time, duration)
  • Conditional execution: Skip resolvers based on runtime conditions
  • Concurrent execution: Resolvers without dependencies run in parallel
  • Value transformations: Transform values using CEL or Go templates
  • Validation: Validate values against regex patterns or CEL expressions
  • Sensitive values: Automatic redaction of sensitive data in logs and errors
  • Metrics collection: Execution timing, provider calls, and resource usage
  • Snapshot support: Capture execution state for debugging and testing

Installation

import "github.com/oakwood-commons/scafctl/pkg/resolver"

Quick Start

Basic Resolver Definition
name: my-solution
version: "1.0.0"
spec:
  resolvers:
    environment:
      type: string
      resolve:
        with:
          - provider: parameter
            inputs:
              key: env
          - provider: static
            inputs:
              value: development

    port:
      type: int
      resolve:
        with:
          - provider: static
            inputs:
              value: 8080

    server-url:
      type: string
      resolve:
        with:
          - provider: cel
            inputs:
              expression:
                expr: "'http://localhost:' + string(_.port)"
Programmatic Execution
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/oakwood-commons/scafctl/pkg/provider/builtin"
    "github.com/oakwood-commons/scafctl/pkg/resolver"
    "github.com/oakwood-commons/scafctl/pkg/solution"
)

func main() {
    ctx := context.Background()

    // Get the default provider registry
    registry, err := builtin.DefaultRegistry(ctx)
    if err != nil {
        panic(err)
    }

    // Create executor with options
    executor := resolver.NewExecutor(registry,
        resolver.WithDefaultTimeout(30*time.Second),
        resolver.WithPhaseTimeout(5*time.Minute),
        resolver.WithMaxValueSize(10*1024*1024), // 10MB
    )

    // Define resolvers
    resolvers := []*resolver.Resolver{
        {
            Name: "greeting",
            Type: resolver.TypeString,
            Resolve: &resolver.ResolvePhase{
                With: []resolver.ProviderSource{
                    {
                        Provider: "static",
                        Inputs: map[string]resolver.ValueRef{
                            "value": {Literal: "Hello, World!"},
                        },
                    },
                },
            },
        },
    }

    // Execute
    result, err := executor.Execute(ctx, resolvers, nil)
    if err != nil {
        panic(err)
    }

    // Access resolved values
    fmt.Println(result.Resolvers["greeting"].Value) // "Hello, World!"
}

Architecture

Resolver Structure

Each resolver has three optional phases that execute in order:

┌─────────────────────────────────────────────────────────┐
│                      Resolver                           │
├─────────────────────────────────────────────────────────┤
│  1. Resolve Phase (required)                            │
│     - Fetch initial value from providers                │
│     - Multiple sources tried until success              │
│                                                         │
│  2. Transform Phase (optional)                          │
│     - Apply transformations to the value                │
│     - CEL expressions or Go templates                   │
│     - Multiple steps executed sequentially              │
│                                                         │
│  3. Validate Phase (optional)                           │
│     - Validate the final value                          │
│     - All rules run (aggregated errors)                 │
│     - Regex patterns or CEL expressions                 │
└─────────────────────────────────────────────────────────┘
Execution Phases

Resolvers are automatically organized into execution phases based on dependencies:

Phase 1: [env, region]          ← No dependencies, run concurrently
         ↓
Phase 2: [port, config-url]     ← Depend on phase 1 resolvers, run concurrently
         ↓
Phase 3: [server-config]        ← Depends on phase 1 and 2 resolvers
Dependency Resolution

Dependencies are automatically extracted from:

  • CEL expressions: _.other_resolver references
  • Go templates: {{.other_resolver}} references (with default delimiters)
  • Resolver references: rslvr: other_resolver in ValueRef

Explicit Dependencies:

Use the dependsOn field when automatic extraction isn't possible (e.g., templates loaded from files):

name: formatted-output
dependsOn:
  - config
  - credentials
resolve:
  with:
    - provider: file
      inputs:
        path: "/path/to/template.tmpl"
transform:
  with:
    - provider: go-template
      inputs:
        template:
          rslvr: formatted-output

Explicit dependsOn dependencies are merged with auto-extracted dependencies.

Self-Reference Handling:

References to a resolver's own name (_.myResolver) in its transform or validate phase are automatically filtered from the dependency graph. These are semantically equivalent to __self and never create circular dependencies. Self-references in the resolve phase remain errors because a resolver cannot use its own value to bootstrap itself.

Provider-Specific Extraction:

Providers can implement custom dependency extraction via ExtractDependencies on their Descriptor. This is useful for providers with custom input formats:

  • cel provider: Extracts _.resolverName patterns from the expression input using CEL AST parsing
  • go-template provider: Extracts references from the template input, respecting custom leftDelim/rightDelim settings

See provider.Descriptor.ExtractDependencies for implementation details.

API Reference

Resolver Type
// Resolver represents a single resolver definition
type Resolver struct {
    Name        string         `json:"name" yaml:"name"`
    Description string         `json:"description,omitempty" yaml:"description,omitempty"`
    Sensitive   bool           `json:"sensitive,omitempty" yaml:"sensitive,omitempty"`
    Type        Type           `json:"type,omitempty" yaml:"type,omitempty"`
    When        *Condition     `json:"when,omitempty" yaml:"when,omitempty"`
    DependsOn   []string       `json:"dependsOn,omitempty" yaml:"dependsOn,omitempty"`  // Explicit dependencies
    Timeout     *time.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
    Resolve     *ResolvePhase  `json:"resolve" yaml:"resolve"`
    Transform   *TransformPhase `json:"transform,omitempty" yaml:"transform,omitempty"`
    Validate    *ValidatePhase  `json:"validate,omitempty" yaml:"validate,omitempty"`
}
Type System

Supported types with automatic coercion:

Type Go Type YAML Example
string string type: string
int int64 type: int
float float64 type: float
bool bool type: bool
array []any type: array
time time.Time type: time
duration time.Duration type: duration
any any type: any (default)

Type aliases:

  • timestamp, datetimetime
  • integerint
  • numberfloat
  • booleanbool
Executor
// NewExecutor creates a new resolver executor
func NewExecutor(registry RegistryInterface, opts ...ExecutorOption) *Executor

// ExecutorOptions
func WithDefaultTimeout(timeout time.Duration) ExecutorOption
func WithPhaseTimeout(timeout time.Duration) ExecutorOption
func WithMaxConcurrency(max int) ExecutorOption
func WithWarnValueSize(bytes int64) ExecutorOption
func WithMaxValueSize(bytes int64) ExecutorOption

// Execute runs all resolvers and returns results
func (e *Executor) Execute(ctx context.Context, resolvers []*Resolver, params map[string]any) (*ExecutionResult, error)
ValueRef

ValueRef enables flexible value references in YAML:

# Literal value
inputs:
  value: "hello"

# Resolver reference
inputs:
  config:
    rslvr: other-resolver

# CEL expression
inputs:
  calculated:
    expr: "_.count * 2"

# Go template
inputs:
  message:
    tmpl: "Hello, {{.name}}!"

Providers

Built-in providers available in the default registry:

Provider Purpose Example
static Static values value: "hello"
parameter CLI parameters name: env, default: prod
env Environment variables name: HOME
cel CEL expressions expression: _.a + _.b
http HTTP requests url: https://api.example.com
file File contents path: ./config.json
exec Command execution command: echo hello
git Git operations operation: branch
hcl Parse HCL files content: 'variable "x" { default = 1 }'
identity Auth identity info operation: status, handler: entra
secret Encrypted secrets operation: get, name: api-key
validation Value validation pattern: ^[a-z]+$
sleep Delay execution duration: 1s
debug Debug utilities message: checkpoint

Conditional Execution

Resolver-level conditions
resolvers:
  prod-config:
    when:
      expr: "_.environment == 'production'"
    resolve:
      with:
        - provider: http
          inputs:
            url: https://config.prod.example.com/settings
Phase-level conditions
resolvers:
  config:
    resolve:
      when:
        expr: "_.use_remote == true"
      with:
        - provider: http
          inputs:
            url: https://config.example.com
    transform:
      when:
        expr: "_.environment == 'prod'"
      with:
        - provider: cel
          inputs:
            expression:
              expr: "__self.merge({'ssl': true})"

Error Handling

Error Behavior

Control how errors are handled:

resolvers:
  config:
    resolve:
      with:
        - provider: http
          error_behavior: continue  # Try next source on error
          inputs:
            url: https://primary.example.com
        - provider: http
          inputs:
            url: https://fallback.example.com
Error Types
// ExecutionError - Error during resolver execution
type ExecutionError struct {
    ResolverName string
    Phase        string  // "resolve", "transform", "validate"
    Step         int
    Provider     string
    Cause        error
}

// AggregatedValidationError - Multiple validation failures
type AggregatedValidationError struct {
    ResolverName string
    Failures     []ValidationFailure
}

// CircularDependencyError - Cycle detected in dependencies
type CircularDependencyError struct {
    Cycle []string
}

Metrics & Observability

Execution Metrics
result, _ := executor.Execute(ctx, resolvers, params)

// Access metrics
fmt.Println(result.Metrics.TotalResolvers)
fmt.Println(result.Metrics.TotalPhases)
fmt.Println(result.Metrics.TotalDuration)

// Per-resolver metrics
for name, r := range result.Resolvers {
    fmt.Printf("%s: %v (calls: %d)\n", name, r.Duration, r.ProviderCalls)
}
Metrics Summary
summary := resolver.NewMetricsSummary(result)
summary.Print(os.Stdout)

Output:

=== Execution Summary ===
Total resolvers: 5
Successful: 4
Failed: 1
Skipped: 0
Total duration: 1.234s

=== Phase Breakdown ===
Phase 1: 3 resolvers (456ms)
Phase 2: 2 resolvers (778ms)

Snapshots

Capture execution state for debugging and testing:

// Capture snapshot
snapshot := resolver.CaptureSnapshot(ctx, 
    "my-solution", "1.0.0", "v0.1.0",
    params, duration, "success")

// Save to file
data, _ := json.MarshalIndent(snapshot, "", "  ")
os.WriteFile("snapshot.json", data, 0644)

// Load and compare
loaded, _ := resolver.LoadSnapshot("snapshot.json")
diff := resolver.DiffSnapshots(expected, actual)

Best Practices

1. Use Appropriate Types
# ✅ Good - explicit types
port:
  type: int
  resolve: ...

# ❌ Avoid - relying on default 'any' type
port:
  resolve: ...
2. Handle Sensitive Values
# ✅ Good - mark sensitive values
api-key:
  sensitive: true
  resolve:
    with:
      - provider: env
        inputs:
          name: API_KEY
3. Use Fallback Sources
# ✅ Good - multiple sources with fallback
config:
  resolve:
    with:
      - provider: http
        error_behavior: continue
        inputs:
          url: https://config.primary.com
      - provider: file
        inputs:
          path: ./fallback-config.json
4. Validate Critical Values
# ✅ Good - validate important values
port:
  type: int
  resolve: ...
  validate:
    with:
      - provider: validation
        inputs:
          expression:
            expr: "__self >= 1 && __self <= 65535"
          message: "Port must be between 1 and 65535"
5. Set Appropriate Timeouts
# ✅ Good - set timeout for slow operations
external-config:
  timeout: 60s  # Override default 30s
  resolve:
    with:
      - provider: http
        inputs:
          url: https://slow-api.example.com

Examples

Configuration with Environment Override
spec:
  resolvers:
    base-config:
      type: any
      resolve:
        with:
          - provider: file
            inputs:
              path: ./config.yaml
              format: yaml

    environment:
      type: string
      resolve:
        with:
          - provider: parameter
            inputs:
              key: env
          - provider: static
            inputs:
              value: development

    final-config:
      type: any
      resolve:
        with:
          - provider: cel
            inputs:
              expression:
                expr: |
                  _.base_config.merge({
                    'environment': _.environment,
                    'debug': _.environment != 'production'
                  })
Multi-stage Pipeline
spec:
  resolvers:
    raw-data:
      resolve:
        with:
          - provider: http
            inputs:
              url: https://api.example.com/data

    parsed-data:
      type: any
      resolve:
        with:
          - provider: cel
            inputs:
              expression:
                expr: "_.raw_data.fromJson()"
      transform:
        with:
          - provider: cel
            inputs:
              expression:
                expr: "__self.items.filter(i, i.active == true)"
      validate:
        with:
          - provider: validation
            inputs:
              expression:
                expr: "size(__self) > 0"
              message: "No active items found"
Conditional Feature Flags
spec:
  resolvers:
    feature-flags:
      type: any
      resolve:
        with:
          - provider: http
            error_behavior: continue
            inputs:
              url: https://flags.example.com/api/flags
          - provider: static
            inputs:
              value:
                new_ui: false
                analytics: true

    ui-version:
      type: string
      when:
        expr: "_.feature_flags.new_ui == true"
      resolve:
        with:
          - provider: static
            inputs:
              value: "v2"

Thread Safety

The resolver package is designed for concurrent use:

  • Executor is safe for concurrent Execute() calls
  • ResolverContext uses sync.Map for thread-safe value storage
  • Metrics collection is thread-safe

Documentation

Overview

Package resolver provides type coercion utilities.

Type coercion functions have been moved to pkg/spec/types.go. This file is kept for backward compatibility - CoerceType is re-exported in resolver.go from the spec package.

Package resolver provides types for defining and executing data resolvers.

This file re-exports types from pkg/spec for backward compatibility. New code should import from pkg/spec directly when possible.

Index

Constants

View Source
const (
	TypeString   = spec.TypeString
	TypeInt      = spec.TypeInt
	TypeFloat    = spec.TypeFloat
	TypeBool     = spec.TypeBool
	TypeArray    = spec.TypeArray
	TypeTime     = spec.TypeTime
	TypeDuration = spec.TypeDuration
	TypeAny      = spec.TypeAny
)

Type constants re-exported from spec for backward compatibility.

View Source
const (
	ErrorBehaviorFail     = spec.OnErrorFail
	ErrorBehaviorContinue = spec.OnErrorContinue
)

ErrorBehavior constants re-exported from spec for backward compatibility.

Variables

View Source
var (
	// ResolverExecutionDuration tracks the duration of resolver executions
	ResolverExecutionDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    fmt.Sprintf("%s_resolver_execution_duration_seconds", settings.CliBinaryName),
		Help:    "Histogram of resolver execution duration in seconds",
		Buckets: []float64{.01, .05, .1, .25, .5, 1, 2.5, 5, 10, 30},
	}, []string{resolverNameLabel, statusLabel})

	// ResolverPhaseDuration tracks the duration of individual resolver phases
	ResolverPhaseDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    fmt.Sprintf("%s_resolver_phase_duration_seconds", settings.CliBinaryName),
		Help:    "Histogram of resolver phase duration in seconds",
		Buckets: []float64{.01, .05, .1, .25, .5, 1, 2.5, 5, 10, 30},
	}, []string{resolverNameLabel, phaseLabel})

	// ResolverExecutionsTotal tracks the total number of resolver executions
	ResolverExecutionsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: fmt.Sprintf("%s_resolver_executions_total", settings.CliBinaryName),
		Help: "Total number of resolver executions",
	}, []string{resolverNameLabel, statusLabel})

	// ResolverProviderCallsTotal tracks the total number of provider calls made by resolvers
	ResolverProviderCallsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: fmt.Sprintf("%s_resolver_provider_calls_total", settings.CliBinaryName),
		Help: "Total number of provider calls made by resolvers",
	}, []string{resolverNameLabel, providerLabel, phaseLabel})

	// ResolverValueSize tracks the size of resolver values in bytes
	ResolverValueSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    fmt.Sprintf("%s_resolver_value_size_bytes", settings.CliBinaryName),
		Help:    "Histogram of resolver value sizes in bytes",
		Buckets: []float64{100, 1000, 10000, 100000, 1000000, 10000000},
	}, []string{resolverNameLabel})

	// ResolverDependencyCount tracks the number of dependencies per resolver
	ResolverDependencyCount = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    fmt.Sprintf("%s_resolver_dependency_count", settings.CliBinaryName),
		Help:    "Histogram of resolver dependency counts",
		Buckets: []float64{0, 1, 2, 5, 10, 20, 50},
	}, []string{resolverNameLabel})

	// ResolverFailedAttemptsTotal tracks the total number of failed provider attempts
	ResolverFailedAttemptsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: fmt.Sprintf("%s_resolver_failed_attempts_total", settings.CliBinaryName),
		Help: "Total number of failed provider attempts before success or final failure",
	}, []string{resolverNameLabel, providerLabel, phaseLabel})

	// ResolverPhaseExecutionsTotal tracks the total number of phase executions
	ResolverPhaseExecutionsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: fmt.Sprintf("%s_resolver_phase_executions_total", settings.CliBinaryName),
		Help: "Total number of resolver phase executions",
	}, []string{phaseLabel})

	// ResolverConcurrentExecutions tracks the current number of concurrent resolver executions
	ResolverConcurrentExecutions = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: fmt.Sprintf("%s_resolver_concurrent_executions", settings.CliBinaryName),
		Help: "Current number of concurrent resolver executions",
	})
)
View Source
var CoerceType = spec.CoerceType

CoerceType is re-exported from spec for backward compatibility.

Functions

func ExtractDependencies added in v0.2.0

func ExtractDependencies(r *Resolver, lookup DescriptorLookup) []string

ExtractDependencies extracts all resolver references from a resolver definition. If lookup is provided, it will use provider-specific ExtractDependencies functions when available. If lookup is nil, only generic extraction is performed. Explicit dependencies from DependsOn are always included and merged with auto-extracted dependencies.

func ExtractInferredDependencies added in v0.17.0

func ExtractInferredDependencies(r *Resolver, lookup DescriptorLookup) []string

ExtractInferredDependencies extracts only auto-inferred dependencies from value references (expr:, rslvr:, tmpl:) and when clauses, excluding explicit DependsOn entries. This is useful for detecting redundant dependsOn declarations.

func ExtractOptionalRefsFromValueRef added in v0.36.0

func ExtractOptionalRefsFromValueRef(ref *ValueRef, optional map[string]bool)

ExtractOptionalRefsFromValueRef collects resolver names that a ValueRef references via optional CEL access (_.?name, _[?"name"]), accumulating them into the provided optional set. Optional access is a CEL-only concept, so direct rslvr: references and Go-template (tmpl:) accessors -- which are always hard -- contribute nothing. Within a single CEL expression, a name also accessed with hard syntax is excluded (hard access dominates). Passing a nil ref or optional map is a no-op (optional must be non-nil to collect results).

func ExtractRefsFromValueRef added in v0.24.0

func ExtractRefsFromValueRef(ref *ValueRef, deps map[string]bool)

ExtractRefsFromValueRef extracts all resolver names referenced by a single ValueRef, accumulating them into the provided deps set. It handles direct resolver references, CEL expressions, Go templates, and nested literal values using the same AST-based logic the dependency graph uses. Passing a nil ref or deps map is a no-op (deps must be non-nil to collect results).

func ExtractRefsFromValueRefs added in v0.19.0

func ExtractRefsFromValueRefs(inputs map[string]*ValueRef) []string

ExtractRefsFromValueRefs extracts all resolver names referenced by a set of ValueRef values. This is useful for determining which resolvers an action depends on based on its inputs map. The result is sorted for deterministic output.

func FormatDiffHuman

func FormatDiffHuman(diff *SnapshotDiff) string

FormatDiffHuman formats the diff in human-readable format

func FormatDiffJSON

func FormatDiffJSON(diff *SnapshotDiff) (string, error)

FormatDiffJSON formats the diff as JSON

func FormatDiffUnified

func FormatDiffUnified(diff *SnapshotDiff) string

FormatDiffUnified formats the diff in unified diff format (similar to git diff)

func FormatMetricsSummary

func FormatMetricsSummary(summary *MetricsSummary) string

FormatMetricsSummary formats the metrics summary for human-readable display

func GetMaxPhase

func GetMaxPhase(phases []*PhaseGroup) int

GetMaxPhase returns the maximum phase number in the phase groups

func GetPhaseForResolver

func GetPhaseForResolver(phases []*PhaseGroup, resolverName string) int

GetPhaseForResolver returns the phase number for a given resolver name

func IsAggregatedExecutionError

func IsAggregatedExecutionError(err error) bool

IsAggregatedExecutionError checks if an error is an AggregatedExecutionError.

func IsCircularDependencyError

func IsCircularDependencyError(err error) bool

IsCircularDependencyError checks if an error is a CircularDependencyError.

func IsExecutionError

func IsExecutionError(err error) bool

IsExecutionError checks if an error is an ExecutionError.

func IsForEachTypeError

func IsForEachTypeError(err error) bool

IsForEachTypeError checks if an error is a ForEachTypeError.

func IsTransitiveDependency added in v0.6.0

func IsTransitiveDependency(resolvers map[string]*Resolver, targetResolver, candidateName string) bool

IsTransitiveDependency checks if candidateName is a direct or transitive dependency of the targetResolver within the solution's resolver map. This is useful for filtering resolver graphs to show only relevant dependencies.

func IsTypeCoercionError

func IsTypeCoercionError(err error) bool

IsTypeCoercionError checks if an error is a TypeCoercionError.

func IsValidationError

func IsValidationError(err error) bool

IsValidationError checks if an error is an AggregatedValidationError.

func IsValueSizeError

func IsValueSizeError(err error) bool

IsValueSizeError checks if an error is a ValueSizeError.

func RecordResolverExecution

func RecordResolverExecution(resolverName string, result *ExecutionResult)

RecordResolverExecution records metrics for a completed resolver execution

func RedactError

func RedactError(err error, sensitive bool) error

RedactError wraps an error with redaction if sensitive is true. The original error is preserved and can be accessed via errors.Unwrap.

func RedactMapValues

func RedactMapValues(m map[string]any, sensitive bool) map[string]any

RedactMapValues redacts all values in a map if sensitive is true. Keys are preserved, only values are replaced with [REDACTED].

func RedactSensitiveValues

func RedactSensitiveValues(snapshot *Snapshot, resolvers []ResolverLike)

RedactSensitiveValues redacts sensitive values in the snapshot based on resolver-like objects

func RedactValue

func RedactValue(value any, sensitive bool) any

RedactValue returns [REDACTED] for any value if sensitive is true, otherwise returns the value unchanged. This is safe for use in logs, error messages, and JSON output.

func RegisterResolverMetrics

func RegisterResolverMetrics()

RegisterResolverMetrics registers all resolver-related Prometheus metrics

func SaveSnapshot

func SaveSnapshot(snapshot *Snapshot, filePath string) error

SaveSnapshot saves a snapshot to a JSON file

func WithContext

func WithContext(ctx context.Context, rc *Context) context.Context

WithContext adds resolver context to a Go context

Types

type AggregatedDeferredValidationError added in v0.34.0

type AggregatedDeferredValidationError struct {
	Failures   []DeferredValidationFailure `json:"failures" yaml:"failures" doc:"Per-resolver deferred validation failures, root-cause-first" minItems:"1"`
	Suppressed []string                    `` /* 135-byte string literal not displayed */
}

AggregatedDeferredValidationError aggregates deferred (cross-resolver) validation failures across resolvers, ordered root-cause-first. Deferred validation runs after every resolver has resolved, so failures cannot be attributed to a single resolve phase; this error type carries per-resolver failure groups plus the set of rules suppressed because an upstream deferred validation already failed.

func (*AggregatedDeferredValidationError) Error added in v0.34.0

Error implements the error interface.

func (*AggregatedDeferredValidationError) HasFailures added in v0.34.0

func (e *AggregatedDeferredValidationError) HasFailures() bool

HasFailures returns true if there are any deferred validation failures.

func (*AggregatedDeferredValidationError) Unwrap added in v0.34.0

Unwrap returns nil since this error aggregates multiple failures. Use errors.As with *ValidationFailure to inspect individual failures.

type AggregatedExecutionError

type AggregatedExecutionError struct {
	Errors         []*FailedResolver `json:"errors" yaml:"errors" doc:"All errors encountered during execution" minItems:"1"`
	SkippedCount   int               `json:"skippedCount" yaml:"skippedCount" doc:"Number of resolvers skipped due to failed dependencies" minimum:"0" example:"3"`
	SkippedNames   []string          `json:"skippedNames" yaml:"skippedNames" doc:"Names of skipped resolvers"`
	SucceededCount int               `json:"succeededCount" yaml:"succeededCount" doc:"Number of resolvers that succeeded" minimum:"0" example:"5"`
}

AggregatedExecutionError represents multiple resolver errors collected during validate-all mode. This is used when the executor is configured to continue execution after failures.

func (*AggregatedExecutionError) Add

func (e *AggregatedExecutionError) Add(resolverName string, phase int, err error)

Add adds a resolver error to the aggregated error.

func (*AggregatedExecutionError) AddSkipped

func (e *AggregatedExecutionError) AddSkipped(resolverName string)

AddSkipped records that a resolver was skipped due to failed dependencies.

func (*AggregatedExecutionError) Error

func (e *AggregatedExecutionError) Error() string

Error implements the error interface.

func (*AggregatedExecutionError) HasErrors

func (e *AggregatedExecutionError) HasErrors() bool

HasErrors returns true if there are any errors.

func (*AggregatedExecutionError) IncrementSucceeded

func (e *AggregatedExecutionError) IncrementSucceeded()

IncrementSucceeded increments the count of succeeded resolvers.

type AggregatedValidationError

type AggregatedValidationError struct {
	ResolverName  string              `json:"resolverName" yaml:"resolverName" doc:"Name of the resolver that failed validation" example:"user-email"`
	Value         any                 `json:"-" yaml:"-" doc:"The value that failed validation"`
	Failures      []ValidationFailure `json:"failures" yaml:"failures" doc:"List of validation failures" minItems:"1"`
	Sensitive     bool                `json:"sensitive,omitempty" yaml:"sensitive,omitempty" doc:"Whether the resolver value is sensitive"`
	CustomMessage string              `json:"customMessage,omitempty" yaml:"customMessage,omitempty" doc:"User-defined error message from messages.error"`
}

AggregatedValidationError represents validation failure with multiple messages. This collects all validation failures from a resolver's validate phase.

func (*AggregatedValidationError) AddFailure

func (e *AggregatedValidationError) AddFailure(failure ValidationFailure)

AddFailure adds a validation failure to the error.

func (*AggregatedValidationError) Error

func (e *AggregatedValidationError) Error() string

Error implements the error interface.

func (*AggregatedValidationError) HasFailures

func (e *AggregatedValidationError) HasFailures() bool

HasFailures returns true if there are any validation failures.

func (*AggregatedValidationError) Unwrap

func (e *AggregatedValidationError) Unwrap() error

Unwrap returns nil since this error aggregates multiple failures. Use errors.As with *ValidationFailure to check individual failures.

type AttemptDetail

type AttemptDetail struct {
	Provider string `json:"provider" yaml:"provider" doc:"Provider name" maxLength:"128" example:"http"`
	Error    string `json:"error" yaml:"error" doc:"Error message" maxLength:"4096" example:"connection refused"`
}

AttemptDetail contains details of a failed attempt

type BuildResult added in v0.7.0

type BuildResult struct {
	Phases []*PhaseGroup       `json:"phases" yaml:"phases" doc:"Execution phase groups"`
	Plan   PlanData            `json:"plan"   yaml:"plan"   doc:"Pre-execution resolver topology snapshot"`
	Deps   map[string][]string `json:"-"      yaml:"-"      doc:"Effective dependency map (reusable by callers)"`
	// DeferredWork lists resolvers with cross-resolver validation rules that must
	// run in the post-resolution validation phase. Empty when no resolver has any
	// deferred validation rules.
	DeferredWork []DeferredValidationUnit `json:"-" yaml:"-" doc:"Deferred cross-resolver validation work"`
}

BuildResult is the output of BuildPhases, bundling the phase groups with the pre-execution plan topology so callers can inject __plan without re-computing deps.

func BuildPhases

func BuildPhases(resolvers []*Resolver, lookup DescriptorLookup) (*BuildResult, error)

BuildPhases groups resolvers into execution phases based on dependencies. Phase numbers are 1-based, with phase 1 being root resolvers (no dependencies). If lookup is provided, provider-specific ExtractDependencies functions will be used when available for more accurate dependency detection. The returned BuildResult includes both the phase groups and PlanData — a static topology snapshot that can be injected as __plan before execution begins.

func BuildPhasesWithCalls added in v0.34.0

func BuildPhasesWithCalls(resolvers []*Resolver, lookup DescriptorLookup, calls map[string]*spec.Call) (*BuildResult, error)

BuildPhasesWithCalls is BuildPhases with awareness of reusable call definitions. Call definitions are strictly isolated (their inputs may not reference solution resolvers), so invoking a call contributes no extra dependencies; the calls map is threaded through for signature parity and future use.

type CircularDependencyError

type CircularDependencyError struct {
	Cycle []string `` /* 144-byte string literal not displayed */
}

CircularDependencyError represents a cycle detected in resolver dependencies.

func NewCircularDependencyError

func NewCircularDependencyError(cycle []string) *CircularDependencyError

NewCircularDependencyError creates a new CircularDependencyError from a cycle path.

func (*CircularDependencyError) Error

func (e *CircularDependencyError) Error() string

Error implements the error interface.

type Condition

type Condition struct {
	Expr *celexp.Expression `json:"expr" yaml:"expr" doc:"CEL expression that must evaluate to boolean" example:"_.environment == 'prod'"`
}

Condition is a resolver-specific condition type with custom YAML/JSON unmarshalling. Unlike spec.Condition, this type only wraps the expr field, keeping backward compatibility with existing resolver YAML files.

Supported YAML/JSON forms:

  • String shorthand: when: "_.environment == 'prod'"
  • Boolean literal: when: true / when: false
  • Object form: when: { expr: "_.environment == 'prod'" }
  • Expression alias: when: { expression: "_.environment == 'prod'" }
  • Null: when: null (treated as unset, nil Expr)

func (*Condition) UnmarshalJSON added in v0.8.0

func (c *Condition) UnmarshalJSON(data []byte) error

UnmarshalJSON supports shorthand forms for conditions.

  • string → treated as a CEL expression
  • bool → converted to literal "true" or "false" CEL expression
  • object → standard {expr: "..."} form

func (*Condition) UnmarshalYAML added in v0.8.0

func (c *Condition) UnmarshalYAML(unmarshal func(any) error) error

UnmarshalYAML supports shorthand forms for conditions.

  • string → treated as a CEL expression
  • bool → converted to literal "true" or "false" CEL expression
  • object → standard {expr: "..."} form

type Config

type Config struct {
	MaxValueSizeBytes  int64         `` /* 152-byte string literal not displayed */
	WarnValueSizeBytes int64         `` /* 153-byte string literal not displayed */
	MaxConcurrency     int           `` /* 164-byte string literal not displayed */
	PhaseTimeout       time.Duration `` /* 139-byte string literal not displayed */
}

Config contains global resolver configuration

type ConfigInput

type ConfigInput struct {
	// Timeout is the default timeout per resolver execution
	Timeout time.Duration `json:"timeout" yaml:"timeout" doc:"Default timeout per resolver execution"`
	// PhaseTimeout is the maximum time for each resolution phase
	PhaseTimeout time.Duration `json:"phaseTimeout" yaml:"phaseTimeout" doc:"Maximum time for each resolution phase"`
	// MaxConcurrency is the maximum concurrent resolvers per phase (0 = unlimited)
	MaxConcurrency int `` /* 132-byte string literal not displayed */
	// WarnValueSize is the warn threshold in bytes (0 = disabled)
	WarnValueSize int64 `json:"warnValueSize" yaml:"warnValueSize" doc:"Warn threshold in bytes (0 = disabled)" example:"1048576"`
	// MaxValueSize is the max value size in bytes (0 = disabled)
	MaxValueSize int64 `json:"maxValueSize" yaml:"maxValueSize" doc:"Max value size in bytes (0 = disabled)" example:"10485760"`
	// ValidateAll enables collecting all errors instead of stopping at first
	ValidateAll bool `json:"validateAll" yaml:"validateAll" doc:"Collect all validation errors instead of stopping at first"`
}

ConfigInput holds the configuration values for resolver executor initialization. This mirrors config.ResolverConfig but avoids circular dependencies.

type Context

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

Context is a thread-safe storage for resolver results and execution metadata

func FromContext

func FromContext(ctx context.Context) (*Context, bool)

FromContext retrieves resolver context from a Go context

func NewContext

func NewContext() *Context

NewContext creates a new resolver context

func (*Context) DeferredValidation added in v0.34.0

func (c *Context) DeferredValidation() *DeferredValidationSummary

DeferredValidation returns the deferred validation summary, or nil if the deferred validation phase did not run.

func (*Context) Get

func (c *Context) Get(name string) (any, bool)

Get retrieves a resolver value (for CEL evaluation)

func (*Context) GetAllResults

func (c *Context) GetAllResults() map[string]*ExecutionResult

GetAllResults returns all resolver execution results with metadata Used for observability, logging, and metrics

func (*Context) GetResult

func (c *Context) GetResult(name string) (*ExecutionResult, bool)

GetResult retrieves the full resolver execution result including metadata

func (*Context) Has

func (c *Context) Has(name string) bool

Has checks if a resolver exists

func (*Context) Set

func (c *Context) Set(name string, value any)

Set stores a resolver value (for backward compatibility)

func (*Context) SetDeferredValidation added in v0.34.0

func (c *Context) SetDeferredValidation(summary *DeferredValidationSummary)

SetDeferredValidation stores the deferred (cross-resolver) validation summary.

func (*Context) SetResult

func (c *Context) SetResult(name string, result *ExecutionResult)

SetResult stores a resolver result with full execution metadata

func (*Context) ToMap

func (c *Context) ToMap() map[string]any

ToMap returns all resolver data as a map (for CEL evaluation) Only returns values, not metadata

type DeferredReportingPlan added in v0.34.0

type DeferredReportingPlan struct {
	// Order lists the resolver names owning deferred units in root-cause-first
	// order: a resolver whose deferred rules reference other resolvers is reported
	// after those referenced resolvers, so upstream failures surface first and can
	// suppress cascaded failures.
	Order []string
	// Cycles lists strongly connected components with more than one member. These
	// are informational: a cycle in the reporting graph is not an error (deferred
	// rules only read final values), but surfacing it helps authors understand why
	// ordering within the component is name-sorted rather than dependency-driven.
	Cycles [][]string
}

DeferredReportingPlan describes the order in which deferred (cross-resolver) validation results should be evaluated and reported.

The reporting graph is derived from DeferredValidationUnit.DependsOn edges (which resolver a rule references). Unlike the resolution DAG, this graph is used only for ordering and reporting -- it MAY contain cycles (two resolvers can validate against each other). Cycles are tolerated: they are condensed into strongly connected components so a deterministic, root-cause-first order can still be produced.

type DeferredValidationFailure added in v0.34.0

type DeferredValidationFailure struct {
	ResolverName string              `json:"resolverName" yaml:"resolverName" doc:"Name of the resolver whose deferred validation failed" example:"checker"`
	Failures     []ValidationFailure `json:"failures" yaml:"failures" doc:"List of deferred validation failures" minItems:"1"`
	Sensitive    bool                `json:"sensitive,omitempty" yaml:"sensitive,omitempty" doc:"Whether the resolver value is sensitive"`
}

DeferredValidationFailure holds the deferred (cross-resolver) validation failures for a single resolver. It reuses ValidationFailure for each failing rule so the inline and deferred paths report failures identically.

type DeferredValidationSummary added in v0.34.0

type DeferredValidationSummary struct {
	// Evaluated is the number of deferred rules whose provider was invoked.
	Evaluated int
	// Failed is the number of deferred rules that failed.
	Failed int
	// Skipped is the number of deferred rules that did not run (owning resolver
	// had no value, or a phase/rule when condition evaluated false).
	Skipped int
	// Results holds the per-resolver failures, ordered root-cause-first.
	Results []DeferredValidationFailure
	// Suppressed lists resolvers whose deferred rules were suppressed because a
	// referenced resolver already failed its own deferred validation.
	Suppressed []string
	// Cycles lists cross-validation cycles detected in the reporting graph. These
	// are informational (tolerated), not errors.
	Cycles [][]string
}

DeferredValidationSummary reports the outcome of the post-resolution deferred (cross-resolver) validation phase. It is attached to the execution context so collect-errors-mode callers (for example `run resolver`) can inspect and gate on deferred validation without treating it as a fatal error.

func DeferredValidationResultFromContext added in v0.34.0

func DeferredValidationResultFromContext(ctx context.Context) (*DeferredValidationSummary, bool)

DeferredValidationResultFromContext returns the deferred validation summary attached to ctx by Execute, if any. Callers use it to gate state persistence and actions on deferred validation results in non-fatal modes.

func (*DeferredValidationSummary) HasFailures added in v0.34.0

func (s *DeferredValidationSummary) HasFailures() bool

HasFailures reports whether any deferred validation rule failed.

type DeferredValidationUnit added in v0.34.0

type DeferredValidationUnit struct {
	// ResolverName is the resolver that owns the deferred rules.
	ResolverName string
	// RuleIndices are indices into the resolver's Validate.With slice identifying
	// which rules are deferred. Preserves declaration order.
	RuleIndices []int
	// DependsOn is the sorted set of foreign resolvers referenced by the deferred
	// rules. Used only to order and report deferred validation results -- these are
	// never resolution-graph edges.
	DependsOn []string
	// PhaseWhenDeferred indicates the phase-level validate.when referenced a
	// foreign resolver, forcing every rule in the block to defer.
	PhaseWhenDeferred bool
}

DeferredValidationUnit describes the deferred (cross-resolver) validation rules for a single resolver. These rules reference other resolvers and are excluded from the resolution DAG; they run in a dedicated post-resolution validation phase once every resolver has resolved.

type DescriptorLookup

type DescriptorLookup func(providerName string) *provider.Descriptor

DescriptorLookup is a function that retrieves a provider descriptor by name. Used during dependency extraction to allow providers to participate in extracting dependencies from their inputs.

type DiffOptions

type DiffOptions struct {
	IgnoreUnchanged bool     `json:"ignoreUnchanged" yaml:"ignoreUnchanged" doc:"Whether to omit unchanged resolvers from output"`
	IgnoreFields    []string `` /* 142-byte string literal not displayed */
	ResolverFilter  string   `` /* 146-byte string literal not displayed */
}

DiffOptions provides options for diff comparison

type DiffSummary

type DiffSummary struct {
	TotalResolvers int `json:"totalResolvers" yaml:"totalResolvers" doc:"Total number of resolvers compared" maximum:"10000" example:"20"`
	Added          int `json:"added" yaml:"added" doc:"Number of resolvers added" maximum:"10000" example:"2"`
	Removed        int `json:"removed" yaml:"removed" doc:"Number of resolvers removed" maximum:"10000" example:"1"`
	Modified       int `json:"modified" yaml:"modified" doc:"Number of resolvers modified" maximum:"10000" example:"3"`
	Unchanged      int `json:"unchanged" yaml:"unchanged" doc:"Number of resolvers unchanged" maximum:"10000" example:"14"`
}

DiffSummary provides a summary of all changes

type DiffType

type DiffType string

DiffType represents the type of difference found

const (
	DiffTypeAdded     DiffType = "added"     // Resolver exists in after but not before
	DiffTypeRemoved   DiffType = "removed"   // Resolver exists in before but not after
	DiffTypeModified  DiffType = "modified"  // Resolver exists in both but values differ
	DiffTypeUnchanged DiffType = "unchanged" // Resolver exists in both and is identical
)

type ErrorBehavior

type ErrorBehavior = spec.OnErrorBehavior

ErrorBehavior is an alias to spec.OnErrorBehavior for backward compatibility. New code should use spec.OnErrorBehavior directly.

type ExecutionError

type ExecutionError struct {
	ResolverName  string `json:"resolverName" yaml:"resolverName" doc:"Name of the resolver that failed" example:"my-resolver"`
	Phase         string `json:"phase" yaml:"phase" doc:"Execution phase where failure occurred" enum:"resolve,transform,validate" example:"resolve"`
	Step          int    `json:"step" yaml:"step" doc:"Step number within the phase (0-indexed)" minimum:"0" example:"0"`
	Provider      string `json:"provider" yaml:"provider" doc:"Provider name that failed" example:"http"`
	Cause         error  `json:"-" yaml:"-" doc:"Underlying error that caused the failure"`
	CustomMessage string `json:"customMessage,omitempty" yaml:"customMessage,omitempty" doc:"User-defined error message from messages.error"`
}

ExecutionError represents an error during resolver execution with context about which resolver, phase, step, and provider were involved.

func NewExecutionError

func NewExecutionError(resolverName, phase, provider string, step int, cause error) *ExecutionError

NewExecutionError creates a new ExecutionError with the given parameters.

func (*ExecutionError) Error

func (e *ExecutionError) Error() string

Error implements the error interface.

func (*ExecutionError) Unwrap

func (e *ExecutionError) Unwrap() error

Unwrap returns the underlying cause for use with errors.Is and errors.As.

type ExecutionResult

type ExecutionResult struct {
	// Value is the actual resolved value
	Value any `json:"value" yaml:"value" doc:"The resolved value"`

	// Metadata (internal use only, not accessible in CEL)
	Status            ExecutionStatus   `json:"status" yaml:"status" doc:"Execution status" example:"success"`
	Phase             int               `json:"phase" yaml:"phase" doc:"Execution phase number (1-based)" example:"1"`
	TotalDuration     time.Duration     `json:"totalDuration" yaml:"totalDuration" doc:"Total execution time across all phases" example:"250ms"`
	StartTime         time.Time         `json:"startTime" yaml:"startTime" doc:"When resolver execution started"`
	EndTime           time.Time         `json:"endTime" yaml:"endTime" doc:"When resolver execution ended"`
	Error             error             `json:"error,omitempty" yaml:"error,omitempty" doc:"Error if failed"`
	PhaseMetrics      []PhaseMetrics    `json:"phaseMetrics" yaml:"phaseMetrics" doc:"Per-phase timing" maxItems:"3"`
	ProviderCallCount int               `json:"providerCallCount" yaml:"providerCallCount" doc:"Number of provider calls made" example:"2"`
	ValueSizeBytes    int64             `json:"valueSizeBytes" yaml:"valueSizeBytes" doc:"Size of the value in bytes" example:"1024"`
	DependencyCount   int               `json:"dependencyCount" yaml:"dependencyCount" doc:"Number of dependencies" example:"3"`
	FailedAttempts    []ProviderAttempt `json:"failedAttempts,omitempty" yaml:"failedAttempts,omitempty" doc:"Failed provider attempts (for debugging)" maxItems:"10"`
}

ExecutionResult contains both the value and execution metadata

type ExecutionStatus

type ExecutionStatus string

ExecutionStatus represents the execution status of a resolver

const (
	ExecutionStatusSuccess ExecutionStatus = "success"
	ExecutionStatusFailed  ExecutionStatus = "failed"
	ExecutionStatusSkipped ExecutionStatus = "skipped"
)

type Executor

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

Executor executes resolvers in phases with concurrency control

func NewExecutor

func NewExecutor(registry RegistryInterface, opts ...ExecutorOption) *Executor

NewExecutor creates a new resolver executor

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, resolvers []*Resolver, params map[string]any) (context.Context, error)

Execute runs all resolvers in phases and returns the enriched context. Use FromContext(ctx) to retrieve the resolver results.

type ExecutorOption

type ExecutorOption func(*Executor)

ExecutorOption is a functional option for configuring the Executor

func OptionsFromAppConfig

func OptionsFromAppConfig(cfg ConfigInput) []ExecutorOption

NewExecutorFromAppConfig creates a new resolver executor using app configuration. CLI flags can override these defaults using the returned executor options.

Example:

cfg := resolver.ResolverConfigInput{
    Timeout:        30 * time.Second,
    PhaseTimeout:   5 * time.Minute,
    MaxConcurrency: 0,
}
opts := resolver.OptionsFromAppConfig(cfg)
executor := resolver.NewExecutor(registry, opts...)

func WithCalls added in v0.34.0

func WithCalls(calls map[string]*spec.Call) ExecutorOption

WithCalls registers the solution's reusable call definitions (spec.calls) so that resolve/transform/validate steps invoking a definition via call + args can be expanded and executed.

func WithDefaultTimeout

func WithDefaultTimeout(timeout time.Duration) ExecutorOption

WithDefaultTimeout sets the default timeout for individual resolver execution

func WithDeferredValidation added in v0.34.0

func WithDeferredValidation(enabled bool) ExecutorOption

WithDeferredValidation enables or disables the post-resolution deferred validation phase, which evaluates cross-resolver validation rules (rules that reference resolvers other than their owner) after every resolver has resolved. It is enabled by default; disable it to evaluate only inline (self-only) validations. Disabling has no effect when validation is already skipped via WithSkipValidation or WithSkipTransform.

func WithMaxConcurrency

func WithMaxConcurrency(maxConcurrency int) ExecutorOption

WithMaxConcurrency sets the maximum number of resolvers that can execute concurrently per phase

func WithMaxValueSize

func WithMaxValueSize(bytes int64) ExecutorOption

WithMaxValueSize sets the maximum allowed value size in bytes Values exceeding this limit will cause resolver execution to fail Set to 0 to disable the limit

func WithMockedResolvers added in v0.11.0

func WithMockedResolvers(mocks map[string]any) ExecutorOption

WithMockedResolvers pre-populates resolver values so that the corresponding resolvers skip execution entirely and return the mocked value. This enables functional testing of downstream resolvers and CEL expressions without hitting external APIs or services.

func WithNonFatalValidation added in v0.27.0

func WithNonFatalValidation(enabled bool) ExecutorOption

WithNonFatalValidation treats validation-rule failures as non-fatal: execution continues and all errors are collected (like validate-all), but a resolver that fails only a validation rule still produces a usable value, so its dependents are NOT skipped. Resolve and transform failures remain fatal and still skip dependents. This powers `run resolver`, which must surface a full, inspectable view of the data layer even when business-rule validations fail.

func WithPhaseTimeout

func WithPhaseTimeout(timeout time.Duration) ExecutorOption

WithPhaseTimeout sets the maximum time allowed for each phase to complete

func WithProgressCallback

func WithProgressCallback(callback ProgressCallback) ExecutorOption

WithProgressCallback sets a callback for receiving execution progress events. This enables real-time progress reporting during resolver execution.

func WithSkipTransform added in v0.2.0

func WithSkipTransform(enabled bool) ExecutorOption

WithSkipTransform disables the transform and validation phases for all resolvers. When enabled, resolvers will execute only the resolve phase, returning the raw resolved value without any transformations or validations applied.

func WithSkipValidation

func WithSkipValidation(enabled bool) ExecutorOption

WithSkipValidation disables the validation phase for all resolvers. When enabled, resolvers will execute their resolve and transform phases but skip validation entirely.

func WithValidateAll

func WithValidateAll(enabled bool) ExecutorOption

WithValidateAll enables validate-all mode where execution continues even when resolvers fail, collecting all errors instead of stopping at the first error. Resolvers that depend on failed resolvers will be skipped.

func WithWarnValueSize

func WithWarnValueSize(bytes int64) ExecutorOption

WithWarnValueSize sets the size threshold for warning about large values Set to 0 to disable warnings

type FailedAttemptSummary

type FailedAttemptSummary struct {
	ResolverName string          `json:"resolverName" yaml:"resolverName" doc:"Resolver name" maxLength:"256" example:"api-data"`
	AttemptCount int             `json:"attemptCount" yaml:"attemptCount" doc:"Number of failed attempts" maximum:"100" example:"2"`
	Attempts     []AttemptDetail `json:"attempts" yaml:"attempts" doc:"Details of each failed attempt" maxItems:"100"`
}

FailedAttemptSummary summarizes failed attempts for a resolver

type FailedResolver

type FailedResolver struct {
	ResolverName string `json:"resolverName" yaml:"resolverName" doc:"Name of the resolver that failed" example:"my-resolver"`
	Phase        int    `json:"phase" yaml:"phase" doc:"Phase number where the error occurred" minimum:"1" example:"1"`
	Err          error  `json:"-" yaml:"-" doc:"The underlying error"`
	ErrMessage   string `json:"error" yaml:"error" doc:"Error message" maxLength:"2000" example:"validation failed: value must be positive"`
}

FailedResolver represents an error from a specific resolver with context. Used within AggregatedExecutionError for validate-all mode.

func (*FailedResolver) Error

func (e *FailedResolver) Error() string

Error implements the error interface.

func (*FailedResolver) Unwrap

func (e *FailedResolver) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

type FailureSummary

type FailureSummary struct {
	Name  string `json:"name" yaml:"name" doc:"Resolver name" maxLength:"256" example:"api-data"`
	Phase int    `json:"phase" yaml:"phase" doc:"Phase where failure occurred" maximum:"100" example:"1"`
	Error string `json:"error" yaml:"error" doc:"Error message" maxLength:"4096" example:"timeout"`
}

FailureSummary contains information about a failed resolver

type FieldChange

type FieldChange struct {
	Field  string `json:"field" yaml:"field" doc:"Field name that changed" maxLength:"128" example:"value"`
	Before any    `json:"before" yaml:"before" doc:"Value before change"`
	After  any    `json:"after" yaml:"after" doc:"Value after change"`
}

FieldChange represents a change in a specific field

type ForEachClause

type ForEachClause = spec.ForEachClause

ForEachClause is an alias to spec.ForEachClause for backward compatibility.

type ForEachIterationResult

type ForEachIterationResult struct {
	Index int    `json:"index" yaml:"index" doc:"Index of the iteration" minimum:"0" example:"1"`
	Data  any    `json:"data,omitempty" yaml:"data,omitempty" doc:"Result data if successful"`
	Error string `json:"error,omitempty" yaml:"error,omitempty" doc:"Error message if failed" maxLength:"1000"`
	Item  any    `json:"item,omitempty" yaml:"item,omitempty" doc:"The item that was processed"`
}

ForEachIterationResult represents the result of a single forEach iteration. Used when onError: continue allows partial results with error metadata.

type ForEachTypeError

type ForEachTypeError struct {
	ResolverName string `json:"resolverName" yaml:"resolverName" doc:"Name of the resolver" example:"regionConfigs"`
	Step         int    `json:"step" yaml:"step" doc:"Transform step number (0-indexed)" minimum:"0" example:"0"`
	ActualType   string `json:"actualType" yaml:"actualType" doc:"Actual type received" example:"string"`
}

ForEachTypeError represents an error when forEach input is not an array.

func (*ForEachTypeError) Error

func (e *ForEachTypeError) Error() string

Error implements the error interface.

type Graph

type Graph struct {
	Nodes    []*GraphNode   `json:"nodes" yaml:"nodes" doc:"Graph nodes" maxItems:"1000"`
	Edges    []*GraphEdge   `json:"edges" yaml:"edges" doc:"Graph edges" maxItems:"10000"`
	Phases   []*PhaseInfo   `json:"phases" yaml:"phases" doc:"Phase information" maxItems:"100"`
	Stats    *GraphStats    `json:"stats" yaml:"stats" doc:"Graph statistics"`
	Diagrams *GraphDiagrams `json:"diagrams" yaml:"diagrams" doc:"Pre-rendered diagram representations"`
}

Graph represents the complete resolver dependency graph

func BuildGraph

func BuildGraph(resolvers []*Resolver, lookup DescriptorLookup) (*Graph, error)

BuildGraph creates a Graph from resolvers. If lookup is provided, provider-specific ExtractDependencies functions will be used when available for more accurate dependency detection.

func (*Graph) RenderASCII

func (g *Graph) RenderASCII(w io.Writer) error

RenderASCII generates ASCII art representation

func (*Graph) RenderDOT

func (g *Graph) RenderDOT(w io.Writer) error

RenderDOT generates GraphViz DOT format

func (*Graph) RenderDiagrams added in v0.2.0

func (g *Graph) RenderDiagrams() error

RenderDiagrams pre-renders all diagram representations and stores them in the Diagrams field. This allows diagram strings to appear in JSON/YAML output without requiring an io.Writer at consumption time.

func (*Graph) RenderMermaid

func (g *Graph) RenderMermaid(w io.Writer) error

RenderMermaid generates Mermaid diagram format

type GraphDependency

type GraphDependency struct {
	Resolver string `json:"resolver" yaml:"resolver" doc:"Target resolver name" maxLength:"256" example:"auth-token"`
	Field    string `json:"field" yaml:"field" doc:"Field name in reference" maxLength:"128" example:"value"`
}

GraphDependency represents a dependency edge

type GraphDiagrams added in v0.2.0

type GraphDiagrams struct {
	ASCII   string `json:"ascii" yaml:"ascii" doc:"ASCII art representation of the graph" maxLength:"65536"`
	DOT     string `json:"dot" yaml:"dot" doc:"Graphviz DOT format representation" maxLength:"65536"`
	Mermaid string `json:"mermaid" yaml:"mermaid" doc:"Mermaid.js diagram representation" maxLength:"65536"`
}

GraphDiagrams contains pre-rendered diagram representations of the dependency graph.

type GraphEdge

type GraphEdge struct {
	From  string `json:"from" yaml:"from" doc:"Source resolver name" maxLength:"256" example:"api-data"`
	To    string `json:"to" yaml:"to" doc:"Target resolver name" maxLength:"256" example:"auth-token"`
	Label string `json:"label" yaml:"label" doc:"Edge label" maxLength:"256" example:"depends_on"`
}

GraphEdge represents a directed edge

type GraphNode

type GraphNode struct {
	Name         string            `json:"id" yaml:"id" doc:"Resolver name" maxLength:"256" example:"api-data"`
	Type         Type              `json:"type" yaml:"type" doc:"Resolver type" maxLength:"64" example:"standard"`
	Phase        int               `json:"phase" yaml:"phase" doc:"Execution phase (1-based)" maximum:"100" example:"1"`
	Conditional  bool              `json:"conditional" yaml:"conditional" doc:"Whether resolver has conditional execution"`
	Dependencies []GraphDependency `json:"dependencies" yaml:"dependencies" doc:"List of dependencies" maxItems:"100"`
}

GraphNode represents a resolver node in the dependency graph

type GraphStats

type GraphStats struct {
	TotalResolvers  int      `json:"totalResolvers" yaml:"totalResolvers" doc:"Total number of resolvers" maximum:"10000" example:"20"`
	TotalPhases     int      `json:"totalPhases" yaml:"totalPhases" doc:"Total number of execution phases" maximum:"100" example:"3"`
	MaxParallelism  int      `json:"maxParallelism" yaml:"maxParallelism" doc:"Maximum parallelism across all phases" maximum:"1000" example:"4"`
	AvgDependencies float64  `json:"avgDependencies" yaml:"avgDependencies" doc:"Average number of dependencies per resolver"`
	CriticalPath    []string `json:"criticalPath" yaml:"criticalPath" doc:"Longest dependency chain in the graph" maxItems:"100"`
	CriticalDepth   int      `json:"criticalDepth" yaml:"criticalDepth" doc:"Length of the critical path" maximum:"100" example:"5"`
}

GraphStats contains graph statistics

type IterationContext

type IterationContext = spec.IterationContext

IterationContext is an alias to spec.IterationContext for backward compatibility. It holds the context for forEach iteration variables.

type Messages added in v0.6.0

type Messages struct {
	// Error is shown when the resolver fails (resolve, transform, or validate phase).
	// Supports static strings, CEL expressions (expr:), and Go templates (tmpl:).
	// The resolver data map is available as _ and the error message as __error.
	Error *ValueRef `json:"error,omitempty" yaml:"error,omitempty" doc:"Custom message on resolver failure"`
}

Messages holds user-defined messages displayed on resolver outcomes.

type MetricsSummary

type MetricsSummary struct {
	TotalResolvers   int                    `json:"totalResolvers" yaml:"totalResolvers" doc:"Total number of resolvers executed" maximum:"10000" example:"20"`
	SuccessCount     int                    `json:"successCount" yaml:"successCount" doc:"Number of successful resolvers" maximum:"10000" example:"18"`
	FailedCount      int                    `json:"failedCount" yaml:"failedCount" doc:"Number of failed resolvers" maximum:"10000" example:"1"`
	SkippedCount     int                    `json:"skippedCount" yaml:"skippedCount" doc:"Number of skipped resolvers" maximum:"10000" example:"1"`
	TotalDuration    time.Duration          `json:"totalDuration" yaml:"totalDuration" doc:"Total execution time"`
	PhaseCount       int                    `json:"phaseCount" yaml:"phaseCount" doc:"Number of execution phases" maximum:"100" example:"3"`
	SlowestResolvers []ResolverMetric       `json:"slowestResolvers,omitempty" yaml:"slowestResolvers,omitempty" doc:"Top resolvers by execution time" maxItems:"20"`
	LargestValues    []ResolverMetric       `json:"largestValues,omitempty" yaml:"largestValues,omitempty" doc:"Top resolvers by value size" maxItems:"20"`
	FailedAttempts   []FailedAttemptSummary `json:"failedAttempts,omitempty" yaml:"failedAttempts,omitempty" doc:"Resolvers with failed provider attempts" maxItems:"100"`
	Failures         []FailureSummary       `json:"failures,omitempty" yaml:"failures,omitempty" doc:"Failed resolver details" maxItems:"100"`
}

MetricsSummary contains aggregated metrics for display

func BuildMetricsSummary

func BuildMetricsSummary(results map[string]*ExecutionResult, phaseCount int) *MetricsSummary

BuildMetricsSummary creates a metrics summary from execution results

type PhaseGroup

type PhaseGroup struct {
	Phase     int         `json:"phase" yaml:"phase" doc:"Phase number (1-based)" minimum:"1"`
	Resolvers []*Resolver `json:"resolvers" yaml:"resolvers" doc:"Resolvers in this phase" minItems:"1"`
}

PhaseGroup represents a group of resolvers that can execute concurrently

type PhaseInfo

type PhaseInfo struct {
	Phase       int      `json:"phase" yaml:"phase" doc:"Phase number (1-based)" maximum:"100" example:"1"`
	Resolvers   []string `json:"resolvers" yaml:"resolvers" doc:"Resolver names in this phase" maxItems:"1000"`
	Parallelism int      `json:"parallelism" yaml:"parallelism" doc:"Number of resolvers that can execute in parallel" maximum:"1000" example:"4"`
}

PhaseInfo contains information about a phase

type PhaseMetrics

type PhaseMetrics struct {
	Phase    string        `json:"phase" yaml:"phase" doc:"Phase name (resolve, transform, validate)" example:"resolve"`
	Duration time.Duration `json:"duration" yaml:"duration" doc:"Time spent in this phase" example:"100ms"`
	Started  time.Time     `json:"started" yaml:"started" doc:"When phase started"`
	Ended    time.Time     `json:"ended" yaml:"ended" doc:"When phase ended"`
}

PhaseMetrics contains timing information for a single phase

type PhaseTimeoutError

type PhaseTimeoutError struct {
	Phase            int      `json:"phase" yaml:"phase" doc:"Phase number that timed out" minimum:"1" example:"1"`
	ResolversWaiting []string `json:"resolversWaiting" yaml:"resolversWaiting" doc:"Resolvers that were still waiting when timeout occurred"`
}

PhaseTimeoutError represents a timeout during phase execution.

func (*PhaseTimeoutError) Error

func (e *PhaseTimeoutError) Error() string

Error implements the error interface.

type Plan added in v0.7.0

type Plan struct {
	Phase           int      `json:"phase"           yaml:"phase"           doc:"Execution phase number (1-based)" minimum:"1"`
	DependsOn       []string `json:"dependsOn"       yaml:"dependsOn"       doc:"Effective dependencies after provider extraction" maxItems:"1000"`
	DependencyCount int      `json:"dependencyCount" yaml:"dependencyCount" doc:"Number of effective dependencies" minimum:"0"`
}

Plan holds pre-execution static topology data for a single resolver. It is populated after DAG construction and before any resolver executes.

type PlanData added in v0.7.0

type PlanData map[string]Plan

PlanData is a pre-execution snapshot of resolver topology, keyed by resolver name. It is injected into the resolver context as __plan before any resolver executes, making topology data available in when conditions and provider inputs.

func (PlanData) ToMap added in v0.7.0

func (p PlanData) ToMap() map[string]any

ToMap converts PlanData into a map[string]any suitable for injection into the resolver CEL context. Each Plan is converted to a map[string]any with keys "phase", "dependsOn", and "dependencyCount".

type ProgressCallback

type ProgressCallback interface {
	// OnPhaseStart is called when a new execution phase begins
	OnPhaseStart(phaseNum int, resolverNames []string)
	// OnResolverComplete is called when a resolver completes successfully.
	// elapsed is the pure execution time of the resolver.
	OnResolverComplete(resolverName string, elapsed time.Duration)
	// OnResolverFailed is called when a resolver fails
	OnResolverFailed(resolverName string, err error)
	// OnResolverSkipped is called when a resolver is skipped due to when condition
	OnResolverSkipped(resolverName string)
}

ProgressCallback is an interface for receiving execution progress events. Implementations can use this to display progress bars, log events, etc.

type ProviderAttempt

type ProviderAttempt struct {
	Provider   string        `json:"provider" yaml:"provider" doc:"Provider name" example:"http"`
	Phase      string        `json:"phase" yaml:"phase" doc:"Phase where provider was called" example:"resolve"`
	Error      string        `json:"error,omitempty" yaml:"error,omitempty" doc:"Error message if failed" maxLength:"500"`
	Duration   time.Duration `json:"duration" yaml:"duration" doc:"Time spent in this attempt" example:"50ms"`
	OnError    string        `json:"onError,omitempty" yaml:"onError,omitempty" doc:"Error handling behavior" example:"continue"`
	Timestamp  time.Time     `json:"timestamp" yaml:"timestamp" doc:"When the attempt occurred"`
	SourceStep int           `json:"sourceStep" yaml:"sourceStep" doc:"Source/step index in phase (0-based)" example:"0"`
}

ProviderAttempt records a single provider execution attempt

type ProviderSource

type ProviderSource struct {
	spec.CallRef    `yaml:",inline"`
	Provider        string               `` /* 274-byte string literal not displayed */
	Inputs          map[string]*ValueRef `json:"inputs,omitempty" yaml:"inputs,omitempty" doc:"Provider inputs" required:"false"`
	When            *Condition           `json:"when,omitempty" yaml:"when,omitempty" doc:"Source-level condition"`
	ContinueOnError *Condition           `` /* 337-byte string literal not displayed */
	OnError         ErrorBehavior        `` /* 321-byte string literal not displayed */
	ForEach         *ForEachClause       `` /* 162-byte string literal not displayed */
}

ProviderSource represents a single source in the resolve phase

type ProviderTransform

type ProviderTransform struct {
	spec.CallRef    `yaml:",inline"`
	Provider        string               `` /* 268-byte string literal not displayed */
	Inputs          map[string]*ValueRef `json:"inputs,omitempty" yaml:"inputs,omitempty" doc:"Provider inputs" required:"false"`
	When            *Condition           `json:"when,omitempty" yaml:"when,omitempty" doc:"Step-level condition"`
	ContinueOnError *Condition           `` /* 330-byte string literal not displayed */
	OnError         ErrorBehavior        `` /* 230-byte string literal not displayed */
	ForEach         *ForEachClause       `json:"forEach,omitempty" yaml:"forEach,omitempty" doc:"Iterate over array, executing provider for each element"`
}

ProviderTransform represents a single transform step

type ProviderValidation

type ProviderValidation struct {
	spec.CallRef `yaml:",inline"`
	Provider     string               `` /* 275-byte string literal not displayed */
	Inputs       map[string]*ValueRef `json:"inputs,omitempty" yaml:"inputs,omitempty" doc:"Provider inputs" required:"false"`
	When         *Condition           `json:"when,omitempty" yaml:"when,omitempty" doc:"Rule-level condition; the rule is skipped when this evaluates to false"`
	Message      *ValueRef            `json:"message,omitempty" yaml:"message,omitempty" doc:"Error message on validation failure"`
}

ProviderValidation represents a single validation rule

type RedactedError

type RedactedError struct {
	Original error  `json:"-" yaml:"-" doc:"Original unredacted error"`
	Redacted string `json:"error" yaml:"error" doc:"Redacted error message safe for display" maxLength:"100" example:"[REDACTED]"`
}

RedactedError wraps an error to provide a redacted version for sensitive contexts.

func NewRedactedError

func NewRedactedError(original error) *RedactedError

NewRedactedError creates a new RedactedError.

func (*RedactedError) Error

func (e *RedactedError) Error() string

Error returns the redacted error message.

func (*RedactedError) Unwrap

func (e *RedactedError) Unwrap() error

Unwrap returns the original error for privileged access.

type RegistryInterface

type RegistryInterface interface {
	Register(p provider.Provider) error
	Get(name string) (provider.Provider, error)
	List() []provider.Provider
	// DescriptorLookup returns a function that looks up provider descriptors by name.
	// Returns nil if the registry does not support descriptor lookup.
	DescriptorLookup() DescriptorLookup
}

type ResolvePhase

type ResolvePhase struct {
	With  []ProviderSource `json:"with" yaml:"with" doc:"Ordered list of value sources" minItems:"1" maxItems:"200"`
	Until *Condition       `json:"until,omitempty" yaml:"until,omitempty" doc:"Stop condition (default: first non-null)"`
	When  *Condition       `json:"when,omitempty" yaml:"when,omitempty" doc:"Phase-level condition"`
}

ResolvePhase defines how to obtain an initial value

type Resolver

type Resolver struct {
	// Metadata
	Name        string `` /* 233-byte string literal not displayed */
	Description string `` /* 159-byte string literal not displayed */
	DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty" doc:"Display name for UI" maxLength:"80" example:"Environment"`
	Sensitive   bool   `` /* 191-byte string literal not displayed */
	Internal    bool   `` /* 153-byte string literal not displayed */
	Explicit    bool   `json:"explicit,omitempty" yaml:"explicit,omitempty" doc:"If true, resolver only runs when explicitly named on the CLI"`
	Example     any    `json:"example,omitempty" yaml:"example,omitempty" doc:"Example value for documentation"`

	// Type declaration
	Type Type `json:"type,omitempty" yaml:"type,omitempty" doc:"Expected type of the resolved value" example:"string"`

	// Conditional execution
	When *Condition `json:"when,omitempty" yaml:"when,omitempty" doc:"Condition for executing this resolver"`

	// Explicit dependencies
	DependsOn []string `` /* 187-byte string literal not displayed */

	// Timeout
	Timeout *time.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty" doc:"Maximum execution time (default: 30s)" example:"30s"`

	// Immutable locks the resolver's value in state after first execution. On subsequent
	// runs, the resolver still executes but its value is compared against the stored value.
	// If the values differ, execution fails with an error. Use this for non-deterministic
	// values (e.g., UUIDs) that must remain stable across runs. Requires the solution to
	// have a state block configured and enabled. Setting Immutable implies Persist.
	Immutable bool `json:"immutable,omitempty" yaml:"immutable,omitempty" doc:"Lock resolver value in state after first execution" example:"true"`

	// Persist records the resolver's value in state after each successful run so it can be
	// read back on a later run via the state provider. Unlike Immutable, the value is not
	// locked or verified -- it is overwritten with the latest value every run. Persistence
	// does not affect replay, does not skip execution, and does not feed the value back into
	// resolver inputs automatically. Requires the solution to have a state block configured
	// and enabled.
	Persist bool `` /* 159-byte string literal not displayed */

	// Phases
	Resolve   *ResolvePhase   `json:"resolve" yaml:"resolve" doc:"Value resolution phase"`
	Transform *TransformPhase `json:"transform,omitempty" yaml:"transform,omitempty" doc:"Value transformation phase"`
	Validate  *ValidatePhase  `json:"validate,omitempty" yaml:"validate,omitempty" doc:"Value validation phase"`

	// Messages contains user-defined messages shown on resolver outcomes.
	Messages *Messages `json:"messages,omitempty" yaml:"messages,omitempty" doc:"Custom messages for resolver outcomes"`
}

Resolver represents a single resolver definition

func GetResolversInPhase

func GetResolversInPhase(phases []*PhaseGroup, phaseNum int) []*Resolver

GetResolversInPhase returns all resolvers in a specific phase

type ResolverDiff

type ResolverDiff struct {
	Type    DiffType          `json:"type" yaml:"type" doc:"Type of difference (added, removed, modified, unchanged)" maxLength:"16" example:"modified"`
	Before  *SnapshotResolver `json:"before,omitempty" yaml:"before,omitempty" doc:"Value before change"`
	After   *SnapshotResolver `json:"after,omitempty" yaml:"after,omitempty" doc:"Value after change"`
	Changes []FieldChange     `json:"changes,omitempty" yaml:"changes,omitempty" doc:"List of field changes" maxItems:"50"`
}

ResolverDiff represents the difference for a single resolver

type ResolverLike

type ResolverLike interface {
	GetName() string
	GetSensitive() bool
}

ResolverLike is an interface for objects that have a Name and Sensitive flag nolint:revive // ResolverLike name is intentional to indicate compatibility with Resolver type

type ResolverMetric

type ResolverMetric struct {
	Name     string        `json:"name" yaml:"name" doc:"Resolver name" maxLength:"256" example:"api-data"`
	Duration time.Duration `json:"duration,omitempty" yaml:"duration,omitempty" doc:"Execution duration"`
	Size     int64         `json:"size,omitempty" yaml:"size,omitempty" doc:"Value size in bytes" maximum:"1073741824" example:"1024"`
	Phase    int           `json:"phase" yaml:"phase" doc:"Execution phase number" maximum:"100" example:"1"`
}

ResolverMetric contains metrics for a single resolver nolint:revive // ResolverMetric name is intentional for clarity in metrics context

type Snapshot

type Snapshot struct {
	Metadata   SnapshotMetadata             `json:"metadata" yaml:"metadata" doc:"Snapshot metadata"`
	Parameters map[string]any               `json:"parameters" yaml:"parameters" doc:"Parameters used in execution"`
	Resolvers  map[string]*SnapshotResolver `json:"resolvers" yaml:"resolvers" doc:"Resolver execution results"`
	Phases     []SnapshotPhase              `json:"phases" yaml:"phases" doc:"Phase execution information" maxItems:"100"`
}

Snapshot represents a complete snapshot of resolver execution

func CaptureSnapshot

func CaptureSnapshot(
	ctx context.Context,
	solutionName string,
	solutionVersion string,
	buildVersion string,
	parameters map[string]any,
	totalDuration time.Duration,
	overallStatus ExecutionStatus,
) (*Snapshot, error)

CaptureSnapshot creates a snapshot from execution context and results

func LoadSnapshot

func LoadSnapshot(filePath string) (*Snapshot, error)

LoadSnapshot loads a snapshot from a JSON file

type SnapshotDiff

type SnapshotDiff struct {
	Before    *SnapshotMetadata        `json:"before" yaml:"before" doc:"Metadata of the before snapshot"`
	After     *SnapshotMetadata        `json:"after" yaml:"after" doc:"Metadata of the after snapshot"`
	Resolvers map[string]*ResolverDiff `json:"resolvers" yaml:"resolvers" doc:"Differences in resolvers"`
	Summary   *DiffSummary             `json:"summary" yaml:"summary" doc:"Summary of changes"`
}

SnapshotDiff represents the differences between two snapshots

func DiffSnapshots

func DiffSnapshots(before, after *Snapshot) *SnapshotDiff

DiffSnapshots compares two snapshots and returns their differences

func DiffSnapshotsWithOptions

func DiffSnapshotsWithOptions(before, after *Snapshot, opts *DiffOptions) *SnapshotDiff

DiffSnapshotsWithOptions compares two snapshots with custom options

type SnapshotFailedAttempt

type SnapshotFailedAttempt struct {
	Provider   string `json:"provider" yaml:"provider" doc:"Provider name" maxLength:"128" example:"http"`
	SourceStep int    `json:"sourceStep" yaml:"sourceStep" doc:"Source/step index in phase" maximum:"100" example:"0"`
	Error      string `json:"error" yaml:"error" doc:"Error message" maxLength:"4096" example:"timeout"`
	Duration   string `json:"duration" yaml:"duration" doc:"Time spent on this attempt" maxLength:"32" example:"5s"`
	Timestamp  string `json:"timestamp" yaml:"timestamp" doc:"When attempt occurred" maxLength:"64" example:"2026-01-29T12:00:00Z"`
}

SnapshotFailedAttempt contains information about a failed provider attempt

type SnapshotMetadata

type SnapshotMetadata struct {
	Solution      string          `json:"solution" yaml:"solution" doc:"Solution name" maxLength:"512" example:"my-solution"`
	Version       string          `json:"version,omitempty" yaml:"version,omitempty" doc:"Solution version" maxLength:"64" example:"1.0.0"`
	Timestamp     time.Time       `json:"timestamp" yaml:"timestamp" doc:"When snapshot was captured"`
	Runtime       SnapshotRuntime `json:"runtime" yaml:"runtime" doc:"Execution provenance (engine vs invoking CLI)"`
	TotalDuration string          `json:"totalDuration" yaml:"totalDuration" doc:"Total execution duration" maxLength:"32" example:"1.5s"`
	Status        string          `json:"status" yaml:"status" doc:"Overall execution status (success, failed)" maxLength:"32" example:"success"`
}

SnapshotMetadata contains metadata about the snapshot

type SnapshotPhase

type SnapshotPhase struct {
	Phase     int      `json:"phase" yaml:"phase" doc:"Phase number (1-based)" maximum:"100" example:"1"`
	Duration  string   `json:"duration" yaml:"duration" doc:"Phase execution duration" maxLength:"32" example:"500ms"`
	Resolvers []string `json:"resolvers" yaml:"resolvers" doc:"Resolver names in this phase" maxItems:"500"`
}

SnapshotPhase contains information about a phase execution

type SnapshotResolver

type SnapshotResolver struct {
	Value          any                     `json:"value" yaml:"value" doc:"Resolved value (or <redacted> if sensitive)"`
	Status         string                  `json:"status" yaml:"status" doc:"Execution status (success, failed, skipped)" maxLength:"32" example:"success"`
	Phase          int                     `json:"phase" yaml:"phase" doc:"Execution phase number" maximum:"100" example:"1"`
	Duration       string                  `json:"duration" yaml:"duration" doc:"Execution duration" maxLength:"32" example:"250ms"`
	ProviderCalls  int                     `json:"providerCalls" yaml:"providerCalls" doc:"Number of provider calls made" maximum:"1000" example:"3"`
	ValueSizeBytes int64                   `` /* 128-byte string literal not displayed */
	Sensitive      bool                    `json:"sensitive,omitempty" yaml:"sensitive,omitempty" doc:"Whether value was redacted"`
	Error          string                  `json:"error,omitempty" yaml:"error,omitempty" doc:"Error message if failed" maxLength:"4096" example:"connection refused"`
	FailedAttempts []SnapshotFailedAttempt `` /* 129-byte string literal not displayed */
}

SnapshotResolver contains execution result for a single resolver

type SnapshotRuntime added in v0.34.0

type SnapshotRuntime struct {
	Engine SnapshotRuntimeComponent `json:"engine" yaml:"engine" doc:"Execution engine (scafctl library) identity"`
	CLI    SnapshotRuntimeComponent `json:"cli" yaml:"cli" doc:"Invoking CLI/frontend identity"`
}

SnapshotRuntime records execution provenance, separating the engine (scafctl library) identity from the invoking CLI/frontend identity. When scafctl runs directly (not embedded), CLI mirrors Engine.

type SnapshotRuntimeComponent added in v0.34.0

type SnapshotRuntimeComponent struct {
	Name    string `json:"name" yaml:"name" doc:"Component name" maxLength:"64" example:"scafctl"`
	Version string `json:"version" yaml:"version" doc:"Component version" maxLength:"64" example:"0.5.0"`
}

SnapshotRuntimeComponent identifies a named, versioned runtime participant.

type TemplateAccessor added in v0.33.0

type TemplateAccessor struct {
	// Resolver is the name of the resolver containing the accessor.
	Resolver string
	// Step identifies where the accessor was found ("resolve" or "transform").
	Step string
	// Name is the unresolved accessor root name.
	Name string
}

TemplateAccessor identifies a root-level Go template accessor in a resolver's resolve or transform step that does not resolve to any known resolver after accounting for data-input keys and forEach aliases. These are likely typos: at runtime a bare {{ .field }} accessor to an unknown root renders empty rather than failing, so they are surfaced as advisory findings rather than hard errors.

func UnresolvedTemplateAccessors added in v0.33.0

func UnresolvedTemplateAccessors(resolvers []*Resolver) []TemplateAccessor

UnresolvedTemplateAccessors scans the go-template "template" input of every resolve and transform step for root-level accessors that cannot resolve to any known resolver, data-input key, or forEach alias. A step whose data input has a dynamically-determined key set is skipped for bare {{ .field }} accessors (its root may be populated at runtime); {{ ._.name }} explicit resolver references are always checked. The returned accessors are likely typos and are intended to be surfaced as lint findings.

type TransformPhase

type TransformPhase struct {
	With []ProviderTransform `json:"with" yaml:"with" doc:"Ordered list of transformations" minItems:"1" maxItems:"200"`
	When *Condition          `json:"when,omitempty" yaml:"when,omitempty" doc:"Phase-level condition"`
}

TransformPhase defines how to derive a new value

type Type

type Type = spec.Type

Type is an alias to spec.Type for backward compatibility.

type TypeCoercionError

type TypeCoercionError struct {
	ResolverName  string `json:"resolverName" yaml:"resolverName" doc:"Name of the resolver" example:"age-value"`
	Phase         string `json:"phase" yaml:"phase" doc:"Phase where coercion was attempted" enum:"resolve,transform" example:"resolve"`
	SourceType    string `json:"sourceType" yaml:"sourceType" doc:"Original type of the value" example:"string"`
	TargetType    Type   `json:"targetType" yaml:"targetType" doc:"Target type for coercion" example:"int"`
	Cause         error  `json:"-" yaml:"-" doc:"Underlying coercion error"`
	CustomMessage string `json:"customMessage,omitempty" yaml:"customMessage,omitempty" doc:"User-defined error message from messages.error"`
}

TypeCoercionError represents a failure to coerce a value to the expected type.

func (*TypeCoercionError) Error

func (e *TypeCoercionError) Error() string

Error implements the error interface.

func (*TypeCoercionError) Unwrap

func (e *TypeCoercionError) Unwrap() error

Unwrap returns the underlying cause.

type ValidatePartition added in v0.34.0

type ValidatePartition struct {
	// InlineRules holds indices into Validate.With for rules that stay inline.
	InlineRules []int
	// DeferredRules holds indices into Validate.With for rules that defer.
	DeferredRules []int
	// DeferredRefs is the union of foreign resolver references contributed by the
	// deferred rules (and the phase-level when, when it forces deferral). These
	// are used only to order and report deferred validation results; they are
	// never resolution-graph edges.
	DeferredRefs map[string]bool
	// PhaseWhenDeferred indicates the phase-level validate.when referenced a
	// foreign resolver, forcing the whole block to defer.
	PhaseWhenDeferred bool
}

ValidatePartition classifies a resolver's validation rules into those that run inline (during resolution, preserving fail-fast) and those deferred to the post-resolution validation phase.

A rule is inline iff it references nothing but the owning resolver itself (self / __self). Any rule that references another resolver -- in its args, inputs, rule-level when, or message template -- is deferred and excluded from the resolution DAG. A phase-level validate.when that references a foreign resolver forces every rule in the block to defer (PhaseWhenDeferred).

func PartitionValidatePhase added in v0.34.0

func PartitionValidatePhase(r *Resolver, lookup DescriptorLookup) ValidatePartition

PartitionValidatePhase splits a resolver's validate phase into inline and deferred (cross-resolver) rule sets. It is the exported entry point used by tooling such as linters and embedders to reason about which validation rules run in the deferred phase. A nil resolver or nil validate phase yields an empty partition.

func (ValidatePartition) HasDeferred added in v0.34.0

func (p ValidatePartition) HasDeferred() bool

HasDeferred reports whether the partition contains any deferred rules.

type ValidatePhase

type ValidatePhase struct {
	With []ProviderValidation `json:"with" yaml:"with" doc:"Validation rules" minItems:"1" maxItems:"200"`
	When *Condition           `json:"when,omitempty" yaml:"when,omitempty" doc:"Phase-level condition"`
}

ValidatePhase defines validation constraints

type ValidationFailure

type ValidationFailure struct {
	Rule      int    `json:"rule" yaml:"rule" doc:"Rule number (0-indexed) that failed" minimum:"0" example:"0"`
	Provider  string `json:"provider" yaml:"provider" doc:"Validation provider name" example:"validation"`
	Message   string `` /* 129-byte string literal not displayed */
	Cause     error  `json:"-" yaml:"-" doc:"Underlying error from the validation provider"`
	Sensitive bool   `json:"sensitive,omitempty" yaml:"sensitive,omitempty" doc:"Whether this failure contains sensitive information"`
}

ValidationFailure represents a single validation rule failure.

func (*ValidationFailure) Error

func (f *ValidationFailure) Error() string

Error implements the error interface for a single failure.

type ValueRef

type ValueRef = spec.ValueRef

ValueRef is an alias to spec.ValueRef for backward compatibility. It represents a value that can be literal, resolver reference, expression, or template.

type ValueSizeError

type ValueSizeError struct {
	ResolverName string `json:"resolverName" yaml:"resolverName" doc:"Name of the resolver with oversized value" example:"large-data"`
	ActualSize   int64  `json:"actualSize" yaml:"actualSize" doc:"Actual size of the value in bytes" minimum:"0" example:"10485760"`
	MaxSize      int64  `json:"maxSize" yaml:"maxSize" doc:"Maximum allowed size in bytes" minimum:"1" example:"1048576"`
}

ValueSizeError represents a value that exceeds the maximum allowed size.

func (*ValueSizeError) Error

func (e *ValueSizeError) Error() string

Error implements the error interface.

Directories

Path Synopsis
Package refs provides functions for extracting resolver references from Go templates and CEL expressions.
Package refs provides functions for extracting resolver references from Go templates and CEL expressions.

Jump to

Keyboard shortcuts

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