resolver

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2026 License: Apache-2.0 Imports: 26 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 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 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)"`
}

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.

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'" }

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) 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) 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 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 WithDefaultTimeout

func WithDefaultTimeout(timeout time.Duration) ExecutorOption

WithDefaultTimeout sets the default timeout for individual resolver execution

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 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 {
	Provider string               `` /* 223-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"`
	OnError  ErrorBehavior        `` /* 222-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 {
	Provider string               `` /* 217-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"`
	OnError  ErrorBehavior        `` /* 131-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 {
	Provider string               `` /* 224-byte string literal not displayed */
	Inputs   map[string]*ValueRef `json:"inputs,omitempty" yaml:"inputs,omitempty" doc:"Provider inputs" required:"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:"50"`
	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 */
	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"`

	// 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"`
	ScafctlVersion string    `json:"scafctlVersion" yaml:"scafctlVersion" doc:"scafctl version" maxLength:"64" example:"0.5.0"`
	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 TransformPhase

type TransformPhase struct {
	With []ProviderTransform `json:"with" yaml:"with" doc:"Ordered list of transformations" minItems:"1" maxItems:"50"`
	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 ValidatePhase

type ValidatePhase struct {
	With []ProviderValidation `json:"with" yaml:"with" doc:"Validation rules" minItems:"1" maxItems:"20"`
	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