2
0

introspection.lua 20 KB

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