forked from udacity/graphb
-
Notifications
You must be signed in to change notification settings - Fork 3
/
public.go
176 lines (151 loc) · 4.68 KB
/
public.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// graphb is a Graph QL client query builder.
// public.go contains public functions (not struct methods) to construct Query(s) and Field(s).
package graphb
import (
"strings"
"github.com/pkg/errors"
)
// StringFromChan builds a string from a channel, assuming the channel has been closed.
func StringFromChan(c <-chan string) string {
var strs []string
for str := range c {
strs = append(strs, str)
}
return strings.Join(strs, "")
}
///////////////////
// Field Factory //
///////////////////
// NewField uses functional options to construct a new Field and returns the pointer to it.
// On error, the pointer is nil.
// To know more about this design pattern, see https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
func NewField(name string, options ...FieldOptionInterface) *Field {
f := &Field{Name: name}
for _, op := range options {
if err := op.runFieldOption(f); err != nil {
f.E = errors.WithStack(err)
return f
}
}
return f
}
// FieldOptionInterface implements functional options for NewField().
type FieldOptionInterface interface {
runFieldOption(f *Field) error
}
// FieldOption implements FieldOptionInterface
type FieldOption func(field *Field) error
func (fco FieldOption) runFieldOption(f *Field) error {
return fco(f)
}
// OfFields returns a FieldOption which sets a list of sub fields of given names of the targeting field.
// All the sub fields only have one level which is their names. That is, no sub fields have sub fields.
func OfFields(name ...string) FieldOption {
return func(f *Field) error {
f.setFields(Fields(name...))
return nil
}
}
func OfAlias(alias string) FieldOption {
return func(f *Field) error {
f.Alias = alias
return errors.WithStack(f.checkAlias())
}
}
// OfArguments returns a FieldOption which sets the arguments of the targeting field.
func OfArguments(arguments ...Argument) FieldOption {
return func(f *Field) error {
f.Arguments = arguments
return nil
}
}
///////////////////
// Query Factory //
///////////////////
// NewQuery uses functional options to construct a new Query and returns the pointer to it.
// On error, the pointer is nil.
// Type is required.
// Other options such as operation name and alias are optional.
func NewQuery(Type operationType, options ...QueryOptionInterface) *Query {
// todo: change to new style error handling
q := &Query{Type: Type, Headers: make(map[string]string)}
for _, op := range options {
if err := op.runQueryOption(q); err != nil {
q.E = errors.WithStack(err)
return q
}
}
return q
}
// QueryOptionInterface implements functional options for NewQuery().
type QueryOptionInterface interface {
runQueryOption(q *Query) error
}
// QueryOption implements QueryOptionInterface
type QueryOption func(query *Query) error
func (qo QueryOption) runQueryOption(query *Query) error {
return qo(query)
}
// OfName returns a QueryOption which validates and sets the operation name of a query.
func OfName(name string) QueryOption {
return func(query *Query) error {
query.Name = name
if err := query.checkName(); err != nil {
return errors.WithStack(err)
}
return nil
}
}
////////////////////////////
// fieldContainer Factory //
////////////////////////////
type fieldContainer interface {
getFields() []*Field
setFields([]*Field)
}
// FieldContainerOption implements FieldOptionInterface and QueryOptionInterface,
// which means, it can be used as the functional option for both NewQuery() and NewField().
// FieldContainerOption is a function which takes in a fieldContainer and config it.
// Both Query and Field are fieldContainer.
type FieldContainerOption func(fc fieldContainer) error
func (fco FieldContainerOption) runQueryOption(q *Query) error {
return fco(q)
}
func (fco FieldContainerOption) runFieldOption(f *Field) error {
return fco(f)
}
// OfField returns a FieldContainerOption and has the same parameter signature of
// NewField(name string, options ...FieldOptionInterface) (*Field, error)
func OfField(name string, options ...FieldOptionInterface) FieldContainerOption {
return func(fc fieldContainer) error {
f := NewField(name, options...)
if f.E != nil {
return errors.WithStack(f.E)
}
fc.setFields(append(fc.getFields(), f))
return nil
}
}
// Fields takes a list of strings and make them a slice of *Field.
// This is useful when you want fields with no sub fields.
// For example:
// query { courses { id, key } }
// can be written as:
// Query{
// Type: "query",
// Fields: []*Field{
// {
// Name: "courses",
// Fields: Fields("id", "key"),
// },
// },
// }
func Fields(args ...string) []*Field {
fields := make([]*Field, len(args))
for i, name := range args {
fields[i] = &Field{
Name: name,
}
}
return fields
}