From c3814286b5e8cef5c51dc514ac1161f0bc496f9f Mon Sep 17 00:00:00 2001 From: Etiene Date: Tue, 11 Mar 2014 17:07:05 +0000 Subject: Initial commit. --- README.md | 69 ++++++++++++++++- valua-0.2.rockspec | 22 ++++++ valua-test.lua | 62 +++++++++++++++ valua.lua | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 367 insertions(+), 3 deletions(-) create mode 100644 valua-0.2.rockspec create mode 100644 valua-test.lua create mode 100644 valua.lua diff --git a/README.md b/README.md index bb12aac..c7d2109 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,67 @@ -valua -===== +##Valua - Validation for Lua -A module for making chained validations in Lua. Create your objects, append your tests, use and reuse it! +A module for making chained validations. Create your objects, append your tests, use and reuse it! + +Originally bundled with Sailor MVC Web Framework, now released as a separated module. + https://github.com/Etiene/sailor + +This module provides tools for validating values, very useful in forms, but also usable elsewhere. It works in appended chains. Create a new validation object and start chaining your test functions. If your value fails a test, it breaks the chain and does not evaluate the rest of it. It returns a boolean and an error string (nil when tests succeeded) + +####Usage +Example 1 - Just create, chain and use: +```lua +valua:new().type("string").len(3,5)("test string!") -- false, "should have 3-5 characters" +``` +Example 2 - Create, chain and later use it multiple times: +```lua +local reusable_validation = valua:new().type("string").len(3,5) +reusable_validation("test string!") -- false, "should have 3-5 characters" +reusable_validation("test!") -- true +``` + +####Current validation functions + + * alnum() - +Checks if string is alfanumeric. + * boolean() - +Checks if value is a boolean. + * compare(another_value) - +Checks if value is equal to another value. + * contains(substr) - +Check if a string contains a substring. + * date() or date(format) - +Checks if a string is a valid date. Default format is UK (dd/mm/yyyy). Also checks for US and ISO formats. + * email() - +Checks if a string is a valid email address. + * empty() - +Checks if a value is empty. + * integer() - +Checks if a number is an integer; + * in_list(list) - +Checks if a value is inside an array. + * len(min,max) - +Checks if a string's length is between min and max. + * match(pattern) - +Checks if a string matches a given pattern. + * max(n) - +Checks if a number is equal or less than n. + * min(n) - +Checks if a number is equal or greater than n. + * not_empty() - +Checks if a value is not empty. + * no_white() - +Checks if a string contains no white spaces. + * number() - +Checks if a value is a number. + * string() - +Checks if a value is a string. + * type(t) - +Checks if a value is of type t. + + + +Copyright (c) 2014 Etiene Dalcol + +http://etiene.net + +License: MIT diff --git a/valua-0.2.rockspec b/valua-0.2.rockspec new file mode 100644 index 0000000..8423994 --- /dev/null +++ b/valua-0.2.rockspec @@ -0,0 +1,22 @@ +package = "Valua" +version = "0.2" +source = { + url = "https://github.com/Etiene/valua" +} +description = { + summary = "Validation for Lua!", + detailed = [[ + This module provides tools for validating values, very useful in forms, but also usable elsewhere. It works in appended chains. Create a new validation object and start chaining your test functions. + ]], + homepage = "https://github.com/Etiene/valua", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.3" +} +build = { + type = "builtin", + modules = { + ["valua"] = "valua.lua", + } +} \ No newline at end of file diff --git a/valua-test.lua b/valua-test.lua new file mode 100644 index 0000000..1d53ecf --- /dev/null +++ b/valua-test.lua @@ -0,0 +1,62 @@ +local v = require "valua" + +local function check(val_test, test_value, expected, n) + local res,err = val_test(test_value) + local msg = "Validation "..n.." " + + if res == expected then + msg = msg.. "succeeded" + else + msg = msg.. "FAILED" + end + msg = msg.." on '"..(tostring(test_value)).."'. Expected: "..tostring(expected)..", result: "..tostring(res)..". " + print(msg) + if err then print("\tTest Msg: value "..(err or "")) end +end + +local test_values = { + "test string!", + "hey", + "", + nil, + true, + 42, + 1337, + '26/10/1980', + '10-26-1980', + '29.02.2014', + '29/02/2016', + 'a@a.com', + 'asd123', + 5.7 +} + +local tests = { + {v:new().type("string").len(3,5),{1,false}}, + {v:new().type("number").len(3,5), {1,false}}, + {v:new().not_empty(),{2,true,3,false,4,false}}, + {v:new().len(2,10),{2,true}}, + {v:new().type("number"),{2,false}}, + {v:new().empty(),{3,true,4,true}}, + {v:new().boolean(),{1,false,5,true}}, + {v:new().compare("hey"),{1,false,2,true}}, + {v:new().number().min(45),{2,false,6,false,7,true}}, + {v:new().number().max(1009),{7,false,6,true}}, + {v:new().date(),{9,false,10,false,11,true,8,true}}, + {v:new().date('us'),{8,false,9,true}}, + {v:new().email(),{13,false,12,true}}, + {v:new().in_list({"hey",42}),{12,false,6,true,2,true}}, + {v:new().match("^%d+%p%d+%p%d%d%d%d$"),{1,false,8,true}}, + {v:new().alnum(),{8,false,13,true}}, + {v:new().integer(),{14,false,6,true}}, + {v:new().string(),{14,false,1,true}}, + {v:new().string().alnum(),{6,false}}, + {v:new().contains(" "),{2,false,1,true}}, + {v:new().no_white(),{1,false,2,true}} +} + +for n,t in ipairs(tests) do + for i = 1, #t[2], 2 do + check(t[1],test_values[t[2][i]],t[2][i+1],n) + end +end diff --git a/valua.lua b/valua.lua new file mode 100644 index 0000000..03fb4c8 --- /dev/null +++ b/valua.lua @@ -0,0 +1,217 @@ +-- Valua 0.2 +-- Copyright (c) 2014 Etiene Dalcol +-- License: MIT +-- +-- Originally bundled with Sailor MVC Web Framework, now released as a separated module. +-- http://sailorproject.org +-- +-- This module provides tools for validating values, very useful in forms, but also usable elsewhere. +-- It works in appended chains. Create a new validation object and start chaining your test functions. +-- If your value fails a test, it breaks the chain and does not evaluate the rest of it. +-- It returns a boolean and an error string (nil when tests succeeded) +-- +-- Example 1 - Just create, chain and use: +-- valua:new().type("string").len(3,5)("test string!") -- false, "should have 3-5 characters" +-- +-- Example 2 - Create, chain and later use it multiple times: +-- local reusable_validation = valua:new().type("string").len(3,5) +-- reusable_validation("test string!") -- false, "should have 3-5 characters" +-- reusable_validation("test!") -- true +-- + +local valua = {} + +local tinsert,setmetatable,len,match,tonumber = table.insert,setmetatable,string.len,string.match,tonumber + +-- CORE +-- Caution, this is confusing + +-- creates a new validation object, useful for reusable stuff and creating many validation tests at a time +function valua:new(obj) + obj = obj or {} + setmetatable(obj,self) + -- __index will be called always when chaining validation functions + self.__index = function(t,k) + --saves a function named _ with its args in a funcs table, to be used later when validating + return function(...) + local args = {...} + local f = function(value) return valua['_'..k](value,unpack(args)) end + tinsert(t.funcs,f) + return t + end + end + + -- __call will run only when the value is validated + self.__call = function(t,value) + local res = true + local fres, err + -- iterates through all chained validations funcs that were packed, passing the value to be validated + for i,f in ipairs(t.funcs) do + fres,err = f(value) + res = res and fres + -- breaks the chain if a test fails + if err then + break + end + end + -- boolean, error message or nil + return res,err + end + obj.funcs = {} + return obj +end +-- + +-- VALIDATION FUNCS +-- Add new funcs at will, they all should have the value to be validated as first parameter +-- and their names must be preceded by '_'. +-- For example, if you want to use .custom_val(42) on your validation chain, you need to create a +-- function valua._custom_val(,). Just remember the value var will be known +-- at the end of the chain and the other var, in this case, will receive '42'. You can add multiple other vars. +-- These functions can be called directly (valua._len("test",2,5))in a non-chained and isolated way of life +-- for quick stuff, but chaining is much cooler! +-- Return false,'' if the value fails the test and simply true if it succeeds. + +-- aux funcs +local function empty(v) + return not v or len(v) == 0 +end +-- + +-- String +function valua._empty(value) + if not empty(value) then return false,"must be empty" end + return true +end + +function valua._not_empty(value) + if empty(value) then return false,"must not be empty" end + return true +end + +function valua._len(value,min,max) + local len = len(value or '') + if len < min or len >max then return false,"should have "..min.."-"..max.." characters" end + return true +end + +function valua._compare(value,another_value) + if value ~= another_value then return false, "values are not equal" end + return true +end + +function valua._email(value) + if not value:match("[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w+") then + return false, "is not a valid email address" + end + return true +end + +function valua._match(value,pattern) + if not value:match(pattern) then return false, "does not match pattern" end + return true +end + +function valua._alnum(value) + if value:match("%W") then return false, "constains improper characters" end + return true +end + +function valua._contains(value,substr) + if not value:find(substr) then return false, "does not contain '"..substr.."'" end + return true +end + +function valua._no_white(value) + if value:find("%s") then return false, "must not contain white spaces" end + return true +end +-- + +-- Numbers +function valua._min(value,n) + if value < n then return false,"must be greater than "..n end + return true +end + +function valua._max(value,n) + if value > n then return false,"must not be greater than "..n end + return true +end + +function valua._integer(value) + if math.floor(value) ~= value then return false, "must be an integer" end + return true +end +-- + +-- Date + +-- Check for a UK date pattern dd/mm/yyyy , dd-mm-yyyy, dd.mm.yyyy +-- or US pattern mm/dd/yyyy, mm-dd-yyyy, mm.dd.yyyy +-- or ISO pattern yyyy/mm/dd, yyyy-mm-dd, yyyy.mm.dd +-- Default is UK +function valua._date(value,format) + local valid = true + if (match(value, "^%d+%p%d+%p%d%d%d%d$")) then + local d, m, y + if format and format:lower() == 'us' then + m, d, y = match(value, "(%d+)%p(%d+)%p(%d+)") + elseif format and format:lower() == 'iso' then + y, m, d = match(value, "(%d+)%p(%d+)%p(%d+)") + else + d, m, y = match(value, "(%d+)%p(%d+)%p(%d+)") + end + d, m, y = tonumber(d), tonumber(m), tonumber(y) + + local dm2 = d*m*m + if d>31 or m>12 or dm2==116 or dm2==120 or dm2==124 or dm2==496 or dm2==1116 or dm2==2511 or dm2==3751 then + -- invalid unless leap year + if not (dm2==116 and (y%400 == 0 or (y%100 ~= 0 and y%4 == 0))) then + valid = false + end + end + else + valid = false + end + if not valid then return false, "is not a valid date" end + return true +end +-- + +-- Abstract +function valua._type(value,value_type) + if type(value) ~= value_type then return false,"must be a "..value_type end + return true +end + +function valua._boolean(value) + if type(value) ~= 'boolean' then return false,"must be a boolean" end + return true +end + +function valua._number(value) + if type(value) ~= 'number' then return false,"must be a number" end + return true +end + +function valua._string(value) + if type(value) ~= 'string' then return false,"must be a string" end + return true +end + +function valua._in_list(value,list) + local valid = false + for _,v in ipairs(list) do + if value == v then + valid = true + break + end + end + if not valid then return false,"is not in the list" end + return true +end +-- + +-- +return valua -- cgit v1.2.3-54-g00ecf