diff --git a/cartridge/graphql/execute.lua b/cartridge/graphql/execute.lua index 9a3ffe2fb..a0077d4c0 100644 --- a/cartridge/graphql/execute.lua +++ b/cartridge/graphql/execute.lua @@ -194,12 +194,16 @@ local function completeValue(fieldType, result, subSelections, context, opts) end if fieldTypeName == 'List' then - local innerType = fieldType.ofType - - if type(result) ~= 'table' then - error('Expected a table for ' .. innerType.name .. ' list') + if not util.is_array(result) then + local resultType = type(result) + if resultType == 'table' then + resultType = 'map' + end + local message = ('Expected %q to be an "array", got %q'):format(fieldName, resultType) + error(message) end + local innerType = fieldType.ofType local values = {} for i, value in ipairs(result) do values[i] = completeValue(innerType, value, subSelections, context) @@ -213,6 +217,10 @@ local function completeValue(fieldType, result, subSelections, context, opts) end if fieldTypeName == 'Object' then + if type(result) ~= 'table' then + local message = ('Expected %q to be a "map", got %q'):format(fieldName, type(result)) + error(message) + end local completed = evaluateSelections(fieldType, result, subSelections, context) setmetatable(completed, serializemap) return completed diff --git a/cartridge/graphql/util.lua b/cartridge/graphql/util.lua index cd8894bc0..c427610a7 100644 --- a/cartridge/graphql/util.lua +++ b/cartridge/graphql/util.lua @@ -190,7 +190,9 @@ end --- case) --- @return[2] `false` otherwise local function is_array(table) - check(table, 'table', 'table') + if type(table) ~= 'table' then + return false + end local max = 0 local count = 0 diff --git a/test/integration/graphql_test.lua b/test/integration/graphql_test.lua index de27d6028..289ef93d3 100644 --- a/test/integration/graphql_test.lua +++ b/test/integration/graphql_test.lua @@ -281,7 +281,7 @@ g.test_reread_request = function() end -g.test_enum = function() +g.test_enum_input = function() local server = cluster.main_server server.net_box:eval([[ @@ -343,6 +343,53 @@ g.test_enum = function() end) end +g.test_enum_output = function() + local server = cluster.main_server + + server.net_box:eval([[ + package.loaded['test'] = package.loaded['test'] or {} + package.loaded['test']['test_enum_output'] = function(_, _) + return {value = 'a'} + end + + local graphql = require('cartridge.graphql') + local types = require('cartridge.graphql.types') + + local simple_enum = types.enum { + name = 'simple_enum_output', + values = { + a = { value = 'a' }, + b = { value = 'b' }, + }, + } + + local object = types.object({ + name = 'simple_object', + fields = { + value = simple_enum, + } + }) + + graphql.add_callback({ + name = 'test_enum_output', + args = {}, + kind = object, + callback = 'test.test_enum_output', + }) + ]]) + + t.assert_equals( + server:graphql({ + query = [[ + query { + test_enum_output{ value } + } + ]], + variables = {}} + ).data.test_enum_output.value, 'a' + ) +end + g.test_unknown_query_mutation = function() local server = cluster.main_server t.assert_error_msg_equals( @@ -687,6 +734,105 @@ g.test_custom_type_scalar_variables = function() end) end +g.test_output_type_mismatch_error = function() + local server = cluster.main_server + + server.net_box:eval([[ + package.loaded['test'] = {} + package.loaded['test']['callback'] = function(_, _) + return true + end + + package.loaded['test']['callback_for_nested'] = function(_, _) + return { values = true } + end + + local graphql = require('cartridge.graphql') + local types = require('cartridge.graphql.types') + + local obj_type = types.object({ + name = 'ObjectWithValue', + fields = { + value = types.string, + }, + }) + + local nested_obj_type = types.object({ + name = 'NestedObjectWithValue', + fields = { + value = types.string, + }, + }) + + local complex_obj_type = types.object({ + name = 'ComplexObjectWithValue', + fields = { + values = types.list(nested_obj_type), + }, + }) + + graphql.add_callback({ + name = 'expected_nonnull_list', + kind = types.list(types.int.nonNull), + callback = 'test.callback', + }) + + graphql.add_callback({ + name = 'expected_obj', + kind = obj_type, + callback = 'test.callback', + }) + + graphql.add_callback({ + name = 'expected_list', + kind = types.list(types.int), + callback = 'test.callback', + }) + + graphql.add_callback({ + name = 'expected_list_with_nested', + kind = types.list(complex_obj_type), + callback = 'test.callback_for_nested', + }) + ]]) + + t.assert_error_msg_equals('Expected "expected_nonnull_list" to be an "array", got "boolean"', function() + return server:graphql({ + query = [[ + query { + expected_nonnull_list + } + ]]}) + end) + + t.assert_error_msg_equals('Expected "expected_obj" to be a "map", got "boolean"', function() + return server:graphql({ + query = [[ + query { + expected_obj { value } + } + ]]}) + end) + + t.assert_error_msg_equals('Expected "expected_list" to be an "array", got "boolean"', function() + return server:graphql({ + query = [[ + query { + expected_list + } + ]]}) + end) + + t.assert_error_msg_equals('Expected "expected_list_with_nested" to be an "array", got "map"', function() + return server:graphql({ + query = [[ + query { + expected_list_with_nested { values { value } } + } + ]]}) + end) +end + function g.test_error_extensions() local request = { query = [[mutation($uuids: [String!]) {