├── LICENSE ├── README.md ├── lua-schema-validation-1.0-1.rockspec └── src └── validation.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 erento 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #lua-schema-validation 2 | 3 | lua-schema-validation is a validation library for Lua. 4 | 5 | ## About 6 | 7 | This library helps to validate data by providing different validators which can be combined in a schema. 8 | 9 | Validators can be combined in any way, just be creative about it :) and feel free to contribute. 10 | 11 | ## Examples 12 | 13 | ```lua 14 | local v = require "validation" 15 | 16 | 17 | -- create a validator ... 18 | local foo = v.is_string() 19 | 20 | -- and validate data against it 21 | local valid, err = foo("bar") 22 | -- valid = true, err = nil 23 | 24 | 25 | -- restricted set of values 26 | local choice = v.in_list{"yes", "not sure", "no"} 27 | 28 | local valid, err = choice("yes") 29 | -- valid = true, err = nil 30 | 31 | 32 | -- array of values 33 | local numbers = v.is_array(v.is_number()) 34 | 35 | local valid, err = numbers{-2, 0, 5} 36 | -- valid = true, err = nil 37 | 38 | 39 | -- schema/tables combining other validators 40 | local schema = v.is_table{ 41 | foo = v.is_string(), 42 | choice = v.in_list{"yes", "not sure", "no"}, 43 | numbers = v.is_array(v.is_number()) 44 | } 45 | 46 | local valid, err = schema{ 47 | foo = "bar", 48 | choice = "yes", 49 | numbers = {-1, 0, 1} 50 | } 51 | -- valid = true, err = nil 52 | 53 | 54 | -- use meta validator, for conditional validation: 55 | -- * validate on certain condition: assert 56 | -- * validate using validator a or b: or_op 57 | -- * optional 58 | local schema = v.is_table{ 59 | type = v.in_list{"a", "b", "c"}, 60 | value = v.assert("type", "a", v.is_integer()), 61 | flag = v.or_op(v.is_integer(), v.is_boolean()), 62 | not_needed = v.optional(v.is_string()) 63 | } 64 | 65 | local valid, err = schema{ 66 | type = "a", 67 | value = 42, 68 | flag = true 69 | } 70 | -- valid = true, err = nil 71 | 72 | 73 | -- nested tables and arrays 74 | local figure = v.is_table{ 75 | details = v.is_table{ 76 | name = v.is_string(), 77 | description = v.is_string(), 78 | }, 79 | coordinates = v.is_array(v.is_table{ x = v.is_number(), y = v.is_number()}), 80 | } 81 | 82 | local valid, err = figure{ 83 | details = { 84 | name = "triangle", 85 | description = "polygon with three edges and three vertices" 86 | }, 87 | coordinates = { 88 | { x = -2, y = -1 }, 89 | { x = 0, y = 3 }, 90 | { x = 1, y = -2 } 91 | } 92 | } 93 | -- valid = true, err = nil 94 | 95 | 96 | -- all combined together 97 | local schema = v.is_table{ 98 | title = v.is_string(), 99 | type = v.in_list{"article", "page"}, 100 | category = v.assert("type", "article", v.is_string()), 101 | rank = v.optional(v.is_integer()), 102 | details = v.is_table{ 103 | author = v.is_string(), 104 | status = v.is_boolean() 105 | }, 106 | tags = v.is_array(v.is_string()) 107 | } 108 | 109 | local valid, err = schema{ 110 | title = "Hello world", 111 | type = "article", 112 | category = "news", 113 | details = { 114 | author = "bob", 115 | status = false 116 | }, 117 | tags = { 118 | "hello", 119 | "world" 120 | } 121 | } 122 | -- valid = true, err = nil 123 | 124 | 125 | -- When data does not match against the schema, err describe every error in the schema. 126 | local valid, err = schema{ 127 | type = 24, 128 | rank = "first", 129 | details = { 130 | author = "bob", 131 | status = 1 132 | } 133 | } 134 | 135 | -- valid = fasle 136 | -- err = { 137 | -- tags = { 138 | -- "is missing and should be an array." 139 | -- }, 140 | -- type = { 141 | -- "is not in list [ 'article' 'page' ]." 142 | -- }, 143 | -- title = "is missing and should be a string.", 144 | -- rank = "is not an integer.", 145 | -- details = { 146 | -- status = "is not a boolean." 147 | -- } 148 | -- } 149 | ``` 150 | 151 | ## Installation 152 | 153 | Install using `luarocks`. 154 | 155 | ```bash 156 | luarocks install lua-schema-validation 157 | ``` 158 | 159 | ## Validators 160 | 161 | Validators are used to verify your data. They can be used independently or combined together in a schema representation. 162 | 163 | List of available validators: 164 | 165 | * `is_string()` : verify that value is of type `string` 166 | * `is_number()` : verify that value is of type `number` 167 | * `is_integer()` : verify that value is of type `number` and is an integer 168 | * `is_boolean()` : verify that value is of type `boolean` 169 | * `in_list(list)` : verify that value is from the given list 170 | * `is_table(schema, tolerant)` : verify that value is of type `table` and validate the schema inside it 171 | * `is_array(validator)` : verify that value is of type `table` and validate every value inside with the given validator 172 | 173 | Meta validators: 174 | 175 | * `optional(validator)` : if key is found run the given validator 176 | * `assert(key, value, validator)` : if data found at key equal value the validator is applied 177 | * `or_op(validator_a, validator_b)` : data is considered valid when validator_a or validator_b return true 178 | 179 | ## Validate 180 | 181 | Validation is performed by calling the function returned by the validator and passing it the data to be validated. 182 | 183 | It return a boolean telling you whether the validation is successfull or not. If not it also return a error table structured as the schema and containing error for every faulty key. 184 | -------------------------------------------------------------------------------- /lua-schema-validation-1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-schema-validation" 2 | version = "1.0-1" 3 | source = { 4 | url = "git://github.com/erento/lua-schema-validation", 5 | tag = "v1.0" 6 | } 7 | description = { 8 | summary = "lua-schema-validation is a validation library for Lua.", 9 | detailed = [[ 10 | This library helps to validate data by providing different validators which can be combined in a schema. 11 | 12 | Validators can be combined in any way, just be creative about it :) and feel free to contribute. 13 | ]], 14 | homepage = "https://github.com/erento/lua-schema-validation", 15 | license = "MIT" 16 | } 17 | dependencies = { 18 | "lua ~> 5.1" 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | validation = "src/validation.lua" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/validation.lua: -------------------------------------------------------------------------------- 1 | -- @file validation.lua 2 | -- @author Théo Brigitte 3 | -- @contributor Henrique Silva 4 | -- @date Thu May 28 16:05:15 2015 5 | -- 6 | -- @brief Lua schema validation library. 7 | -- 8 | -- Validation is achieved by matching data against a schema. 9 | -- 10 | -- A schema is a representation of the expected structure of the data. It is 11 | -- a combination of what we call "validators". 12 | -- Validators are clojures which build accurante validation function for each 13 | -- element of the schema. 14 | -- Meta-validators allow to extend the logic of the schema by providing an 15 | -- additional logic layer around validators. 16 | -- e.g. optional() 17 | -- 18 | 19 | -- Import from global environment. 20 | local type = type 21 | local pairs = pairs 22 | local print = print 23 | local format = string.format 24 | local floor = math.floor 25 | local insert = table.insert 26 | local next = next 27 | 28 | -- Disable global environment. 29 | if _G.setfenv then 30 | setfenv(1, {}) 31 | else -- Lua 5.2. 32 | _ENV = {} 33 | end 34 | 35 | local M = { _NAME = 'validation' } 36 | 37 | --- Generate error message for validators. 38 | -- 39 | -- @param data mixed 40 | -- Value that failed validation. 41 | -- @param expected_type string 42 | -- Expected type for data 43 | -- 44 | -- @return 45 | -- String describing the error. 46 | --- 47 | local function error_message(data, expected_type) 48 | if data then 49 | return format('is not %s.', expected_type) 50 | end 51 | 52 | return format('is missing and should be %s.', expected_type) 53 | end 54 | 55 | --- Create a readable string output from the validation errors output. 56 | -- 57 | -- @param error_list table 58 | -- Nested table identifying where the error occured. 59 | -- e.g. { price = { rule_value = 'error message' } } 60 | -- @param parents string 61 | -- String of dot separated parents keys 62 | -- 63 | -- @return string 64 | -- Message describing where the error occured. e.g. price.rule_value = "error message" 65 | --- 66 | function M.print_err(error_list, parents) 67 | -- Makes prefix not nil, for posterior concatenation. 68 | local error_output = '' 69 | local parents = parents or '' 70 | if not error_list then return false end 71 | -- Iterates over the list of messages. 72 | for key, err in pairs(error_list) do 73 | -- If it is a node, print it. 74 | if type(err) == 'string' then 75 | error_output = format('%s\n%s%s %s', error_output, parents ,key, err) 76 | else 77 | -- If it is a table, recurse it. 78 | error_output = format('%s%s', error_output, M.print_err(err, format('%s%s.', parents, key))) 79 | end 80 | end 81 | 82 | return error_output 83 | end 84 | 85 | --- Validators. 86 | -- 87 | -- A validator is a function in charge of verifying data compliance. 88 | -- 89 | -- Prototype: 90 | -- @key 91 | -- Key of data being validated. 92 | -- @data 93 | -- Current data tree level. Meta-validator might need to verify other keys. e.g. assert() 94 | -- 95 | -- @return 96 | -- true on success, false and message describing the error 97 | --- 98 | 99 | 100 | --- Generates string validator. 101 | -- 102 | -- @return 103 | -- String validator function. 104 | --- 105 | function M.is_string() 106 | return function(value) 107 | if type(value) ~= 'string' then 108 | return false, error_message(value, 'a string') 109 | end 110 | return true 111 | end 112 | end 113 | 114 | --- Generates integer validator. 115 | -- 116 | -- @return 117 | -- Integer validator function. 118 | --- 119 | function M.is_integer() 120 | return function(value) 121 | if type(value) ~= 'number' or value%1 ~= 0 then 122 | return false, error_message(value, 'an integer') 123 | end 124 | return true 125 | end 126 | end 127 | 128 | --- Generates number validator. 129 | -- 130 | -- @return 131 | -- Number validator function. 132 | --- 133 | function M.is_number() 134 | return function(value) 135 | if type(value) ~= 'number' then 136 | return false, error_message(value, 'a number') 137 | end 138 | return true 139 | end 140 | end 141 | 142 | --- Generates boolean validator. 143 | -- 144 | -- @return 145 | -- Boolean validator function. 146 | --- 147 | function M.is_boolean() 148 | return function(value) 149 | if type(value) ~= 'boolean' then 150 | return false, error_message(value, 'a boolean') 151 | end 152 | return true 153 | end 154 | end 155 | 156 | --- Generates an array validator. 157 | -- 158 | -- Validate an array by applying same validator to all elements. 159 | -- 160 | -- @param validator function 161 | -- Function used to validate the values. 162 | -- @param is_object boolean (optional) 163 | -- When evaluted to false (default), it enforce all key to be of type number. 164 | -- 165 | -- @return 166 | -- Array validator function. 167 | -- This validator return value is either true on success or false and 168 | -- a table holding child_validator errors. 169 | --- 170 | function M.is_array(child_validator, is_object) 171 | return function(value, key, data) 172 | local result, err = nil 173 | local err_array = {} 174 | 175 | -- Iterate the array and validate them. 176 | if type(value) == 'table' then 177 | for index in pairs(value) do 178 | if not is_object and type(index) ~= 'number' then 179 | insert(err_array, error_message(value, 'an array') ) 180 | else 181 | result, err = child_validator(value[index], index, value) 182 | if not result then 183 | err_array[index] = err 184 | end 185 | end 186 | end 187 | else 188 | insert(err_array, error_message(value, 'an array') ) 189 | end 190 | 191 | if next(err_array) == nil then 192 | return true 193 | else 194 | return false, err_array 195 | end 196 | end 197 | end 198 | 199 | --- Generates optional validator. 200 | -- 201 | -- When data is present apply the given validator on data. 202 | -- 203 | -- @param validator function 204 | -- Function used to validate value. 205 | -- 206 | -- @return 207 | -- Optional validator function. 208 | -- This validator return true or the result from the given validator. 209 | --- 210 | function M.optional(validator) 211 | return function(value, key, data) 212 | if not value then return true 213 | else 214 | return validator(value, key, data) 215 | end 216 | end 217 | end 218 | 219 | --- Generates or meta validator. 220 | -- 221 | -- Allow data validation using two different validators and applying 222 | -- or condition between results. 223 | -- 224 | -- @param validator_a function 225 | -- Function used to validate value. 226 | -- @param validator_b function 227 | -- Function used to validate value. 228 | -- 229 | -- @return 230 | -- Or validator function. 231 | -- This validator return true or the result from the given validator. 232 | --- 233 | function M.or_op(validator_a, validator_b) 234 | return function(value, key, data) 235 | if not value then return true 236 | else 237 | local valid, err_a = validator_a(value, key, data) 238 | if not valid then 239 | valid, err_b = validator_b(value, key, data) 240 | end 241 | if not valid then 242 | return valid, err_a .. " OR " .. err_b 243 | else 244 | return valid, nil 245 | end 246 | end 247 | end 248 | end 249 | 250 | --- Generates assert validator. 251 | -- 252 | -- This function enforces the existence of key/value with the 253 | -- verification of the key_check. 254 | -- 255 | -- @param key_check mixed 256 | -- Key used to check the optionality of the asserted key. 257 | -- @param match mixed 258 | -- Comparation value. 259 | -- @param validator function 260 | -- Function that validates the type of the data. 261 | -- 262 | -- @return 263 | -- Assert validator function. 264 | -- This validator return true, the result from the given validator or false 265 | -- when the assertion fails. 266 | --- 267 | function M.assert(key_check, match, validator) 268 | return function(value, key, data) 269 | if data[key_check] == match then 270 | return validator(value, key, data) 271 | else 272 | return true 273 | end 274 | end 275 | end 276 | 277 | --- Generates list validator. 278 | -- 279 | -- Ensure the value is contained in the given list. 280 | -- 281 | -- @param list table 282 | -- Set of allowed values. 283 | -- @param value mixed 284 | -- Comparation value. 285 | -- @param validator function 286 | -- Function that validates the type of the data. 287 | -- 288 | -- @return 289 | -- In list validator function. 290 | --- 291 | function M.in_list(list) 292 | return function(value) 293 | local printed_list = "[" 294 | for _, word in pairs(list) do 295 | if word == value then 296 | return true 297 | end 298 | printed_list = printed_list .. " '" .. word .. "'" 299 | end 300 | 301 | printed_list = printed_list .. " ]" 302 | return false, { error_message(value, 'in list ' .. printed_list) } 303 | end 304 | end 305 | 306 | --- Generates table validator. 307 | -- 308 | -- Validate table data by using appropriate schema. 309 | -- 310 | -- @param schema table 311 | -- Schema used to validate the table. 312 | -- 313 | -- @return 314 | -- Table validator function. 315 | -- This validator return value is either true on success or false and 316 | -- a nested table holding all errors. 317 | --- 318 | function M.is_table(schema, tolerant) 319 | return function(value) 320 | local result, err = nil 321 | 322 | if type(value) ~= 'table' then 323 | -- Enforce errors of childs value. 324 | _, err = validate_table({}, schema, tolerant) 325 | if not err then err = {} end 326 | result = false 327 | insert(err, error_message(value, 'a table') ) 328 | else 329 | result, err = validate_table(value, schema, tolerant) 330 | end 331 | 332 | return result, err 333 | end 334 | end 335 | 336 | --- Validate function. 337 | -- 338 | -- @param data 339 | -- Table containing the pairs to be validated. 340 | -- @param schema 341 | -- Schema against which the data will be validated. 342 | -- 343 | -- @return 344 | -- String describing the error or true. 345 | --- 346 | function validate_table(data, schema, tolerant) 347 | 348 | -- Array of error messages. 349 | local errs = {} 350 | -- Check if the data is empty. 351 | 352 | -- Check if all data keys are present in the schema. 353 | if not tolerant then 354 | for key in pairs(data) do 355 | if schema[key] == nil then 356 | errs[key] = 'is not allowed.' 357 | end 358 | end 359 | end 360 | 361 | -- Iterates over the keys of the data table. 362 | for key in pairs(schema) do 363 | -- Calls a function in the table and validates it. 364 | local result, err = schema[key](data[key], key, data) 365 | 366 | -- If validation fails, print the result and return it. 367 | if not result then 368 | errs[key] = err 369 | end 370 | end 371 | 372 | -- Lua does not give size of table holding only string as keys. 373 | -- Despite the use of #table we have to manually loop over it. 374 | for _ in pairs(errs) do 375 | return false, errs 376 | end 377 | 378 | return true 379 | end 380 | 381 | return M 382 | --------------------------------------------------------------------------------