Skip to content

Commit

Permalink
Merge pull request Kong#277 from Mashape/feature/arrays
Browse files Browse the repository at this point in the history
[feature] new `array` type for schema fields

Former-commit-id: f8b370fea7fc518fe1e7f7ff0629b06e1250f26e
  • Loading branch information
thibaultcha committed May 29, 2015
2 parents 999d934 + 6d44382 commit b922421
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 177 deletions.
97 changes: 56 additions & 41 deletions kong/dao/schemas_validation.lua
Original file line number Diff line number Diff line change
@@ -1,71 +1,85 @@
local utils = require "kong.tools.utils"
local stringy = require "stringy"
local constants = require "kong.constants"

local LUA_TYPES = {
boolean = true,
local POSSIBLE_TYPES = {
id = true,
table = true,
array = true,
string = true,
number = true,
table = true
boolean = true,
timestamp = true
}

local LUA_TYPE_ALIASES = {
[constants.DATABASE_TYPES.ID] = "string",
[constants.DATABASE_TYPES.TIMESTAMP] = "number"
local types_validation = {
[constants.DATABASE_TYPES.ID] = function(v) return type(v) == "string" end,
[constants.DATABASE_TYPES.TIMESTAMP] = function(v) return type(v) == "number" end,
["array"] = function(v) return utils.is_array(v) end
}

local _M = {}

-- Returns the proper Lua type from a schema type, handling aliases
-- @param {string} type_val The type of the schema property
-- @return {string} A valid Lua type
function _M.get_type(type_val)
local alias = LUA_TYPE_ALIASES[type_val]
return alias and alias or type_val
local function validate_type(field_type, value)
if types_validation[field_type] then
return types_validation[field_type](value)
end
return type(value) == field_type
end

local _M = {}

-- Validate a table against a given schema
-- @param {table} t Table to validate
-- @param {table} schema Schema against which to validate the table
-- @param {boolean} is_update For an entity update, we might want a slightly different behaviour
-- @return {boolean} Success of validation
-- @return {table} A list of encountered errors during the validation
-- @param `t` Entity to validate, as a table.
-- @param `schema` Schema against which to validate the entity.
-- @param `is_update` For an entity update, check immutable fields. Set to true.
-- @return `valid` Success of validation. True or false.
-- @return `errors` A list of encountered errors during the validation.
function _M.validate(t, schema, is_update)
local errors

-- Check the given table against a given schema
for column, v in pairs(schema) do

-- Set default value for the field if given
-- [DEFAULT] Set default value for the field if given
if t[column] == nil and v.default ~= nil then
if type(v.default) == "function" then
t[column] = v.default(t)
else
t[column] = v.default
end
-- [IMMUTABLE] check immutability of a field if updating
elseif is_update and t[column] ~= nil and v.immutable and not v.required then
-- is_update check immutability of a field
errors = utils.add_error(errors, column, column.." cannot be updated")
end

-- Check if type is valid boolean and numbers as strings are accepted and converted
-- [TYPE] Check if type is valid. Boolean and Numbers as strings are accepted and converted
if v.type ~= nil and t[column] ~= nil then
local valid
if _M.get_type(v.type) == "number" and type(t[column]) == "string" then -- a number can also be sent as a string
t[column] = tonumber(t[column])
valid = t[column] ~= nil
elseif _M.get_type(v.type) == "boolean" and type(t[column]) == "string" then
local bool = t[column]:lower()
valid = bool == "true" or bool == "false"
t[column] = bool == "true"
local is_valid_type

-- ALIASES: number, boolean and array can be passed as strings and will be converted
if type(t[column]) == "string" then
if v.type == "number" then
t[column] = tonumber(t[column])
is_valid_type = t[column] ~= nil
elseif v.type == "boolean" then
local bool = t[column]:lower()
is_valid_type = bool == "true" or bool == "false"
t[column] = bool == "true"
elseif v.type == "array" then
t[column] = stringy.split(t[column], ",")
is_valid_type = validate_type(v.type, t[column])
else -- if string
is_valid_type = validate_type(v.type, t[column])
end
else
valid = type(t[column]) == _M.get_type(v.type)
is_valid_type = validate_type(v.type, t[column])
end
if not valid and LUA_TYPES[v.type] then

if not is_valid_type and POSSIBLE_TYPES[v.type] then
errors = utils.add_error(errors, column, column.." is not a "..v.type)
end
end

-- Check type if value is allowed in the enum
-- [ENUM] Check if the value is allowed in the enum.
if v.enum and t[column] ~= nil then
local found = false
for _, allowed in ipairs(v.enum) do
Expand All @@ -80,14 +94,14 @@ function _M.validate(t, schema, is_update)
end
end

-- Check field against a regex if specified
-- [REGEX] Check field against a regex if specified
if t[column] ~= nil and v.regex then
if not ngx.re.match(t[column], v.regex) then
errors = utils.add_error(errors, column, column.." has an invalid value")
end
end

-- validate a subschema
-- [SCHEMA] Validate a sub-schema from a table or retrived by a function
if v.schema then
local sub_schema, err
if type(v.schema) == "function" then
Expand All @@ -102,19 +116,19 @@ function _M.validate(t, schema, is_update)
end

if sub_schema then
-- Check for sub-schema defaults and required properties
-- Check for sub-schema defaults and required properties in advance
for sub_field_k, sub_field in pairs(sub_schema) do
if t[column] == nil then
if sub_field.default then
if sub_field.default then -- Sub-value has a default, be polite and pre-assign the sub-value
t[column] = {}
elseif sub_field.required then -- only check required if field doesn't have a default
elseif sub_field.required then -- Only check required if field doesn't have a default
errors = utils.add_error(errors, column, column.."."..sub_field_k.." is required")
end
end
end

if t[column] and type(t[column]) == "table" then
-- validating subschema
-- Actually validating the sub-schema
local s_ok, s_errors = _M.validate(t[column], sub_schema, is_update)
if not s_ok then
for s_k, s_v in pairs(s_errors) do
Expand All @@ -125,12 +139,13 @@ function _M.validate(t, schema, is_update)
end
end

-- Check required fields are set
-- [REQUIRED] Check that required fields are set. Now that default and most other checks
-- have been run.
if v.required and (t[column] == nil or t[column] == "") then
errors = utils.add_error(errors, column, column.." is required")
end

-- Check field against a custom function only if there is no error on that field already
-- [FUNC] Check field against a custom function only if there is no error on that field already
if v.func and type(v.func) == "function" and (errors == nil or errors[column] == nil) then
local ok, err, new_fields = v.func(t[column], t)
if not ok and err then
Expand Down
12 changes: 1 addition & 11 deletions kong/plugins/keyauth/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,7 @@ local function default_key_names(t)
end
end

local function validate_key_names(t)
if type(t) == "table" and not utils.is_array(t) then
local printable_mt = require "kong.tools.printable"
setmetatable(t, printable_mt)
return false, "key_names must be an array. '"..t.."' is a table. Lua tables must have integer indexes starting at 1."
end

return true
end

return {
key_names = { required = true, type = "table", default = default_key_names, func = validate_key_names },
key_names = { required = true, type = "array", default = default_key_names },
hide_credentials = { type = "boolean", default = false }
}
1 change: 0 additions & 1 deletion kong/plugins/request_transformer/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ local function get_content_type(request)
if header_value then
return stringy.strip(header_value)
end
return nil
end

function _M.execute(conf)
Expand Down
18 changes: 10 additions & 8 deletions kong/plugins/request_transformer/schema.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
return {
add = { type = "table", schema = {
form = { type = "table" },
headers = { type = "table" },
querystring = { type = "table" }
add = { type = "table",
schema = {
form = { type = "array" },
headers = { type = "array" },
querystring = { type = "array" }
}
},
remove = { type = "table", schema = {
form = { type = "table" },
headers = { type = "table" },
querystring = { type = "table" }
remove = { type = "table",
schema = {
form = { type = "array" },
headers = { type = "array" },
querystring = { type = "array" }
}
}
}
Loading

0 comments on commit b922421

Please sign in to comment.