github.com/gopherd/gonum@v0.0.4/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 "github.com/gopherd/gonum/graph" 14 "github.com/gopherd/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. 21 func Unmarshal(data []byte, uid string, dst encoding.Builder) error { 22 if uid == "" { 23 return errors.New("graphql: invalid UID field name") 24 } 25 var src json.RawMessage 26 err := json.Unmarshal(data, &src) 27 if err != nil { 28 return err 29 } 30 gen := generator{dst: dst, uidName: uid, nodes: make(map[string]graph.Node)} 31 return gen.walk(src, nil, "") 32 } 33 34 // StringIDSetter is a graph node that can set its ID based on the given uid string. 35 type StringIDSetter interface { 36 SetIDFromString(uid string) error 37 } 38 39 // LabelSetter is a graph edge that can set its label. 40 type LabelSetter interface { 41 SetLabel(string) 42 } 43 44 type generator struct { 45 dst encoding.Builder 46 47 // uidName is the name of the UID field in the source JSON. 48 uidName string 49 // nodes maps from GraphQL UID string to graph.Node. 50 nodes map[string]graph.Node 51 } 52 53 func (g *generator) walk(src json.RawMessage, node graph.Node, attr string) error { 54 switch src[0] { 55 case '{': 56 var val map[string]json.RawMessage 57 err := json.Unmarshal(src, &val) 58 if err != nil { 59 return err 60 } 61 if next, ok := val[g.uidName]; !ok { 62 if node != nil { 63 var buf bytes.Buffer 64 err := json.Compact(&buf, src) 65 if err != nil { 66 panic(err) 67 } 68 return fmt.Errorf("graphql: no UID for node: `%s`", &buf) 69 } 70 } else { 71 var v interface{} 72 err = json.Unmarshal(next, &v) 73 if err != nil { 74 return err 75 } 76 value := fmt.Sprint(v) 77 child, ok := g.nodes[value] 78 if !ok { 79 child = g.dst.NewNode() 80 s, ok := child.(StringIDSetter) 81 if !ok { 82 return errors.New("graphql: cannot set UID") 83 } 84 err = s.SetIDFromString(value) 85 if err != nil { 86 return err 87 } 88 g.nodes[value] = child 89 g.dst.AddNode(child) 90 } 91 if node != nil { 92 e := g.dst.NewEdge(node, child) 93 if s, ok := e.(LabelSetter); ok { 94 s.SetLabel(attr) 95 } 96 g.dst.SetEdge(e) 97 } 98 node = child 99 } 100 for attr, src := range val { 101 if attr == g.uidName { 102 continue 103 } 104 err = g.walk(src, node, attr) 105 if err != nil { 106 return err 107 } 108 } 109 110 case '[': 111 var val []json.RawMessage 112 err := json.Unmarshal(src, &val) 113 if err != nil { 114 return err 115 } 116 for _, src := range val { 117 err = g.walk(src, node, attr) 118 if err != nil { 119 return err 120 } 121 } 122 123 default: 124 var v interface{} 125 err := json.Unmarshal(src, &v) 126 if err != nil { 127 return err 128 } 129 if attr == g.uidName { 130 value := fmt.Sprint(v) 131 if s, ok := node.(StringIDSetter); ok { 132 if _, ok := g.nodes[value]; !ok { 133 err = s.SetIDFromString(value) 134 if err != nil { 135 return err 136 } 137 g.nodes[value] = node 138 } 139 } else { 140 return errors.New("graphql: cannot set ID") 141 } 142 } else if s, ok := node.(encoding.AttributeSetter); ok { 143 var value string 144 if _, ok := v.(float64); ok { 145 value = string(src) 146 } else { 147 value = fmt.Sprint(v) 148 } 149 err = s.SetAttribute(encoding.Attribute{Key: attr, Value: value}) 150 if err != nil { 151 return err 152 } 153 } 154 } 155 156 return nil 157 }