1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
-- Valua 0.2.1
-- 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 tinsert,setmetatable,len,match = table.insert,setmetatable,string.len,string.match
local tonumber,tostring = tonumber,tostring
local next,type,floor,ipairs = next,type,math.floor, ipairs
local unpack = unpack or table.unpack
local pack = table.pack or function(...) return { n = select('#', ...), ... } end
local _ENV = nil
local valua = {}
-- 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 _<index> with its args in a funcs table, to be used later when validating
return function(...)
local args = pack(...)
if k == 'optional' then
obj.allow_nil = true
else
local f = function(value) return valua['_'..k](value, unpack(args, 1, args.n)) end
tinsert(t.funcs,f)
end
return t
end
end
-- __call will run only when the value is validated
self.__call = function(t,value)
local res = true
local err = nil
local fres
if value == nil and t.allow_nil then
return res, err
end
-- iterates through all chained validations funcs that were packed, passing the value to be validated
for _,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 = {}
obj.allow_nil = false
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(<value var>,<other var>). 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,'<error message>' if the value fails the test and simply true if it succeeds.
-- aux funcs
local function empty(v)
return not v or (type(v)=='string' and len(v) == 0) or (type(v)=='table' and not next(v))
end
--
-- String
function valua._len(value,min,max)
local l = len(value or '')
if l < min or l > max then return false,"should have "..tostring(min).."-"..tostring(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 empty(value) and not value:match("^[%w+%.%-_]+@[%w+%.%-_]+%.%a%a+$") then
return false, "is not a valid email address"
end
return true
end
function valua._match(value,pattern)
if not empty(value) and not value:match(pattern) then return false, "does not match pattern" end
return true
end
function valua._alnum(value)
if not empty(value) and value:match("%W") then return false, "constains improper characters" end
return true
end
function valua._contains(value,substr)
if not empty(value) and not value:find(substr) then return false, "does not contain '"..substr.."'" end
return true
end
function valua._no_white(value)
if not empty(value) and value:find("%s") then return false, "must not contain white spaces" end
return true
end
--
-- Numbers
function valua._min(value,n)
if not empty(value) and value < n then return false,"must be greater than "..tostring(n) end
return true
end
function valua._max(value,n)
if not empty(value) and value > n then return false,"must not be greater than "..tostring(n) end
return true
end
function valua._integer(value)
if not empty(value) and floor(tonumber(value)) ~= tonumber(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
--
-- Datetime
-- Check for a UK date pattern dd/mm/yyyy hh:mi:ss, 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._datetime(value,format)
local valid = true
if (match(value, "^%d+%p%d+%p%d%d%d%d %d%d%p%d%d%p%d%d$")) then
local d, m, y, hh, mm, ss
if format and format:lower() == 'us' then
m, d, y, hh, mm, ss = match(value, "(%d+)%p(%d+)%p(%d+) (%d%d)%p(%d%d)%p(%d%d)")
elseif format and format:lower() == 'iso' then
y, m, d, hh, mm, ss = match(value, "(%d+)%p(%d+)%p(%d+) (%d%d)%p(%d%d)%p(%d%d)")
else
d, m, y, hh, mm, ss = match(value, "(%d+)%p(%d+)%p(%d+) (%d%d)%p(%d%d)%p(%d%d)")
end
d, m, y, hh, mm, ss = tonumber(d), tonumber(m), tonumber(y), tonumber(hh), tonumber(mm), tonumber(ss)
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
-- time validation
if not (hh >= 0 and hh <= 24) then
valid = false
end
if not (mm >= 0 and mm <= 60) then
valid = false
end
if not (ss >= 0 and ss <= 60) then
valid = false
end
else
valid = false
end
if not valid then return false, "is not a valid datetime" end
return true
end
--
-- Abstract
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._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
|