This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const CurrentSchemaVersion = 2
|
||||
|
||||
var slugPattern = regexp.MustCompile("^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
||||
|
||||
type Document map[string]any
|
||||
|
||||
func Validate(document Document) error {
|
||||
if document == nil {
|
||||
return errors.New("dashboard document is required")
|
||||
}
|
||||
if version, ok := number(document["schemaVersion"]); !ok || version < 1 || version > CurrentSchemaVersion {
|
||||
return errors.New("unsupported dashboard schema version")
|
||||
}
|
||||
for _, field := range []string{"id", "slug", "name", "scope", "variables", "widgets", "settings"} {
|
||||
if _, ok := document[field]; !ok {
|
||||
return fmt.Errorf("dashboard field %q is required", field)
|
||||
}
|
||||
}
|
||||
slug, ok := document["slug"].(string)
|
||||
if !ok || !slugPattern.MatchString(slug) || len(slug) > 80 {
|
||||
return errors.New("invalid dashboard slug")
|
||||
}
|
||||
name, ok := document["name"].(string)
|
||||
if !ok || name == "" || len(name) > 120 {
|
||||
return errors.New("invalid dashboard name")
|
||||
}
|
||||
scope, ok := document["scope"].(string)
|
||||
if !ok || scope != "personal" && scope != "shared" && scope != "system" {
|
||||
return errors.New("invalid dashboard scope")
|
||||
}
|
||||
if _, ok := document["widgets"].([]any); !ok {
|
||||
return errors.New("dashboard widgets must be an array")
|
||||
}
|
||||
if _, ok := document["variables"].([]any); !ok {
|
||||
return errors.New("dashboard variables must be an array")
|
||||
}
|
||||
if _, ok := document["settings"].(map[string]any); !ok {
|
||||
return errors.New("dashboard settings must be an object")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Migrate(document Document) (Document, error) {
|
||||
if err := Validate(document); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
version, _ := number(document["schemaVersion"])
|
||||
if version == CurrentSchemaVersion {
|
||||
return clone(document), nil
|
||||
}
|
||||
migrated := clone(document)
|
||||
migrated["schemaVersion"] = CurrentSchemaVersion
|
||||
settings := migrated["settings"].(map[string]any)
|
||||
if _, ok := settings["live"]; !ok {
|
||||
settings["live"] = false
|
||||
}
|
||||
if _, ok := settings["refreshSeconds"]; !ok {
|
||||
settings["refreshSeconds"] = 30
|
||||
}
|
||||
return migrated, nil
|
||||
}
|
||||
func clone(document Document) Document {
|
||||
encoded, _ := json.Marshal(document)
|
||||
var result Document
|
||||
_ = json.Unmarshal(encoded, &result)
|
||||
return result
|
||||
}
|
||||
func number(value any) (int, bool) {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return v, true
|
||||
case float64:
|
||||
return int(v), v == float64(int(v))
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user