Merge branch 'dashboard-install'

This commit is contained in:
Torkel Ödegaard
2016-03-12 10:13:59 +01:00
64 changed files with 4434 additions and 382 deletions
+1 -1
View File
@@ -176,7 +176,7 @@ func Register(r *macaron.Macaron) {
r.Get("/", wrap(GetPluginList))
r.Get("/dashboards/:pluginId", wrap(GetPluginDashboards))
r.Post("/dashboards/install", bind(dtos.InstallPluginDashboardCmd{}), wrap(InstallPluginDashboard))
r.Post("/dashboards/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
r.Get("/:pluginId/settings", wrap(GetPluginSettingById))
r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
+5 -2
View File
@@ -130,11 +130,14 @@ func handleGetNamespaces(req *cwRequest, c *middleware.Context) {
for key := range metricsMap {
keys = append(keys, key)
}
if customMetricsNamespaces, ok := req.DataSource.JsonData["customMetricsNamespaces"].(string); ok {
for _, key := range strings.Split(customMetricsNamespaces, ",") {
customNamespaces := req.DataSource.JsonData.Get("customMetricsNamespaces").MustString()
if customNamespaces != "" {
for _, key := range strings.Split(customNamespaces, ",") {
keys = append(keys, key)
}
}
sort.Sort(sort.StringSlice(keys))
result := []interface{}{}
-2
View File
@@ -53,7 +53,6 @@ func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapsho
}
func GetDashboardSnapshot(c *middleware.Context) {
key := c.Params(":key")
query := &m.GetDashboardSnapshotQuery{Key: key}
@@ -136,5 +135,4 @@ func SearchDashboardSnapshots(c *middleware.Context) Response {
}
return Json(200, dtos)
//return Json(200, searchQuery.Result)
}
+18 -17
View File
@@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
@@ -52,26 +53,26 @@ type DashboardMeta struct {
}
type DashboardFullWithMeta struct {
Meta DashboardMeta `json:"meta"`
Dashboard map[string]interface{} `json:"dashboard"`
Meta DashboardMeta `json:"meta"`
Dashboard *simplejson.Json `json:"dashboard"`
}
type DataSource struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData,omitempty"`
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData,omitempty"`
}
type MetricQueryResultDto struct {
+5 -5
View File
@@ -26,9 +26,9 @@ type PluginListItem struct {
Info *plugins.PluginInfo `json:"info"`
}
type InstallPluginDashboardCmd struct {
PluginId string `json:"pluginId"`
Path string `json:"path"`
Reinstall bool `json:"reinstall"`
Inputs map[string]interface{} `json:"inputs"`
type ImportDashboardCommand struct {
PluginId string `json:"pluginId"`
Path string `json:"path"`
Reinstall bool `json:"reinstall"`
Inputs []plugins.ImportDashboardInput `json:"inputs"`
}
+1 -1
View File
@@ -59,7 +59,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
defaultDatasource = ds.Name
}
if len(ds.JsonData) > 0 {
if len(ds.JsonData.MustMap()) > 0 {
dsMap["jsonData"] = ds.JsonData
}
+2 -2
View File
@@ -122,9 +122,9 @@ func GetPluginDashboards(c *middleware.Context) Response {
}
}
func InstallPluginDashboard(c *middleware.Context, apiCmd dtos.InstallPluginDashboardCmd) Response {
func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response {
cmd := plugins.InstallPluginDashboardCommand{
cmd := plugins.ImportDashboardCommand{
OrgId: c.OrgId,
UserId: c.UserId,
PluginId: apiCmd.PluginId,
+817
View File
@@ -0,0 +1,817 @@
// 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
}
func (v *Value) StringMap() map[string]interface{} {
return v.data.(map[string]interface{})
}
// 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,
}
}
+313
View File
@@ -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")
})
}
+468
View File
@@ -0,0 +1,468 @@
package simplejson
import (
"bytes"
"encoding/json"
"errors"
"log"
)
// returns the current implementation version
func Version() string {
return "0.5.0"
}
type Json struct {
data interface{}
}
func (j *Json) FromDB(data []byte) error {
j.data = make(map[string]interface{})
dec := json.NewDecoder(bytes.NewBuffer(data))
dec.UseNumber()
return dec.Decode(&j.data)
}
func (j *Json) ToDB() ([]byte, error) {
if j == nil || j.data == nil {
return nil, nil
}
return j.Encode()
}
// NewJson returns a pointer to a new `Json` object
// after unmarshaling `body` bytes
func NewJson(body []byte) (*Json, error) {
j := new(Json)
err := j.UnmarshalJSON(body)
if err != nil {
return nil, err
}
return j, nil
}
// New returns a pointer to a new, empty `Json` object
func New() *Json {
return &Json{
data: make(map[string]interface{}),
}
}
// New returns a pointer to a new, empty `Json` object
func NewFromAny(data interface{}) *Json {
return &Json{data: data}
}
// Interface returns the underlying data
func (j *Json) Interface() interface{} {
return j.data
}
// Encode returns its marshaled data as `[]byte`
func (j *Json) Encode() ([]byte, error) {
return j.MarshalJSON()
}
// EncodePretty returns its marshaled data as `[]byte` with indentation
func (j *Json) EncodePretty() ([]byte, error) {
return json.MarshalIndent(&j.data, "", " ")
}
// Implements the json.Marshaler interface.
func (j *Json) MarshalJSON() ([]byte, error) {
return json.Marshal(&j.data)
}
// Set modifies `Json` map by `key` and `value`
// Useful for changing single key/value in a `Json` object easily.
func (j *Json) Set(key string, val interface{}) {
m, err := j.Map()
if err != nil {
return
}
m[key] = val
}
// SetPath modifies `Json`, recursively checking/creating map keys for the supplied path,
// and then finally writing in the value
func (j *Json) SetPath(branch []string, val interface{}) {
if len(branch) == 0 {
j.data = val
return
}
// in order to insert our branch, we need map[string]interface{}
if _, ok := (j.data).(map[string]interface{}); !ok {
// have to replace with something suitable
j.data = make(map[string]interface{})
}
curr := j.data.(map[string]interface{})
for i := 0; i < len(branch)-1; i++ {
b := branch[i]
// key exists?
if _, ok := curr[b]; !ok {
n := make(map[string]interface{})
curr[b] = n
curr = n
continue
}
// make sure the value is the right sort of thing
if _, ok := curr[b].(map[string]interface{}); !ok {
// have to replace with something suitable
n := make(map[string]interface{})
curr[b] = n
}
curr = curr[b].(map[string]interface{})
}
// add remaining k/v
curr[branch[len(branch)-1]] = val
}
// Del modifies `Json` map by deleting `key` if it is present.
func (j *Json) Del(key string) {
m, err := j.Map()
if err != nil {
return
}
delete(m, key)
}
// Get returns a pointer to a new `Json` object
// for `key` in its `map` representation
//
// useful for chaining operations (to traverse a nested JSON):
// js.Get("top_level").Get("dict").Get("value").Int()
func (j *Json) Get(key string) *Json {
m, err := j.Map()
if err == nil {
if val, ok := m[key]; ok {
return &Json{val}
}
}
return &Json{nil}
}
// GetPath searches for the item as specified by the branch
// without the need to deep dive using Get()'s.
//
// js.GetPath("top_level", "dict")
func (j *Json) GetPath(branch ...string) *Json {
jin := j
for _, p := range branch {
jin = jin.Get(p)
}
return jin
}
// GetIndex returns a pointer to a new `Json` object
// for `index` in its `array` representation
//
// this is the analog to Get when accessing elements of
// a json array instead of a json object:
// js.Get("top_level").Get("array").GetIndex(1).Get("key").Int()
func (j *Json) GetIndex(index int) *Json {
a, err := j.Array()
if err == nil {
if len(a) > index {
return &Json{a[index]}
}
}
return &Json{nil}
}
// CheckGet returns a pointer to a new `Json` object and
// a `bool` identifying success or failure
//
// useful for chained operations when success is important:
// if data, ok := js.Get("top_level").CheckGet("inner"); ok {
// log.Println(data)
// }
func (j *Json) CheckGet(key string) (*Json, bool) {
m, err := j.Map()
if err == nil {
if val, ok := m[key]; ok {
return &Json{val}, true
}
}
return nil, false
}
// Map type asserts to `map`
func (j *Json) Map() (map[string]interface{}, error) {
if m, ok := (j.data).(map[string]interface{}); ok {
return m, nil
}
return nil, errors.New("type assertion to map[string]interface{} failed")
}
// Array type asserts to an `array`
func (j *Json) Array() ([]interface{}, error) {
if a, ok := (j.data).([]interface{}); ok {
return a, nil
}
return nil, errors.New("type assertion to []interface{} failed")
}
// Bool type asserts to `bool`
func (j *Json) Bool() (bool, error) {
if s, ok := (j.data).(bool); ok {
return s, nil
}
return false, errors.New("type assertion to bool failed")
}
// String type asserts to `string`
func (j *Json) String() (string, error) {
if s, ok := (j.data).(string); ok {
return s, nil
}
return "", errors.New("type assertion to string failed")
}
// Bytes type asserts to `[]byte`
func (j *Json) Bytes() ([]byte, error) {
if s, ok := (j.data).(string); ok {
return []byte(s), nil
}
return nil, errors.New("type assertion to []byte failed")
}
// StringArray type asserts to an `array` of `string`
func (j *Json) StringArray() ([]string, error) {
arr, err := j.Array()
if err != nil {
return nil, err
}
retArr := make([]string, 0, len(arr))
for _, a := range arr {
if a == nil {
retArr = append(retArr, "")
continue
}
s, ok := a.(string)
if !ok {
return nil, err
}
retArr = append(retArr, s)
}
return retArr, nil
}
// MustArray guarantees the return of a `[]interface{}` (with optional default)
//
// useful when you want to interate over array values in a succinct manner:
// for i, v := range js.Get("results").MustArray() {
// fmt.Println(i, v)
// }
func (j *Json) MustArray(args ...[]interface{}) []interface{} {
var def []interface{}
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustArray() received too many arguments %d", len(args))
}
a, err := j.Array()
if err == nil {
return a
}
return def
}
// MustMap guarantees the return of a `map[string]interface{}` (with optional default)
//
// useful when you want to interate over map values in a succinct manner:
// for k, v := range js.Get("dictionary").MustMap() {
// fmt.Println(k, v)
// }
func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} {
var def map[string]interface{}
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustMap() received too many arguments %d", len(args))
}
a, err := j.Map()
if err == nil {
return a
}
return def
}
// MustString guarantees the return of a `string` (with optional default)
//
// useful when you explicitly want a `string` in a single value return context:
// myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default"))
func (j *Json) MustString(args ...string) string {
var def string
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustString() received too many arguments %d", len(args))
}
s, err := j.String()
if err == nil {
return s
}
return def
}
// MustStringArray guarantees the return of a `[]string` (with optional default)
//
// useful when you want to interate over array values in a succinct manner:
// for i, s := range js.Get("results").MustStringArray() {
// fmt.Println(i, s)
// }
func (j *Json) MustStringArray(args ...[]string) []string {
var def []string
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustStringArray() received too many arguments %d", len(args))
}
a, err := j.StringArray()
if err == nil {
return a
}
return def
}
// MustInt guarantees the return of an `int` (with optional default)
//
// useful when you explicitly want an `int` in a single value return context:
// myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150))
func (j *Json) MustInt(args ...int) int {
var def int
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustInt() received too many arguments %d", len(args))
}
i, err := j.Int()
if err == nil {
return i
}
return def
}
// MustFloat64 guarantees the return of a `float64` (with optional default)
//
// useful when you explicitly want a `float64` in a single value return context:
// myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150))
func (j *Json) MustFloat64(args ...float64) float64 {
var def float64
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustFloat64() received too many arguments %d", len(args))
}
f, err := j.Float64()
if err == nil {
return f
}
return def
}
// MustBool guarantees the return of a `bool` (with optional default)
//
// useful when you explicitly want a `bool` in a single value return context:
// myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(true))
func (j *Json) MustBool(args ...bool) bool {
var def bool
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustBool() received too many arguments %d", len(args))
}
b, err := j.Bool()
if err == nil {
return b
}
return def
}
// MustInt64 guarantees the return of an `int64` (with optional default)
//
// useful when you explicitly want an `int64` in a single value return context:
// myFunc(js.Get("param1").MustInt64(), js.Get("optional_param").MustInt64(5150))
func (j *Json) MustInt64(args ...int64) int64 {
var def int64
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustInt64() received too many arguments %d", len(args))
}
i, err := j.Int64()
if err == nil {
return i
}
return def
}
// MustUInt64 guarantees the return of an `uint64` (with optional default)
//
// useful when you explicitly want an `uint64` in a single value return context:
// myFunc(js.Get("param1").MustUint64(), js.Get("optional_param").MustUint64(5150))
func (j *Json) MustUint64(args ...uint64) uint64 {
var def uint64
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustUint64() received too many arguments %d", len(args))
}
i, err := j.Uint64()
if err == nil {
return i
}
return def
}
@@ -0,0 +1,89 @@
// +build go1.1
package simplejson
import (
"bytes"
"encoding/json"
"errors"
"io"
"reflect"
"strconv"
)
// Implements the json.Unmarshaler interface.
func (j *Json) UnmarshalJSON(p []byte) error {
dec := json.NewDecoder(bytes.NewBuffer(p))
dec.UseNumber()
return dec.Decode(&j.data)
}
// NewFromReader returns a *Json by decoding from an io.Reader
func NewFromReader(r io.Reader) (*Json, error) {
j := new(Json)
dec := json.NewDecoder(r)
dec.UseNumber()
err := dec.Decode(&j.data)
return j, err
}
// Float64 coerces into a float64
func (j *Json) Float64() (float64, error) {
switch j.data.(type) {
case json.Number:
return j.data.(json.Number).Float64()
case float32, float64:
return reflect.ValueOf(j.data).Float(), nil
case int, int8, int16, int32, int64:
return float64(reflect.ValueOf(j.data).Int()), nil
case uint, uint8, uint16, uint32, uint64:
return float64(reflect.ValueOf(j.data).Uint()), nil
}
return 0, errors.New("invalid value type")
}
// Int coerces into an int
func (j *Json) Int() (int, error) {
switch j.data.(type) {
case json.Number:
i, err := j.data.(json.Number).Int64()
return int(i), err
case float32, float64:
return int(reflect.ValueOf(j.data).Float()), nil
case int, int8, int16, int32, int64:
return int(reflect.ValueOf(j.data).Int()), nil
case uint, uint8, uint16, uint32, uint64:
return int(reflect.ValueOf(j.data).Uint()), nil
}
return 0, errors.New("invalid value type")
}
// Int64 coerces into an int64
func (j *Json) Int64() (int64, error) {
switch j.data.(type) {
case json.Number:
return j.data.(json.Number).Int64()
case float32, float64:
return int64(reflect.ValueOf(j.data).Float()), nil
case int, int8, int16, int32, int64:
return reflect.ValueOf(j.data).Int(), nil
case uint, uint8, uint16, uint32, uint64:
return int64(reflect.ValueOf(j.data).Uint()), nil
}
return 0, errors.New("invalid value type")
}
// Uint64 coerces into an uint64
func (j *Json) Uint64() (uint64, error) {
switch j.data.(type) {
case json.Number:
return strconv.ParseUint(j.data.(json.Number).String(), 10, 64)
case float32, float64:
return uint64(reflect.ValueOf(j.data).Float()), nil
case int, int8, int16, int32, int64:
return uint64(reflect.ValueOf(j.data).Int()), nil
case uint, uint8, uint16, uint32, uint64:
return reflect.ValueOf(j.data).Uint(), nil
}
return 0, errors.New("invalid value type")
}
@@ -0,0 +1,248 @@
package simplejson
import (
"encoding/json"
"testing"
"github.com/bmizerany/assert"
)
func TestSimplejson(t *testing.T) {
var ok bool
var err error
js, err := NewJson([]byte(`{
"test": {
"string_array": ["asdf", "ghjk", "zxcv"],
"string_array_null": ["abc", null, "efg"],
"array": [1, "2", 3],
"arraywithsubs": [{"subkeyone": 1},
{"subkeytwo": 2, "subkeythree": 3}],
"int": 10,
"float": 5.150,
"string": "simplejson",
"bool": true,
"sub_obj": {"a": 1}
}
}`))
assert.NotEqual(t, nil, js)
assert.Equal(t, nil, err)
_, ok = js.CheckGet("test")
assert.Equal(t, true, ok)
_, ok = js.CheckGet("missing_key")
assert.Equal(t, false, ok)
aws := js.Get("test").Get("arraywithsubs")
assert.NotEqual(t, nil, aws)
var awsval int
awsval, _ = aws.GetIndex(0).Get("subkeyone").Int()
assert.Equal(t, 1, awsval)
awsval, _ = aws.GetIndex(1).Get("subkeytwo").Int()
assert.Equal(t, 2, awsval)
awsval, _ = aws.GetIndex(1).Get("subkeythree").Int()
assert.Equal(t, 3, awsval)
i, _ := js.Get("test").Get("int").Int()
assert.Equal(t, 10, i)
f, _ := js.Get("test").Get("float").Float64()
assert.Equal(t, 5.150, f)
s, _ := js.Get("test").Get("string").String()
assert.Equal(t, "simplejson", s)
b, _ := js.Get("test").Get("bool").Bool()
assert.Equal(t, true, b)
mi := js.Get("test").Get("int").MustInt()
assert.Equal(t, 10, mi)
mi2 := js.Get("test").Get("missing_int").MustInt(5150)
assert.Equal(t, 5150, mi2)
ms := js.Get("test").Get("string").MustString()
assert.Equal(t, "simplejson", ms)
ms2 := js.Get("test").Get("missing_string").MustString("fyea")
assert.Equal(t, "fyea", ms2)
ma2 := js.Get("test").Get("missing_array").MustArray([]interface{}{"1", 2, "3"})
assert.Equal(t, ma2, []interface{}{"1", 2, "3"})
msa := js.Get("test").Get("string_array").MustStringArray()
assert.Equal(t, msa[0], "asdf")
assert.Equal(t, msa[1], "ghjk")
assert.Equal(t, msa[2], "zxcv")
msa2 := js.Get("test").Get("string_array").MustStringArray([]string{"1", "2", "3"})
assert.Equal(t, msa2[0], "asdf")
assert.Equal(t, msa2[1], "ghjk")
assert.Equal(t, msa2[2], "zxcv")
msa3 := js.Get("test").Get("missing_array").MustStringArray([]string{"1", "2", "3"})
assert.Equal(t, msa3, []string{"1", "2", "3"})
mm2 := js.Get("test").Get("missing_map").MustMap(map[string]interface{}{"found": false})
assert.Equal(t, mm2, map[string]interface{}{"found": false})
strs, err := js.Get("test").Get("string_array").StringArray()
assert.Equal(t, err, nil)
assert.Equal(t, strs[0], "asdf")
assert.Equal(t, strs[1], "ghjk")
assert.Equal(t, strs[2], "zxcv")
strs2, err := js.Get("test").Get("string_array_null").StringArray()
assert.Equal(t, err, nil)
assert.Equal(t, strs2[0], "abc")
assert.Equal(t, strs2[1], "")
assert.Equal(t, strs2[2], "efg")
gp, _ := js.GetPath("test", "string").String()
assert.Equal(t, "simplejson", gp)
gp2, _ := js.GetPath("test", "int").Int()
assert.Equal(t, 10, gp2)
assert.Equal(t, js.Get("test").Get("bool").MustBool(), true)
js.Set("float2", 300.0)
assert.Equal(t, js.Get("float2").MustFloat64(), 300.0)
js.Set("test2", "setTest")
assert.Equal(t, "setTest", js.Get("test2").MustString())
js.Del("test2")
assert.NotEqual(t, "setTest", js.Get("test2").MustString())
js.Get("test").Get("sub_obj").Set("a", 2)
assert.Equal(t, 2, js.Get("test").Get("sub_obj").Get("a").MustInt())
js.GetPath("test", "sub_obj").Set("a", 3)
assert.Equal(t, 3, js.GetPath("test", "sub_obj", "a").MustInt())
}
func TestStdlibInterfaces(t *testing.T) {
val := new(struct {
Name string `json:"name"`
Params *Json `json:"params"`
})
val2 := new(struct {
Name string `json:"name"`
Params *Json `json:"params"`
})
raw := `{"name":"myobject","params":{"string":"simplejson"}}`
assert.Equal(t, nil, json.Unmarshal([]byte(raw), val))
assert.Equal(t, "myobject", val.Name)
assert.NotEqual(t, nil, val.Params.data)
s, _ := val.Params.Get("string").String()
assert.Equal(t, "simplejson", s)
p, err := json.Marshal(val)
assert.Equal(t, nil, err)
assert.Equal(t, nil, json.Unmarshal(p, val2))
assert.Equal(t, val, val2) // stable
}
func TestSet(t *testing.T) {
js, err := NewJson([]byte(`{}`))
assert.Equal(t, nil, err)
js.Set("baz", "bing")
s, err := js.GetPath("baz").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bing", s)
}
func TestReplace(t *testing.T) {
js, err := NewJson([]byte(`{}`))
assert.Equal(t, nil, err)
err = js.UnmarshalJSON([]byte(`{"baz":"bing"}`))
assert.Equal(t, nil, err)
s, err := js.GetPath("baz").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bing", s)
}
func TestSetPath(t *testing.T) {
js, err := NewJson([]byte(`{}`))
assert.Equal(t, nil, err)
js.SetPath([]string{"foo", "bar"}, "baz")
s, err := js.GetPath("foo", "bar").String()
assert.Equal(t, nil, err)
assert.Equal(t, "baz", s)
}
func TestSetPathNoPath(t *testing.T) {
js, err := NewJson([]byte(`{"some":"data","some_number":1.0,"some_bool":false}`))
assert.Equal(t, nil, err)
f := js.GetPath("some_number").MustFloat64(99.0)
assert.Equal(t, f, 1.0)
js.SetPath([]string{}, map[string]interface{}{"foo": "bar"})
s, err := js.GetPath("foo").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bar", s)
f = js.GetPath("some_number").MustFloat64(99.0)
assert.Equal(t, f, 99.0)
}
func TestPathWillAugmentExisting(t *testing.T) {
js, err := NewJson([]byte(`{"this":{"a":"aa","b":"bb","c":"cc"}}`))
assert.Equal(t, nil, err)
js.SetPath([]string{"this", "d"}, "dd")
cases := []struct {
path []string
outcome string
}{
{
path: []string{"this", "a"},
outcome: "aa",
},
{
path: []string{"this", "b"},
outcome: "bb",
},
{
path: []string{"this", "c"},
outcome: "cc",
},
{
path: []string{"this", "d"},
outcome: "dd",
},
}
for _, tc := range cases {
s, err := js.GetPath(tc.path...).String()
assert.Equal(t, nil, err)
assert.Equal(t, tc.outcome, s)
}
}
func TestPathWillOverwriteExisting(t *testing.T) {
// notice how "a" is 0.1 - but then we'll try to set at path a, foo
js, err := NewJson([]byte(`{"this":{"a":0.1,"b":"bb","c":"cc"}}`))
assert.Equal(t, nil, err)
js.SetPath([]string{"this", "a", "foo"}, "bar")
s, err := js.GetPath("this", "a", "foo").String()
assert.Equal(t, nil, err)
assert.Equal(t, "bar", s)
}
+9 -5
View File
@@ -1,6 +1,10 @@
package models
import "time"
import (
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
)
// DashboardSnapshot model
type DashboardSnapshot struct {
@@ -17,7 +21,7 @@ type DashboardSnapshot struct {
Created time.Time
Updated time.Time
Dashboard map[string]interface{}
Dashboard *simplejson.Json
}
// DashboardSnapshotDTO without dashboard map
@@ -40,9 +44,9 @@ type DashboardSnapshotDTO struct {
// COMMANDS
type CreateDashboardSnapshotCommand struct {
Dashboard map[string]interface{} `json:"dashboard" binding:"Required"`
Name string `json:"name" binding:"Required"`
Expires int64 `json:"expires"`
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
Name string `json:"name" binding:"Required"`
Expires int64 `json:"expires"`
// these are passed when storing an external snapshot ref
External bool `json:"external"`
+21 -31
View File
@@ -6,6 +6,7 @@ import (
"time"
"github.com/gosimple/slug"
"github.com/grafana/grafana/pkg/components/simplejson"
)
// Typed errors
@@ -37,14 +38,14 @@ type Dashboard struct {
CreatedBy int64
Title string
Data map[string]interface{}
Data *simplejson.Json
}
// NewDashboard creates a new dashboard
func NewDashboard(title string) *Dashboard {
dash := &Dashboard{}
dash.Data = make(map[string]interface{})
dash.Data["title"] = title
dash.Data = simplejson.New()
dash.Data.Set("title", title)
dash.Title = title
dash.Created = time.Now()
dash.Updated = time.Now()
@@ -54,34 +55,24 @@ func NewDashboard(title string) *Dashboard {
// GetTags turns the tags in data json into go string array
func (dash *Dashboard) GetTags() []string {
jsonTags := dash.Data["tags"]
if jsonTags == nil || jsonTags == "" {
return []string{}
}
arr := jsonTags.([]interface{})
b := make([]string, len(arr))
for i := range arr {
b[i] = arr[i].(string)
}
return b
return dash.Data.Get("tags").MustStringArray()
}
func NewDashboardFromJson(data map[string]interface{}) *Dashboard {
func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
dash := &Dashboard{}
dash.Data = data
dash.Title = dash.Data["title"].(string)
dash.Title = dash.Data.Get("title").MustString()
dash.UpdateSlug()
if dash.Data["id"] != nil {
dash.Id = int64(dash.Data["id"].(float64))
if id, err := dash.Data.Get("id").Float64(); err == nil {
dash.Id = int64(id)
if dash.Data["version"] != nil {
dash.Version = int(dash.Data["version"].(float64))
if version, err := dash.Data.Get("version").Float64(); err == nil {
dash.Version = int(version)
dash.Updated = time.Now()
}
} else {
dash.Data["version"] = 0
dash.Data.Set("version", 0)
dash.Created = time.Now()
dash.Updated = time.Now()
}
@@ -92,9 +83,11 @@ func NewDashboardFromJson(data map[string]interface{}) *Dashboard {
// GetDashboardModel turns the command into the savable model
func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
dash := NewDashboardFromJson(cmd.Dashboard)
if dash.Data["version"] == 0 {
if dash.Data.Get("version").MustInt(0) == 0 {
dash.CreatedBy = cmd.UserId
}
dash.UpdatedBy = cmd.UserId
dash.OrgId = cmd.OrgId
dash.UpdateSlug()
@@ -103,15 +96,12 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
// GetString a
func (dash *Dashboard) GetString(prop string, defaultValue string) string {
if val, exists := dash.Data[prop]; exists {
return val.(string)
}
return defaultValue
return dash.Data.Get(prop).MustString(defaultValue)
}
// UpdateSlug updates the slug
func (dash *Dashboard) UpdateSlug() {
title := strings.ToLower(dash.Data["title"].(string))
title := strings.ToLower(dash.Data.Get("title").MustString())
dash.Slug = slug.Make(title)
}
@@ -120,10 +110,10 @@ func (dash *Dashboard) UpdateSlug() {
//
type SaveDashboardCommand struct {
Dashboard map[string]interface{} `json:"dashboard" binding:"Required"`
UserId int64 `json:"userId"`
OrgId int64 `json:"-"`
Overwrite bool `json:"overwrite"`
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
UserId int64 `json:"userId"`
OrgId int64 `json:"-"`
Overwrite bool `json:"overwrite"`
Result *Dashboard
}
+4 -4
View File
@@ -3,6 +3,7 @@ package models
import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
. "github.com/smartystreets/goconvey/convey"
)
@@ -16,12 +17,11 @@ func TestDashboardModel(t *testing.T) {
})
Convey("Given a dashboard json", t, func() {
json := map[string]interface{}{
"title": "test dash",
}
json := simplejson.New()
json.Set("title", "test dash")
Convey("With tags as string value", func() {
json["tags"] = ""
json.Set("tags", "")
dash := NewDashboardFromJson(json)
So(len(dash.GetTags()), ShouldEqual, 0)
+29 -27
View File
@@ -3,6 +3,8 @@ package models
import (
"errors"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
)
const (
@@ -42,7 +44,7 @@ type DataSource struct {
BasicAuthPassword string
WithCredentials bool
IsDefault bool
JsonData map[string]interface{}
JsonData *simplejson.Json
Created time.Time
Updated time.Time
@@ -74,19 +76,19 @@ func IsKnownDataSourcePlugin(dsType string) bool {
// Also acts as api DTO
type AddDataSourceCommand struct {
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
Database string `json:"database"`
User string `json:"user"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
Database string `json:"database"`
User string `json:"user"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
OrgId int64 `json:"-"`
@@ -95,19 +97,19 @@ type AddDataSourceCommand struct {
// Also acts as api DTO
type UpdateDataSourceCommand struct {
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData map[string]interface{} `json:"jsonData"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
OrgId int64 `json:"-"`
Id int64 `json:"-"`
+171
View File
@@ -0,0 +1,171 @@
package plugins
import (
"encoding/json"
"fmt"
"regexp"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"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"`
}
type DashboardInputMissingError struct {
VariableName string
}
func (e DashboardInputMissingError) Error() string {
return fmt.Sprintf("Dashbord input variable: %v missing from import command", e.VariableName)
}
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
}
evaluator := &DashTemplateEvaluator{
template: dashboard.Data,
inputs: cmd.Inputs,
}
generatedDash, err := evaluator.Eval()
if err != nil {
return err
}
saveCmd := m.SaveDashboardCommand{
Dashboard: generatedDash,
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 *simplejson.Json
inputs []ImportDashboardInput
variables map[string]string
result *simplejson.Json
varRegex *regexp.Regexp
}
func (this *DashTemplateEvaluator) findInput(varName string, varDef *simplejson.Json) *ImportDashboardInput {
inputType := varDef.Get("type").MustString()
for _, input := range this.inputs {
if inputType == input.Type && (input.Name == varName || input.Name == "*") {
return &input
}
}
return nil
}
func (this *DashTemplateEvaluator) Eval() (*simplejson.Json, error) {
this.result = simplejson.New()
this.variables = make(map[string]string)
this.varRegex, _ = regexp.Compile("\\$__(\\w+)")
// check that we have all inputs we need
if inputDefs := this.template.Get("__inputs"); inputDefs != nil {
for varName, value := range inputDefs.MustMap() {
input := this.findInput(varName, simplejson.NewFromAny(value))
if input == nil {
return nil, &DashboardInputMissingError{VariableName: varName}
}
this.variables["$__"+varName] = input.Value
}
} else {
log.Info("Import: dashboard has no __import section")
}
return simplejson.NewFromAny(this.evalObject(this.template)), nil
}
func (this *DashTemplateEvaluator) evalValue(source *simplejson.Json) interface{} {
sourceValue := source.Interface()
switch v := sourceValue.(type) {
case string:
interpolated := this.varRegex.ReplaceAllStringFunc(v, func(match string) string {
return this.variables[match]
})
return interpolated
case bool:
return v
case json.Number:
return v
case map[string]interface{}:
return this.evalObject(source)
case []interface{}:
array := make([]interface{}, 0)
for _, item := range v {
array = append(array, this.evalValue(simplejson.NewFromAny(item)))
}
return array
}
return nil
}
func (this *DashTemplateEvaluator) evalObject(source *simplejson.Json) interface{} {
result := make(map[string]interface{})
for key, value := range source.MustMap() {
if key == "__inputs" {
continue
}
result[key] = this.evalValue(simplejson.NewFromAny(value))
}
return result
}
+93
View File
@@ -0,0 +1,93 @@
package plugins
import (
"io/ioutil"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
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{
{Name: "*", Type: "datasource", Value: "graphite"},
},
}
err = ImportDashboard(&cmd)
So(err, ShouldBeNil)
Convey("should install dashboard", func() {
So(importedDash, ShouldNotBeNil)
resultStr, _ := importedDash.Data.EncodePretty()
expectedBytes, _ := ioutil.ReadFile("../../tests/test-app/dashboards/connections_result.json")
expectedJson, _ := simplejson.NewJson(expectedBytes)
expectedStr, _ := expectedJson.EncodePretty()
So(string(resultStr), ShouldEqual, string(expectedStr))
panel := importedDash.Data.Get("rows").GetIndex(0).Get("panels").GetIndex(0)
So(panel.Get("datasource").MustString(), ShouldEqual, "graphite")
})
})
Convey("When evaling dashboard template", t, func() {
template, _ := simplejson.NewJson([]byte(`{
"__inputs": {
"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.GetPath("test", "prop").MustString(), ShouldEqual, "my-server")
})
Convey("should not include inputs in output", func() {
inputs := res.Get("__inputs")
So(inputs.Interface(), ShouldBeNil)
})
})
}
-57
View File
@@ -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
}
+3 -5
View File
@@ -1,11 +1,11 @@
package plugins
import (
"encoding/json"
"os"
"path/filepath"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
@@ -52,10 +52,8 @@ func loadPluginDashboard(plugin *PluginBase, path string) (*m.Dashboard, error)
defer reader.Close()
jsonParser := json.NewDecoder(reader)
var data map[string]interface{}
if err := jsonParser.Decode(&data); err != nil {
data, err := simplejson.NewFromReader(reader)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -23,7 +23,7 @@ func TestPluginDashboards(t *testing.T) {
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
if query.Slug == "nginx-connections" {
dash := m.NewDashboard("Nginx Connections")
dash.Data["revision"] = "1.1"
dash.Data.Set("revision", "1.1")
query.Result = dash
return nil
}
+3 -5
View File
@@ -1,12 +1,12 @@
package search
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
)
@@ -120,10 +120,8 @@ func loadDashboardFromFile(filename string) (*JsonDashIndexItem, error) {
}
defer reader.Close()
jsonParser := json.NewDecoder(reader)
var data map[string]interface{}
if err := jsonParser.Decode(&data); err != nil {
data, err := simplejson.NewFromReader(reader)
if err != nil {
return nil, err
}
+2 -2
View File
@@ -69,7 +69,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
affectedRows, err = sess.Insert(dash)
} else {
dash.Version += 1
dash.Data["version"] = dash.Version
dash.Data.Set("version", dash.Version)
affectedRows, err = sess.Id(dash.Id).Update(dash)
}
@@ -108,7 +108,7 @@ func GetDashboard(query *m.GetDashboardQuery) error {
return m.ErrDashboardNotFound
}
dashboard.Data["id"] = dashboard.Id
dashboard.Data.Set("id", dashboard.Id)
query.Result = &dashboard
return nil
@@ -5,6 +5,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
@@ -16,9 +17,9 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
Convey("Given saved snaphot", func() {
cmd := m.CreateDashboardSnapshotCommand{
Key: "hej",
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"hello": "mupp",
},
}),
}
err := CreateDashboardSnapshot(&cmd)
So(err, ShouldBeNil)
@@ -29,7 +30,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
So(query.Result.Dashboard["hello"], ShouldEqual, "mupp")
So(query.Result.Dashboard.Get("hello").MustString(), ShouldEqual, "mupp")
})
})
+9 -8
View File
@@ -5,6 +5,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
)
@@ -12,11 +13,11 @@ import (
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
cmd := m.SaveDashboardCommand{
OrgId: orgId,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
},
}),
}
err := SaveDashboard(&cmd)
@@ -58,11 +59,11 @@ func TestDashboardDataAccess(t *testing.T) {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Overwrite: true,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(123412321),
"title": "Expect error",
"tags": []interface{}{},
},
}),
}
err := SaveDashboard(&cmd)
@@ -76,11 +77,11 @@ func TestDashboardDataAccess(t *testing.T) {
cmd := m.SaveDashboardCommand{
OrgId: 2,
Overwrite: true,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(query.Result.Id),
"title": "Expect error",
"tags": []interface{}{},
},
}),
}
err := SaveDashboard(&cmd)
@@ -135,11 +136,11 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should not be able to save dashboard with same name", func() {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Dashboard: map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": "test dash 23",
"tags": []interface{}{},
},
}),
}
err := SaveDashboard(&cmd)