Skip to content

Commit 7f3658c

Browse files
no1semanDifferentialOrange
authored andcommitted
Support custom directives
GraphQL custom directives was introduced in spec #3.13 in October 2021 release [1]. Directives are the preferred way to extend GraphQL with custom or experimental behavior. This patch adds support of custom directives, as well as several location adjustments and repeatable directive option needed for full support of custom directives. This patch also adds introspection test for schema with custom directives to validate schema. Introspection of schema is described in spec #4.2 [2]. 1. https://spec.graphql.org/October2021/#sec-Type-System.Directives.Custom-Directives 2. https://spec.graphql.org/October2021/#sec-Schema-Introspection Based on PR #20 by @no1seman
1 parent 1a2f46b commit 7f3658c

File tree

8 files changed

+445
-41
lines changed

8 files changed

+445
-41
lines changed

graphql/execute.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,36 @@ local function getFieldEntry(objectType, object, fields, context)
286286

287287
arguments = setmetatable(arguments, {__index=positions})
288288

289+
local directiveMap = {}
290+
for _, directive in ipairs(firstField.directives or {}) do
291+
directiveMap[directive.name.value] = directive
292+
end
293+
294+
local directives = {}
295+
296+
if next(directiveMap) ~= nil then
297+
util.map_name(context.schema.directives or {}, function(directive, directive_name)
298+
local supplied_directive = directiveMap[directive_name]
299+
if supplied_directive == nil then
300+
return nil
301+
end
302+
303+
local directiveArgumentMap = {}
304+
for _, argument in ipairs(supplied_directive.arguments or {}) do
305+
directiveArgumentMap[argument.name.value] = argument
306+
end
307+
308+
directives[directive_name] = util.map(directive.arguments or {}, function(argument, name)
309+
local supplied = directiveArgumentMap[name] and directiveArgumentMap[name].value
310+
if argument.kind then argument = argument.kind end
311+
return util.coerceValue(supplied, argument, context.variables, {
312+
strict_non_null = true,
313+
defaultValues = defaultValues,
314+
})
315+
end)
316+
end)
317+
end
318+
289319
local info = {
290320
context = context,
291321
fieldName = fieldName,
@@ -298,6 +328,7 @@ local function getFieldEntry(objectType, object, fields, context)
298328
operation = context.operation,
299329
variableValues = context.variables,
300330
defaultValues = context.defaultValues,
331+
directives = directives,
301332
}
302333

303334
local resolvedObject, err = (fieldType.resolve or defaultResolver)(object, arguments, info)

graphql/introspection.lua

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,18 @@ __Directive = types.object({
109109
if directive.onFragmentDefinition then table.insert(res, 'FRAGMENT_DEFINITION') end
110110
if directive.onFragmentSpread then table.insert(res, 'FRAGMENT_SPREAD') end
111111
if directive.onInlineFragment then table.insert(res, 'INLINE_FRAGMENT') end
112+
if directive.onVariableDefinition then table.insert(res, 'VARIABLE_DEFINITION') end
113+
if directive.onSchema then table.insert(res, 'SCHEMA') end
114+
if directive.onScalar then table.insert(res, 'SCALAR') end
115+
if directive.onObject then table.insert(res, 'OBJECT') end
116+
if directive.onFieldDefinition then table.insert(res, 'FIELD_DEFINITION') end
117+
if directive.onArgumentDefinition then table.insert(res, 'ARGUMENT_DEFINITION') end
118+
if directive.onInterface then table.insert(res, 'INTERFACE') end
119+
if directive.onUnion then table.insert(res, 'UNION') end
120+
if directive.onEnum then table.insert(res, 'ENUM') end
121+
if directive.onEnumValue then table.insert(res, 'ENUM_VALUE') end
122+
if directive.onInputObject then table.insert(res, 'INPUT_OBJECT') end
123+
if directive.onInputFieldDefinition then table.insert(res, 'INPUT_FIELD_DEFINITION') end
112124

113125
return res
114126
end
@@ -117,8 +129,16 @@ __Directive = types.object({
117129
args = {
118130
kind = types.nonNull(types.list(types.nonNull(__InputValue))),
119131
resolve = resolveArgs
120-
}
132+
},
133+
134+
isRepeatable = {
135+
kind = types.nonNull(types.boolean),
136+
resolve = function(directive)
137+
return directive.isRepeatable == true
138+
end,
139+
},
121140
}
141+
122142
end
123143
})
124144

@@ -159,8 +179,68 @@ __DirectiveLocation = types.enum({
159179
INLINE_FRAGMENT = {
160180
value = 'INLINE_FRAGMENT',
161181
description = 'Location adjacent to an inline fragment.'
162-
}
163-
}
182+
},
183+
184+
VARIABLE_DEFINITION = {
185+
value = 'VARIABLE_DEFINITION',
186+
description = 'Location adjacent to a variable definition.',
187+
},
188+
189+
SCHEMA = {
190+
value = 'SCHEMA',
191+
description = 'Location adjacent to schema.',
192+
},
193+
194+
SCALAR = {
195+
value = 'SCALAR',
196+
description = 'Location adjacent to a scalar.',
197+
},
198+
199+
OBJECT = {
200+
value = 'OBJECT',
201+
description = 'Location adjacent to an object.',
202+
},
203+
204+
FIELD_DEFINITION = {
205+
value = 'FIELD_DEFINITION',
206+
description = 'Location adjacent to a field definition.',
207+
},
208+
209+
ARGUMENT_DEFINITION = {
210+
value = 'ARGUMENT_DEFINITION',
211+
description = 'Location adjacent to an argument definition.',
212+
},
213+
214+
INTERFACE = {
215+
value = 'INTERFACE',
216+
description = 'Location adjacent to an interface.',
217+
},
218+
219+
UNION = {
220+
value = 'UNION',
221+
description = 'Location adjacent to an union.',
222+
},
223+
224+
ENUM = {
225+
value = 'ENUM',
226+
description = 'Location adjacent to an enum.',
227+
},
228+
229+
ENUM_VALUE = {
230+
value = 'ENUM_VALUE',
231+
description = 'Location adjacent to an enum value.',
232+
},
233+
234+
INPUT_OBJECT = {
235+
value = 'INPUT_OBJECT',
236+
description = 'Location adjacent to an input object.',
237+
},
238+
239+
INPUT_FIELD_DEFINITION = {
240+
value = 'INPUT_FIELD_DEFINITION',
241+
description = 'Location adjacent to an input field definition.',
242+
},
243+
},
164244
})
165245

166246
__Type = types.object({

graphql/schema.lua

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,28 @@ end
9999
function schema:generateDirectiveMap()
100100
for _, directive in ipairs(self.directives) do
101101
self.directiveMap[directive.name] = directive
102+
if directive.arguments then
103+
for name, argument in pairs(directive.arguments) do
104+
105+
-- BEGIN_HACK: resolve type names to real types
106+
if type(argument) == 'string' then
107+
argument = types.resolve(argument, self.name)
108+
directive.arguments[name] = argument
109+
end
110+
111+
if type(argument.kind) == 'string' then
112+
argument.kind = types.resolve(argument.kind, self.name)
113+
end
114+
-- END_HACK: resolve type names to real types
115+
116+
local argumentType = argument.__type and argument or argument.kind
117+
if argumentType == nil then
118+
error('Must supply type for argument "' .. name .. '" on "' .. directive.name .. '"')
119+
end
120+
argumentType.defaultValue = argument.defaultValue
121+
self:generateTypeMap(argumentType)
122+
end
123+
end
102124
end
103125
end
104126

graphql/types.lua

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,20 @@ function types.directive(config)
418418
onField = config.onField,
419419
onFragmentDefinition = config.onFragmentDefinition,
420420
onFragmentSpread = config.onFragmentSpread,
421-
onInlineFragment = config.onInlineFragment
421+
onInlineFragment = config.onInlineFragment,
422+
onVariableDefinition = config.onVariableDefinition,
423+
onSchema = config.onSchema,
424+
onScalar = config.onScalar,
425+
onObject = config.onObject,
426+
onFieldDefinition = config.onFieldDefinition,
427+
onArgumentDefinition = config.onArgumentDefinition,
428+
onInterface = config.onInterface,
429+
onUnion = config.onUnion,
430+
onEnum = config.onEnum,
431+
onEnumValue = config.onEnumValue,
432+
onInputObject = config.onInputObject,
433+
onInputFieldDefinition = config.onInputFieldDefinition,
434+
isRepeatable = config.isRepeatable or false,
422435
}
423436

424437
return instance

graphql/util.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ local function map(t, fn)
1111
return res
1212
end
1313

14+
local function map_name(t, fn)
15+
local res = {}
16+
for _, v in pairs(t or {}) do
17+
if v.name then
18+
res[v.name] = fn(v, v.name)
19+
end
20+
end
21+
return res
22+
end
23+
1424
local function find(t, fn)
1525
for k, v in pairs(t) do
1626
if fn(v, k) then return v end
@@ -270,6 +280,7 @@ end
270280

271281
return {
272282
map = map,
283+
map_name = map_name,
273284
find = find,
274285
filter = filter,
275286
values = values,

0 commit comments

Comments
 (0)