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
5package graphql
6
7import (
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.
21func 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.
35type StringIDSetter interface {
36	SetIDFromString(uid string) error
37}
38
39// LabelSetter is a graph edge that can set its label.
40type LabelSetter interface {
41	SetLabel(string)
42}
43
44type 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
53func (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}
158