types.lua 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. local path = (...):gsub('%.[^%.]+$', '')
  2. local util = require(path .. '.util')
  3. local types = {}
  4. function types.nonNull(kind)
  5. assert(kind, 'Must provide a type')
  6. return {
  7. __type = 'NonNull',
  8. ofType = kind
  9. }
  10. end
  11. function types.list(kind)
  12. assert(kind, 'Must provide a type')
  13. return {
  14. __type = 'List',
  15. ofType = kind
  16. }
  17. end
  18. function types.scalar(config)
  19. assert(type(config.name) == 'string', 'type name must be provided as a string')
  20. assert(type(config.serialize) == 'function', 'serialize must be a function')
  21. if config.parseValue or config.parseLiteral then
  22. assert(
  23. type(config.parseValue) == 'function' and type(config.parseLiteral) == 'function',
  24. 'must provide both parseValue and parseLiteral to scalar type'
  25. )
  26. end
  27. local instance = {
  28. __type = 'Scalar',
  29. name = config.name,
  30. description = config.description,
  31. serialize = config.serialize,
  32. parseValue = config.parseValue,
  33. parseLiteral = config.parseLiteral
  34. }
  35. instance.nonNull = types.nonNull(instance)
  36. return instance
  37. end
  38. function types.object(config)
  39. assert(type(config.name) == 'string', 'type name must be provided as a string')
  40. if config.isTypeOf then
  41. assert(type(config.isTypeOf) == 'function', 'must provide isTypeOf as a function')
  42. end
  43. local fields
  44. if type(config.fields) == 'function' then
  45. fields = util.compose(util.bind1(initFields, 'Object'), config.fields)
  46. else
  47. fields = initFields('Object', config.fields)
  48. end
  49. local instance = {
  50. __type = 'Object',
  51. name = config.name,
  52. description = config.description,
  53. isTypeOf = config.isTypeOf,
  54. fields = fields,
  55. interfaces = config.interfaces
  56. }
  57. instance.nonNull = types.nonNull(instance)
  58. return instance
  59. end
  60. function types.interface(config)
  61. assert(type(config.name) == 'string', 'type name must be provided as a string')
  62. assert(type(config.fields) == 'table', 'fields table must be provided')
  63. if config.resolveType then
  64. assert(type(config.resolveType) == 'function', 'must provide resolveType as a function')
  65. end
  66. local fields
  67. if type(config.fields) == 'function' then
  68. fields = util.compose(util.bind1(initFields, 'Interface'), config.fields)
  69. else
  70. fields = initFields('Interface', config.fields)
  71. end
  72. local instance = {
  73. __type = 'Interface',
  74. name = config.name,
  75. description = config.description,
  76. fields = fields,
  77. resolveType = config.resolveType
  78. }
  79. instance.nonNull = types.nonNull(instance)
  80. return instance
  81. end
  82. function initFields(kind, fields)
  83. assert(type(fields) == 'table', 'fields table must be provided')
  84. local result = {}
  85. for fieldName, field in pairs(fields) do
  86. field = field.__type and { kind = field } or field
  87. result[fieldName] = {
  88. name = fieldName,
  89. kind = field.kind,
  90. description = field.description,
  91. deprecationReason = field.deprecationReason,
  92. arguments = field.arguments or {},
  93. resolve = kind == 'Object' and field.resolve or nil
  94. }
  95. end
  96. return result
  97. end
  98. function types.enum(config)
  99. assert(type(config.name) == 'string', 'type name must be provided as a string')
  100. assert(type(config.values) == 'table', 'values table must be provided')
  101. local instance
  102. local values = {}
  103. for name, entry in pairs(config.values) do
  104. entry = type(entry) == 'table' and entry or { value = entry }
  105. values[name] = {
  106. name = name,
  107. description = entry.description,
  108. deprecationReason = entry.deprecationReason,
  109. value = entry.value
  110. }
  111. end
  112. instance = {
  113. __type = 'Enum',
  114. name = config.name,
  115. description = config.description,
  116. values = values,
  117. serialize = function(name)
  118. return instance.values[name] and instance.values[name].value or name
  119. end
  120. }
  121. instance.nonNull = types.nonNull(instance)
  122. return instance
  123. end
  124. function types.union(config)
  125. assert(type(config.name) == 'string', 'type name must be provided as a string')
  126. assert(type(config.types) == 'table', 'types table must be provided')
  127. local instance = {
  128. __type = 'Union',
  129. name = config.name,
  130. types = config.types
  131. }
  132. instance.nonNull = types.nonNull(instance)
  133. return instance
  134. end
  135. function types.inputObject(config)
  136. assert(type(config.name) == 'string', 'type name must be provided as a string')
  137. local fields = {}
  138. for fieldName, field in pairs(config.fields) do
  139. field = field.__type and { kind = field } or field
  140. fields[fieldName] = {
  141. name = fieldName,
  142. kind = field.kind
  143. }
  144. end
  145. local instance = {
  146. __type = 'InputObject',
  147. name = config.name,
  148. description = config.description,
  149. fields = fields
  150. }
  151. return instance
  152. end
  153. local coerceInt = function(value)
  154. value = tonumber(value)
  155. if not value then return end
  156. if value == value and value < 2 ^ 32 and value >= -2 ^ 32 then
  157. return value < 0 and math.ceil(value) or math.floor(value)
  158. end
  159. end
  160. types.int = types.scalar({
  161. name = 'Int',
  162. description = "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ",
  163. serialize = coerceInt,
  164. parseValue = coerceInt,
  165. parseLiteral = function(node)
  166. if node.kind == 'int' then
  167. return coerceInt(node.value)
  168. end
  169. end
  170. })
  171. types.float = types.scalar({
  172. name = 'Float',
  173. serialize = tonumber,
  174. parseValue = tonumber,
  175. parseLiteral = function(node)
  176. if node.kind == 'float' or node.kind == 'int' then
  177. return tonumber(node.value)
  178. end
  179. end
  180. })
  181. types.string = types.scalar({
  182. name = 'String',
  183. description = "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
  184. serialize = tostring,
  185. parseValue = tostring,
  186. parseLiteral = function(node)
  187. if node.kind == 'string' then
  188. return node.value
  189. end
  190. end
  191. })
  192. local function toboolean(x)
  193. return (x and x ~= 'false') and true or false
  194. end
  195. types.boolean = types.scalar({
  196. name = 'Boolean',
  197. description = "The `Boolean` scalar type represents `true` or `false`.",
  198. serialize = toboolean,
  199. parseValue = toboolean,
  200. parseLiteral = function(node)
  201. if node.kind == 'boolean' then
  202. return toboolean(node.value)
  203. else
  204. return nil
  205. end
  206. end
  207. })
  208. types.id = types.scalar({
  209. name = 'ID',
  210. serialize = tostring,
  211. parseValue = tostring,
  212. parseLiteral = function(node)
  213. return node.kind == 'string' or node.kind == 'int' and node.value or nil
  214. end
  215. })
  216. function types.directive(config)
  217. assert(type(config.name) == 'string', 'type name must be provided as a string')
  218. local instance = {
  219. __type = 'Directive',
  220. name = config.name,
  221. description = config.description,
  222. arguments = config.arguments,
  223. onQuery = config.onQuery,
  224. onMutation = config.onMutation,
  225. onField = config.onField,
  226. onFragmentDefinition = config.onFragmentDefinition,
  227. onFragmentSpread = config.onFragmentSpread,
  228. onInlineFragment = config.onInlineFragment
  229. }
  230. return instance
  231. end
  232. types.include = types.directive({
  233. name = 'include',
  234. description = 'Directs the executor to include this field or fragment only when the `if` argument is true.',
  235. arguments = {
  236. ['if'] = { kind = types.boolean.nonNull, description = 'Included when true.'}
  237. },
  238. onField = true,
  239. onFragmentSpread = true,
  240. onInlineFragment = true
  241. })
  242. types.skip = types.directive({
  243. name = 'skip',
  244. description = 'Directs the executor to skip this field or fragment when the `if` argument is true.',
  245. arguments = {
  246. ['if'] = { kind = types.boolean.nonNull, description = 'Skipped when true.' }
  247. },
  248. onField = true,
  249. onFragmentSpread = true,
  250. onInlineFragment = true
  251. })
  252. return types