Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

[WIP] Sb/avro arr #48

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
589778a
add comments on gql queries assert and compile functions
SudoBobo Feb 19, 2018
417c83e
do small refactor; add comments
SudoBobo Feb 20, 2018
24ec6c4
add support for array avro type (only with scalars); change args
SudoBobo Feb 21, 2018
9a43096
simplify conditional statements
SudoBobo Feb 22, 2018
6bd1ca2
add mised simple test for array support
SudoBobo Feb 22, 2018
31897d6
add simple test for array avro type usage
SudoBobo Feb 23, 2018
f0fb9f4
move nullable function to utils as it is will be
SudoBobo Feb 23, 2018
ac9f0ed
readability refactor; clean up comments;
SudoBobo Feb 23, 2018
8110208
Update tarantool_graphql.lua
SudoBobo Feb 23, 2018
f50f9fc
Merge remote-tracking branch 'origin/sb/avro_arr' into sb/avro_arr
SudoBobo Feb 24, 2018
043d6f9
small style fixes
SudoBobo Feb 25, 2018
7714393
readability and comments fixes
SudoBobo Feb 26, 2018
470e8e1
remove excess test
SudoBobo Feb 26, 2018
0160c69
remove redundant 'map' test cases
SudoBobo Feb 26, 2018
19b8d8b
fix small error; now general convert_scalar_type accepts nil as opt
SudoBobo Feb 26, 2018
1377c75
add test for avro-array usage (with space accessor);
SudoBobo Feb 26, 2018
5af08d8
move nullable back to tarantool_graphql from utils
SudoBobo Feb 26, 2018
be8457c
add support of avro map and test for simple case with scalar map values
SudoBobo Feb 27, 2018
fec3b31
add test for map with space accessor
SudoBobo Feb 27, 2018
0423ec4
add gql support of avro map/array with records as items/values
SudoBobo Feb 28, 2018
a936771
style/readability fixes after review
SudoBobo Feb 28, 2018
c8abf95
remove executable bit
SudoBobo Feb 28, 2018
951e7ea
add info about Map
SudoBobo Feb 28, 2018
91bd8bf
typo fix
SudoBobo Feb 28, 2018
148f69f
Merge branch 'master' into sb/avro_arr
SudoBobo Feb 28, 2018
d9a7623
remove whitespaces at the end of lines
SudoBobo Feb 28, 2018
31abefa
Merge remote-tracking branch 'origin/sb/avro_arr' into sb/avro_arr
SudoBobo Feb 28, 2018
1f2583a
remove all debug prints
SudoBobo Feb 28, 2018
e406a31
make Map and Object explanation more specific
SudoBobo Feb 28, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 125 additions & 31 deletions graphql/tarantool_graphql.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ local function avro_type(avro_schema)
return 'record*'
elseif utils.is_array(avro_schema) then
return 'union'
elseif avro_schema.type == 'array' then
return 'array'
elseif avro_schema.type == 'array*' then
return 'array*'
elseif avro_schema.type == 'map' then
return 'map'
elseif avro_schema.type == 'map*' then
return 'map*'
end
elseif type(avro_schema) == 'string' then
if avro_schema == 'int' then
Expand All @@ -49,16 +57,7 @@ local function avro_type(avro_schema)
error('unrecognized avro-schema type: ' .. json.encode(avro_schema))
end

-- XXX: recursive skip several NonNull's?
local function nullable(gql_class)
assert(type(gql_class) == 'table', 'gql_class must be a table, got ' ..
type(gql_class))

if gql_class.__type ~= 'NonNull' then return gql_class end

assert(gql_class.ofType ~= nil, 'gql_class.ofType must not be nil')
return gql_class.ofType
end
local nullable = utils.nullable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utils.lua is for auxiliary functions that do not contain domain-specific abstractions or logic. We work with graphql-lua types in core/*.lua and tarantool_graphql.lua. It is better to keep these objects within these files (or create new modules working within such terms), but don't mix abstraction layers. In other words, I propose to give back this function to this module. You can create graphql_utils.lua alternatively, but I don't think we have enough helpers to charge it.


local types_long = types.scalar({
name = 'Long',
Expand Down Expand Up @@ -96,15 +95,25 @@ local function convert_scalar_type(avro_schema, opts)
elseif avro_t == 'string*' then
return types.string
end

if opts.is_items_type then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// This is not more actual because I proposed to remove this option, but I kept this comment here to give you more on our conventions within the project.

Please, keep consistency with asserts and default values for options within the 'opts' parameter. Consider 'raise' as the example. More specific: add the local variable with a default value (or false), add assert for a type of this option. This is more about readability, then about runtime checks, because such function preamble give a developer information re function contract.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't sure we should pass this option to this function, because it is out of the scope of converting scalar types (this is what the function does). By the way, this option breaks the raise option behavior (now it cannot be described just as 'raise = true let the function raise an error on a wrong scalar avro type, raise = false let the function return nil in the case'). I have strong opinion that you should process this situation in a callee code.

error('avro array items must have know scalar type, not: ' ..
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A user must have info to distinguish not-implemented-yet errors from its mistakes in configuration options (like avro-schemas). Proposed wording: "only scalars are supported as array items for now".

json.encode(avro_schema))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use 4 spaces as indent.

end

if raise then
error('unrecognized avro-schema scalar type: ' ..
json.encode(avro_schema))
json.encode(avro_schema))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Cannot get you. Why do you remove the indent? Now it feels as broken.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping?

end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use python-style double empty lines (moreover, here they are within a function, so don't fit PEP8 too). Consider our lua style guidelines: https://tarantool.org/en/doc/1.7/dev_guide/lua_style_guide.html


return nil
end

--- Non-recursive version of the @{gql_type} function that returns
--- InputObject instead of Object.
--- An error will be raised in case of fields that are not scalar types
--- as there are no sense in non scalar arguments.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. You described the case when a record was passed as a function parameter, state that explicitly.
  2. The record case is about a non-scalar argument, we are support that for offset when a primary key is compound. We'll support more non-scalar arguments soon (likely for nested filtering from JOIN semantic for nested objects filtering #39, maybe for complex filters from Add built-in filter function for every Avro class #13).
  3. Try being as specific as you can when writing a documentation. 'There are no sense' comment has no sense itself in the most cases: a) when it is obvious; b) when it is not obvious, but there are no mention of the certain reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. So the point here is that currently we do not support triple nesting level for arguments, but do support double nesting level?

local function gql_argument_type(state, avro_schema)
assert(type(state) == 'table',
'state must be a table, got ' .. type(state))
Expand All @@ -115,17 +124,21 @@ local function gql_argument_type(state, avro_schema)
assert(type(avro_schema.name) == 'string',
('avro_schema.name must be a string, got %s (avro_schema %s)')
:format(type(avro_schema.name), json.encode(avro_schema)))

assert(type(avro_schema.fields) == 'table',
('avro_schema.fields must be a table, got %s (avro_schema %s)')
:format(type(avro_schema.fields), json.encode(avro_schema)))

local fields = {}
for _, field in ipairs(fields) do
for _, field in ipairs(avro_schema.fields) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. Thanks for pointing it out! Filed #53 for that.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to use single empty line as the separator for code blocks, but I propose to don't separate a loop header from its body (or a first code block of the body). The same for empty lines before / after else / elseif / end.

If you have some certain view how we should change our code style, let discuss it first, then (maybe) make changes.

assert(type(field.name) == 'string',
('field.name must be a string, got %s (schema %s)')
:format(type(field.name), json.encode(field)))

local gql_field_type = convert_scalar_type(
field.type, {raise = true})

fields[field.name] = {
name = field.name,
kind = types.nonNull(gql_field_type),
Expand All @@ -149,44 +162,71 @@ local function gql_argument_type(state, avro_schema)
end
end


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line.

--- Recursively convert each field of an avro-schema to a graphql type and
--- corresponding argument for an upper graphql type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Non-recursively.
  2. The wording 'graphql type and argument' sounds confusing for me (expected two arguments). Proposed wording is below.

Convert list of fields in the avro-schema format to list of GraphQL types with intention to use it as GraphQL arguments later.

It uses the @{gql_argument_type} function to convert each field, then skips fields of array and map types and gives the resulting list of converted fields.

---
--- @tparam table state
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add XXX comment if you make some kind of placeholder or partial functionality / documentation, but don't finish it in some manner. Here I mean comments for parameters of the function. If you describe it in some other place, you can use @{...} ldoc syntax to point reader to the right place to get info re a parameter.

--- @tparam table fields
local function convert_record_fields_to_args(state, fields)
local args = {}
for _, field in ipairs(fields) do

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line after the for header, check the related comment above.

assert(type(field.name) == 'string',
('field.name must be a string, got %s (schema %s)')
:format(type(field.name), json.encode(field)))

local gql_class = gql_argument_type(state, field.type)
args[field.name] = nullable(gql_class)

-- arrays (gql lists) and maps can't be arguments
-- so these kinds are to be skipped
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should use the term 'kind' in sense 'a graphql type in the graphql-lua format'. We use 'graphql type' term for that already.

General note: I suggest to try hard to use one term for an one abstraction with a project and use different terms for different abstractions.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't separate a code block comment from its code block with empty line.

if (nullable(gql_class) ~= 'List'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add extra parenthesis around an if condition (it is not required by the Lua syntax).

and nullable(gql_class) ~= 'Map') then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent here is strange. Use double indent (8 spaces) when you need to carry the line with an if condition.

args[field.name] = nullable(gql_class)
end
end
return args
end

--- Convert each field of an avro-schema to a graphql type and corresponding
--- argument for an upper graphql type.
--- Recursively convert each field of an avro-schema to a graphql type and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that such implementation details is not for the header string of the function. You can write details below, like so:

--- What the function does.
---
--- Any details here.
---
--- ...params and return values...
local function...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping?

--- corresponding argument for an upper graphql type.
---
--- @tparam table state for read state.accessor and previously filled
--- state.types
--- @tparam table fields fields part from an avro-schema
local function convert_record_fields(state, fields)
--- @tparam table opts include is_for_args flag to specify
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter is redundant. You always set it to true when use the object_args result. Just do true-case action always.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping?

--- case when the function is used to collect arguments
local function convert_record_fields(state, fields, opts)
local res = {}
local object_args = {}
local opts = opts or {}
local is_for_args = opts.is_for_args or false

for _, field in ipairs(fields) do
assert(type(field.name) == 'string',
('field.name must be a string, got %s (schema %s)')
:format(type(field.name), json.encode(field)))

res[field.name] = {
name = field.name,
kind = gql_type(state, field.type),
}
object_args[field.name] = nullable(res[field.name].kind)


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use single empty line to split code blocks.

-- arrays (gql lists) and maps can't be arguments
if not is_for_args or (nullable(res[field.name].kind) ~= 'List'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, it is better to use avro_type(field.type) here. Maybe I think so because I don't know exact structure of graphql-lua types implementation.

and nullable(res[field.name].kind) ~= 'Map') then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use double indent for such cases.

object_args[field.name] = nullable(res[field.name].kind)
end
end
return res, object_args
end

--- The function recursively converts passed avro-schema to a graphql type.
--- The function recursively converts passed avro-schema to a graphql type (kind).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I keep my lines within 79 symbols. The limit can be discussed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I write above I think we need to omit usage of the term 'kind' for graphql types.

---
--- @tparam table state for read state.accessor and previously filled
--- state.types
--- state.types (state.types are gql types)
--- @tparam table avro_schema input avro-schema
--- @tparam[opt] table collection table with schema_name, connections fields
--- described a collection (e.g. tarantool's spaces)
Expand All @@ -212,12 +252,14 @@ gql_type = function(state, avro_schema, collection, collection_name)
('collection and collection_name must be nils or ' ..
'non-nils simultaneously, got: %s and %s'):format(type(collection),
type(collection_name)))

local accessor = state.accessor
assert(accessor ~= nil, 'state.accessor must not be nil')
assert(accessor.select ~= nil, 'state.accessor.select must not be nil')
assert(accessor.list_args ~= nil,
'state.accessor.list_args must not be nil')

-- type of the top element in the avro schema
local avro_t = avro_type(avro_schema)

if avro_t == 'record' or avro_t == 'record*' then
Expand All @@ -230,6 +272,7 @@ gql_type = function(state, avro_schema, collection, collection_name)

local fields, _ = convert_record_fields(state, avro_schema.fields)

-- if collection param is passed then go over all connections
for _, c in ipairs((collection or {}).connections or {}) do
assert(type(c.type) == 'string',
'connection.type must be a string, got ' .. type(c.type))
Expand All @@ -243,6 +286,7 @@ gql_type = function(state, avro_schema, collection, collection_name)
assert(type(c.parts) == 'table',
'connection.parts must be a string, got ' .. type(c.parts))

-- gql type of connection field
local destination_type =
state.types[c.destination_collection]
assert(destination_type ~= nil,
Expand All @@ -261,20 +305,24 @@ gql_type = function(state, avro_schema, collection, collection_name)

local c_list_args = state.list_arguments[c.destination_collection]

-- change fields that are represented by connections
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not about changing, here we just add fields by connections.

fields[c.name] = {
name = c.name,
kind = destination_type,
arguments = c_args,
resolve = function(parent, args_instance, info)
local destination_args_names = {}
local destination_args_values = {}

for _, part in ipairs(c.parts) do

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line after the for header.

assert(type(part.source_field) == 'string',
'part.source_field must be a string, got ' ..
type(part.destination_field))
assert(type(part.destination_field) == 'string',
'part.destination_field must be a string, got ' ..
type(part.destination_field))

destination_args_names[#destination_args_names + 1] =
part.destination_field
destination_args_values[#destination_args_values + 1] =
Expand Down Expand Up @@ -318,24 +366,50 @@ gql_type = function(state, avro_schema, collection, collection_name)
}
end

-- create gql schema
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema -> type

local res = types.object({
name = collection ~= nil and collection.name or avro_schema.name,
description = 'generated from avro-schema for ' ..
avro_schema.name,
fields = fields,
})
return avro_t == 'enum' and types.nonNull(res) or res

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line before elseif.

elseif avro_t == 'enum' then
error('enums not implemented yet') -- XXX

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line before elseif.

elseif avro_t == 'array' or avro_t == 'array*' then

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line afterelseif.

assert(avro_schema.items ~= nil,
'items field must not be nil in array avro schema')
assert(type(avro_schema.items) == 'string',
'avro_schema.items must be a string, got ' .. type(avro_schema.item))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line is longer than 79 symbols.


local gql_items_type = convert_scalar_type(avro_schema.items,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to leave XXX comment here that it is not clear what to do with complex types inside arrays (just pass to results or allow to use filters and so on), so we allow only scalar arrays for now.

{is_items_type=true, raise=true})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You duplicate logic of the raise flag with the is_items_type flag, yep? I don't get why. If you want to make custom error message then use raise = false and assert for nil result, I use that approach with this function (grep it).


local gql_array = types.list(gql_items_type)

if avro_t == 'array*' then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Double space after if.
  2. IMO that two ifs would look better with Lua's 'ternary operator': return avro_t == 'array' and types.nonNull(gql_array) or gql_array.

return gql_array
end

if avro_t == 'array' then
return types.nonNull(gql_array)
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line before else.

else
local res = convert_scalar_type(avro_schema, {raise = false})
if res == nil then
error('unrecognized avro-schema type: ' .. json.encode(avro_schema))
end
return res

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line before end.

end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line before end.

end


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use python-style double empty lines.

local function parse_cfg(cfg)
local state = {}
state.types = utils.gen_booking_table({})
Expand All @@ -356,32 +430,41 @@ local function parse_cfg(cfg)

local fields = {}

for name, collection in pairs(state.collections) do
collection.name = name
for collection_name, collection in pairs(state.collections) do
collection.name = collection_name
assert(collection.schema_name ~= nil,
'collection.schema_name must not be nil')

local schema = cfg.schemas[collection.schema_name]
assert(schema ~= nil, ('cfg.schemas[%s] must not be nil'):format(
tostring(collection.schema_name)))
assert(schema.name == nil or schema.name == collection.schema_name,
('top-level schema name does not match the name in ' ..
'the schema itself: "%s" vs "%s"'):format(collection.schema_name,
schema.name))
state.types[name] = gql_type(state, schema, collection, name)

-- recursively converts all avro types into gql types in the given schema
assert(schema.type == 'record',
'top-level schema must have record avro type, not' .. schema.type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space is missing after 'not'.

state.types[collection_name] = gql_type(state, schema, collection, collection_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

79 symbols.


-- prepare arguments (their kinds)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed to don't use the term 'kind' for graphql types (see above).

local _, object_args = convert_record_fields(state,
schema.fields)
schema.fields, {is_for_args=true})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed to remove the parameter (see above).

local list_args = convert_record_fields_to_args(
state, accessor:list_args(name))
state, accessor:list_args(collection_name), {is_for_args=true})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. You pass a parameter that you don't define in the function profile.
  2. We must not filter accessor-provided arguments. Feel free to add asserts that all arguments we got here are correct.

local args = utils.merge_tables(object_args, list_args)
state.object_arguments[name] = object_args
state.list_arguments[name] = list_args
state.all_arguments[name] = args

-- list and map (avro array and map) can't be arguments
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get, how this comment related to the code above? Maybe we need to move it to the right place.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't separate a code block comment from it code blocks by an empty line.

state.object_arguments[collection_name] = object_args
state.list_arguments[collection_name] = list_args
state.all_arguments[collection_name] = args

-- create entry points from collection names
fields[name] = {
kind = types.nonNull(types.list(state.types[name])),
arguments = state.all_arguments[name],
fields[collection_name] = {
kind = types.nonNull(types.list(state.types[collection_name])),
arguments = state.all_arguments[collection_name],
resolve = function(rootValue, args_instance, info)
local object_args_instance = {} -- passed to 'filter'
local list_args_instance = {} -- passed to 'args'
Expand All @@ -397,7 +480,7 @@ local function parse_cfg(cfg)
end
end
local from = nil
return accessor:select(rootValue, name, from,
return accessor:select(rootValue, collection_name, from,
object_args_instance, list_args_instance)
end,
}
Expand All @@ -414,6 +497,8 @@ local function parse_cfg(cfg)
return state
end

--- The function checks if given query has an appropriate type 'query'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the function does the other action: it checks that one and only one GraphQL operation (query/mutation/subscription) is defined in the AST. That just the minimum check that we got an query AST and not mistakely some other value.

--- (mutations are not supported yet).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That note is unrelated to the function. It will doing what it doing despite types of GraphQL operations.

local function assert_gql_query_ast(func_name, ast)
assert(#ast.definitions == 1,
func_name .. ': expected an one query')
Expand All @@ -425,6 +510,8 @@ local function assert_gql_query_ast(func_name, ast)
type(operation_name))
end

--- The function just makes some reasonable assertions on input
--- and then call graphql-lua execute.
local function gql_execute(qstate, variables)
assert(qstate.state)
local state = qstate.state
Expand All @@ -442,6 +529,12 @@ local function gql_execute(qstate, variables)
operation_name)
end

--- The function parses a raw query string, validate the resulting query
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The term 'raw' is a bit confusing, I think. Just 'a query string'.
  2. 'Validate the resulting query'. Too common. Make it specific by adding 'against the GraphQL scheme.
  3. 'Make it ready for execution'. Too common. You can say 'provides an object with the function to execute the query with specific variables values).

--- and make it ready for execution.
---
--- @tparam table state current state of graphql-lib, including
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't used the term 'graphql-lib' before.

--- schemas, collections and accessor
--- @tparam string query raw query string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed to omit the word 'raw'.

local function gql_compile(state, query)
assert(type(state) == 'table' and type(query) == 'string',
'use :validate(...) instead of .validate(...)')
Expand All @@ -458,6 +551,7 @@ local function gql_compile(state, query)
ast = ast,
operation_name = operation_name,
}

local gql_query = setmetatable(qstate, {
__index = {
execute = gql_execute,
Expand Down
12 changes: 12 additions & 0 deletions graphql/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,16 @@ function utils.gen_booking_table(data)
})
end

-- XXX: recursive skip several NonNull's?
function utils.nullable(gql_class)
assert(type(gql_class) == 'table', 'gql_class must be a table, got ' ..
type(gql_class))

if gql_class.__type ~= 'NonNull' then return gql_class end

assert(gql_class.ofType ~= nil, 'gql_class.ofType must not be nil')
return gql_class.ofType
end


return utils
Loading