types.lua 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 values = {}
  102. for k, v in pairs(config.values) do
  103. local val = type(v) == 'table' and v or {value = v}
  104. values[k] = {
  105. name = k,
  106. description = val.description,
  107. deprecationReason = val.deprecationReason,
  108. value = val.value
  109. }
  110. end
  111. local instance = {
  112. __type = 'Enum',
  113. name = config.name,
  114. description = config.description,
  115. serialize = function(self, v)
  116. if self.values[v] then return self.values[v].value
  117. else return nil
  118. end
  119. end,
  120. values = values
  121. }
  122. instance.nonNull = types.nonNull(instance)
  123. return instance
  124. end
  125. function types.union(config)
  126. assert(type(config.name) == 'string', 'type name must be provided as a string')
  127. assert(type(config.types) == 'table', 'types table must be provided')
  128. local instance = {
  129. __type = 'Union',
  130. name = config.name,
  131. types = config.types
  132. }
  133. instance.nonNull = types.nonNull(instance)
  134. return instance
  135. end
  136. function types.inputObject(config)
  137. assert(type(config.name) == 'string', 'type name must be provided as a string')
  138. local fields = {}
  139. for fieldName, field in pairs(config.fields) do
  140. field = field.__type and { kind = field } or field
  141. fields[fieldName] = {
  142. name = fieldName,
  143. kind = field.kind
  144. }
  145. end
  146. local instance = {
  147. __type = 'InputObject',
  148. name = config.name,
  149. description = config.description,
  150. fields = fields
  151. }
  152. return instance
  153. end
  154. local coerceInt = function(value)
  155. value = tonumber(value)
  156. if not value then return end
  157. if value == value and value < 2 ^ 32 and value >= -2 ^ 32 then
  158. return value < 0 and math.ceil(value) or math.floor(value)
  159. end
  160. end
  161. types.int = types.scalar({
  162. name = 'Int',
  163. description = "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ",
  164. serialize = coerceInt,
  165. parseValue = coerceInt,
  166. parseLiteral = function(node)
  167. if node.kind == 'int' then
  168. return coerceInt(node.value)
  169. end
  170. end
  171. })
  172. types.float = types.scalar({
  173. name = 'Float',
  174. serialize = tonumber,
  175. parseValue = tonumber,
  176. parseLiteral = function(node)
  177. if node.kind == 'float' or node.kind == 'int' then
  178. return tonumber(node.value)
  179. end
  180. end
  181. })
  182. types.string = types.scalar({
  183. name = 'String',
  184. 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.",
  185. serialize = tostring,
  186. parseValue = tostring,
  187. parseLiteral = function(node)
  188. if node.kind == 'string' then
  189. return node.value
  190. end
  191. end
  192. })
  193. local function toboolean(x)
  194. return (x and x ~= 'false') and true or false
  195. end
  196. types.boolean = types.scalar({
  197. name = 'Boolean',
  198. description = "The `Boolean` scalar type represents `true` or `false`.",
  199. serialize = toboolean,
  200. parseValue = toboolean,
  201. parseLiteral = function(node)
  202. if node.kind == 'boolean' then
  203. return toboolean(node.value)
  204. else
  205. return nil
  206. end
  207. end
  208. })
  209. types.id = types.scalar({
  210. name = 'ID',
  211. serialize = tostring,
  212. parseValue = tostring,
  213. parseLiteral = function(node)
  214. return node.kind == 'string' or node.kind == 'int' and node.value or nil
  215. end
  216. })
  217. function types.directive(config)
  218. assert(type(config.name) == 'string', 'type name must be provided as a string')
  219. local instance = {
  220. __type = 'Directive',
  221. name = config.name,
  222. description = config.description,
  223. arguments = config.arguments,
  224. onQuery = config.onQuery or false,
  225. onMutation = config.onMutation or false,
  226. onSubscription = config.onSubscription or false,
  227. onField = config.onField or false,
  228. onFragmentDefinition = config.onFragmentDefinition or false,
  229. onFragmentSpread = config.onFragmentSpread or false,
  230. onInlineFragment = config.onInlineFragment or false,
  231. }
  232. return instance
  233. end
  234. types.include = types.directive({
  235. name = 'include',
  236. description = 'Directs the executor to include this field or fragment only when the `if` argument is true.',
  237. arguments = {
  238. ['if'] = { kind = types.boolean.nonNull, description = 'Included when true.'}
  239. },
  240. onField = true,
  241. onFragmentSpread = true,
  242. onInlineFragment = true
  243. })
  244. types.skip = types.directive({
  245. name = 'skip',
  246. description = 'Directs the executor to skip this field or fragment when the `if` argument is true.',
  247. arguments = {
  248. ['if'] = { kind = types.boolean.nonNull, description = 'Skipped when true.' }
  249. },
  250. onField = true,
  251. onFragmentSpread = true,
  252. onInlineFragment = true
  253. })
  254. return types