-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathquery.go
191 lines (150 loc) · 5.46 KB
/
query.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
package robin
import (
"fmt"
"strings"
"go.trulyao.dev/robin/internal/guarded"
"go.trulyao.dev/robin/types"
)
type (
QueryFn[ReturnType any, ParamsType any] func(ctx *Context, body ParamsType) (ReturnType, error)
query[ReturnType any, ParamsType any] struct {
// The name of the query
name string
// The function that will be called when the query is executed
fn QueryFn[ReturnType, ParamsType]
// A placeholder for the type of the body that the query expects
// WARNING: This never really has a value, it's just used for "type inference/reflection" during runtime
in ParamsType
// A placeholder for the return type of the query
// WARNING: This never really has a value, it's just used for "type inference/reflection" during runtime
out ReturnType
// Middleware functions to be executed before the mutation is called
middlewareFns []types.Middleware
// Whether the query expects a payload or not
expectsPayload bool
// Excluded middleware functions
excludedMiddleware *types.ExclusionList
// The query alias
alias string
}
)
// Creates a new query with the given name and handler function
func Query[R any, B any](name string, fn QueryFn[R, B]) *query[R, B] {
var body B
expectsPayload := guarded.ExpectsPayload(body)
q := &query[R, B]{
name: name,
fn: fn,
expectsPayload: expectsPayload,
excludedMiddleware: &types.ExclusionList{},
}
q.alias = q.NormalizeProcedureName()
return q
}
// Alias for `Query` to create a new query procedure
func Q[R any, B any](name string, fn QueryFn[R, B]) *query[R, B] {
return Query(name, fn)
}
// Creates a new query with the given name, handler function and middleware functions
func QueryWithMiddleware[R any, B any](
name string,
fn QueryFn[R, B],
middleware ...types.Middleware,
) *query[R, B] {
q := Query(name, fn)
q.middlewareFns = middleware
return q
}
// Name returns the name of the query
func (q *query[_, _]) Name() string {
return q.name
}
// Returns the type of the procedure, one of 'query' or 'mutation' - in this case, it's always 'query'
func (q *query[_, _]) Type() ProcedureType {
return ProcedureTypeQuery
}
// PayloadInterface returns a placeholder variable with the type of the payload that the query expects, this value is empty and only used for type inference/reflection during runtime
func (q *query[_, _]) PayloadInterface() any {
return q.in
}
// ReturnInterface returns a placeholder variable with the type of the return value of the query, this value is empty and only used for type inference/reflection during runtime
func (q *query[_, _]) ReturnInterface() any {
return q.out
}
// NormalizeProcedureName normalizes the procedure name to a more human-readable format for use in the REST API
func (q *query[_, _]) NormalizeProcedureName() string {
var alias string
// Replace all non-alphanumeric characters with dot
alias = ReAlphaNumeric.ReplaceAllString(q.name, ".")
// Replace all multiple dots with a single dot
alias = ReIllegalDot.ReplaceAllString(alias, ".")
// Remove all words that are associable with the query type
alias = ReQueryWords.ReplaceAllString(alias, "")
// Remove all leading and trailing dots and spaces
alias = strings.TrimSpace(alias)
alias = strings.Trim(alias, ".")
return alias
}
// Alias returns the alias of the query
func (q *query[_, _]) Alias() string { return q.alias }
// WithAlias sets the alias of the query
func (q *query[_, _]) WithAlias(alias string) Procedure {
q.alias = alias
return q
}
// Calls the query with the given context and params
func (q *query[ReturnType, ParamsType]) Call(ctx *Context, rawParams any) (any, error) {
params, err := guarded.CastType(rawParams, q.in)
if err != nil {
return nil, err
}
if q.fn == nil {
return nil, RobinError{Reason: fmt.Sprintf("Procedure %s has no function attached", q.name)}
}
return q.fn(ctx, params)
}
// ExpectsPayload returns whether the query expects a payload or not
func (q *query[_, _]) ExpectsPayload() bool {
return q.expectsPayload
}
// Validate validates the query
func (q *query[_, _]) Validate() error {
// Check if the query name is valid
if q.name == "" {
return RobinError{Reason: "Query name cannot be empty"}
}
if !ReValidProcedureName.MatchString(q.name) {
return RobinError{
Reason: fmt.Sprintf(
"Invalid procedure name: `%s`, expected string matching regex `%s` (example: `get_user`, `todo.create`)",
q.name,
ReValidProcedureName,
),
}
}
return nil
}
// MiddlewareFns returns the middleware functions to be executed before the query is called
func (q *query[_, _]) MiddlewareFns() []types.Middleware {
return q.middlewareFns
}
// PrependMiddleware sets the middleware functions for the mutation at the beginning of the middleware chain
func (q *query[_, _]) PrependMiddleware(fns ...types.Middleware) Procedure {
q.middlewareFns = append(fns, q.middlewareFns...)
return q
}
// Add the middleware functions for the query
func (q *query[_, _]) WithMiddleware(fns ...types.Middleware) Procedure {
q.middlewareFns = append(q.middlewareFns, fns...)
return q
}
// ExcludeMiddleware takes a list of global middleware names and excludes them from the query
func (q *query[_, _]) ExcludeMiddleware(names ...string) types.Procedure {
q.excludedMiddleware.AddMany(names)
return q
}
// ExcludedMiddleware returns the list of middleware functions that are excluded from the query
func (q *query[_, _]) ExcludedMiddleware() *types.ExclusionList {
return q.excludedMiddleware
}
var _ Procedure = (*query[any, any])(nil)