2014-10-22 11:27:51 -07:00
|
|
|
/*
|
|
|
|
Copyright 2014 CoreOS, Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package validate
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"regexp"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
yamlKey = regexp.MustCompile(`^ *-? ?(?P<key>.*?):`)
|
|
|
|
yamlElem = regexp.MustCompile(`^ *-`)
|
|
|
|
)
|
|
|
|
|
|
|
|
type node struct {
|
|
|
|
name string
|
|
|
|
line int
|
|
|
|
children []node
|
|
|
|
field reflect.StructField
|
|
|
|
reflect.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
// Child attempts to find the child with the given name in the node's list of
|
|
|
|
// children. If no such child is found, an invalid node is returned.
|
|
|
|
func (n node) Child(name string) node {
|
|
|
|
for _, c := range n.children {
|
|
|
|
if c.name == name {
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return node{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// HumanType returns the human-consumable string representation of the type of
|
|
|
|
// the node.
|
|
|
|
func (n node) HumanType() string {
|
|
|
|
switch k := n.Kind(); k {
|
|
|
|
case reflect.Slice:
|
|
|
|
c := n.Type().Elem()
|
|
|
|
return "[]" + node{Value: reflect.New(c).Elem()}.HumanType()
|
|
|
|
default:
|
|
|
|
return k.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNode returns the node representation of the given value. The context
|
|
|
|
// will be used in an attempt to determine line numbers for the given value.
|
|
|
|
func NewNode(value interface{}, context context) node {
|
|
|
|
var n node
|
|
|
|
toNode(value, context, &n)
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
// toNode converts the given value into a node and then recursively processes
|
|
|
|
// each of the nodes components (e.g. fields, array elements, keys).
|
|
|
|
func toNode(v interface{}, c context, n *node) {
|
|
|
|
vv := reflect.ValueOf(v)
|
|
|
|
if !vv.IsValid() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
n.Value = vv
|
|
|
|
switch vv.Kind() {
|
|
|
|
case reflect.Struct:
|
|
|
|
// Walk over each field in the structure, skipping unexported fields,
|
|
|
|
// and create a node for it.
|
|
|
|
for i := 0; i < vv.Type().NumField(); i++ {
|
|
|
|
ft := vv.Type().Field(i)
|
|
|
|
k := ft.Tag.Get("yaml")
|
|
|
|
if k == "-" || k == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
cn := node{name: k, field: ft}
|
|
|
|
c, ok := findKey(cn.name, c)
|
|
|
|
if ok {
|
|
|
|
cn.line = c.lineNumber
|
|
|
|
}
|
|
|
|
toNode(vv.Field(i).Interface(), c, &cn)
|
|
|
|
n.children = append(n.children, cn)
|
|
|
|
}
|
|
|
|
case reflect.Map:
|
|
|
|
// Walk over each key in the map and create a node for it.
|
|
|
|
v := v.(map[interface{}]interface{})
|
|
|
|
for k, cv := range v {
|
|
|
|
cn := node{name: fmt.Sprintf("%s", k)}
|
|
|
|
c, ok := findKey(cn.name, c)
|
|
|
|
if ok {
|
|
|
|
cn.line = c.lineNumber
|
|
|
|
}
|
|
|
|
toNode(cv, c, &cn)
|
|
|
|
n.children = append(n.children, cn)
|
|
|
|
}
|
|
|
|
case reflect.Slice:
|
|
|
|
// Walk over each element in the slice and create a node for it.
|
|
|
|
// While iterating over the slice, preserve the context after it
|
|
|
|
// is modified. This allows the line numbers to reflect the current
|
|
|
|
// element instead of the first.
|
|
|
|
for i := 0; i < vv.Len(); i++ {
|
|
|
|
cn := node{
|
|
|
|
name: fmt.Sprintf("%s[%d]", n.name, i),
|
|
|
|
field: n.field,
|
|
|
|
}
|
|
|
|
var ok bool
|
|
|
|
c, ok = findElem(c)
|
|
|
|
if ok {
|
|
|
|
cn.line = c.lineNumber
|
|
|
|
}
|
|
|
|
toNode(vv.Index(i).Interface(), c, &cn)
|
|
|
|
n.children = append(n.children, cn)
|
|
|
|
c.Increment()
|
|
|
|
}
|
2014-11-21 15:43:48 -08:00
|
|
|
case reflect.String, reflect.Int, reflect.Bool, reflect.Float64:
|
2014-10-22 11:27:51 -07:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("toNode(): unhandled kind %s", vv.Kind()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// findKey attempts to find the requested key within the provided context.
|
|
|
|
// A modified copy of the context is returned with every line up to the key
|
|
|
|
// incremented past. A boolean, true if the key was found, is also returned.
|
|
|
|
func findKey(key string, context context) (context, bool) {
|
|
|
|
return find(yamlKey, key, context)
|
|
|
|
}
|
|
|
|
|
|
|
|
// findElem attempts to find an array element within the provided context.
|
|
|
|
// A modified copy of the context is returned with every line up to the array
|
|
|
|
// element incremented past. A boolean, true if the key was found, is also
|
|
|
|
// returned.
|
|
|
|
func findElem(context context) (context, bool) {
|
|
|
|
return find(yamlElem, "", context)
|
|
|
|
}
|
|
|
|
|
|
|
|
func find(exp *regexp.Regexp, key string, context context) (context, bool) {
|
|
|
|
for len(context.currentLine) > 0 || len(context.remainingLines) > 0 {
|
|
|
|
matches := exp.FindStringSubmatch(context.currentLine)
|
|
|
|
if len(matches) > 0 && (key == "" || matches[1] == key) {
|
|
|
|
return context, true
|
|
|
|
}
|
|
|
|
|
|
|
|
context.Increment()
|
|
|
|
}
|
|
|
|
return context, false
|
|
|
|
}
|