diff --git a/graphql/introspection.lua b/graphql/introspection.lua index 985e489..6cb0c42 100644 --- a/graphql/introspection.lua +++ b/graphql/introspection.lua @@ -347,6 +347,15 @@ __Type = types.object({ end, }, + specifiedByUrl = { + kind = types.string, + resolve = function(kind) + if kind.__type == 'Scalar' then + return kind.specifiedByUrl + end + end, + }, + ofType = { kind = __Type, }, diff --git a/graphql/schema.lua b/graphql/schema.lua index a031817..a73af3b 100644 --- a/graphql/schema.lua +++ b/graphql/schema.lua @@ -22,6 +22,7 @@ function schema.create(config, name) self.directives = self.directives or { types.include, types.skip, + types.specifiedBy, } self.typeMap = {} diff --git a/graphql/types.lua b/graphql/types.lua index 908b392..1d36d93 100644 --- a/graphql/types.lua +++ b/graphql/types.lua @@ -97,6 +97,7 @@ function types.scalar(config) parseValue = config.parseValue, parseLiteral = config.parseLiteral, isValueOfTheType = config.isValueOfTheType, + specifiedByUrl = config.specifiedByUrl, } instance.nonNull = types.nonNull(instance) @@ -459,6 +460,15 @@ types.skip = types.directive({ onInlineFragment = true, }) +types.specifiedBy = types.directive({ + name = 'specifiedBy', + description = 'Custom scalar specification.', + arguments = { + ['url'] = { kind = types.string.nonNull, description = 'Scalar specification URL.', } + }, + onScalar = true, +}) + types.resolve = function(type_name_or_obj, schema) if type(type_name_or_obj) == 'table' then return type_name_or_obj diff --git a/graphql/util.lua b/graphql/util.lua index b6329b4..5347d0b 100644 --- a/graphql/util.lua +++ b/graphql/util.lua @@ -27,6 +27,13 @@ local function find(t, fn) end end +local function find_by_name(t, name) + for _, v in pairs(t or {}) do + if v.name == name then return v end + end + return nil +end + local function filter(t, fn) local res = {} for _,v in pairs(t) do @@ -282,6 +289,7 @@ return { map = map, map_name = map_name, find = find, + find_by_name = find_by_name, filter = filter, values = values, compose = compose, diff --git a/test/integration/graphql_test.lua b/test/integration/graphql_test.lua index 11673fc..ac2ed52 100644 --- a/test/integration/graphql_test.lua +++ b/test/integration/graphql_test.lua @@ -4,6 +4,7 @@ local schema = require('graphql.schema') local parse = require('graphql.parse') local validate = require('graphql.validate') local execute = require('graphql.execute') +local util = require('graphql.util') local introspection = require('test.integration.introspection') local t = require('luatest') @@ -1425,3 +1426,86 @@ function g.test_custom_directives() t.assert_equals(data, { test_G = '{"num":5,"str":"news"}' }) t.assert_equals(errors, nil) end + +function g.test_specifiedByUrl_scalar_field() + local function callback(_, _) + return nil + end + + local custom_scalar = types.scalar({ + name = 'CustomInt', + description = "The `CustomInt` scalar type represents non-fractional signed whole numeric values. " .. + "Int can represent values from -(2^31) to 2^31 - 1, inclusive.", + serialize = function(value) + return value + end, + parseLiteral = function(node) + return node.value + end, + isValueOfTheType = function(_) + return true + end, + specifiedByUrl = 'http://localhost', + }) + + local query_schema = { + ['test'] = { + kind = types.string.nonNull, + arguments = { + arg = custom_scalar, + }, + resolve = callback, + } + } + + local data, errors = check_request(introspection.query, query_schema) + local CustomInt_schema = util.find_by_name(data.__schema.types, 'CustomInt') + t.assert_type(CustomInt_schema, 'table', 'CustomInt schema found on introspection') + t.assert_equals(CustomInt_schema.specifiedByUrl, 'http://localhost') + t.assert_equals(errors, nil) +end + +function g.test_specifiedBy_directive() + local function callback(_, args, info) + local v = args[1].value + local dir = info.directives + if dir ~= nil and dir.specifiedBy ~= nil then + return { value = v, url = dir.specifiedBy.url } + end + + return { value = v } + end + + local custom_scalar = types.scalar({ + name = 'CustomInt', + description = "The `CustomInt` scalar type represents non-fractional signed whole numeric values. " .. + "Int can represent values from -(2^31) to 2^31 - 1, inclusive.", + serialize = function(value) + return value + end, + parseLiteral = function(node) + return node.value + end, + isValueOfTheType = function(_) + return true + end, + }) + + local query_schema = { + ['test'] = { + kind = custom_scalar, + arguments = { + arg = custom_scalar, + }, + resolve = callback, + } + } + + local query = [[query { + test_A: test(arg: 1)@specifiedBy(url: "http://localhost") + }]] + + local data, errors = check_request(query, query_schema) + t.assert_equals(data, { test_A = { url = "http://localhost", value = "1" } }) + t.assert_equals(errors, nil) +end diff --git a/test/unit/graphql_test.lua b/test/unit/graphql_test.lua index e67e192..67e75da 100644 --- a/test/unit/graphql_test.lua +++ b/test/unit/graphql_test.lua @@ -1069,3 +1069,22 @@ function g.test_util_map_name() res = util.map_name({ entry_a = { name = 'a' }, entry_b = { name = 'b' }, }, function(v) return v end) t.assert_equals(res, {a = {name = 'a'}, b = {name = 'b'}}) end + +function g.test_util_find_by_name() + local res = util.find_by_name({}, 'var') + t.assert_equals(res, nil) + + res = util.find_by_name({ { name = 'avr' } }, 'var') + t.assert_equals(res, nil) + + res = util.find_by_name({ { name = 'avr', value = 1 }, { name = 'var', value = 2 } }, 'var') + t.assert_equals(res, { name = 'var', value = 2 }) + + res = util.find_by_name( + { + entry1 = { name = 'avr', value = 1 }, + entry2 = { name = 'var', value = 2 } + }, + 'var') + t.assert_equals(res, { name = 'var', value = 2 }) +end