238 lines
6.3 KiB
Go
238 lines
6.3 KiB
Go
|
// Copyright (c) 2016 Arista Networks, Inc.
|
||
|
// Use of this source code is governed by the Apache License 2.0
|
||
|
// that can be found in the COPYING file.
|
||
|
|
||
|
package openconfig
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/openconfig/reference/rpc/openconfig"
|
||
|
)
|
||
|
|
||
|
// joinPath builds a string out of an Element
|
||
|
func joinPath(path *openconfig.Path) string {
|
||
|
if path == nil {
|
||
|
return ""
|
||
|
}
|
||
|
return strings.Join(path.Element, "/")
|
||
|
}
|
||
|
|
||
|
func convertUpdate(update *openconfig.Update) (interface{}, error) {
|
||
|
switch update.Value.Type {
|
||
|
case openconfig.Type_JSON:
|
||
|
var value interface{}
|
||
|
decoder := json.NewDecoder(bytes.NewReader(update.Value.Value))
|
||
|
decoder.UseNumber()
|
||
|
if err := decoder.Decode(&value); err != nil {
|
||
|
return nil, fmt.Errorf("Malformed JSON update %q in %s",
|
||
|
update.Value.Value, update)
|
||
|
}
|
||
|
return value, nil
|
||
|
case openconfig.Type_BYTES:
|
||
|
return update.Value.Value, nil
|
||
|
default:
|
||
|
return nil,
|
||
|
fmt.Errorf("Unhandled type of value %v in %s", update.Value.Type, update)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NotificationToJSON converts a Notification into a JSON string
|
||
|
func NotificationToJSON(notif *openconfig.Notification) (string, error) {
|
||
|
m := make(map[string]interface{}, 1)
|
||
|
m["timestamp"] = notif.Timestamp
|
||
|
m["path"] = "/" + joinPath(notif.Prefix)
|
||
|
if len(notif.Update) != 0 {
|
||
|
updates := make(map[string]interface{}, len(notif.Update))
|
||
|
var err error
|
||
|
for _, update := range notif.Update {
|
||
|
updates[joinPath(update.Path)], err = convertUpdate(update)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
}
|
||
|
m["updates"] = updates
|
||
|
}
|
||
|
if len(notif.Delete) != 0 {
|
||
|
deletes := make([]string, len(notif.Delete))
|
||
|
for i, del := range notif.Delete {
|
||
|
deletes[i] = joinPath(del)
|
||
|
}
|
||
|
m["deletes"] = deletes
|
||
|
}
|
||
|
m = map[string]interface{}{"notification": m}
|
||
|
js, err := json.MarshalIndent(m, "", " ")
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return string(js), nil
|
||
|
}
|
||
|
|
||
|
// SubscribeResponseToJSON converts a SubscribeResponse into a JSON string
|
||
|
func SubscribeResponseToJSON(resp *openconfig.SubscribeResponse) (string, error) {
|
||
|
m := make(map[string]interface{}, 1)
|
||
|
var err error
|
||
|
switch resp := resp.Response.(type) {
|
||
|
case *openconfig.SubscribeResponse_Update:
|
||
|
return NotificationToJSON(resp.Update)
|
||
|
case *openconfig.SubscribeResponse_Heartbeat:
|
||
|
m["heartbeat"] = resp.Heartbeat.Interval
|
||
|
case *openconfig.SubscribeResponse_SyncResponse:
|
||
|
m["syncResponse"] = resp.SyncResponse
|
||
|
default:
|
||
|
return "", fmt.Errorf("Unknown type of response: %T: %s", resp, resp)
|
||
|
}
|
||
|
js, err := json.MarshalIndent(m, "", " ")
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return string(js), nil
|
||
|
}
|
||
|
|
||
|
// EscapeFunc is the escaping method for attribute names
|
||
|
type EscapeFunc func(k string) string
|
||
|
|
||
|
// escapeValue looks for maps in an interface and escapes their keys
|
||
|
func escapeValue(value interface{}, escape EscapeFunc) interface{} {
|
||
|
valueMap, ok := value.(map[string]interface{})
|
||
|
if !ok {
|
||
|
return value
|
||
|
}
|
||
|
escapedMap := make(map[string]interface{}, len(valueMap))
|
||
|
for k, v := range valueMap {
|
||
|
escapedKey := escape(k)
|
||
|
escapedMap[escapedKey] = escapeValue(v, escape)
|
||
|
}
|
||
|
return escapedMap
|
||
|
}
|
||
|
|
||
|
// addPathToMap creates a map[string]interface{} from a path. It returns the node in
|
||
|
// the map corresponding to the last element in the path
|
||
|
func addPathToMap(root map[string]interface{}, path []string, escape EscapeFunc) (
|
||
|
map[string]interface{}, error) {
|
||
|
parent := root
|
||
|
for _, element := range path {
|
||
|
k := escape(element)
|
||
|
node, found := parent[k]
|
||
|
if !found {
|
||
|
node = map[string]interface{}{}
|
||
|
parent[k] = node
|
||
|
}
|
||
|
var ok bool
|
||
|
parent, ok = node.(map[string]interface{})
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf(
|
||
|
"Node %s is of type %T (expected map[string]interface traversing %q)",
|
||
|
element, node, path)
|
||
|
}
|
||
|
}
|
||
|
return parent, nil
|
||
|
}
|
||
|
|
||
|
// NotificationToMap maps a Notification into a nested map of entities
|
||
|
func NotificationToMap(addr string, notification *openconfig.Notification,
|
||
|
escape EscapeFunc) (map[string]interface{}, error) {
|
||
|
if escape == nil {
|
||
|
escape = func(name string) string {
|
||
|
return name
|
||
|
}
|
||
|
}
|
||
|
prefix := notification.GetPrefix()
|
||
|
|
||
|
// Convert deletes
|
||
|
var deletes map[string]interface{}
|
||
|
notificationDeletes := notification.GetDelete()
|
||
|
if notificationDeletes != nil {
|
||
|
deletes = make(map[string]interface{})
|
||
|
node := deletes
|
||
|
if prefix != nil {
|
||
|
var err error
|
||
|
node, err = addPathToMap(node, prefix.Element, escape)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
for _, delete := range notificationDeletes {
|
||
|
_, err := addPathToMap(node, delete.Element, escape)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert updates
|
||
|
var updates map[string]interface{}
|
||
|
notificationUpdates := notification.GetUpdate()
|
||
|
if notificationUpdates != nil {
|
||
|
updates = make(map[string]interface{})
|
||
|
node := updates
|
||
|
if prefix != nil {
|
||
|
var err error
|
||
|
node, err = addPathToMap(node, prefix.Element, escape)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
for _, update := range notificationUpdates {
|
||
|
updateNode := node
|
||
|
path := update.GetPath()
|
||
|
elementLen := len(path.Element)
|
||
|
|
||
|
// Convert all elements before the leaf
|
||
|
if elementLen > 1 {
|
||
|
parentElements := path.Element[:elementLen-1]
|
||
|
var err error
|
||
|
updateNode, err = addPathToMap(updateNode, parentElements, escape)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert the value in the leaf
|
||
|
value := update.GetValue()
|
||
|
var unmarshaledValue interface{}
|
||
|
switch value.Type {
|
||
|
case openconfig.Type_JSON:
|
||
|
if err := json.Unmarshal(value.Value, &unmarshaledValue); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
case openconfig.Type_BYTES:
|
||
|
unmarshaledValue = update.Value.Value
|
||
|
default:
|
||
|
return nil, fmt.Errorf("Unexpected value type %s for path %v",
|
||
|
value.Type, path)
|
||
|
}
|
||
|
updateNode[escape(path.Element[elementLen-1])] = escapeValue(
|
||
|
unmarshaledValue, escape)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Build the complete map to return
|
||
|
root := map[string]interface{}{
|
||
|
"timestamp": notification.Timestamp,
|
||
|
}
|
||
|
if addr != "" {
|
||
|
root["dataset"] = addr
|
||
|
}
|
||
|
if deletes != nil {
|
||
|
root["delete"] = deletes
|
||
|
}
|
||
|
if updates != nil {
|
||
|
root["update"] = updates
|
||
|
}
|
||
|
return root, nil
|
||
|
}
|
||
|
|
||
|
// NotificationToJSONDocument maps a Notification into a single JSON document
|
||
|
func NotificationToJSONDocument(addr string, notification *openconfig.Notification,
|
||
|
escape EscapeFunc) ([]byte, error) {
|
||
|
m, err := NotificationToMap(addr, notification, escape)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return json.Marshal(m)
|
||
|
}
|