This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
dagger/pkg/cuetils/scan.go

137 lines
2.7 KiB
Go
Raw Normal View History

package cuetils
import (
"cuelang.org/go/cue"
)
// ScanForInputs walks a Value looking for potential inputs
// - non-concrete values or values with defaults
// - exclude @dagger(computed) and #up
// - exclude values which have references
func ScanForInputs(value cue.Value) ([]cue.Value, error) {
var (
vals []cue.Value
err error
)
// walk before function, bool return is if the walk should recurse again
before := func(v cue.Value) (bool, error) {
// explicit phase
// look for #up
label, _ := v.Label()
if label == "#up" {
return false, nil
}
// look to exclude any @dagger(computed)
attrs := v.Attributes(cue.ValueAttr)
for _, attr := range attrs {
name := attr.Name()
// match `@dagger(...)`
if name == "dagger" {
// loop over args (CSV content in attribute)
for i := 0; i < attr.NumArgs(); i++ {
key, _ := attr.Arg(i)
// we found an explicit computed value
if key == "computed" {
return false, nil
}
}
}
}
// inference phase
switch v.IncompleteKind() {
case cue.StructKind:
return true, nil
case cue.ListKind:
if !v.IsConcrete() {
vals = append(vals, v)
return false, nil
}
return true, nil
default:
// a leaf with default?
_, has := v.Default()
if has {
vals = append(vals, v)
// recurse here?
return false, nil
}
// is this leaf not concrete? (should cause an error)
if v.Validate(cue.Concrete(true), cue.Optional(true)) != nil {
vals = append(vals, v)
}
return false, nil
}
}
// walk
err = walkValue(value, before, nil)
if err != nil {
return nil, err
}
return vals, nil
}
// walkValue is a custome walk function so that we recurse into more types than CUE's buildin walk
// specificially, we need to customize the options to val.Fields when val is a struct
func walkValue(val cue.Value, before func(cue.Value) (bool, error), after func(cue.Value) error) error {
if before != nil {
recurse, err := before(val)
if err != nil {
return err
}
// should we recurse into fields
if recurse {
switch val.IncompleteKind() {
case cue.StructKind:
// provide custom args to ensure we walk nested defs
// and that optionals are included
iter, err := val.Fields(
cue.Definitions(true),
cue.Optional(true),
)
if err != nil {
return err
}
for iter.Next() {
err := walkValue(iter.Value(), before, after)
if err != nil {
return err
}
}
case cue.ListKind:
iter, err := val.List()
if err != nil {
return err
}
for iter.Next() {
err := walkValue(iter.Value(), before, after)
if err != nil {
return err
}
}
}
}
}
if after != nil {
err := after(val)
if err != nil {
return err
}
}
return nil
}