mirror of https://github.com/grafana/grafana
parent
d2b0bad1cf
commit
c5a817194a
@ -0,0 +1,813 @@ |
||||
// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
|
||||
// MIT Licence
|
||||
|
||||
package dynmap |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"strings" |
||||
) |
||||
|
||||
// Error values returned when validation functions fail
|
||||
var ( |
||||
ErrNotNull = errors.New("is not null") |
||||
ErrNotArray = errors.New("Not an array") |
||||
ErrNotNumber = errors.New("not a number") |
||||
ErrNotBool = errors.New("no bool") |
||||
ErrNotObject = errors.New("not an object") |
||||
ErrNotObjectArray = errors.New("not an object array") |
||||
ErrNotString = errors.New("not a string") |
||||
) |
||||
|
||||
type KeyNotFoundError struct { |
||||
Key string |
||||
} |
||||
|
||||
func (k KeyNotFoundError) Error() string { |
||||
if k.Key != "" { |
||||
return fmt.Sprintf("key '%s' not found", k.Key) |
||||
} |
||||
|
||||
return "key not found" |
||||
} |
||||
|
||||
// Value represents an arbitrary JSON value.
|
||||
// It may contain a bool, number, string, object, array or null.
|
||||
type Value struct { |
||||
data interface{} |
||||
exists bool // Used to separate nil and non-existing values
|
||||
} |
||||
|
||||
// Object represents an object JSON object.
|
||||
// It inherets from Value but with an additional method to access
|
||||
// a map representation of it's content. It's useful when iterating.
|
||||
type Object struct { |
||||
Value |
||||
m map[string]*Value |
||||
valid bool |
||||
} |
||||
|
||||
// Returns the golang map.
|
||||
// Needed when iterating through the values of the object.
|
||||
func (v *Object) Map() map[string]*Value { |
||||
return v.m |
||||
} |
||||
|
||||
func NewFromMap(data map[string]interface{}) *Object { |
||||
val := &Value{data: data, exists: true} |
||||
obj, _ := val.Object() |
||||
return obj |
||||
} |
||||
|
||||
func NewObject() *Object { |
||||
val := &Value{data: make(map[string]interface{}), exists: true} |
||||
obj, _ := val.Object() |
||||
return obj |
||||
} |
||||
|
||||
// Creates a new value from an io.reader.
|
||||
// Returns an error if the reader does not contain valid json.
|
||||
// Useful for parsing the body of a net/http response.
|
||||
// Example: NewFromReader(res.Body)
|
||||
func NewValueFromReader(reader io.Reader) (*Value, error) { |
||||
j := new(Value) |
||||
d := json.NewDecoder(reader) |
||||
d.UseNumber() |
||||
err := d.Decode(&j.data) |
||||
return j, err |
||||
} |
||||
|
||||
// Creates a new value from bytes.
|
||||
// Returns an error if the bytes are not valid json.
|
||||
func NewValueFromBytes(b []byte) (*Value, error) { |
||||
r := bytes.NewReader(b) |
||||
return NewValueFromReader(r) |
||||
} |
||||
|
||||
func objectFromValue(v *Value, err error) (*Object, error) { |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
o, err := v.Object() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return o, nil |
||||
} |
||||
|
||||
func NewObjectFromBytes(b []byte) (*Object, error) { |
||||
return objectFromValue(NewValueFromBytes(b)) |
||||
} |
||||
|
||||
func NewObjectFromReader(reader io.Reader) (*Object, error) { |
||||
return objectFromValue(NewValueFromReader(reader)) |
||||
} |
||||
|
||||
// Marshal into bytes.
|
||||
func (v *Value) Marshal() ([]byte, error) { |
||||
return json.Marshal(v.data) |
||||
} |
||||
|
||||
// Get the interyling data as interface
|
||||
func (v *Value) Interface() interface{} { |
||||
return v.data |
||||
} |
||||
|
||||
// Private Get
|
||||
func (v *Value) get(key string) (*Value, error) { |
||||
|
||||
// Assume this is an object
|
||||
obj, err := v.Object() |
||||
|
||||
if err == nil { |
||||
child, ok := obj.Map()[key] |
||||
if ok { |
||||
return child, nil |
||||
} else { |
||||
return nil, KeyNotFoundError{key} |
||||
} |
||||
} |
||||
|
||||
return nil, err |
||||
} |
||||
|
||||
// Private get path
|
||||
func (v *Value) getPath(keys []string) (*Value, error) { |
||||
current := v |
||||
var err error |
||||
for _, key := range keys { |
||||
current, err = current.get(key) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
return current, nil |
||||
} |
||||
|
||||
// Gets the value at key path.
|
||||
// Returns error if the value does not exist.
|
||||
// Consider using the more specific Get<Type>(..) methods instead.
|
||||
// Example:
|
||||
// value, err := GetValue("address", "street")
|
||||
func (v *Object) GetValue(keys ...string) (*Value, error) { |
||||
return v.getPath(keys) |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an object.
|
||||
// Returns error if the value is not a json object.
|
||||
// Example:
|
||||
// object, err := GetObject("person", "address")
|
||||
func (v *Object) GetObject(keys ...string) (*Object, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
obj, err := child.Object() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
return obj, nil |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a string.
|
||||
// Returns error if the value is not a json string.
|
||||
// Example:
|
||||
// string, err := GetString("address", "street")
|
||||
func (v *Object) GetString(keys ...string) (string, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} else { |
||||
return child.String() |
||||
} |
||||
} |
||||
|
||||
func (v *Object) MustGetString(path string, def string) string { |
||||
keys := strings.Split(path, ".") |
||||
if str, err := v.GetString(keys...); err != nil { |
||||
return def |
||||
} else { |
||||
return str |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into null.
|
||||
// Returns error if the value is not json null.
|
||||
// Example:
|
||||
// err := GetNull("address", "street")
|
||||
func (v *Object) GetNull(keys ...string) error { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return child.Null() |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a number.
|
||||
// Returns error if the value is not a json number.
|
||||
// Example:
|
||||
// n, err := GetNumber("address", "street_number")
|
||||
func (v *Object) GetNumber(keys ...string) (json.Number, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} else { |
||||
|
||||
n, err := child.Number() |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} else { |
||||
return n, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a float64.
|
||||
// Returns error if the value is not a json number.
|
||||
// Example:
|
||||
// n, err := GetNumber("address", "street_number")
|
||||
func (v *Object) GetFloat64(keys ...string) (float64, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} else { |
||||
|
||||
n, err := child.Float64() |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} else { |
||||
return n, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a float64.
|
||||
// Returns error if the value is not a json number.
|
||||
// Example:
|
||||
// n, err := GetNumber("address", "street_number")
|
||||
func (v *Object) GetInt64(keys ...string) (int64, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} else { |
||||
|
||||
n, err := child.Int64() |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} else { |
||||
return n, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a float64.
|
||||
// Returns error if the value is not a json number.
|
||||
// Example:
|
||||
// v, err := GetInterface("address", "anything")
|
||||
func (v *Object) GetInterface(keys ...string) (interface{}, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
return child.Interface(), nil |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a bool.
|
||||
// Returns error if the value is not a json boolean.
|
||||
// Example:
|
||||
// married, err := GetBoolean("person", "married")
|
||||
func (v *Object) GetBoolean(keys ...string) (bool, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
return child.Boolean() |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array.
|
||||
// Returns error if the value is not a json array.
|
||||
// Consider using the more specific Get<Type>Array() since it may reduce later type casts.
|
||||
// Example:
|
||||
// friends, err := GetValueArray("person", "friends")
|
||||
// for i, friend := range friends {
|
||||
// ... // friend will be of type Value here
|
||||
// }
|
||||
func (v *Object) GetValueArray(keys ...string) ([]*Value, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
return child.Array() |
||||
|
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of objects.
|
||||
// Returns error if the value is not a json array or if any of the contained objects are not objects.
|
||||
// Example:
|
||||
// friends, err := GetObjectArray("person", "friends")
|
||||
// for i, friend := range friends {
|
||||
// ... // friend will be of type Object here
|
||||
// }
|
||||
func (v *Object) GetObjectArray(keys ...string) ([]*Object, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
array, err := child.Array() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
typedArray := make([]*Object, len(array)) |
||||
|
||||
for index, arrayItem := range array { |
||||
typedArrayItem, err := arrayItem. |
||||
Object() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
typedArray[index] = typedArrayItem |
||||
} |
||||
|
||||
} |
||||
return typedArray, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of string.
|
||||
// Returns error if the value is not a json array or if any of the contained objects are not strings.
|
||||
// Gets the value at key path and attempts to typecast the value into an array of objects.
|
||||
// Returns error if the value is not a json array or if any of the contained objects are not objects.
|
||||
// Example:
|
||||
// friendNames, err := GetStringArray("person", "friend_names")
|
||||
// for i, friendName := range friendNames {
|
||||
// ... // friendName will be of type string here
|
||||
// }
|
||||
func (v *Object) GetStringArray(keys ...string) ([]string, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
array, err := child.Array() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
typedArray := make([]string, len(array)) |
||||
|
||||
for index, arrayItem := range array { |
||||
typedArrayItem, err := arrayItem.String() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
typedArray[index] = typedArrayItem |
||||
} |
||||
|
||||
} |
||||
return typedArray, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of numbers.
|
||||
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
|
||||
// Example:
|
||||
// friendAges, err := GetNumberArray("person", "friend_ages")
|
||||
// for i, friendAge := range friendAges {
|
||||
// ... // friendAge will be of type float64 here
|
||||
// }
|
||||
func (v *Object) GetNumberArray(keys ...string) ([]json.Number, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
array, err := child.Array() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
typedArray := make([]json.Number, len(array)) |
||||
|
||||
for index, arrayItem := range array { |
||||
typedArrayItem, err := arrayItem.Number() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
typedArray[index] = typedArrayItem |
||||
} |
||||
|
||||
} |
||||
return typedArray, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of floats.
|
||||
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
|
||||
func (v *Object) GetFloat64Array(keys ...string) ([]float64, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
array, err := child.Array() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
typedArray := make([]float64, len(array)) |
||||
|
||||
for index, arrayItem := range array { |
||||
typedArrayItem, err := arrayItem.Float64() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
typedArray[index] = typedArrayItem |
||||
} |
||||
|
||||
} |
||||
return typedArray, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of ints.
|
||||
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
|
||||
func (v *Object) GetInt64Array(keys ...string) ([]int64, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
array, err := child.Array() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
typedArray := make([]int64, len(array)) |
||||
|
||||
for index, arrayItem := range array { |
||||
typedArrayItem, err := arrayItem.Int64() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
typedArray[index] = typedArrayItem |
||||
} |
||||
|
||||
} |
||||
return typedArray, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of bools.
|
||||
// Returns error if the value is not a json array or if any of the contained objects are not booleans.
|
||||
func (v *Object) GetBooleanArray(keys ...string) ([]bool, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
array, err := child.Array() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
|
||||
typedArray := make([]bool, len(array)) |
||||
|
||||
for index, arrayItem := range array { |
||||
typedArrayItem, err := arrayItem.Boolean() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
typedArray[index] = typedArrayItem |
||||
} |
||||
|
||||
} |
||||
return typedArray, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of nulls.
|
||||
// Returns length, or an error if the value is not a json array or if any of the contained objects are not nulls.
|
||||
func (v *Object) GetNullArray(keys ...string) (int64, error) { |
||||
child, err := v.getPath(keys) |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} else { |
||||
|
||||
array, err := child.Array() |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} else { |
||||
|
||||
var length int64 = 0 |
||||
|
||||
for _, arrayItem := range array { |
||||
err := arrayItem.Null() |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} else { |
||||
length++ |
||||
} |
||||
|
||||
} |
||||
return length, nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Returns an error if the value is not actually null
|
||||
func (v *Value) Null() error { |
||||
var valid bool |
||||
|
||||
// Check the type of this data
|
||||
switch v.data.(type) { |
||||
case nil: |
||||
valid = v.exists // Valid only if j also exists, since other values could possibly also be nil
|
||||
break |
||||
} |
||||
|
||||
if valid { |
||||
return nil |
||||
} |
||||
|
||||
return ErrNotNull |
||||
|
||||
} |
||||
|
||||
// Attempts to typecast the current value into an array.
|
||||
// Returns error if the current value is not a json array.
|
||||
// Example:
|
||||
// friendsArray, err := friendsValue.Array()
|
||||
func (v *Value) Array() ([]*Value, error) { |
||||
var valid bool |
||||
|
||||
// Check the type of this data
|
||||
switch v.data.(type) { |
||||
case []interface{}: |
||||
valid = true |
||||
break |
||||
} |
||||
|
||||
// Unsure if this is a good way to use slices, it's probably not
|
||||
var slice []*Value |
||||
|
||||
if valid { |
||||
|
||||
for _, element := range v.data.([]interface{}) { |
||||
child := Value{element, true} |
||||
slice = append(slice, &child) |
||||
} |
||||
|
||||
return slice, nil |
||||
} |
||||
|
||||
return slice, ErrNotArray |
||||
|
||||
} |
||||
|
||||
// Attempts to typecast the current value into a number.
|
||||
// Returns error if the current value is not a json number.
|
||||
// Example:
|
||||
// ageNumber, err := ageValue.Number()
|
||||
func (v *Value) Number() (json.Number, error) { |
||||
var valid bool |
||||
|
||||
// Check the type of this data
|
||||
switch v.data.(type) { |
||||
case json.Number: |
||||
valid = true |
||||
break |
||||
} |
||||
|
||||
if valid { |
||||
return v.data.(json.Number), nil |
||||
} |
||||
|
||||
return "", ErrNotNumber |
||||
} |
||||
|
||||
// Attempts to typecast the current value into a float64.
|
||||
// Returns error if the current value is not a json number.
|
||||
// Example:
|
||||
// percentage, err := v.Float64()
|
||||
func (v *Value) Float64() (float64, error) { |
||||
n, err := v.Number() |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
return n.Float64() |
||||
} |
||||
|
||||
// Attempts to typecast the current value into a int64.
|
||||
// Returns error if the current value is not a json number.
|
||||
// Example:
|
||||
// id, err := v.Int64()
|
||||
func (v *Value) Int64() (int64, error) { |
||||
n, err := v.Number() |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
return n.Int64() |
||||
} |
||||
|
||||
// Attempts to typecast the current value into a bool.
|
||||
// Returns error if the current value is not a json boolean.
|
||||
// Example:
|
||||
// marriedBool, err := marriedValue.Boolean()
|
||||
func (v *Value) Boolean() (bool, error) { |
||||
var valid bool |
||||
|
||||
// Check the type of this data
|
||||
switch v.data.(type) { |
||||
case bool: |
||||
valid = true |
||||
break |
||||
} |
||||
|
||||
if valid { |
||||
return v.data.(bool), nil |
||||
} |
||||
|
||||
return false, ErrNotBool |
||||
} |
||||
|
||||
// Attempts to typecast the current value into an object.
|
||||
// Returns error if the current value is not a json object.
|
||||
// Example:
|
||||
// friendObject, err := friendValue.Object()
|
||||
func (v *Value) Object() (*Object, error) { |
||||
|
||||
var valid bool |
||||
|
||||
// Check the type of this data
|
||||
switch v.data.(type) { |
||||
case map[string]interface{}: |
||||
valid = true |
||||
break |
||||
} |
||||
|
||||
if valid { |
||||
obj := new(Object) |
||||
obj.valid = valid |
||||
|
||||
m := make(map[string]*Value) |
||||
|
||||
if valid { |
||||
for key, element := range v.data.(map[string]interface{}) { |
||||
m[key] = &Value{element, true} |
||||
|
||||
} |
||||
} |
||||
|
||||
obj.data = v.data |
||||
obj.m = m |
||||
|
||||
return obj, nil |
||||
} |
||||
|
||||
return nil, ErrNotObject |
||||
} |
||||
|
||||
// Attempts to typecast the current value into an object arrau.
|
||||
// Returns error if the current value is not an array of json objects
|
||||
// Example:
|
||||
// friendObjects, err := friendValues.ObjectArray()
|
||||
func (v *Value) ObjectArray() ([]*Object, error) { |
||||
|
||||
var valid bool |
||||
|
||||
// Check the type of this data
|
||||
switch v.data.(type) { |
||||
case []interface{}: |
||||
valid = true |
||||
break |
||||
} |
||||
|
||||
// Unsure if this is a good way to use slices, it's probably not
|
||||
var slice []*Object |
||||
|
||||
if valid { |
||||
|
||||
for _, element := range v.data.([]interface{}) { |
||||
childValue := Value{element, true} |
||||
childObject, err := childValue.Object() |
||||
|
||||
if err != nil { |
||||
return nil, ErrNotObjectArray |
||||
} |
||||
slice = append(slice, childObject) |
||||
} |
||||
|
||||
return slice, nil |
||||
} |
||||
|
||||
return nil, ErrNotObjectArray |
||||
|
||||
} |
||||
|
||||
// Attempts to typecast the current value into a string.
|
||||
// Returns error if the current value is not a json string
|
||||
// Example:
|
||||
// nameObject, err := nameValue.String()
|
||||
func (v *Value) String() (string, error) { |
||||
var valid bool |
||||
|
||||
// Check the type of this data
|
||||
switch v.data.(type) { |
||||
case string: |
||||
valid = true |
||||
break |
||||
} |
||||
|
||||
if valid { |
||||
return v.data.(string), nil |
||||
} |
||||
|
||||
return "", ErrNotString |
||||
} |
||||
|
||||
// Returns the value a json formatted string.
|
||||
// Note: The method named String() is used by golang's log method for logging.
|
||||
// Example:
|
||||
func (v *Object) String() string { |
||||
|
||||
f, err := json.Marshal(v.data) |
||||
if err != nil { |
||||
return err.Error() |
||||
} |
||||
|
||||
return string(f) |
||||
|
||||
} |
||||
|
||||
func (v *Object) SetValue(key string, value interface{}) *Value { |
||||
data := v.Interface().(map[string]interface{}) |
||||
data[key] = value |
||||
|
||||
return &Value{ |
||||
data: value, |
||||
exists: true, |
||||
} |
||||
} |
@ -0,0 +1,313 @@ |
||||
// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
|
||||
// MIT Licence
|
||||
|
||||
package dynmap |
||||
|
||||
import ( |
||||
"log" |
||||
"testing" |
||||
|
||||
. "github.com/smartystreets/goconvey/convey" |
||||
) |
||||
|
||||
type Assert struct { |
||||
T *testing.T |
||||
} |
||||
|
||||
func NewAssert(t *testing.T) *Assert { |
||||
return &Assert{ |
||||
T: t, |
||||
} |
||||
} |
||||
|
||||
func (assert *Assert) True(value bool, message string) { |
||||
if value == false { |
||||
log.Panicln("Assert: ", message) |
||||
} |
||||
} |
||||
|
||||
func TestFirst(t *testing.T) { |
||||
|
||||
assert := NewAssert(t) |
||||
|
||||
testJSON := `{ |
||||
"name": "anton", |
||||
"age": 29, |
||||
"nothing": null, |
||||
"true": true, |
||||
"false": false, |
||||
"list": [ |
||||
"first", |
||||
"second" |
||||
], |
||||
"list2": [ |
||||
{ |
||||
"street": "Street 42", |
||||
"city": "Stockholm" |
||||
}, |
||||
{ |
||||
"street": "Street 42", |
||||
"city": "Stockholm" |
||||
} |
||||
], |
||||
"address": { |
||||
"street": "Street 42", |
||||
"city": "Stockholm" |
||||
}, |
||||
"country": { |
||||
"name": "Sweden" |
||||
} |
||||
}` |
||||
|
||||
j, err := NewObjectFromBytes([]byte(testJSON)) |
||||
|
||||
a, err := j.GetObject("address") |
||||
assert.True(a != nil && err == nil, "failed to create json from string") |
||||
|
||||
assert.True(err == nil, "failed to create json from string") |
||||
|
||||
s, err := j.GetString("name") |
||||
assert.True(s == "anton" && err == nil, "name should be a string") |
||||
|
||||
s = j.MustGetString("name", "fallback") |
||||
assert.True(s == "anton", "must get string") |
||||
|
||||
s = j.MustGetString("adsasdas", "fallback") |
||||
assert.True(s == "fallback", "must get string return fallback") |
||||
|
||||
s, err = j.GetString("name") |
||||
assert.True(s == "anton" && err == nil, "name shoud match") |
||||
|
||||
s, err = j.GetString("address", "street") |
||||
assert.True(s == "Street 42" && err == nil, "street shoud match") |
||||
//log.Println("s: ", s.String())
|
||||
|
||||
_, err = j.GetNumber("age") |
||||
assert.True(err == nil, "age should be a number") |
||||
|
||||
n, err := j.GetInt64("age") |
||||
assert.True(n == 29 && err == nil, "age mismatch") |
||||
|
||||
ageInterface, err := j.GetInterface("age") |
||||
assert.True(ageInterface != nil, "should be defined") |
||||
assert.True(err == nil, "age interface error") |
||||
|
||||
invalidInterface, err := j.GetInterface("not_existing") |
||||
assert.True(invalidInterface == nil, "should not give error here") |
||||
assert.True(err != nil, "should give error here") |
||||
|
||||
age, err := j.GetValue("age") |
||||
assert.True(age != nil && err == nil, "age should exist") |
||||
|
||||
age2, err := j.GetValue("age2") |
||||
assert.True(age2 == nil && err != nil, "age2 should not exist") |
||||
|
||||
address, err := j.GetObject("address") |
||||
assert.True(address != nil && err == nil, "address should be an object") |
||||
|
||||
//log.Println("address: ", address)
|
||||
|
||||
s, err = address.GetString("street") |
||||
|
||||
addressAsString, err := j.GetString("address") |
||||
assert.True(addressAsString == "" && err != nil, "address should not be an string") |
||||
|
||||
s, err = j.GetString("address", "street") |
||||
assert.True(s == "Street 42" && err == nil, "street mismatching") |
||||
|
||||
s, err = j.GetString("address", "name2") |
||||
assert.True(s == "" && err != nil, "nonexistent string fail") |
||||
|
||||
b, err := j.GetBoolean("true") |
||||
assert.True(b == true && err == nil, "bool true test") |
||||
|
||||
b, err = j.GetBoolean("false") |
||||
assert.True(b == false && err == nil, "bool false test") |
||||
|
||||
b, err = j.GetBoolean("invalid_field") |
||||
assert.True(b == false && err != nil, "bool invalid test") |
||||
|
||||
list, err := j.GetValueArray("list") |
||||
assert.True(list != nil && err == nil, "list should be an array") |
||||
|
||||
list2, err := j.GetValueArray("list2") |
||||
assert.True(list2 != nil && err == nil, "list2 should be an array") |
||||
|
||||
list2Array, err := j.GetValueArray("list2") |
||||
assert.True(err == nil, "List2 should not return error on AsArray") |
||||
assert.True(len(list2Array) == 2, "List2 should should have length 2") |
||||
|
||||
list2Value, err := j.GetValue("list2") |
||||
assert.True(err == nil, "List2 should not return error on value") |
||||
|
||||
list2ObjectArray, err := list2Value.ObjectArray() |
||||
assert.True(err == nil, "list2Value should not return error on ObjectArray") |
||||
assert.True(len(list2ObjectArray) == 2, "list2ObjectArray should should have length 2") |
||||
|
||||
for _, elementValue := range list2Array { |
||||
//assert.True(element.IsObject() == true, "first fail")
|
||||
|
||||
element, err := elementValue.Object() |
||||
|
||||
s, err = element.GetString("street") |
||||
assert.True(s == "Street 42" && err == nil, "second fail") |
||||
} |
||||
|
||||
obj, err := j.GetObject("country") |
||||
assert.True(obj != nil && err == nil, "country should not return error on AsObject") |
||||
for key, value := range obj.Map() { |
||||
|
||||
assert.True(key == "name", "country name key incorrect") |
||||
|
||||
s, err = value.String() |
||||
assert.True(s == "Sweden" && err == nil, "country name should be Sweden") |
||||
} |
||||
} |
||||
|
||||
func TestSecond(t *testing.T) { |
||||
json := ` |
||||
{ |
||||
"data": [ |
||||
{ |
||||
"id": "X999_Y999", |
||||
"from": { |
||||
"name": "Tom Brady", "id": "X12" |
||||
}, |
||||
"message": "Looking forward to 2010!", |
||||
"actions": [ |
||||
{ |
||||
"name": "Comment", |
||||
"link": "http://www.facebook.com/X999/posts/Y999" |
||||
}, |
||||
{ |
||||
"name": "Like", |
||||
"link": "http://www.facebook.com/X999/posts/Y999" |
||||
} |
||||
], |
||||
"type": "status", |
||||
"created_time": "2010-08-02T21:27:44+0000", |
||||
"updated_time": "2010-08-02T21:27:44+0000" |
||||
}, |
||||
{ |
||||
"id": "X998_Y998", |
||||
"from": { |
||||
"name": "Peyton Manning", "id": "X18" |
||||
}, |
||||
"message": "Where's my contract?", |
||||
"actions": [ |
||||
{ |
||||
"name": "Comment", |
||||
"link": "http://www.facebook.com/X998/posts/Y998" |
||||
}, |
||||
{ |
||||
"name": "Like", |
||||
"link": "http://www.facebook.com/X998/posts/Y998" |
||||
} |
||||
], |
||||
"type": "status", |
||||
"created_time": "2010-08-02T21:27:44+0000", |
||||
"updated_time": "2010-08-02T21:27:44+0000" |
||||
} |
||||
] |
||||
}` |
||||
|
||||
assert := NewAssert(t) |
||||
j, err := NewObjectFromBytes([]byte(json)) |
||||
|
||||
assert.True(j != nil && err == nil, "failed to parse json") |
||||
|
||||
dataObject, err := j.GetObject("data") |
||||
assert.True(dataObject == nil && err != nil, "data should not be an object") |
||||
|
||||
dataArray, err := j.GetObjectArray("data") |
||||
assert.True(dataArray != nil && err == nil, "data should be an object array") |
||||
|
||||
for index, dataItem := range dataArray { |
||||
|
||||
if index == 0 { |
||||
id, err := dataItem.GetString("id") |
||||
assert.True(id == "X999_Y999" && err == nil, "item id mismatch") |
||||
|
||||
fromName, err := dataItem.GetString("from", "name") |
||||
assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch") |
||||
|
||||
actions, err := dataItem.GetObjectArray("actions") |
||||
|
||||
for index, action := range actions { |
||||
|
||||
if index == 1 { |
||||
name, err := action.GetString("name") |
||||
assert.True(name == "Like" && err == nil, "name mismatch") |
||||
|
||||
link, err := action.GetString("link") |
||||
assert.True(link == "http://www.facebook.com/X999/posts/Y999" && err == nil, "Like mismatch") |
||||
|
||||
} |
||||
|
||||
} |
||||
} else if index == 1 { |
||||
id, err := dataItem.GetString("id") |
||||
assert.True(id == "X998_Y998" && err == nil, "item id mismatch") |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
func TestErrors(t *testing.T) { |
||||
json := ` |
||||
{ |
||||
"string": "hello", |
||||
"number": 1, |
||||
"array": [1,2,3] |
||||
}` |
||||
|
||||
errstr := "expected an error getting %s, but got '%s'" |
||||
|
||||
j, err := NewObjectFromBytes([]byte(json)) |
||||
if err != nil { |
||||
t.Fatal("failed to parse json") |
||||
} |
||||
|
||||
if _, err = j.GetObject("string"); err != ErrNotObject { |
||||
t.Errorf(errstr, "object", err) |
||||
} |
||||
|
||||
if err = j.GetNull("string"); err != ErrNotNull { |
||||
t.Errorf(errstr, "null", err) |
||||
} |
||||
|
||||
if _, err = j.GetStringArray("string"); err != ErrNotArray { |
||||
t.Errorf(errstr, "array", err) |
||||
} |
||||
|
||||
if _, err = j.GetStringArray("array"); err != ErrNotString { |
||||
t.Errorf(errstr, "string array", err) |
||||
} |
||||
|
||||
if _, err = j.GetNumber("array"); err != ErrNotNumber { |
||||
t.Errorf(errstr, "number", err) |
||||
} |
||||
|
||||
if _, err = j.GetBoolean("array"); err != ErrNotBool { |
||||
t.Errorf(errstr, "boolean", err) |
||||
} |
||||
|
||||
if _, err = j.GetString("number"); err != ErrNotString { |
||||
t.Errorf(errstr, "string", err) |
||||
} |
||||
|
||||
_, err = j.GetString("not_found") |
||||
if e, ok := err.(KeyNotFoundError); !ok { |
||||
t.Errorf(errstr, "key not found error", e) |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestWriting(t *testing.T) { |
||||
Convey("When writing", t, func() { |
||||
j, _ := NewObjectFromBytes([]byte(`{}`)) |
||||
j.SetValue("prop", "value") |
||||
So(j.MustGetString("prop", ""), ShouldEqual, "value") |
||||
}) |
||||
} |
@ -0,0 +1,130 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/components/dynmap" |
||||
"github.com/grafana/grafana/pkg/log" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
) |
||||
|
||||
type ImportDashboardCommand struct { |
||||
Path string `json:"string"` |
||||
Inputs []ImportDashboardInput `json:"inputs"` |
||||
|
||||
OrgId int64 `json:"-"` |
||||
UserId int64 `json:"-"` |
||||
PluginId string `json:"-"` |
||||
Result *PluginDashboardInfoDTO |
||||
} |
||||
|
||||
type ImportDashboardInput struct { |
||||
Type string `json:"type"` |
||||
PluginId string `json:"pluginId"` |
||||
Name string `json:"name"` |
||||
Value string `json:"value"` |
||||
} |
||||
|
||||
func init() { |
||||
bus.AddHandler("plugins", ImportDashboard) |
||||
} |
||||
|
||||
func ImportDashboard(cmd *ImportDashboardCommand) error { |
||||
plugin, exists := Plugins[cmd.PluginId] |
||||
|
||||
if !exists { |
||||
return PluginNotFoundError{cmd.PluginId} |
||||
} |
||||
|
||||
var dashboard *m.Dashboard |
||||
var err error |
||||
|
||||
if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil { |
||||
return err |
||||
} |
||||
|
||||
saveCmd := m.SaveDashboardCommand{ |
||||
Dashboard: dashboard.Data, |
||||
OrgId: cmd.OrgId, |
||||
UserId: cmd.UserId, |
||||
} |
||||
|
||||
if err := bus.Dispatch(&saveCmd); err != nil { |
||||
return err |
||||
} |
||||
|
||||
cmd.Result = &PluginDashboardInfoDTO{ |
||||
PluginId: cmd.PluginId, |
||||
Title: dashboard.Title, |
||||
Path: cmd.Path, |
||||
Revision: dashboard.GetString("revision", "1.0"), |
||||
InstalledUri: "db/" + saveCmd.Result.Slug, |
||||
InstalledRevision: dashboard.GetString("revision", "1.0"), |
||||
Installed: true, |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
type DashTemplateEvaluator struct { |
||||
template *dynmap.Object |
||||
inputs []ImportDashboardInput |
||||
variables map[string]string |
||||
result *dynmap.Object |
||||
} |
||||
|
||||
// func (this *DashTemplateEvaluator) getObject(path string) map[string]interface{} {
|
||||
// if obj, exists := this.template[path]; exists {
|
||||
// return obj.(map[string]interface{})
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (this *DashTemplateEvaluator) findInput(varName string, varDef *dynmap.Object) *ImportDashboardInput { |
||||
inputType, _ := varDef.GetString("type") |
||||
|
||||
for _, input := range this.inputs { |
||||
if inputType == input.Type && (input.Name == varName || input.Name == "*") { |
||||
return &input |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (this *DashTemplateEvaluator) Eval() (*dynmap.Object, error) { |
||||
this.result = dynmap.NewObject() |
||||
this.variables = make(map[string]string) |
||||
|
||||
// check that we have all inputs we need
|
||||
if requiredInputs, err := this.template.GetObject("__inputs"); err == nil { |
||||
for varName, value := range requiredInputs.Map() { |
||||
varDef, _ := value.Object() |
||||
input := this.findInput(varName, varDef) |
||||
this.variables[varName] = input.Value |
||||
} |
||||
} |
||||
|
||||
this.EvalObject(this.template, this.result) |
||||
return this.result, nil |
||||
} |
||||
|
||||
func (this *DashTemplateEvaluator) EvalObject(source *dynmap.Object, writer *dynmap.Object) { |
||||
|
||||
for key, value := range source.Map() { |
||||
if key == "__input" { |
||||
continue |
||||
} |
||||
|
||||
goValue := value.Interface() |
||||
switch goValue.(type) { |
||||
case string: |
||||
writer.SetValue(key, goValue) |
||||
case map[string]interface{}: |
||||
childSource, _ := value.Object() |
||||
childWriter, _ := writer.SetValue(key, map[string]interface{}{}).Object() |
||||
this.EvalObject(childSource, childWriter) |
||||
default: |
||||
log.Error(3, "Unknown json type key: %v , type: %v", key, goValue) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,81 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/components/dynmap" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
. "github.com/smartystreets/goconvey/convey" |
||||
"gopkg.in/ini.v1" |
||||
) |
||||
|
||||
func TestDashboardImport(t *testing.T) { |
||||
|
||||
Convey("When importing plugin dashboard", t, func() { |
||||
setting.Cfg = ini.Empty() |
||||
sec, _ := setting.Cfg.NewSection("plugin.test-app") |
||||
sec.NewKey("path", "../../tests/test-app") |
||||
err := Init() |
||||
|
||||
So(err, ShouldBeNil) |
||||
|
||||
var importedDash *m.Dashboard |
||||
bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error { |
||||
importedDash = cmd.GetDashboardModel() |
||||
cmd.Result = importedDash |
||||
return nil |
||||
}) |
||||
|
||||
cmd := ImportDashboardCommand{ |
||||
PluginId: "test-app", |
||||
Path: "dashboards/connections.json", |
||||
OrgId: 1, |
||||
UserId: 1, |
||||
Inputs: []ImportDashboardInput{}, |
||||
} |
||||
|
||||
err = ImportDashboard(&cmd) |
||||
So(err, ShouldBeNil) |
||||
|
||||
Convey("should install dashboard", func() { |
||||
So(importedDash, ShouldNotBeNil) |
||||
rows := importedDash.Data["rows"].([]interface{}) |
||||
row1 := rows[0].(map[string]interface{}) |
||||
panels := row1["panels"].([]interface{}) |
||||
panel := panels[0].(map[string]interface{}) |
||||
|
||||
So(panel["datasource"], ShouldEqual, "graphite") |
||||
So(importedDash.Data["__inputs"], ShouldBeNil) |
||||
}) |
||||
}) |
||||
|
||||
Convey("When evaling dashboard template", t, func() { |
||||
template, _ := dynmap.NewObjectFromBytes([]byte(`{ |
||||
"__input": { |
||||
"graphite": { |
||||
"type": "datasource" |
||||
} |
||||
}, |
||||
"test": { |
||||
"prop": "$__graphite" |
||||
} |
||||
}`)) |
||||
|
||||
evaluator := &DashTemplateEvaluator{ |
||||
template: template, |
||||
inputs: []ImportDashboardInput{ |
||||
{Name: "*", Type: "datasource", Value: "my-server"}, |
||||
}, |
||||
} |
||||
|
||||
res, err := evaluator.Eval() |
||||
So(err, ShouldBeNil) |
||||
|
||||
Convey("should render template", func() { |
||||
So(res.MustGetString("test.prop", ""), ShouldEqual, "my-server") |
||||
}) |
||||
}) |
||||
|
||||
} |
@ -1,57 +0,0 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
) |
||||
|
||||
type InstallPluginDashboardCommand struct { |
||||
Path string `json:"string"` |
||||
Inputs map[string]interface{} `json:"inputs"` |
||||
|
||||
OrgId int64 `json:"-"` |
||||
UserId int64 `json:"-"` |
||||
PluginId string `json:"-"` |
||||
Result *PluginDashboardInfoDTO |
||||
} |
||||
|
||||
func init() { |
||||
bus.AddHandler("plugins", InstallPluginDashboard) |
||||
} |
||||
|
||||
func InstallPluginDashboard(cmd *InstallPluginDashboardCommand) error { |
||||
plugin, exists := Plugins[cmd.PluginId] |
||||
|
||||
if !exists { |
||||
return PluginNotFoundError{cmd.PluginId} |
||||
} |
||||
|
||||
var dashboard *m.Dashboard |
||||
var err error |
||||
|
||||
if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil { |
||||
return err |
||||
} |
||||
|
||||
saveCmd := m.SaveDashboardCommand{ |
||||
Dashboard: dashboard.Data, |
||||
OrgId: cmd.OrgId, |
||||
UserId: cmd.UserId, |
||||
} |
||||
|
||||
if err := bus.Dispatch(&saveCmd); err != nil { |
||||
return err |
||||
} |
||||
|
||||
cmd.Result = &PluginDashboardInfoDTO{ |
||||
PluginId: cmd.PluginId, |
||||
Title: dashboard.Title, |
||||
Path: cmd.Path, |
||||
Revision: dashboard.GetString("revision", "1.0"), |
||||
InstalledUri: "db/" + saveCmd.Result.Slug, |
||||
InstalledRevision: dashboard.GetString("revision", "1.0"), |
||||
Installed: true, |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,5 +1,24 @@ |
||||
{ |
||||
"__inputs": { |
||||
"graphite": { |
||||
"type": "datasource", |
||||
"pluginId": "graphite", |
||||
"description": "Graphite datasource" |
||||
} |
||||
}, |
||||
|
||||
"title": "Nginx Connections", |
||||
"revision": "1.5", |
||||
"schemaVersion": 11 |
||||
"schemaVersion": 11, |
||||
|
||||
"rows": [ |
||||
{ |
||||
"panels": [ |
||||
{ |
||||
"type": "graph", |
||||
"datasource": "$__graphite" |
||||
} |
||||
] |
||||
} |
||||
] |
||||
} |
||||
|
Loading…
Reference in new issue