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 }