json.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. package openconfig
  5. import (
  6. "bytes"
  7. "encoding/json"
  8. "fmt"
  9. "strings"
  10. "github.com/openconfig/reference/rpc/openconfig"
  11. )
  12. // joinPath builds a string out of an Element
  13. func joinPath(path *openconfig.Path) string {
  14. if path == nil {
  15. return ""
  16. }
  17. return strings.Join(path.Element, "/")
  18. }
  19. func convertUpdate(update *openconfig.Update) (interface{}, error) {
  20. switch update.Value.Type {
  21. case openconfig.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 openconfig.Type_BYTES:
  31. return 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. // NotificationToJSON converts a Notification into a JSON string
  38. func NotificationToJSON(notif *openconfig.Notification) (string, error) {
  39. m := make(map[string]interface{}, 1)
  40. m["timestamp"] = notif.Timestamp
  41. m["path"] = "/" + joinPath(notif.Prefix)
  42. if len(notif.Update) != 0 {
  43. updates := make(map[string]interface{}, len(notif.Update))
  44. var err error
  45. for _, update := range notif.Update {
  46. updates[joinPath(update.Path)], err = convertUpdate(update)
  47. if err != nil {
  48. return "", err
  49. }
  50. }
  51. m["updates"] = updates
  52. }
  53. if len(notif.Delete) != 0 {
  54. deletes := make([]string, len(notif.Delete))
  55. for i, del := range notif.Delete {
  56. deletes[i] = joinPath(del)
  57. }
  58. m["deletes"] = deletes
  59. }
  60. m = map[string]interface{}{"notification": m}
  61. js, err := json.MarshalIndent(m, "", " ")
  62. if err != nil {
  63. return "", err
  64. }
  65. return string(js), nil
  66. }
  67. // SubscribeResponseToJSON converts a SubscribeResponse into a JSON string
  68. func SubscribeResponseToJSON(resp *openconfig.SubscribeResponse) (string, error) {
  69. m := make(map[string]interface{}, 1)
  70. var err error
  71. switch resp := resp.Response.(type) {
  72. case *openconfig.SubscribeResponse_Update:
  73. return NotificationToJSON(resp.Update)
  74. case *openconfig.SubscribeResponse_Heartbeat:
  75. m["heartbeat"] = resp.Heartbeat.Interval
  76. case *openconfig.SubscribeResponse_SyncResponse:
  77. m["syncResponse"] = resp.SyncResponse
  78. default:
  79. return "", fmt.Errorf("Unknown type of response: %T: %s", resp, resp)
  80. }
  81. js, err := json.MarshalIndent(m, "", " ")
  82. if err != nil {
  83. return "", err
  84. }
  85. return string(js), nil
  86. }
  87. // EscapeFunc is the escaping method for attribute names
  88. type EscapeFunc func(k string) string
  89. // escapeValue looks for maps in an interface and escapes their keys
  90. func escapeValue(value interface{}, escape EscapeFunc) interface{} {
  91. valueMap, ok := value.(map[string]interface{})
  92. if !ok {
  93. return value
  94. }
  95. escapedMap := make(map[string]interface{}, len(valueMap))
  96. for k, v := range valueMap {
  97. escapedKey := escape(k)
  98. escapedMap[escapedKey] = escapeValue(v, escape)
  99. }
  100. return escapedMap
  101. }
  102. // addPathToMap creates a map[string]interface{} from a path. It returns the node in
  103. // the map corresponding to the last element in the path
  104. func addPathToMap(root map[string]interface{}, path []string, escape EscapeFunc) (
  105. map[string]interface{}, error) {
  106. parent := root
  107. for _, element := range path {
  108. k := escape(element)
  109. node, found := parent[k]
  110. if !found {
  111. node = map[string]interface{}{}
  112. parent[k] = node
  113. }
  114. var ok bool
  115. parent, ok = node.(map[string]interface{})
  116. if !ok {
  117. return nil, fmt.Errorf(
  118. "Node %s is of type %T (expected map[string]interface traversing %q)",
  119. element, node, path)
  120. }
  121. }
  122. return parent, nil
  123. }
  124. // NotificationToMap maps a Notification into a nested map of entities
  125. func NotificationToMap(addr string, notification *openconfig.Notification,
  126. escape EscapeFunc) (map[string]interface{}, error) {
  127. if escape == nil {
  128. escape = func(name string) string {
  129. return name
  130. }
  131. }
  132. prefix := notification.GetPrefix()
  133. // Convert deletes
  134. var deletes map[string]interface{}
  135. notificationDeletes := notification.GetDelete()
  136. if notificationDeletes != nil {
  137. deletes = make(map[string]interface{})
  138. node := deletes
  139. if prefix != nil {
  140. var err error
  141. node, err = addPathToMap(node, prefix.Element, escape)
  142. if err != nil {
  143. return nil, err
  144. }
  145. }
  146. for _, delete := range notificationDeletes {
  147. _, err := addPathToMap(node, delete.Element, escape)
  148. if err != nil {
  149. return nil, err
  150. }
  151. }
  152. }
  153. // Convert updates
  154. var updates map[string]interface{}
  155. notificationUpdates := notification.GetUpdate()
  156. if notificationUpdates != nil {
  157. updates = make(map[string]interface{})
  158. node := updates
  159. if prefix != nil {
  160. var err error
  161. node, err = addPathToMap(node, prefix.Element, escape)
  162. if err != nil {
  163. return nil, err
  164. }
  165. }
  166. for _, update := range notificationUpdates {
  167. updateNode := node
  168. path := update.GetPath()
  169. elementLen := len(path.Element)
  170. // Convert all elements before the leaf
  171. if elementLen > 1 {
  172. parentElements := path.Element[:elementLen-1]
  173. var err error
  174. updateNode, err = addPathToMap(updateNode, parentElements, escape)
  175. if err != nil {
  176. return nil, err
  177. }
  178. }
  179. // Convert the value in the leaf
  180. value := update.GetValue()
  181. var unmarshaledValue interface{}
  182. switch value.Type {
  183. case openconfig.Type_JSON:
  184. if err := json.Unmarshal(value.Value, &unmarshaledValue); err != nil {
  185. return nil, err
  186. }
  187. case openconfig.Type_BYTES:
  188. unmarshaledValue = update.Value.Value
  189. default:
  190. return nil, fmt.Errorf("Unexpected value type %s for path %v",
  191. value.Type, path)
  192. }
  193. updateNode[escape(path.Element[elementLen-1])] = escapeValue(
  194. unmarshaledValue, escape)
  195. }
  196. }
  197. // Build the complete map to return
  198. root := map[string]interface{}{
  199. "timestamp": notification.Timestamp,
  200. }
  201. if addr != "" {
  202. root["dataset"] = addr
  203. }
  204. if deletes != nil {
  205. root["delete"] = deletes
  206. }
  207. if updates != nil {
  208. root["update"] = updates
  209. }
  210. return root, nil
  211. }
  212. // NotificationToJSONDocument maps a Notification into a single JSON document
  213. func NotificationToJSONDocument(addr string, notification *openconfig.Notification,
  214. escape EscapeFunc) ([]byte, error) {
  215. m, err := NotificationToMap(addr, notification, escape)
  216. if err != nil {
  217. return nil, err
  218. }
  219. return json.Marshal(m)
  220. }