databricks-cli/libs/dyn/yamlloader/loader.go

231 lines
5.3 KiB
Go
Raw Permalink Normal View History

2023-10-20 12:56:59 +00:00
package yamlloader
import (
"fmt"
"math"
"strconv"
"strings"
"time"
"github.com/databricks/cli/libs/dyn"
2023-10-20 12:56:59 +00:00
"gopkg.in/yaml.v3"
)
type loader struct {
path string
}
func errorf(loc dyn.Location, format string, args ...interface{}) error {
2023-10-20 12:56:59 +00:00
return fmt.Errorf("yaml (%s): %s", loc, fmt.Sprintf(format, args...))
}
func newLoader(path string) *loader {
return &loader{
path: path,
}
}
func (d *loader) location(node *yaml.Node) dyn.Location {
return dyn.Location{
2023-10-20 12:56:59 +00:00
File: d.path,
Line: node.Line,
Column: node.Column,
}
}
func (d *loader) load(node *yaml.Node) (dyn.Value, error) {
loc := dyn.Location{
2023-10-20 12:56:59 +00:00
File: d.path,
Line: node.Line,
Column: node.Column,
}
var value dyn.Value
2023-10-20 12:56:59 +00:00
var err error
switch node.Kind {
case yaml.DocumentNode:
value, err = d.loadDocument(node, loc)
case yaml.SequenceNode:
value, err = d.loadSequence(node, loc)
case yaml.MappingNode:
value, err = d.loadMapping(node, loc)
case yaml.ScalarNode:
value, err = d.loadScalar(node, loc)
case yaml.AliasNode:
value, err = d.loadAlias(node, loc)
default:
return dyn.NilValue, errorf(loc, "unknown node kind: %v", node.Kind)
2023-10-20 12:56:59 +00:00
}
if err != nil {
return value, err
}
// Mark value as anchor if needed.
// If this node doesn't map to a type, we don't need to warn about it.
if node.Anchor != "" {
value = value.MarkAnchor()
}
return value, nil
}
func (d *loader) loadDocument(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
2023-10-20 12:56:59 +00:00
return d.load(node.Content[0])
}
func (d *loader) loadSequence(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
acc := make([]dyn.Value, len(node.Content))
2023-10-20 12:56:59 +00:00
for i, n := range node.Content {
v, err := d.load(n)
if err != nil {
return dyn.NilValue, err
2023-10-20 12:56:59 +00:00
}
acc[i] = v
}
return dyn.NewValue(acc, loc), nil
2023-10-20 12:56:59 +00:00
}
func (d *loader) loadMapping(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
2023-10-20 12:56:59 +00:00
var merge *yaml.Node
acc := dyn.NewMapping()
2023-10-20 12:56:59 +00:00
for i := 0; i < len(node.Content); i += 2 {
key := node.Content[i]
val := node.Content[i+1]
// Assert that keys are strings
if key.Kind != yaml.ScalarNode {
return dyn.NilValue, errorf(loc, "key is not a scalar")
2023-10-20 12:56:59 +00:00
}
st := key.ShortTag()
switch st {
case "!!str":
// OK
case "!!merge":
if merge != nil {
panic("merge node already set")
}
merge = val
continue
default:
return dyn.NilValue, errorf(loc, "invalid key tag: %v", st)
2023-10-20 12:56:59 +00:00
}
k, err := d.load(key)
if err != nil {
return dyn.NilValue, err
}
2023-10-20 12:56:59 +00:00
v, err := d.load(val)
if err != nil {
return dyn.NilValue, err
2023-10-20 12:56:59 +00:00
}
acc.Set(k, v)
2023-10-20 12:56:59 +00:00
}
if merge == nil {
return dyn.NewValue(acc, loc), nil
2023-10-20 12:56:59 +00:00
}
// Build location for the merge node.
var mloc = d.location(merge)
var merr = errorf(mloc, "map merge requires map or sequence of maps as the value")
// Flatten the merge node into a slice of nodes.
// It can be either a single node or a sequence of nodes.
var mnodes []*yaml.Node
switch merge.Kind {
case yaml.SequenceNode:
mnodes = merge.Content
case yaml.AliasNode:
mnodes = []*yaml.Node{merge}
default:
return dyn.NilValue, merr
2023-10-20 12:56:59 +00:00
}
// Build a sequence of values to merge.
// The entries that we already accumulated have precedence.
var seq []dyn.Mapping
2023-10-20 12:56:59 +00:00
for _, n := range mnodes {
v, err := d.load(n)
if err != nil {
return dyn.NilValue, err
2023-10-20 12:56:59 +00:00
}
m, ok := v.AsMap()
if !ok {
return dyn.NilValue, merr
2023-10-20 12:56:59 +00:00
}
seq = append(seq, m)
}
// Append the accumulated entries to the sequence.
seq = append(seq, acc)
out := dyn.NewMapping()
2023-10-20 12:56:59 +00:00
for _, m := range seq {
out.Merge(m)
2023-10-20 12:56:59 +00:00
}
return dyn.NewValue(out, loc), nil
2023-10-20 12:56:59 +00:00
}
func (d *loader) loadScalar(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
2023-10-20 12:56:59 +00:00
st := node.ShortTag()
switch st {
case "!!str":
return dyn.NewValue(node.Value, loc), nil
2023-10-20 12:56:59 +00:00
case "!!bool":
switch strings.ToLower(node.Value) {
case "true":
return dyn.NewValue(true, loc), nil
2023-10-20 12:56:59 +00:00
case "false":
return dyn.NewValue(false, loc), nil
2023-10-20 12:56:59 +00:00
default:
return dyn.NilValue, errorf(loc, "invalid bool value: %v", node.Value)
2023-10-20 12:56:59 +00:00
}
case "!!int":
i64, err := strconv.ParseInt(node.Value, 10, 64)
if err != nil {
return dyn.NilValue, errorf(loc, "invalid int value: %v", node.Value)
2023-10-20 12:56:59 +00:00
}
// Use regular int type instead of int64 if possible.
if i64 >= math.MinInt32 && i64 <= math.MaxInt32 {
return dyn.NewValue(int(i64), loc), nil
2023-10-20 12:56:59 +00:00
}
return dyn.NewValue(i64, loc), nil
2023-10-20 12:56:59 +00:00
case "!!float":
f64, err := strconv.ParseFloat(node.Value, 64)
if err != nil {
return dyn.NilValue, errorf(loc, "invalid float value: %v", node.Value)
2023-10-20 12:56:59 +00:00
}
return dyn.NewValue(f64, loc), nil
2023-10-20 12:56:59 +00:00
case "!!null":
return dyn.NewValue(nil, loc), nil
2023-10-20 12:56:59 +00:00
case "!!timestamp":
// Try a couple of layouts
for _, layout := range []string{
"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
"2006-1-2 15:4:5.999999999", // space separated with no time zone
"2006-1-2", // date only
} {
t, terr := time.Parse(layout, node.Value)
if terr == nil {
return dyn.NewValue(t, loc), nil
2023-10-20 12:56:59 +00:00
}
}
return dyn.NilValue, errorf(loc, "invalid timestamp value: %v", node.Value)
2023-10-20 12:56:59 +00:00
default:
return dyn.NilValue, errorf(loc, "unknown tag: %v", st)
2023-10-20 12:56:59 +00:00
}
}
func (d *loader) loadAlias(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
2023-10-20 12:56:59 +00:00
return d.load(node.Alias)
}