/* Copyright 2018 The Kubernetes Authors. 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 value import ( "encoding/json" "fmt" "gopkg.in/yaml.v2" ) // FromYAML is a helper function for reading a YAML document; it attempts to // preserve order of keys within maps/structs. This is as a convenience to // humans keeping YAML documents, not because there is a behavior difference. // // Known bug: objects with top-level arrays don't parse correctly. func FromYAML(input []byte) (Value, error) { var decoded interface{} if len(input) == 4 && string(input) == "null" { // Special case since the yaml package doesn't accurately // preserve this. return Value{Null: true}, nil } // This attempts to enable order sensitivity; note the yaml package is // broken for documents that have root-level arrays, hence the two-step // approach. TODO: This is a horrific hack. Is it worth it? var ms yaml.MapSlice if err := yaml.Unmarshal(input, &ms); err == nil { decoded = ms } else if err := yaml.Unmarshal(input, &decoded); err != nil { return Value{}, err } v, err := FromUnstructured(decoded) if err != nil { return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input) } return v, nil } // FromJSON is a helper function for reading a JSON document func FromJSON(input []byte) (Value, error) { var decoded interface{} if err := json.Unmarshal(input, &decoded); err != nil { return Value{}, err } v, err := FromUnstructured(decoded) if err != nil { return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input) } return v, nil } // FromUnstructured will convert a go interface to a Value. // It's most commonly expected to be used with map[string]interface{} as the // input. `in` must not have any structures with cycles in them. // yaml.MapSlice may be used for order-preservation. func FromUnstructured(in interface{}) (Value, error) { if in == nil { return Value{Null: true}, nil } switch t := in.(type) { case map[interface{}]interface{}: m := Map{} for rawKey, rawVal := range t { k, ok := rawKey.(string) if !ok { return Value{}, fmt.Errorf("key %#v: not a string", k) } v, err := FromUnstructured(rawVal) if err != nil { return Value{}, fmt.Errorf("key %v: %v", k, err) } m.Set(k, v) } return Value{MapValue: &m}, nil case map[string]interface{}: m := Map{} for k, rawVal := range t { v, err := FromUnstructured(rawVal) if err != nil { return Value{}, fmt.Errorf("key %v: %v", k, err) } m.Set(k, v) } return Value{MapValue: &m}, nil case yaml.MapSlice: m := Map{} for _, item := range t { k, ok := item.Key.(string) if !ok { return Value{}, fmt.Errorf("key %#v is not a string", item.Key) } v, err := FromUnstructured(item.Value) if err != nil { return Value{}, fmt.Errorf("key %v: %v", k, err) } m.Set(k, v) } return Value{MapValue: &m}, nil case []interface{}: l := List{} for i, rawVal := range t { v, err := FromUnstructured(rawVal) if err != nil { return Value{}, fmt.Errorf("index %v: %v", i, err) } l.Items = append(l.Items, v) } return Value{ListValue: &l}, nil case int: n := Int(t) return Value{IntValue: &n}, nil case int8: n := Int(t) return Value{IntValue: &n}, nil case int16: n := Int(t) return Value{IntValue: &n}, nil case int32: n := Int(t) return Value{IntValue: &n}, nil case int64: n := Int(t) return Value{IntValue: &n}, nil case uint: n := Int(t) return Value{IntValue: &n}, nil case uint8: n := Int(t) return Value{IntValue: &n}, nil case uint16: n := Int(t) return Value{IntValue: &n}, nil case uint32: n := Int(t) return Value{IntValue: &n}, nil case float32: f := Float(t) return Value{FloatValue: &f}, nil case float64: f := Float(t) return Value{FloatValue: &f}, nil case string: return StringValue(t), nil case bool: return BooleanValue(t), nil default: return Value{}, fmt.Errorf("type unimplemented: %t", in) } } // ToYAML is a helper function for producing a YAML document; it attempts to // preserve order of keys within maps/structs. This is as a convenience to // humans keeping YAML documents, not because there is a behavior difference. func (v *Value) ToYAML() ([]byte, error) { return yaml.Marshal(v.ToUnstructured(true)) } // ToJSON is a helper function for producing a JSon document. func (v *Value) ToJSON() ([]byte, error) { return json.Marshal(v.ToUnstructured(false)) } // ToUnstructured will convert the Value into a go-typed object. // If preserveOrder is true, then maps will be converted to the yaml.MapSlice // type. Otherwise, map[string]interface{} must be used-- this destroys // ordering information and is not recommended if the result of this will be // serialized. Other types: // * list -> []interface{} // * others -> corresponding go type, wrapped in an interface{} // // Of note, floats and ints will always come out as float64 and int64, // respectively. func (v *Value) ToUnstructured(preserveOrder bool) interface{} { switch { case v.FloatValue != nil: f := float64(*v.FloatValue) return f case v.IntValue != nil: i := int64(*v.IntValue) return i case v.StringValue != nil: return string(*v.StringValue) case v.BooleanValue != nil: return bool(*v.BooleanValue) case v.ListValue != nil: out := []interface{}{} for _, item := range v.ListValue.Items { out = append(out, item.ToUnstructured(preserveOrder)) } return out case v.MapValue != nil: m := v.MapValue if preserveOrder { ms := make(yaml.MapSlice, len(m.Items)) for i := range m.Items { ms[i] = yaml.MapItem{ Key: m.Items[i].Name, Value: m.Items[i].Value.ToUnstructured(preserveOrder), } } return ms } // This case is unavoidably lossy. out := map[string]interface{}{} for i := range m.Items { out[m.Items[i].Name] = m.Items[i].Value.ToUnstructured(preserveOrder) } return out default: fallthrough case v.Null == true: return nil } }