123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605 |
- local path = (...):gsub('%.[^%.]+$', '')
- local types = require(path .. '.types')
- local util = require(path .. '.util')
- local cjson = require 'cjson' -- needs to be cloned from here https://github.com/openresty/lua-cjson for cjson.empty_array feature
- local function isNullish(value)
- return value == nil
- end
- local function instanceof(t, s)
- return t.__type == s
- end
- local function resolveDirective(directive)
- local res = {}
- if directive.onQuery then table.insert(res, 'QUERY') end
- if directive.onMutation then table.insert(res, 'MUTATION') end
- if directive.onSubscription then table.insert(res, 'SUBSCRIPTION') end
- if directive.onField then table.insert(res, 'FIELD') end
- if directive.onFragmentDefinition then table.insert(res, 'FRAGMENT_DEFINITION') end
- if directive.onFragmentSpread then table.insert(res, 'FRAGMENT_SPREAD') end
- if directive.onInlineFragment then table.insert(res, 'INLINE_FRAGMENT') end
- return res
- end
- local function mapToList(m)
- local r = {}
- for k,v in pairs(m) do
- table.insert(r, v)
- end
- return r
- end
- local __Schema, __Directive, __DirectiveLocation, __Type, __Field, __InputValue,__EnumValue, TypeKind, __TypeKind, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, astFromValue, printAst, printers
- local DirectiveLocation = {
- QUERY = 'QUERY', MUTATION = 'MUTATION', SUBSCRIPTION = 'SUBSCRIPTION', FIELD = 'FIELD', FRAGMENT_DEFINITION = 'FRAGMENT_DEFINITION', FRAGMENT_SPREAD = 'FRAGMENT_SPREAD', INLINE_FRAGMENT = 'INLINE_FRAGMENT'
- }
- __Schema = types.object({
- name = '__Schema',
- description =
- 'A GraphQL Schema defines the capabilities of a GraphQL server. It ' ..
- 'exposes all available types and directives on the server, as well as ' ..
- 'the entry points for query, mutation, and subscription operations.',
- fields = function() return {
- types = {
- description = 'A list of all types supported by this server.',
- kind = types.nonNull(types.list(types.nonNull(__Type))),
- resolve = function(schema)
- local typeMap = schema:getTypeMap(); local res = {}
- for k,v in pairs(typeMap) do table.insert(res, typeMap[k]) end; return res
- end
- },
- queryType = {
- description = 'The type that query operations will be rooted at.',
- kind = types.nonNull(__Type),
- resolve = function(schema) return schema:getQueryType() end
- },
- mutationType = {
- description = 'If this server supports mutation, the type that ' ..
- 'mutation operations will be rooted at.',
- kind = __Type,
- resolve = function(schema) return schema:getMutationType() end
- },
- subscriptionType = {
- description = 'If this server support subscription, the type that ' ..
- 'subscription operations will be rooted at.',
- kind = __Type,
- resolve = function(schema) return schema:getSubscriptionType() end
- },
- directives = {
- description = 'A list of all directives supported by this server.',
- kind =
- types.nonNull(types.list(types.nonNull(__Directive))),
- resolve = function(schema) return schema.directives end
- }
- } end
- });
- __Directive = types.object({
- name = '__Directive',
- description =
- 'A Directive provides a way to describe alternate runtime execution and ' ..
- 'type validation behavior in a GraphQL document.' ..
- '\n\nIn some cases, you need to provide options to alter GraphQL’s ' ..
- 'execution behavior in ways field arguments will not suffice, such as ' ..
- 'conditionally including or skipping a field. Directives provide this by ' ..
- 'describing additional information to the executor.',
- fields = function() return {
- name = types.nonNull(types.string),
- description = types.string,
- locations = {
- kind = types.nonNull(types.list(types.nonNull(
- __DirectiveLocation
- ))), resolve = resolveDirective
- },
- args = {
- kind =
- types.nonNull(types.list(types.nonNull(__InputValue))),
- --resolve = function(directive) return directive.arguments or {} end
- resolve = function(field)
- local args = {}
- local transform = function(a, n)
- if a.__type then
- return {kind = a, name = n}
- else
- if not a.name then
- local r = {name = n}
- for k,v in pairs(a) do
- r[k] = v
- end
- return r
- else
- return a
- end
- end
- end
- for k, v in pairs(field.arguments or {}) do table.insert(args, transform(v, k)) end
- -- p(args)
- -- return args
- if #args > 0 then return args else return cjson.empty_array end
- end
- },
- -- NOTE = the following three fields are deprecated and are no longer part
- -- of the GraphQL specification.
- onOperation = {
- deprecationReason = 'Use `locations`.',
- kind = types.nonNull(types.boolean),
- resolve = function(d) return
- d.locations:find(DirectiveLocation.QUERY) ~= nil or
- d.locations:find(DirectiveLocation.MUTATION) ~= nil or
- d.locations:find(DirectiveLocation.SUBSCRIPTION) ~= nil end
- },
- onFragment = {
- deprecationReason = 'Use `locations`.',
- kind = types.nonNull(types.boolean),
- resolve = function(d) return
- d.locations:find(DirectiveLocation.FRAGMENT_SPREAD) ~= nil or
- d.locations:find(DirectiveLocation.INLINE_FRAGMENT) ~= nil or
- d.locations:find(DirectiveLocation.FRAGMENT_DEFINITION) ~= nil end
- },
- onField = {
- deprecationReason = 'Use `locations`.',
- kind = types.nonNull(types.boolean),
- resolve = function(d) return d.locations:find(DirectiveLocation.FIELD) ~= nil end
- }
- } end
- });
- __DirectiveLocation = types.enum({
- name = '__DirectiveLocation',
- description =
- 'A Directive can be adjacent to many parts of the GraphQL language, a ' ..
- '__DirectiveLocation describes one such possible adjacencies.',
- values = {
- QUERY = {
- value = DirectiveLocation.QUERY,
- description = 'Location adjacent to a query operation.'
- },
- MUTATION = {
- value = DirectiveLocation.MUTATION,
- description = 'Location adjacent to a mutation operation.'
- },
- SUBSCRIPTION = {
- value = DirectiveLocation.SUBSCRIPTION,
- description = 'Location adjacent to a subscription operation.'
- },
- FIELD = {
- value = DirectiveLocation.FIELD,
- description = 'Location adjacent to a field.'
- },
- FRAGMENT_DEFINITION = {
- value = DirectiveLocation.FRAGMENT_DEFINITION,
- description = 'Location adjacent to a fragment definition.'
- },
- FRAGMENT_SPREAD = {
- value = DirectiveLocation.FRAGMENT_SPREAD,
- description = 'Location adjacent to a fragment spread.'
- },
- INLINE_FRAGMENT = {
- value = DirectiveLocation.INLINE_FRAGMENT,
- description = 'Location adjacent to an inline fragment.'
- },
- }
- });
- __Type = types.object({
- name = '__Type',
- description =
- 'The fundamental unit of any GraphQL Schema is the type. There are ' ..
- 'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' ..
- '\n\nDepending on the kind of a type, certain fields describe ' ..
- 'information about that type. Scalar types provide no information ' ..
- 'beyond a name and description, while Enum types provide their values. ' ..
- 'Object and Interface types provide the fields they describe. Abstract ' ..
- 'types, Union and Interface, provide the Object types possible ' ..
- 'at runtime. List and NonNull types compose other types.',
- fields = function() return {
- kind = {
- kind = types.nonNull(__TypeKind),
- resolve = function (type)
- if instanceof(type, 'Scalar') then
- return TypeKind.SCALAR;
- elseif instanceof(type, 'Object') then
- return TypeKind.OBJECT;
- elseif instanceof(type, 'Interface') then
- return TypeKind.INTERFACE;
- elseif instanceof(type, 'Union') then
- return TypeKind.UNION;
- elseif instanceof(type, 'Enum') then
- return TypeKind.ENUM;
- elseif instanceof(type, 'InputObject') then
- return TypeKind.INPUT_OBJECT;
- elseif instanceof(type, 'List') then
- return TypeKind.LIST;
- elseif instanceof(type, 'NonNull') then
- return TypeKind.NON_NULL;
- end
- error('Unknown kind of kind = ' .. type);
- end
- },
- name = types.string,
- description = types.string,
- fields = {
- kind = types.list(types.nonNull(__Field)),
- arguments = {
- -- includeDeprecated = types.boolean
- includeDeprecated = { kind = types.boolean, defaultValue = false }
- },
- resolve = function(t, args)
- if instanceof(t, 'Object') or
- instanceof(t, 'Interface') then
-
- local fieldMap = t.fields;
- local fields = {}; for k,v in pairs(fieldMap) do table.insert(fields, fieldMap[k]) end
- if not args.includeDeprecated then
- fields = util.filter(fields, function(field) return not field.deprecationReason end);
- end
- if #fields > 0 then return fields else return cjson.empty_array end
- end
- return nil;
- end
- },
- interfaces = {
- kind = types.list(types.nonNull(__Type)),
- resolve = function(type)
- if instanceof(type, 'Object') then
- return type.interfaces and type.interfaces or cjson.empty_array;
- end
- end
- },
- possibleTypes = {
- kind = types.list(types.nonNull(__Type)),
- resolve = function(type, args, context, obj)
- if instanceof(type, 'Interface') or
- instanceof(type, 'Union') then
- return context.schema:getPossibleTypes(type);
- end
- end
- },
- enumValues = {
- kind = types.list(types.nonNull(__EnumValue)),
- arguments = {
- -- includeDeprecated = types.boolean
- includeDeprecated = { kind = types.boolean, defaultValue = false }
- },
- resolve = function(type, args)
- if instanceof(type, 'Enum') then
- local values = type.values;
- if not args.includeDeprecated then
- values = util.filter(values, function(value) return not value.deprecationReason end);
- end
- return mapToList(values);
- end
- end
- },
- inputFields = {
- kind = types.list(types.nonNull(__InputValue)),
- resolve = function(type)
- if instanceof(type, 'InputObject') then
- local fieldMap = type.fields;
- local fields = {}; for k,v in pairs(fieldMap) do table.insert(fields, fieldMap[k]) end; return fields
- end
- end
- },
- ofType = { kind = __Type }
- } end
- });
- __Field = types.object({
- name = '__Field',
- description =
- 'Object and Interface types are described by a list of Fields, each of ' ..
- 'which has a name, potentially a list of arguments, and a return type.',
- fields = function() return {
- name = types.nonNull(types.string),
- description = types.string,
- args = {
- -- kind = types.list(__InputValue),
- kind = types.nonNull(types.list(types.nonNull(__InputValue))),
- resolve = function(field)
- local args = {}
- local transform = function(a, n)
- if a.__type then
- return {kind = a, name = n}
- else
- if not a.name then
- local r = {name = n}
- for k,v in pairs(a) do
- r[k] = v
- end
- return r
- else
- return a
- end
- end
- end
- for k, v in pairs(field.arguments or {}) do table.insert(args, transform(v, k)) end
- -- return args
- if #args > 0 then return args else return cjson.empty_array end
- end
- },
- type = { kind = types.nonNull(__Type), resolve = function(field) return field.kind end },
- isDeprecated = {
- kind = types.nonNull(types.boolean),
- resolve = function(field) return not isNullish(field.deprecationReason) end
- },
- deprecationReason = types.string
- } end
- });
- __InputValue = types.object({
- name = '__InputValue',
- description =
- 'Arguments provided to Fields or Directives and the input fields of an ' ..
- 'InputObject are represented as Input Values which describe their type ' ..
- 'and optionally a default value.',
- fields = function() return {
- name = types.nonNull(types.string),
- description = types.string,
- type = { kind = types.nonNull(__Type), resolve = function(field) return field.kind end },
- defaultValue = {
- kind = types.string,
- description =
- 'A GraphQL-formatted string representing the default value for this ' ..
- 'input value.',
- resolve = function(inputVal) if isNullish(inputVal.defaultValue)
- then return nil
- else return printAst(astFromValue(inputVal.defaultValue, inputVal)) end end
- }
- } end
- });
- __EnumValue = types.object({
- name = '__EnumValue',
- description =
- 'One possible value for a given Enum. Enum values are unique values, not ' ..
- 'a placeholder for a string or numeric value. However an Enum value is ' ..
- 'returned in a JSON response as a string.',
- fields = function() return {
- name = types.nonNull(types.string),
- description = types.string,
- isDeprecated = {
- kind = types.nonNull(types.boolean),
- resolve = function(enumValue) return not isNullish(enumValue.deprecationReason) end
- },
- deprecationReason =
- types.string
- } end
- });
- TypeKind = {
- SCALAR = 'SCALAR',
- OBJECT = 'OBJECT',
- INTERFACE = 'INTERFACE',
- UNION = 'UNION',
- ENUM = 'ENUM',
- INPUT_OBJECT = 'INPUT_OBJECT',
- LIST = 'LIST',
- NON_NULL = 'NON_NULL'
- };
- __TypeKind = types.enum({
- name = '__TypeKind',
- description = 'An enum describing what kind of type a given `__Type` is.',
- values = {
- SCALAR = {
- value = TypeKind.SCALAR,
- description = 'Indicates this type is a scalar.'
- },
- OBJECT = {
- value = TypeKind.OBJECT,
- description = 'Indicates this type is an object. ' ..
- '`fields` and `interfaces` are valid fields.'
- },
- INTERFACE = {
- value = TypeKind.INTERFACE,
- description = 'Indicates this type is an interface. ' ..
- '`fields` and `possibleTypes` are valid fields.'
- },
- UNION = {
- value = TypeKind.UNION,
- description = 'Indicates this type is a union. ' ..
- '`possibleTypes` is a valid field.'
- },
- ENUM = {
- value = TypeKind.ENUM,
- description = 'Indicates this type is an enum. ' ..
- '`enumValues` is a valid field.'
- },
- INPUT_OBJECT = {
- value = TypeKind.INPUT_OBJECT,
- description = 'Indicates this type is an input object. ' ..
- '`inputFields` is a valid field.'
- },
- LIST = {
- value = TypeKind.LIST,
- description = 'Indicates this type is a list. ' ..
- '`ofType` is a valid field.'
- },
- NON_NULL = {
- value = TypeKind.NON_NULL,
- description = 'Indicates this type is a non-null. ' ..
- '`ofType` is a valid field.'
- }
- }
- });
- --
- -- Note that these are GraphQLFieldDefinition and not GraphQLFieldConfig,
- -- so the format for args is different.
- --
- SchemaMetaFieldDef = {
- name = '__schema',
- kind = types.nonNull(__Schema),
- description = 'Access the current type schema of this server.',
- arguments = {},
- resolve = function(source, args, context, obj) return context.schema end
- };
- TypeMetaFieldDef = {
- name = '__type',
- kind = __Type,
- description = 'Request the type information of a single type.',
- arguments = {
- name = types.nonNull(types.string)
- }
- --,resolve = function(source, { name } = { name = string }, context, { schema })
- -- return schema.getType(name) end
- };
- TypeNameMetaFieldDef = {
- name = '__typename',
- kind = types.nonNull(types.string),
- description = 'The name of the current Object type at runtime.',
- arguments = {},
- resolve = function(source, args, context, obj) return obj.parentType.name end
- };
- -- Produces a GraphQL Value AST given a lua value.
- -- Optionally, a GraphQL type may be provided, which will be used to
- -- disambiguate between value primitives.
- -- | JSON Value | GraphQL Value |
- -- | ------------- | -------------------- |
- -- | Object | Input Object |
- -- | Array | List |
- -- | Boolean | Boolean |
- -- | String | String / Enum Value |
- -- | Number | Int / Float |
- local Kind = {
- LIST = 'ListValue',
- BOOLEAN = 'BooleanValue',
- FLOAT = 'FloatValue',
- INT = 'IntValue',
- FLOAT = 'FloatValue',
- ENUM = 'EnumValue',
- STRING = 'StringValue',
- OBJECT_FIELD = 'ObjectField',
- NAME = 'Name',
- OBJECT = 'ObjectValue'
- }
- printers = {
- IntValue = function(v) return v.value end,
- FloatValue = function(v) return v.value end,
- StringValue = function(v) return cjson.encode(v.value) end,
- BooleanValue = function(v) return cjson.encode(v.value) end,
- EnumValue = function(v) return v.value end,
- ListValue = function(v) return '[' .. table.concat(util.map(v.values, printAst), ', ') .. ']' end,
- ObjectValue = function(v) return '{' .. table.concat(util.map(v.fields, printAst), ', ') .. '}' end,
- ObjectField = function(v) return v.name .. ': ' .. v.value end
- }
- printAst = function(v)
- return printers[v.kind](v)
- end
- astFromValue = function(value, tt)
- -- Ensure flow knows that we treat function params as const.
- local _value = value
- if instanceof(tt,'NonNull') then
- -- Note: we're not checking that the result is non-null.
- -- This function is not responsible for validating the input value.
- return astFromValue(_value, tt.ofType)
- end
- if isNullish(_value) then
- return nil
- end
- -- Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
- -- the value is not an array, convert the value using the list's item type.
- if type(_value) == 'table' and #_value > 0 then
- local itemType = instanceof(tt, 'List') and tt.ofType or nil
- return {
- kind = Kind.LIST,
- values = util.map(_value, function(item)
- local itemValue = astFromValue(item, itemType)
- assert(itemValue, 'Could not create AST item.')
- return itemValue
- end)
- }
- elseif instanceof(tt, 'List') then
- -- Because GraphQL will accept single values as a "list of one" when
- -- expecting a list, if there's a non-array value and an expected list type,
- -- create an AST using the list's item type.
- return astFromValue(_value, tt.ofType)
- end
- if type(_value) == 'boolean' then
- return { kind = Kind.BOOLEAN, value = _value }
- end
- -- JavaScript numbers can be Float or Int values. Use the GraphQLType to
- -- differentiate if available, otherwise prefer Int if the value is a
- -- valid Int.
- if type(_value) == 'number' then
- local stringNum = String(_value)
- local isIntValue = _value%1 == 0
- if isIntValue then
- if tt == types.float then
- return { kind = Kind.FLOAT, value = stringNum .. '.0' }
- end
- return { kind = Kind.INT, value = stringNum }
- end
- return { kind = Kind.FLOAT, value = stringNum }
- end
- -- JavaScript strings can be Enum values or String values. Use the
- -- GraphQLType to differentiate if possible.
- if type(_value) == 'string' then
- if instanceof(tt, 'Enum') and _value:match('/^[_a-zA-Z][_a-zA-Z0-9]*$/') then
- return { kind =Kind.ENUM, value = _value }
- end
- -- Use JSON stringify, which uses the same string encoding as GraphQL,
- -- then remove the quotes.
- return {
- kind = Kind.STRING,
- value = (cjson.encode(_value)):sub(1, -1)
- }
- end
- -- last remaining possible typeof
- assert(type(_value) == 'table')
- assert(_value ~= nil)
- -- Populate the fields of the input object by creating ASTs from each value
- -- in the JavaScript object.
- local fields = {}
- for fieldName,v in pairs(_value) do
- local fieldType
- if instanceof(tt, 'InputObject') then
- local fieldDef = tt.fields[fieldName];
- fieldType = fieldDef and fieldDef.kind;
- end
- local fieldValue = astFromValue(_value[fieldName], fieldType);
- if fieldValue then
- table.insert(fields, {
- kind = Kind.OBJECT_FIELD,
- name = { kind = Kind.NAME, value = fieldName },
- value = fieldValue
- })
- end
- end
- return { kind = Kind.OBJECT, fields = fields }
- end
- return {
- __Schema = __Schema,
- __Directive = __Directive,
- __DirectiveLocation = __DirectiveLocation,
- __Type = __Type,
- __Field = __Field,
- __EnumValue = __EnumValue,
- TypeKind = TypeKind,
- __TypeKind = __TypeKind,
- SchemaMetaFieldDef = SchemaMetaFieldDef,
- TypeMetaFieldDef = TypeMetaFieldDef,
- TypeNameMetaFieldDef = TypeNameMetaFieldDef
- }
|