gonum.org/v1/gonum@v0.14.0/graph/encoding/graphql/decode.go (about) 1 // Copyright ©2017 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package graphql 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "errors" 11 "fmt" 12 13 "gonum.org/v1/gonum/graph" 14 "gonum.org/v1/gonum/graph/encoding" 15 ) 16 17 // Unmarshal parses the JSON-encoded data and stores the result in dst. 18 // Node IDs are obtained from the JSON fields identified by the uid parameter. 19 // UIDs obtained from the JSON encoding must map to unique node ID values 20 // consistently across the JSON-encoded spanning tree. graph.Node values 21 // returned by dst.NewNode must satisfy StringIDSetter. 22 func Unmarshal(data []byte, uid string, dst encoding.Builder) error { 23 if uid == "" { 24 return errors.New("graphql: invalid UID field name") 25 } 26 var src json.RawMessage 27 err := json.Unmarshal(data, &src) 28 if err != nil { 29 return err 30 } 31 gen := generator{dst: dst, uidName: uid, nodes: make(map[string]graph.Node)} 32 return gen.walk(src, nil, "") 33 } 34 35 // StringIDSetter is a graph node that can set its ID based on the given uid string. 36 type StringIDSetter interface { 37 SetIDFromString(uid string) error 38 } 39 40 // LabelSetter is a graph edge that can set its label. 41 type LabelSetter interface { 42 SetLabel(string) 43 } 44 45 type generator struct { 46 dst encoding.Builder 47 48 // uidName is the name of the UID field in the source JSON. 49 uidName string 50 // nodes maps from GraphQL UID string to graph.Node. 51 nodes map[string]graph.Node 52 } 53 54 func (g *generator) walk(src json.RawMessage, node graph.Node, attr string) error { 55 switch src[0] { 56 case '{': 57 var val map[string]json.RawMessage 58 err := json.Unmarshal(src, &val) 59 if err != nil { 60 return err 61 } 62 if next, ok := val[g.uidName]; !ok { 63 if node != nil { 64 var buf bytes.Buffer 65 err := json.Compact(&buf, src) 66 if err != nil { 67 panic(err) 68 } 69 return fmt.Errorf("graphql: no UID for node: `%s`", &buf) 70 } 71 } else { 72 var v interface{} 73 err = json.Unmarshal(next, &v) 74 if err != nil { 75 return err 76 } 77 value := fmt.Sprint(v) 78 child, ok := g.nodes[value] 79 if !ok { 80 child = g.dst.NewNode() 81 s, ok := child.(StringIDSetter) 82 if !ok { 83 return errors.New("graphql: cannot set UID") 84 } 85 err = s.SetIDFromString(value) 86 if err != nil { 87 return err 88 } 89 g.nodes[value] = child 90 g.dst.AddNode(child) 91 } 92 if node != nil { 93 e := g.dst.NewEdge(node, child) 94 if s, ok := e.(LabelSetter); ok { 95 s.SetLabel(attr) 96 } 97 g.dst.SetEdge(e) 98 } 99 node = child 100 } 101 for attr, src := range val { 102 if attr == g.uidName { 103 continue 104 } 105 err = g.walk(src, node, attr) 106 if err != nil { 107 return err 108 } 109 } 110 111 case '[': 112 var val []json.RawMessage 113 err := json.Unmarshal(src, &val) 114 if err != nil { 115 return err 116 } 117 for _, src := range val { 118 err = g.walk(src, node, attr) 119 if err != nil { 120 return err 121 } 122 } 123 124 default: 125 var v interface{} 126 err := json.Unmarshal(src, &v) 127 if err != nil { 128 return err 129 } 130 if attr == g.uidName { 131 value := fmt.Sprint(v) 132 if s, ok := node.(StringIDSetter); ok { 133 if _, ok := g.nodes[value]; !ok { 134 err = s.SetIDFromString(value) 135 if err != nil { 136 return err 137 } 138 g.nodes[value] = node 139 } 140 } else { 141 return errors.New("graphql: cannot set ID") 142 } 143 } else if s, ok := node.(encoding.AttributeSetter); ok { 144 var value string 145 if _, ok := v.(float64); ok { 146 value = string(src) 147 } else { 148 value = fmt.Sprint(v) 149 } 150 err = s.SetAttribute(encoding.Attribute{Key: attr, Value: value}) 151 if err != nil { 152 return err 153 } 154 } 155 } 156 157 return nil 158 }