mirror of
https://github.com/gonum/gonum.git
synced 2025-10-13 02:43:59 +08:00
graph/encoding/graphql: add new package for decoding GraphQL output
This commit is contained in:
157
graph/encoding/graphql/decode.go
Normal file
157
graph/encoding/graphql/decode.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright ©2017 The gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gonum.org/v1/gonum/graph"
|
||||
"gonum.org/v1/gonum/graph/encoding"
|
||||
)
|
||||
|
||||
// Unmarshal parses the the JSON-encoded data and stores the result in dst.
|
||||
// Node IDs are obtained from the JSON fields identified by the uid parameter.
|
||||
// UIDs obtained from the JSON encoding must map to unique node ID values
|
||||
// consistently across the JSON-encoded spanning tree.
|
||||
func Unmarshal(data []byte, uid string, dst encoding.Builder) error {
|
||||
if uid == "" {
|
||||
return errors.New("graphql: invalid UID field name")
|
||||
}
|
||||
var src json.RawMessage
|
||||
err := json.Unmarshal(data, &src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gen := generator{dst: dst, uidName: uid, nodes: make(map[string]graph.Node)}
|
||||
return gen.walk(src, nil, "")
|
||||
}
|
||||
|
||||
// StringIDSetter is a graph node that can set its ID based on the given uid string.
|
||||
type StringIDSetter interface {
|
||||
SetIDFromString(uid string) error
|
||||
}
|
||||
|
||||
// LabelSetter is a graph edge that can set its label.
|
||||
type LabelSetter interface {
|
||||
SetLabel(string)
|
||||
}
|
||||
|
||||
type generator struct {
|
||||
dst encoding.Builder
|
||||
|
||||
// uidName is the name of the UID field in the source JSON.
|
||||
uidName string
|
||||
// nodes maps from GraphQL UID string to graph.Node.
|
||||
nodes map[string]graph.Node
|
||||
}
|
||||
|
||||
func (g *generator) walk(src json.RawMessage, node graph.Node, attr string) error {
|
||||
switch src[0] {
|
||||
case '{':
|
||||
var val map[string]json.RawMessage
|
||||
err := json.Unmarshal(src, &val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if next, ok := val[g.uidName]; !ok {
|
||||
if node != nil {
|
||||
var buf bytes.Buffer
|
||||
err := json.Compact(&buf, src)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fmt.Errorf("graphql: no UID for node: `%s`", &buf)
|
||||
}
|
||||
} else {
|
||||
var v interface{}
|
||||
err = json.Unmarshal(next, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value := fmt.Sprint(v)
|
||||
child, ok := g.nodes[value]
|
||||
if !ok {
|
||||
child = g.dst.NewNode()
|
||||
s, ok := child.(StringIDSetter)
|
||||
if !ok {
|
||||
return errors.New("graphql: cannot set UID")
|
||||
}
|
||||
err = s.SetIDFromString(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.nodes[value] = child
|
||||
g.dst.AddNode(child)
|
||||
}
|
||||
if node != nil {
|
||||
e := g.dst.NewEdge(node, child)
|
||||
if s, ok := e.(LabelSetter); ok {
|
||||
s.SetLabel(attr)
|
||||
}
|
||||
g.dst.SetEdge(e)
|
||||
}
|
||||
node = child
|
||||
}
|
||||
for attr, src := range val {
|
||||
if attr == g.uidName {
|
||||
continue
|
||||
}
|
||||
err = g.walk(src, node, attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case '[':
|
||||
var val []json.RawMessage
|
||||
err := json.Unmarshal(src, &val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, src := range val {
|
||||
err = g.walk(src, node, attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
var v interface{}
|
||||
err := json.Unmarshal(src, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if attr == g.uidName {
|
||||
value := fmt.Sprint(v)
|
||||
if s, ok := node.(StringIDSetter); ok {
|
||||
if _, ok := g.nodes[value]; !ok {
|
||||
err = s.SetIDFromString(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.nodes[value] = node
|
||||
}
|
||||
} else {
|
||||
return errors.New("graphql: cannot set ID")
|
||||
}
|
||||
} else if s, ok := node.(encoding.AttributeSetter); ok {
|
||||
var value string
|
||||
if _, ok := v.(float64); ok {
|
||||
value = string(src)
|
||||
} else {
|
||||
value = fmt.Sprint(v)
|
||||
}
|
||||
err = s.SetAttribute(encoding.Attribute{Key: attr, Value: value})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user