github.com/psiphon-inc/goarista@v0.0.0-20160825065156-d002785f4c67/openconfig/json.go (about)

     1  // Copyright (C) 2016  Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package openconfig
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  func joinPath(path *Path) string {
    16  	return strings.Join(path.Element, "/")
    17  }
    18  
    19  func convertUpdate(update *Update) (interface{}, error) {
    20  	switch update.Value.Type {
    21  	case Type_JSON:
    22  		var value interface{}
    23  		decoder := json.NewDecoder(bytes.NewReader(update.Value.Value))
    24  		decoder.UseNumber()
    25  		if err := decoder.Decode(&value); err != nil {
    26  			return nil, fmt.Errorf("Malformed JSON update %q in %s",
    27  				update.Value.Value, update)
    28  		}
    29  		return value, nil
    30  	case Type_BYTES:
    31  		return strconv.Quote(string(update.Value.Value)), nil
    32  	default:
    33  		return nil,
    34  			fmt.Errorf("Unhandled type of value %v in %s", update.Value.Type, update)
    35  	}
    36  }
    37  
    38  // SubscribeResponseToJSON converts a SubscribeResponse into a JSON string
    39  func SubscribeResponseToJSON(resp *SubscribeResponse) (string, error) {
    40  	m := make(map[string]interface{}, 1)
    41  	var err error
    42  	switch resp := resp.Response.(type) {
    43  	case *SubscribeResponse_Update:
    44  		notif := resp.Update
    45  		m["timestamp"] = notif.Timestamp
    46  		m["path"] = "/" + joinPath(notif.Prefix)
    47  		if len(notif.Update) != 0 {
    48  			updates := make(map[string]interface{}, len(notif.Update))
    49  			for _, update := range notif.Update {
    50  				updates[joinPath(update.Path)], err = convertUpdate(update)
    51  				if err != nil {
    52  					return "", err
    53  				}
    54  			}
    55  			m["updates"] = updates
    56  		}
    57  		if len(notif.Delete) != 0 {
    58  			deletes := make([]string, len(notif.Delete))
    59  			for i, del := range notif.Delete {
    60  				deletes[i] = joinPath(del)
    61  			}
    62  			m["deletes"] = deletes
    63  		}
    64  		m = map[string]interface{}{"notification": m}
    65  	case *SubscribeResponse_Heartbeat:
    66  		m["heartbeat"] = resp.Heartbeat.Interval
    67  	case *SubscribeResponse_SyncResponse:
    68  		m["syncResponse"] = resp.SyncResponse
    69  	default:
    70  		return "", fmt.Errorf("Unknown type of response: %T: %s", resp, resp)
    71  	}
    72  	js, err := json.MarshalIndent(m, "", "  ")
    73  	if err != nil {
    74  		return "", err
    75  	}
    76  	return string(js), nil
    77  }
    78  
    79  // EscapeFunc is the escaping method for attribute names
    80  type EscapeFunc func(k string) string
    81  
    82  // escapeValue looks for maps in an interface and escapes their keys
    83  func escapeValue(value interface{}, escape EscapeFunc) interface{} {
    84  	valueMap, ok := value.(map[string]interface{})
    85  	if !ok {
    86  		return value
    87  	}
    88  	escapedMap := make(map[string]interface{}, len(valueMap))
    89  	for k, v := range valueMap {
    90  		escapedKey := escape(k)
    91  		escapedMap[escapedKey] = escapeValue(v, escape)
    92  	}
    93  	return escapedMap
    94  }
    95  
    96  // addPathToMap creates a map[string]interface{} from a path. It returns the node in
    97  // the map corresponding to the last element in the path
    98  func addPathToMap(parent map[string]interface{}, path []string, escape EscapeFunc) (
    99  	map[string]interface{}, error) {
   100  	for _, element := range path[:len(path)-1] {
   101  		escapedElement := escape(element)
   102  		node, found := parent[escapedElement]
   103  		if !found {
   104  			node = map[string]interface{}{}
   105  			parent[escapedElement] = node
   106  		}
   107  		var ok bool
   108  		parent, ok = node.(map[string]interface{})
   109  		if !ok {
   110  			return nil, fmt.Errorf(
   111  				"Node %s is of type %T (expected map[string]interface traversing %q)",
   112  				element, node, path)
   113  		}
   114  	}
   115  	return parent, nil
   116  }
   117  
   118  // NotificationToMap maps a Notification into a nested map of entities
   119  func NotificationToMap(notification *Notification,
   120  	escape EscapeFunc) (map[string]interface{}, error) {
   121  	if escape == nil {
   122  		escape = func(name string) string {
   123  			return name
   124  		}
   125  	}
   126  	prefix := notification.GetPrefix()
   127  	root := map[string]interface{}{
   128  		"_timestamp": notification.Timestamp,
   129  	}
   130  	prefixLeaf := root
   131  	if prefix != nil {
   132  		parent := root
   133  		for _, element := range prefix.Element {
   134  			node := map[string]interface{}{}
   135  			parent[escape(element)] = node
   136  			parent = node
   137  		}
   138  		prefixLeaf = parent
   139  	}
   140  	for _, update := range notification.GetUpdate() {
   141  		parent := prefixLeaf
   142  		path := update.GetPath()
   143  		elementLen := len(path.Element)
   144  		if elementLen > 1 {
   145  			parentElements := path.Element[:elementLen-1]
   146  			var err error
   147  			parent, err = addPathToMap(prefixLeaf, parentElements, escape)
   148  			if err != nil {
   149  				return nil, err
   150  			}
   151  		}
   152  		value := update.GetValue()
   153  		if value.Type != Type_JSON {
   154  			return nil, fmt.Errorf("Unexpected value type %s for path %v",
   155  				value.Type, path)
   156  		}
   157  		var unmarshaledValue interface{}
   158  		if err := json.Unmarshal(value.Value, &unmarshaledValue); err != nil {
   159  			return nil, err
   160  		}
   161  		parent[escape(path.Element[elementLen-1])] = escapeValue(unmarshaledValue,
   162  			escape)
   163  	}
   164  	return root, nil
   165  }
   166  
   167  // NotificationToJSONDocument maps a Notification into a single JSON document
   168  func NotificationToJSONDocument(notification *Notification,
   169  	escape EscapeFunc) ([]byte, error) {
   170  	m, err := NotificationToMap(notification, escape)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return json.Marshal(m)
   175  }