Skip to content

Commit

Permalink
chore(dao) allow a DAO to override insert values
Browse files Browse the repository at this point in the history
- New property on a schema field: `dao_insert_value` to specify this
  value will be set by the DAO. Any previously set value will be
  overriden. Ignored on update operation.
- This property calls a function expected as being part of the `options`
  table given to `validate`. This function will take the field
  definition as a parameter.
- No more constants for schema types for several reasons: lua types
  aren't constants, so string, number, or id are now all on the same
  level: plain strings. It should be considered a **litteral**. It's
  also painful to import constants everywhere, especially in plugins.
  Not much benefits from making those constants.

Partially fixes Kong#103


Former-commit-id: 50d0832c2786f235e8b5766be2a6d281a2bc8d11
  • Loading branch information
thibaultcha committed Jun 23, 2015
1 parent 4fde8a4 commit d33704f
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 42 deletions.
4 changes: 0 additions & 4 deletions kong/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ return {
UNIQUE = "unique",
FOREIGN = "foreign"
},
DATABASE_TYPES = {
ID = "id",
TIMESTAMP = "timestamp"
},
-- Non standard headers, specific to Kong
HEADERS = {
HOST_OVERRIDE = "X-Host-Override",
Expand Down
21 changes: 11 additions & 10 deletions kong/dao/cassandra/base_dao.lua
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,13 @@ local function encode_cassandra_args(schema, t, args_keys)
local schema_field = schema[column]
local value = t[column]

if schema_field.type == constants.DATABASE_TYPES.ID and value then
if schema_field.type == "id" and value then
if validations.is_valid_uuid(value) then
value = cassandra.uuid(value)
else
errors = utils.add_error(errors, column, value.." is an invalid uuid")
end
elseif schema_field.type == constants.DATABASE_TYPES.TIMESTAMP and value then
elseif schema_field.type == "timestamp" and value then
value = cassandra.timestamp(value)
elseif value == nil then
value = cassandra.null
Expand Down Expand Up @@ -427,7 +427,6 @@ end

-- Execute the INSERT kong_query of a DAO entity.
-- Validates the entity's schema + UNIQUE values + FOREIGN KEYS.
-- Generates id and created_at fields.
-- @param `t` A table representing the entity to insert
-- @return `result` Inserted entity or nil
-- @return `error` Error if any during the execution
Expand All @@ -437,12 +436,14 @@ function BaseDao:insert(t)
return nil, DaoError("Cannot insert a nil element", error_types.SCHEMA)
end

-- Override created_at and id by default value
t.created_at = timestamp.get_utc()
t.id = uuid()

-- Validate schema
ok, errors = validate(t, self._schema)
-- Populate the entity with any default/overriden values and validate it
ok, errors = validate(t, self._schema, { dao_insert = function(field)
if field.type == "id" then
return uuid()
elseif field.type == "timestamp" then
return timestamp.get_utc()
end
end })
if not ok then
return nil, DaoError(errors, error_types.SCHEMA)
end
Expand Down Expand Up @@ -501,7 +502,7 @@ function BaseDao:update(t)
end

-- Validate schema
ok, errors = validate(t, self._schema, true)
ok, errors = validate(t, self._schema, {is_update = true})
if not ok then
return nil, DaoError(errors, error_types.SCHEMA)
end
Expand Down
6 changes: 3 additions & 3 deletions kong/dao/schemas/apis.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ local function check_path(path, api_t)
end

return {
id = { type = "id" },
id = { type = "id", dao_insert_value = true },
created_at = { type = "timestamp", dao_insert_value = true },
name = { type = "string", unique = true, queryable = true, default = function(api_t) return api_t.public_dns end },
public_dns = { type = "string", unique = true, queryable = true,
func = check_public_dns_and_path,
regex = "([a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*)" },
path = { type = "string", queryable = true, unique = true, func = check_path },
strip_path = { type = "boolean" },
target_url = { type = "string", required = true, func = validate_target_url },
created_at = { type = "timestamp" }
target_url = { type = "string", required = true, func = validate_target_url }
}
7 changes: 3 additions & 4 deletions kong/dao/schemas/consumers.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
local stringy = require "stringy"
local constants = require "kong.constants"

local function check_custom_id_and_username(value, consumer_t)
local username = type(consumer_t.username) == "string" and stringy.strip(consumer_t.username) or ""
Expand All @@ -13,8 +12,8 @@ local function check_custom_id_and_username(value, consumer_t)
end

return {
id = { type = constants.DATABASE_TYPES.ID },
id = { type = "id", dao_insert_value = true },
created_at = { type = "timestamp", dao_insert_value = true },
custom_id = { type = "string", unique = true, queryable = true, func = check_custom_id_and_username },
username = { type = "string", unique = true, queryable = true, func = check_custom_id_and_username },
created_at = { type = constants.DATABASE_TYPES.TIMESTAMP }
username = { type = "string", unique = true, queryable = true, func = check_custom_id_and_username }
}
10 changes: 5 additions & 5 deletions kong/dao/schemas/plugins_configurations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ local function load_value_schema(plugin_t)
end

return {
id = { type = constants.DATABASE_TYPES.ID },
api_id = { type = constants.DATABASE_TYPES.ID, required = true, foreign = true, queryable = true },
consumer_id = { type = constants.DATABASE_TYPES.ID, foreign = true, queryable = true, default = constants.DATABASE_NULL_ID },
id = { type = "id", dao_insert_value = true },
created_at = { type = "timestamp", dao_insert_value = true },
api_id = { type = "id", required = true, foreign = true, queryable = true },
consumer_id = { type = "id", foreign = true, queryable = true, default = constants.DATABASE_NULL_ID },
name = { type = "string", required = true, queryable = true, immutable = true },
value = { type = "table", schema = load_value_schema },
enabled = { type = "boolean", default = true },
created_at = { type = constants.DATABASE_TYPES.TIMESTAMP }
enabled = { type = "boolean", default = true }
}
28 changes: 20 additions & 8 deletions kong/dao/schemas_validation.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
local uuid = require "uuid"
local utils = require "kong.tools.utils"
local stringy = require "stringy"
local constants = require "kong.constants"
local timestamp = require "kong.tools.timestamp"

uuid.seed()

local POSSIBLE_TYPES = {
id = true,
Expand All @@ -13,8 +17,8 @@ local POSSIBLE_TYPES = {
}

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,
["id"] = function(v) return type(v) == "string" end,
["timestamp"] = function(v) return type(v) == "number" end,
["array"] = function(v) return utils.is_array(v) end
}

Expand All @@ -30,10 +34,13 @@ local _M = {}
-- Validate a table against a given schema
-- @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.
-- @param `options`
-- `dao_insert` A function called foe each field with a `dao_insert_value` property.
-- `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)
function _M.validate(t, schema, options)
if not options then options = {} end
local errors

-- Check the given table against a given schema
Expand All @@ -47,18 +54,23 @@ function _M.validate(t, schema, is_update)
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
elseif options.is_update and t[column] ~= nil and v.immutable and not v.required then
errors = utils.add_error(errors, column, column.." cannot be updated")
end

-- [TYPE] Check if type is valid. Boolean and Numbers as strings are accepted and converted
-- [INSERT_VALUE]
if v.dao_insert_value and not options.is_update and type(options.dao_insert) == "function" then
t[column] = options.dao_insert(v)
end

if v.type ~= nil and t[column] ~= nil then
-- [TYPE] Check if type is valid. Boolean and Numbers as strings are accepted and converted
local is_valid_type

-- ALIASES: number, timestamp, boolean and array can be passed as strings and will be converted
if type(t[column]) == "string" then
t[column] = stringy.strip(t[column])
if v.type == "number" or v .type == constants.DATABASE_TYPES.TIMESTAMP then
if v.type == "number" or v .type == "timestamp" then
t[column] = tonumber(t[column])
is_valid_type = t[column] ~= nil
elseif v.type == "boolean" then
Expand Down Expand Up @@ -133,7 +145,7 @@ function _M.validate(t, schema, is_update)

if t[column] and type(t[column]) == "table" then
-- Actually validating the sub-schema
local s_ok, s_errors = _M.validate(t[column], sub_schema, is_update)
local s_ok, s_errors = _M.validate(t[column], sub_schema, options)
if not s_ok then
for s_k, s_v in pairs(s_errors) do
errors = utils.add_error(errors, column.."."..s_k, s_v)
Expand Down
6 changes: 3 additions & 3 deletions kong/plugins/basicauth/daos.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
local BaseDao = require "kong.dao.cassandra.base_dao"

local SCHEMA = {
id = { type = "id" },
id = { type = "id", dao_insert_value = true },
created_at = { type = "timestamp", dao_insert_value = true },
consumer_id = { type = "id", required = true, foreign = true, queryable = true },
username = { type = "string", required = true, unique = true, queryable = true },
password = { type = "string" },
created_at = { type = "timestamp" }
password = { type = "string" }
}

local BasicAuthCredentials = BaseDao:extend()
Expand Down
6 changes: 3 additions & 3 deletions kong/plugins/keyauth/daos.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
local BaseDao = require "kong.dao.cassandra.base_dao"

local SCHEMA = {
id = { type = "id" },
id = { type = "id", dao_insert_value = true },
created_at = { type = "timestamp", dao_insert_value = true },
consumer_id = { type = "id", required = true, foreign = true, queryable = true },
key = { type = "string", required = true, unique = true, queryable = true },
created_at = { type = "timestamp" }
key = { type = "string", required = true, unique = true, queryable = true }
}

local KeyAuth = BaseDao:extend()
Expand Down
42 changes: 40 additions & 2 deletions spec/unit/schemas_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ describe("Schemas", function()
assert.truthy(valid)
assert.are.same("abcdef", values.default)
end)

end)

describe("[regex]", function()
Expand Down Expand Up @@ -292,7 +293,7 @@ describe("Schemas", function()
assert.truthy(valid)

-- Failure
local valid, err = validate(values, schema, true)
local valid, err = validate(values, schema, {is_update = true})
assert.falsy(valid)
assert.truthy(err)
assert.are.same("date cannot be updated", err.date)
Expand All @@ -301,12 +302,49 @@ describe("Schemas", function()
it("should ignore required properties if they are immutable and we are updating", function()
local values = { string = "somestring" }

local valid, err = validate(values, schema, true)
local valid, err = validate(values, schema, {is_update = true})
assert.falsy(err)
assert.truthy(valid)
end)
end)

describe("[dao_insert_value]", function()
local schema = {
string = { type = "string"},
id = { type = "id", dao_insert_value = true },
timestamp = { type = "timestamp", dao_insert_value = true }
}

it("should call a given function when encountering a field with `dao_insert_value`", function()
local values = { string = "hello", id = "0000" }

local valid, err = validate(values, schema, { dao_insert = function(field)
if field.type == "id" then
return "1234"
elseif field.type == "timestamp" then
return 0000
end
end })
assert.falsy(err)
assert.True(valid)
assert.equal("1234", values.id)
assert.equal(0000, values.timestamp)
assert.equal("hello", values.string)
end)

it("should not raise any error if the function is not given", function()
local values = { string = "hello", id = "0000" }

local valid, err = validate(values, schema, { dao_insert = true }) -- invalid type
assert.falsy(err)
assert.True(valid)
assert.equal("0000", values.id)
assert.equal("hello", values.string)
assert.falsy(values.timestamp)
end)

end)

it("should return error when unexpected values are included in the schema", function()
local values = { string = "mockbin entity", url = "mockbin.com", unexpected = "abcdef" }

Expand Down

0 comments on commit d33704f

Please sign in to comment.