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  }