From d33704fb3bbdce4ba18c5c09b1cbeaf820e75fb3 Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Wed, 10 Jun 2015 22:15:52 +0200 Subject: [PATCH] chore(dao) allow a DAO to override insert values - 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 #103 Former-commit-id: 50d0832c2786f235e8b5766be2a6d281a2bc8d11 --- kong/constants.lua | 4 -- kong/dao/cassandra/base_dao.lua | 21 ++++++----- kong/dao/schemas/apis.lua | 6 +-- kong/dao/schemas/consumers.lua | 7 ++-- kong/dao/schemas/plugins_configurations.lua | 10 ++--- kong/dao/schemas_validation.lua | 28 ++++++++++---- kong/plugins/basicauth/daos.lua | 6 +-- kong/plugins/keyauth/daos.lua | 6 +-- spec/unit/schemas_spec.lua | 42 ++++++++++++++++++++- 9 files changed, 88 insertions(+), 42 deletions(-) diff --git a/kong/constants.lua b/kong/constants.lua index edda67b766f..b5e359b08e5 100644 --- a/kong/constants.lua +++ b/kong/constants.lua @@ -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", diff --git a/kong/dao/cassandra/base_dao.lua b/kong/dao/cassandra/base_dao.lua index f1a90bb0434..b92b09405a0 100644 --- a/kong/dao/cassandra/base_dao.lua +++ b/kong/dao/cassandra/base_dao.lua @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/kong/dao/schemas/apis.lua b/kong/dao/schemas/apis.lua index d7280f7a87d..4fd0774482e 100644 --- a/kong/dao/schemas/apis.lua +++ b/kong/dao/schemas/apis.lua @@ -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 } } diff --git a/kong/dao/schemas/consumers.lua b/kong/dao/schemas/consumers.lua index 69fe5183a8f..8fde694d539 100644 --- a/kong/dao/schemas/consumers.lua +++ b/kong/dao/schemas/consumers.lua @@ -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 "" @@ -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 } } diff --git a/kong/dao/schemas/plugins_configurations.lua b/kong/dao/schemas/plugins_configurations.lua index 2ac86d2f981..85fce54450b 100644 --- a/kong/dao/schemas/plugins_configurations.lua +++ b/kong/dao/schemas/plugins_configurations.lua @@ -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 } } diff --git a/kong/dao/schemas_validation.lua b/kong/dao/schemas_validation.lua index 43bb1ed2ad6..53d5befb9fd 100644 --- a/kong/dao/schemas_validation.lua +++ b/kong/dao/schemas_validation.lua @@ -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, @@ -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 } @@ -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 @@ -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 @@ -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) diff --git a/kong/plugins/basicauth/daos.lua b/kong/plugins/basicauth/daos.lua index 1ff53ca242d..8cf84ad79e2 100644 --- a/kong/plugins/basicauth/daos.lua +++ b/kong/plugins/basicauth/daos.lua @@ -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() diff --git a/kong/plugins/keyauth/daos.lua b/kong/plugins/keyauth/daos.lua index c6e04e08348..e6472f14481 100644 --- a/kong/plugins/keyauth/daos.lua +++ b/kong/plugins/keyauth/daos.lua @@ -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() diff --git a/spec/unit/schemas_spec.lua b/spec/unit/schemas_spec.lua index b3e1364a87a..513de35930e 100644 --- a/spec/unit/schemas_spec.lua +++ b/spec/unit/schemas_spec.lua @@ -231,6 +231,7 @@ describe("Schemas", function() assert.truthy(valid) assert.are.same("abcdef", values.default) end) + end) describe("[regex]", function() @@ -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) @@ -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" }