introspection.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. local path = (...):gsub('%.[^%.]+$', '')
  2. local types = require(path .. '.types')
  3. local util = require(path .. '.util')
  4. local cjson = require 'cjson' -- needs to be cloned from here https://github.com/openresty/lua-cjson for cjson.empty_array feature
  5. local function instanceof(t, s)
  6. return t.__type == s
  7. end
  8. local function trim(s)
  9. return s:gsub('^%s+', ''):gsub('%s$', ''):gsub('%s%s+', ' ')
  10. end
  11. local __Directive, __DirectiveLocation, __Type, __Field, __InputValue,__EnumValue, __TypeKind, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, astFromValue, printAst, printers
  12. local __Schema = types.object({
  13. name = '__Schema',
  14. description = trim [[
  15. A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types
  16. and directives on the server, as well as the entry points for query and mutation operations.
  17. ]],
  18. fields = function()
  19. return {
  20. types = {
  21. description = 'A list of all types supported by this server.',
  22. kind = types.nonNull(types.list(types.nonNull(__Type))),
  23. resolve = function(schema)
  24. return util.values(schema:getTypeMap())
  25. end
  26. },
  27. queryType = {
  28. description = 'The type that query operations will be rooted at.',
  29. kind = __Type.nonNull,
  30. resolve = function(schema)
  31. return schema:getQueryType()
  32. end
  33. },
  34. mutationType = {
  35. description = 'If this server supports mutation, the type that mutation operations will be rooted at.',
  36. kind = __Type,
  37. resolve = function(schema)
  38. return schema:getMutationType()
  39. end
  40. },
  41. directives = {
  42. description = 'A list of all directives supported by this server.',
  43. kind = types.nonNull(types.list(types.nonNull(__Directive))),
  44. resolve = function(schema)
  45. return schema.directives
  46. end
  47. }
  48. }
  49. end
  50. })
  51. __Directive = types.object({
  52. name = '__Directive',
  53. description = trim [[
  54. A Directive provides a way to describe alternate runtime execution and type validation behavior
  55. in a GraphQL document.
  56. In some cases, you need to provide options to alter GraphQL’s execution
  57. behavior in ways field arguments will not suffice, such as conditionally including or skipping a
  58. field. Directives provide this by describing additional information to the executor.
  59. ]],
  60. fields = function()
  61. return {
  62. name = types.nonNull(types.string),
  63. description = types.string,
  64. locations = {
  65. kind = types.nonNull(types.list(types.nonNull(
  66. __DirectiveLocation
  67. )))
  68. },
  69. args = {
  70. kind = types.nonNull(types.list(types.nonNull(__InputValue))),
  71. resolve = function(field)
  72. local args = {}
  73. local transform = function(a, n)
  74. if a.__type then
  75. return { kind = a, name = n }
  76. else
  77. if a.name then return a end
  78. local r = { name = n }
  79. for k,v in pairs(a) do
  80. r[k] = v
  81. end
  82. return r
  83. end
  84. end
  85. for k, v in pairs(field.arguments or {}) do
  86. table.insert(args, transform(v, k))
  87. end
  88. if #args > 0 then
  89. return args
  90. else
  91. return cjson.empty_array
  92. end
  93. end
  94. }
  95. }
  96. end
  97. })
  98. __DirectiveLocation = types.enum({
  99. name = '__DirectiveLocation',
  100. description = trim [[
  101. A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation
  102. describes one such possible adjacencies.
  103. ]],
  104. values = {
  105. QUERY = {
  106. value = 'QUERY',
  107. description = 'Location adjacent to a query operation.'
  108. },
  109. MUTATION = {
  110. value = 'MUTATION',
  111. description = 'Location adjacent to a mutation operation.'
  112. },
  113. FIELD = {
  114. value = 'FIELD',
  115. description = 'Location adjacent to a field.'
  116. },
  117. FRAGMENT_DEFINITION = {
  118. value = 'FRAGMENT_DEFINITION',
  119. description = 'Location adjacent to a fragment definition.'
  120. },
  121. FRAGMENT_SPREAD = {
  122. value = 'FRAGMENT_SPREAD',
  123. description = 'Location adjacent to a fragment spread.'
  124. },
  125. INLINE_FRAGMENT = {
  126. value = 'INLINE_FRAGMENT',
  127. description = 'Location adjacent to an inline fragment.'
  128. }
  129. }
  130. })
  131. __Type = types.object({
  132. name = '__Type',
  133. description = trim [[
  134. The fundamental unit of any GraphQL Schema is the type. There are
  135. many kinds of types in GraphQL as represented by the `__TypeKind` enum.
  136. Depending on the kind of a type, certain fields describe
  137. information about that type. Scalar types provide no information
  138. beyond a name and description, while Enum types provide their values.
  139. Object and Interface types provide the fields they describe. Abstract
  140. types, Union and Interface, provide the Object types possible
  141. at runtime. List and NonNull types compose other types.
  142. ]],
  143. fields = function()
  144. return {
  145. name = types.string,
  146. description = types.string,
  147. kind = {
  148. kind = __TypeKind.nonNull,
  149. resolve = function(type)
  150. if instanceof(type, 'Scalar') then
  151. return 'SCALAR'
  152. elseif instanceof(type, 'Object') then
  153. return 'OBJECT'
  154. elseif instanceof(type, 'Interface') then
  155. return 'INTERFACE'
  156. elseif instanceof(type, 'Union') then
  157. return 'UNION'
  158. elseif instanceof(type, 'Enum') then
  159. return 'ENUM'
  160. elseif instanceof(type, 'InputObject') then
  161. return 'INPUT_OBJECT'
  162. elseif instanceof(type, 'List') then
  163. return 'LIST'
  164. elseif instanceof(type, 'NonNull') then
  165. return 'NON_NULL'
  166. end
  167. error('Unknown kind of kind = ' .. type)
  168. end
  169. },
  170. fields = {
  171. kind = types.list(types.nonNull(__Field)),
  172. arguments = {
  173. includeDeprecated = { kind = types.boolean, defaultValue = false }
  174. },
  175. resolve = function(type, arguments)
  176. if instanceof(type, 'Object') or instanceof(type, 'Interface') then
  177. return util.filter(util.values(type.fields), function(field)
  178. return arguments.includeDeprecated or field.deprecationReason == nil
  179. end)
  180. end
  181. return nil
  182. end
  183. },
  184. interfaces = {
  185. kind = types.list(types.nonNull(__Type)),
  186. resolve = function(type)
  187. if instanceof(type, 'Object') then
  188. return type.interfaces
  189. end
  190. end
  191. },
  192. possibleTypes = {
  193. kind = types.list(types.nonNull(__Type)),
  194. resolve = function(type, arguments, context)
  195. if instanceof(type, 'Interface') or instanceof(type, 'Union') then
  196. return context.schema:getPossibleTypes(type)
  197. end
  198. end
  199. },
  200. enumValues = {
  201. kind = types.list(types.nonNull(__EnumValue)),
  202. arguments = {
  203. includeDeprecated = { kind = types.boolean, defaultValue = false }
  204. },
  205. resolve = function(type, arguments)
  206. if instanceof(type, 'Enum') then
  207. return util.filter(util.values(type.values), function(value)
  208. return arguments.includeDeprecated or not value.deprecationReason
  209. end)
  210. end
  211. end
  212. },
  213. inputFields = {
  214. kind = types.list(types.nonNull(__InputValue)),
  215. resolve = function(type)
  216. if instanceof(type, 'InputObject') then
  217. return util.values(type.fields)
  218. end
  219. end
  220. },
  221. ofType = {
  222. kind = __Type
  223. }
  224. }
  225. end
  226. })
  227. __Field = types.object({
  228. name = '__Field',
  229. description = trim [[
  230. Object and Interface types are described by a list of Fields, each of
  231. which has a name, potentially a list of arguments, and a return type.
  232. ]],
  233. fields = function()
  234. return {
  235. name = types.string.nonNull,
  236. description = types.string,
  237. args = {
  238. -- kind = types.list(__InputValue),
  239. kind = types.nonNull(types.list(types.nonNull(__InputValue))),
  240. resolve = function(field)
  241. return util.map(field.arguments or {}, function(a, n)
  242. if a.__type then
  243. return { kind = a, name = n }
  244. else
  245. if not a.name then
  246. local r = { name = n }
  247. for k,v in pairs(a) do
  248. r[k] = v
  249. end
  250. return r
  251. else
  252. return a
  253. end
  254. end
  255. end)
  256. end
  257. },
  258. type = {
  259. kind = __Type.nonNull,
  260. resolve = function(field)
  261. return field.kind
  262. end
  263. },
  264. isDeprecated = {
  265. kind = types.boolean.nonNull,
  266. resolve = function(field)
  267. return field.deprecationReason ~= nil
  268. end
  269. },
  270. deprecationReason = types.string
  271. }
  272. end
  273. })
  274. __InputValue = types.object({
  275. name = '__InputValue',
  276. description = trim [[
  277. Arguments provided to Fields or Directives and the input fields of an
  278. InputObject are represented as Input Values which describe their type
  279. and optionally a default value.
  280. ]],
  281. fields = function()
  282. return {
  283. name = types.string.nonNull,
  284. description = types.string,
  285. type = {
  286. kind = types.nonNull(__Type),
  287. resolve = function(field)
  288. return field.kind
  289. end
  290. },
  291. defaultValue = {
  292. kind = types.string,
  293. description = 'A GraphQL-formatted string representing the default value for this input value.',
  294. resolve = function(inputVal)
  295. return inputVal.defaultValue and printAst(astFromValue(inputVal.defaultValue, inputVal)) or nil
  296. end
  297. }
  298. }
  299. end
  300. })
  301. __EnumValue = types.object({
  302. name = '__EnumValue',
  303. description = [[
  304. One possible value for a given Enum. Enum values are unique values, not
  305. a placeholder for a string or numeric value. However an Enum value is
  306. returned in a JSON response as a string.
  307. ]],
  308. fields = function()
  309. return {
  310. name = types.string.nonNull,
  311. description = types.string,
  312. isDeprecated = {
  313. kind = types.boolean.nonNull,
  314. resolve = function(enumValue) return enumValue.deprecationReason ~= nil end
  315. },
  316. deprecationReason = types.string
  317. }
  318. end
  319. })
  320. __TypeKind = types.enum({
  321. name = '__TypeKind',
  322. description = 'An enum describing what kind of type a given `__Type` is.',
  323. values = {
  324. SCALAR = {
  325. value = 'SCALAR',
  326. description = 'Indicates this type is a scalar.'
  327. },
  328. OBJECT = {
  329. value = 'OBJECT',
  330. description = 'Indicates this type is an object. `fields` and `interfaces` are valid fields.'
  331. },
  332. INTERFACE = {
  333. value = 'INTERFACE',
  334. description = 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.'
  335. },
  336. UNION = {
  337. value = 'UNION',
  338. description = 'Indicates this type is a union. `possibleTypes` is a valid field.'
  339. },
  340. ENUM = {
  341. value = 'ENUM',
  342. description = 'Indicates this type is an enum. `enumValues` is a valid field.'
  343. },
  344. INPUT_OBJECT = {
  345. value = 'INPUT_OBJECT',
  346. description = 'Indicates this type is an input object. `inputFields` is a valid field.'
  347. },
  348. LIST = {
  349. value = 'LIST',
  350. description = 'Indicates this type is a list. `ofType` is a valid field.'
  351. },
  352. NON_NULL = {
  353. value = 'NON_NULL',
  354. description = 'Indicates this type is a non-null. `ofType` is a valid field.'
  355. }
  356. }
  357. })
  358. --
  359. -- Note that these are GraphQLFieldDefinition and not GraphQLFieldConfig,
  360. -- so the format for args is different.
  361. --
  362. SchemaMetaFieldDef = {
  363. name = '__schema',
  364. kind = __Schema.nonNull,
  365. description = 'Access the current type schema of this server.',
  366. arguments = {},
  367. resolve = function(_, _, info)
  368. return info.schema
  369. end
  370. }
  371. TypeMetaFieldDef = {
  372. name = '__type',
  373. kind = __Type,
  374. description = 'Request the type information of a single type.',
  375. arguments = {
  376. name = types.string.nonNull
  377. },
  378. resolve = function(_, arguments, info)
  379. return info.schema:getType(arguments.name)
  380. end
  381. }
  382. TypeNameMetaFieldDef = {
  383. name = '__typename',
  384. kind = types.string.nonNull,
  385. description = 'The name of the current Object type at runtime.',
  386. arguments = {},
  387. resolve = function(_, _, info)
  388. return info.parentType.name
  389. end
  390. }
  391. -- Produces a GraphQL Value AST given a lua value.
  392. -- Optionally, a GraphQL type may be provided, which will be used to
  393. -- disambiguate between value primitives.
  394. -- | JSON Value | GraphQL Value |
  395. -- | ------------- | -------------------- |
  396. -- | Object | Input Object |
  397. -- | Array | List |
  398. -- | Boolean | Boolean |
  399. -- | String | String / Enum Value |
  400. -- | Number | Int / Float |
  401. local Kind = {
  402. LIST = 'ListValue',
  403. BOOLEAN = 'BooleanValue',
  404. FLOAT = 'FloatValue',
  405. INT = 'IntValue',
  406. FLOAT = 'FloatValue',
  407. ENUM = 'EnumValue',
  408. STRING = 'StringValue',
  409. OBJECT_FIELD = 'ObjectField',
  410. NAME = 'Name',
  411. OBJECT = 'ObjectValue'
  412. }
  413. printers = {
  414. IntValue = function(v) return v.value end,
  415. FloatValue = function(v) return v.value end,
  416. StringValue = function(v) return cjson.encode(v.value) end,
  417. BooleanValue = function(v) return cjson.encode(v.value) end,
  418. EnumValue = function(v) return v.value end,
  419. ListValue = function(v) return '[' .. table.concat(util.map(v.values, printAst), ', ') .. ']' end,
  420. ObjectValue = function(v) return '{' .. table.concat(util.map(v.fields, printAst), ', ') .. '}' end,
  421. ObjectField = function(v) return v.name .. ': ' .. v.value end
  422. }
  423. printAst = function(v)
  424. return printers[v.kind](v)
  425. end
  426. astFromValue = function(value, tt)
  427. -- Ensure flow knows that we treat function params as const.
  428. local _value = value
  429. if instanceof(tt,'NonNull') then
  430. -- Note: we're not checking that the result is non-null.
  431. -- This function is not responsible for validating the input value.
  432. return astFromValue(_value, tt.ofType)
  433. end
  434. if value == nil then
  435. return nil
  436. end
  437. -- Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
  438. -- the value is not an array, convert the value using the list's item type.
  439. if type(_value) == 'table' and #_value > 0 then
  440. local itemType = instanceof(tt, 'List') and tt.ofType or nil
  441. return {
  442. kind = Kind.LIST,
  443. values = util.map(_value, function(item)
  444. local itemValue = astFromValue(item, itemType)
  445. assert(itemValue, 'Could not create AST item.')
  446. return itemValue
  447. end)
  448. }
  449. elseif instanceof(tt, 'List') then
  450. -- Because GraphQL will accept single values as a "list of one" when
  451. -- expecting a list, if there's a non-array value and an expected list type,
  452. -- create an AST using the list's item type.
  453. return astFromValue(_value, tt.ofType)
  454. end
  455. if type(_value) == 'boolean' then
  456. return { kind = Kind.BOOLEAN, value = _value }
  457. end
  458. -- JavaScript numbers can be Float or Int values. Use the GraphQLType to
  459. -- differentiate if available, otherwise prefer Int if the value is a
  460. -- valid Int.
  461. if type(_value) == 'number' then
  462. local stringNum = String(_value)
  463. local isIntValue = _value%1 == 0
  464. if isIntValue then
  465. if tt == types.float then
  466. return { kind = Kind.FLOAT, value = stringNum .. '.0' }
  467. end
  468. return { kind = Kind.INT, value = stringNum }
  469. end
  470. return { kind = Kind.FLOAT, value = stringNum }
  471. end
  472. -- JavaScript strings can be Enum values or String values. Use the
  473. -- GraphQLType to differentiate if possible.
  474. if type(_value) == 'string' then
  475. if instanceof(tt, 'Enum') and _value:match('/^[_a-zA-Z][_a-zA-Z0-9]*$/') then
  476. return { kind =Kind.ENUM, value = _value }
  477. end
  478. -- Use JSON stringify, which uses the same string encoding as GraphQL,
  479. -- then remove the quotes.
  480. return {
  481. kind = Kind.STRING,
  482. value = (cjson.encode(_value)):sub(1, -1)
  483. }
  484. end
  485. -- last remaining possible typeof
  486. assert(type(_value) == 'table')
  487. assert(_value ~= nil)
  488. -- Populate the fields of the input object by creating ASTs from each value
  489. -- in the JavaScript object.
  490. local fields = {}
  491. for fieldName,v in pairs(_value) do
  492. local fieldType
  493. if instanceof(tt, 'InputObject') then
  494. local fieldDef = tt.fields[fieldName]
  495. fieldType = fieldDef and fieldDef.kind
  496. end
  497. local fieldValue = astFromValue(_value[fieldName], fieldType)
  498. if fieldValue then
  499. table.insert(fields, {
  500. kind = Kind.OBJECT_FIELD,
  501. name = { kind = Kind.NAME, value = fieldName },
  502. value = fieldValue
  503. })
  504. end
  505. end
  506. return { kind = Kind.OBJECT, fields = fields }
  507. end
  508. return {
  509. __Schema = __Schema,
  510. __Directive = __Directive,
  511. __DirectiveLocation = __DirectiveLocation,
  512. __Type = __Type,
  513. __Field = __Field,
  514. __EnumValue = __EnumValue,
  515. __TypeKind = __TypeKind,
  516. SchemaMetaFieldDef = SchemaMetaFieldDef,
  517. TypeMetaFieldDef = TypeMetaFieldDef,
  518. TypeNameMetaFieldDef = TypeNameMetaFieldDef
  519. }