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/compiler/value.go
Andrea Luzzardi b5e2ba63c6 runner: fix empty CUE value check
Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
2021-11-24 16:08:40 -08:00

278 lines
5.8 KiB
Go

package compiler
import (
"sort"
"strconv"
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
cueformat "cuelang.org/go/cue/format"
)
// Value is a wrapper around cue.Value.
// Use instead of cue.Value and cue.Instance
type Value struct {
val cue.Value
cc *Compiler
}
// FillPath fills the value in-place
func (v *Value) FillPath(p cue.Path, x interface{}) error {
v.cc.lock()
defer v.cc.unlock()
// If calling Fill() with a Value, we want to use the underlying
// cue.Value to fill.
if val, ok := x.(*Value); ok {
v.val = v.val.FillPath(p, val.val)
} else {
v.val = v.val.FillPath(p, x)
}
return v.val.Err()
}
// LookupPath is a concurrency safe wrapper around cue.Value.LookupPath
func (v *Value) LookupPath(p cue.Path) *Value {
v.cc.rlock()
defer v.cc.runlock()
return v.cc.Wrap(v.val.LookupPath(p))
}
// Lookup is a helper function to lookup by path parts.
func (v *Value) Lookup(path string) *Value {
return v.LookupPath(cue.ParsePath(path))
}
func (v *Value) ReferencePath() (*Value, cue.Path) {
vv, p := v.val.ReferencePath()
return v.cc.Wrap(vv), p
}
// Proxy function to the underlying cue.Value
func (v *Value) Len() cue.Value {
return v.val.Len()
}
// Proxy function to the underlying cue.Value
func (v *Value) Kind() cue.Kind {
return v.val.Kind()
}
// Proxy function to the underlying cue.Value
func (v *Value) IncompleteKind() cue.Kind {
return v.Cue().IncompleteKind()
}
// Field represents a struct field
type Field struct {
Selector cue.Selector
Value *Value
}
// Label returns the unquoted selector
func (f Field) Label() string {
l := f.Selector.String()
if unquoted, err := strconv.Unquote(l); err == nil {
return unquoted
}
return l
}
// Proxy function to the underlying cue.Value
// Field ordering is guaranteed to be stable.
func (v *Value) Fields(opts ...cue.Option) ([]Field, error) {
it, err := v.val.Fields(opts...)
if err != nil {
return nil, err
}
fields := []Field{}
for it.Next() {
fields = append(fields, Field{
Selector: it.Selector(),
Value: v.cc.Wrap(it.Value()),
})
}
sort.SliceStable(fields, func(i, j int) bool {
return fields[i].Selector.String() < fields[j].Selector.String()
})
return fields, nil
}
// Proxy function to the underlying cue.Value
func (v *Value) Struct() (*cue.Struct, error) {
return v.val.Struct()
}
// Proxy function to the underlying cue.Value
func (v *Value) Exists() bool {
return v.val.Exists()
}
// Proxy function to the underlying cue.Value
func (v *Value) Bytes() ([]byte, error) {
return v.val.Bytes()
}
// Proxy function to the underlying cue.Value
func (v *Value) String() (string, error) {
return v.val.String()
}
// Proxy function to the underlying cue.Value
func (v *Value) Int64() (int64, error) {
return v.val.Int64()
}
// Proxy function to the underlying cue.Value
func (v *Value) Path() cue.Path {
return v.val.Path()
}
// Proxy function to the underlying cue.Value
func (v *Value) Decode(x interface{}) error {
return v.val.Decode(x)
}
func (v *Value) List() ([]*Value, error) {
l := []*Value{}
it, err := v.val.List()
if err != nil {
return nil, err
}
for it.Next() {
l = append(l, v.cc.Wrap(it.Value()))
}
return l, nil
}
func (v *Value) IsConcrete() bool {
return v.val.IsConcrete()
}
// Recursive concreteness check.
func (v *Value) IsConcreteR(opts ...cue.Option) error {
o := []cue.Option{cue.Concrete(true)}
o = append(o, opts...)
return v.val.Validate(o...)
}
func (v *Value) Walk(before func(*Value) bool, after func(*Value)) {
// FIXME: lock?
var (
llBefore func(cue.Value) bool
llAfter func(cue.Value)
)
if before != nil {
llBefore = func(child cue.Value) bool {
return before(v.cc.Wrap(child))
}
}
if after != nil {
llAfter = func(child cue.Value) {
after(v.cc.Wrap(child))
}
}
v.val.Walk(llBefore, llAfter)
}
// Export concrete values to JSON. ignoring non-concrete values.
// Contrast with cue.Value.MarshalJSON which requires all values
// to be concrete.
func (v *Value) JSON() JSON {
cuePathToStrings := func(p cue.Path) []string {
selectors := p.Selectors()
out := make([]string, len(selectors))
for i, sel := range selectors {
out[i] = sel.String()
}
return out
}
var out JSON
v.val.Walk(
func(v cue.Value) bool {
b, err := v.MarshalJSON()
if err == nil {
newOut, err := out.Set(b, cuePathToStrings(v.Path())...)
if err == nil {
out = newOut
}
return false
}
return true
},
nil,
)
out, _ = out.Get(cuePathToStrings(v.Path())...)
return out
}
// Check that a value is valid. Optionally check that it matches
// all the specified spec definitions..
func (v *Value) Validate() error {
return v.val.Validate()
}
// Return cue source for this value
func (v *Value) Source(opts ...cue.Option) ([]byte, error) {
v.cc.rlock()
defer v.cc.runlock()
return cueformat.Node(v.val.Eval().Syntax(opts...),
cueformat.UseSpaces(4),
cueformat.TabIndent(false),
)
}
func (v *Value) Cue() cue.Value {
return v.val
}
// Returns true if value has a dagger attribute (eg. artifact, secret, input)
func (v *Value) HasAttr(filter ...string) bool {
attrs := v.val.Attributes(cue.ValueAttr)
for _, attr := range attrs {
name := attr.Name()
// match `@dagger(...)`
if name == "dagger" {
// did not provide filter, match any @dagger attr
if len(filter) == 0 {
return true
}
// loop over args (CSV content in attribute)
for i := 0; i < attr.NumArgs(); i++ {
key, _ := attr.Arg(i)
// one or several values where provided, filter
for _, val := range filter {
if key == val {
return true
}
}
}
}
}
return false
}
func (v *Value) Dereference() *Value {
dVal := cue.Dereference(v.val)
return v.cc.Wrap(dVal)
}
func (v *Value) Default() (*Value, bool) {
val, hasDef := v.val.Default()
return v.cc.Wrap(val), hasDef
}
func (v *Value) Doc() []*ast.CommentGroup {
return v.Cue().Doc()
}