-
Notifications
You must be signed in to change notification settings - Fork 0
/
definition.go
343 lines (295 loc) · 9.41 KB
/
definition.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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
package gqlfiltergen
import (
"fmt"
"github.com/vektah/gqlparser/v2/ast"
)
type ProcessingField struct {
Field *ast.FieldDefinition
IsMinmaxeable bool
}
type ProcessingObject struct {
Fields []*ProcessingField
Definition *ast.Definition
}
type AstAndTemplateData struct {
Ast *ast.Definition
TypeData *TypeData
}
func generateMainFilterDefinition(ot map[string]*ProcessingObject) map[string]*AstAndTemplateData {
out := make(map[string]*AstAndTemplateData)
seen := make(map[string]bool)
for objectName := range ot {
generateMainFilterDefinitionLoop(ot, out, seen, false, objectName)
}
return out
}
func generateMainFilterDefinitionLoop(ot map[string]*ProcessingObject, processed map[string]*AstAndTemplateData, seen map[string]bool, nested bool, objectName string) string {
field := processField(objectName, objectName)
if field != nil {
return field.Type.NamedType
}
filterName := fmt.Sprintf("Filter%s", objectName)
if nested {
filterName = fmt.Sprintf("NestedFilter%s", objectName)
}
if ok := seen[filterName]; ok {
fmt.Println("filter already on the loop:", filterName)
return filterName
}
seen[filterName] = true
objDef := filterDefinition(filterName, objectName)
typeData := &TypeData{
TypeName: objectName,
FilterName: filterName,
}
pfs, ok := ot[objectName]
if !ok {
panic(fmt.Errorf("error creating field for type %q. Check that you added some fields as @filterable", objectName))
}
switch pfs.Definition.Kind {
case ast.Union:
isFiltered := false
for _, t := range pfs.Definition.Types {
if _, ok := ot[t]; !ok {
fmt.Println("type in an union with no filters:", t)
continue
}
isFiltered = true
fd := &ast.FieldDefinition{
Description: fmt.Sprintf("filter for %s union type.", t),
Name: t,
Type: ast.NamedType(generateMainFilterDefinitionLoop(ot, processed, seen, true, t), nil),
}
objDef.Fields = append(objDef.Fields, fd)
tf := &FieldMapping{
Field: t,
FilterField: t,
TypeName: fd.Type.Name(),
}
typeData.Fields = append(typeData.Fields, tf)
}
typeData.IsUnion = true
if isFiltered {
processed[filterName] = &AstAndTemplateData{
Ast: objDef,
TypeData: typeData,
}
}
return filterName
case ast.Object:
for _, pf := range pfs.Fields {
f := pf.Field
if f.Type.Name() != "Int" && pf.IsMinmaxeable {
panic(fmt.Errorf("only Int types can be minmaxeables. Field: %s Type: %s", f.Name, f.Type.Name()))
}
fd := &ast.FieldDefinition{
Description: fmt.Sprintf("filter for %s field.", f.Name),
Name: f.Name,
}
var isSlice bool
var isSliceBasicType bool
var isNested bool
var isUnion bool
if possibleUnion, ok := ot[f.Type.NamedType]; ok {
isUnion = possibleUnion.Definition.Kind == ast.Union
}
field := processField(f.Name, f.Type.NamedType)
switch {
case field != nil:
fd = field
case f.Type.NamedType == "": // it is a list
if possibleUnion, ok := ot[f.Type.Elem.NamedType]; ok {
isUnion = possibleUnion.Definition.Kind == ast.Union
}
fd.Type = ast.NamedType(generateMainFilterDefinitionLoop(ot, processed, seen, true, f.Type.Elem.NamedType), nil)
isSliceBasicType = isBasicType(f.Type.Elem.NamedType)
isSlice = true
case isUnion:
fd.Type = ast.NamedType(generateMainFilterDefinitionLoop(ot, processed, seen, true, f.Type.NamedType), nil)
default: // It is a named custom named type
isNested = true
fd.Type = ast.NamedType(generateMainFilterDefinitionLoop(ot, processed, seen, true, f.Type.NamedType), nil)
}
objDef.Fields = append(objDef.Fields, fd)
tf := &FieldMapping{
Field: f.Name,
TypeName: fd.Type.Name(),
IsSlice: isSlice,
IsNested: isNested,
IsPointer: !f.Type.NonNull,
IsSliceBasicType: isSliceBasicType,
IsUnion: isUnion,
IsMinmaxeable: pf.IsMinmaxeable,
}
typeData.Fields = append(typeData.Fields, tf)
}
processed[filterName] = &AstAndTemplateData{
Ast: objDef,
TypeData: typeData,
}
default:
panic(fmt.Errorf("unsupported type: %q", pfs.Definition.Kind))
}
return filterName
}
func isBasicType(t string) bool {
switch t {
case "String":
return true
case "Int":
return true
case "Boolean":
return true
}
return false
}
func processField(name, typeName string) *ast.FieldDefinition {
fd := &ast.FieldDefinition{
Description: fmt.Sprintf("filter for %s field.", name),
Name: name,
}
switch typeName {
case "String":
fd.Type = ast.NamedType(filterStringName, nil)
case "Int":
fd.Type = ast.NamedType(filterIntName, nil)
case "Time":
fd.Type = ast.NamedType(filterTimeName, nil)
case "Boolean":
fd.Type = ast.NamedType(filterBooleanName, nil)
default:
return nil
}
return fd
}
func filterDefinition(filterName, objectName string) *ast.Definition {
return &ast.Definition{
Kind: ast.InputObject,
Name: filterName,
Description: fmt.Sprintf("filter for %s objects", objectName),
Fields: ast.FieldList{
&ast.FieldDefinition{
Type: ast.ListType(ast.NamedType(filterName, nil), nil),
Name: "_and",
Description: fmt.Sprintf("logical operator for %s that will combine two or more conditions, returning true if all of them are true.", objectName),
},
&ast.FieldDefinition{
Type: ast.ListType(ast.NamedType(filterName, nil), nil),
Name: "_or",
Description: fmt.Sprintf("logical operator for %s that will combine two or more conditions, returning true if at least one of them is true.", objectName),
},
&ast.FieldDefinition{
Type: ast.NamedType(filterName, nil),
Name: "_not",
Description: fmt.Sprintf("logical operator for %s that will reverse conditions.", objectName),
},
},
}
}
const (
filterStringName = "FilterString"
filterIntName = "FilterInt"
filterTimeName = "FilterTime"
filterBooleanName = "FilterBoolean"
)
func filterString(name string) *ast.Definition {
return &ast.Definition{
Kind: ast.InputObject,
Description: "Filter type for string fields. It contains a variety of filter types for string types. All added filters here are processed as AND operators.",
Name: name,
Fields: ast.FieldList{
&ast.FieldDefinition{
Description: "Filter a string field checking if it exists or not.",
Name: "exists",
Type: ast.NamedType("Boolean", nil),
},
&ast.FieldDefinition{
Description: "Filter a string field checking if it is equals to the specified value.",
Name: "eq",
Type: ast.NamedType("String", nil),
},
&ast.FieldDefinition{
Description: "Filter a string field checking if it is like the specified value. You can use standard Go RegEx expressions here.",
Name: "like",
Type: ast.NamedType("String", nil),
},
},
}
}
func filterNumber(name string) *ast.Definition {
return &ast.Definition{
Kind: ast.InputObject,
Description: "Filter type for number fields. All added filters here are processed as AND operators.",
Name: name,
Fields: ast.FieldList{
&ast.FieldDefinition{
Description: "Filter a number field checking if it exists or not.",
Name: "exists",
Type: ast.NamedType("Boolean", nil),
},
&ast.FieldDefinition{
Description: "Filter a number field checking if it is equals to the specified value.",
Name: "eq",
Type: ast.NamedType("Int", nil),
},
&ast.FieldDefinition{
Description: "Filter a number field checking if it is greater than the specified value.",
Name: "gt",
Type: ast.NamedType("Int", nil),
},
&ast.FieldDefinition{
Description: "Filter a number field checking if it is less than the specified value.",
Name: "lt",
Type: ast.NamedType("Int", nil),
},
},
}
}
func filterTime(name string) *ast.Definition {
return &ast.Definition{
Kind: ast.InputObject,
Description: "Filter type for time fields. All added filters here are processed as AND operators.",
Name: name,
Fields: ast.FieldList{
&ast.FieldDefinition{
Description: "Filter a time field checking if it exists or not.",
Name: "exists",
Type: ast.NamedType("Boolean", nil),
},
&ast.FieldDefinition{
Description: "Filter a time field checking if it is equals to the specified value.",
Name: "eq",
Type: ast.NamedType("Time", nil),
},
&ast.FieldDefinition{
Description: "Filter a time field checking if it is before than the specified value.",
Name: "before",
Type: ast.NamedType("Time", nil),
},
&ast.FieldDefinition{
Description: "Filter a time field checking if it is after the specified value.",
Name: "after",
Type: ast.NamedType("Time", nil),
},
},
}
}
func filterBoolean(name string) *ast.Definition {
return &ast.Definition{
Kind: ast.InputObject,
Description: "Filter type for boolean fields. All added filters here are processed as AND operators.",
Name: name,
Fields: ast.FieldList{
&ast.FieldDefinition{
Description: "Filter a boolean field checking if it exists or not.",
Name: "exists",
Type: ast.NamedType("Boolean", nil),
},
&ast.FieldDefinition{
Description: "Filter a boolean field checking if it is equals to the specified value.",
Name: "eq",
Type: ast.NamedType("Boolean", nil),
},
},
}
}