Базовый пакет проверки входных данных в Lua


Я очень новой для Lua (и программирования в целом). Я хотел начать с простого пакета, который проверяет входные данные на основе набора правил.

Код работает, как задумано, но то, что я написал что-либо хорошее? Я хотел бы услышать ваши отзывы!

validator.lua

--- An input validation package written in Lua.
-- @module validator
local validator = {}

--- Local table that holds all validation functions.
-- @within rules
local rules = {}

--- Check if input holds a value.
-- @param input The input to check.
-- @return True if a input holds a value, false and error otherwise.
-- @within rules
function rules.required(input)
    if (type(input) == "string" and input:len() == 0) or
       (type(input) == "table" and not next(input))
    then
        return false, "%s has no value"
    end
    return true
end

--- Check if string or number length is greater than or equal to a given size.
-- @param input The input to check.
-- @param size The minimum size.
-- @return True if input is at least the given size, false and error otherwise.
-- @within rules
function rules.min(input, size)
    if (type(input) == "string" and input:len() < size) or
       (type(input) == "number" and input < size)
    then
        return false, "%s must be a minimum of "..size 
    end
    return true
end

--- Check if string or number length is less than or equal to a given size.
-- @param input The input to check.
-- @param size The maximum size.
-- @return True if input is at most the given size, false and error otherwise.
-- @within rules
function rules.max(input, size)
    if (type(input) == "string" and input:len() > size) or
       (type(input) == "number" and input > size)
    then
        return false, "%s must be a maximum of "..size
    end
    return true
end

--- Check if string only contains letters.
-- @param input The input to check.
-- @return True if input only contains letters, false and error otherwise.
-- @within rules
function rules.alpha(input)
    if input:find("%A") then
        return false, "%s must be alphabetic"
    end
    return true
end

--- Check if string only contains digits.
-- @param input The input to check.
-- @return True if input only contains digits, false and error otherwise.
-- @within rules
function rules.int(input)
    if input:find("%D") then
        return false, "%s must be a number"
    end
    return true
end

--- Check if string only contains letters or digits.
-- @param input The input to check.
-- @return True if input only contains letters or digits, false and error otherwise.
-- @within rules
function rules.alnum(input)
    if input:find("%W") then
        return false, "%s must be alphanumeric"
    end
    return true
end

function rules.__index()
    error("Trying to validate with non-existant rule")
end

--- Extracts all rules from a string.
-- @param[opt] rules_string The string of rules.
-- @return Table with all the rules.
-- @usage extract_rules("required|min(12)") --> {"required", "min(12)"}
local function extract_rules(rules_string)
    local rules = {}
    if rules_string then
        for rule in rules_string:gmatch("[^|]+") do
            table.insert(rules, rule)
        end
    end
    return rules
end

--- Extracts the argument from a rule.
-- @param rule The rule to extract the argument from.
-- @return The name of the rule, and the argument or nil
-- @usage extract_rule_arg("min(12)") --> "min", 12
-- @usage extract_rule_arg("required") --> "required", nil
local function extract_rule_arg(rule)
    local func_name = rule:match("%a+")
    local func_arg = rule:match("%((.+)%)")
    func_arg = tonumber(func_arg) or func_arg
    return func_name, func_arg
end

--- Validate using input and rules
-- @param inputs A table of inputs
-- @param inputs_rules A table of rules
-- @return True if validation passes, false and table with errors otherwise.
function validator.validate(inputs, inputs_rules)
    local errors = {}
    local has_errors = false

    for field, input in pairs(inputs) do
        errors[field] = {}
        local input_rules = extract_rules(inputs_rules[field])
        for _, input_rule in pairs(input_rules) do
            local rule_func_name, rule_arg = extract_rule_arg(input_rule)
            local is_valid, err = rules[rule_func_name](input, rule_arg)
            if not is_valid then
                has_errors = true
                table.insert(errors[field], err:format(field))
            end
        end
    end

    if has_errors then
        return false, errors
    end
    return true
end

return validator

Вот пример, как использовать его:

local validator = require("validator")

local inputs = {
    username = "CodeReviewer",
    password = "Password123",
}

local rules = {
    username = "required|min(3)|max(20)",
    password = "required|min(8)|max(99)",
}

local is_valid, errors = validator.validate(inputs, rules)

if is_valid then
    -- Success!
else
    for field, field_errors in pairs(errors) do
        print("> "..field)
        for i=1, #field_errors do print(field_errors[i]) end
    end
end

Любой вклад будет высоко оценен! Я хочу обратить внимание на то, чтобы стать лучшим программистом в любом случае я могу.



193
5
задан 7 апреля 2018 в 05:04 Источник Поделиться
Комментарии
1 ответ

Я побежал пример вам обеспечены некоторые неверные правила. На выходе я получаю:

> username
> password
password must be a minimum of 12
password must be a maximum of 6

что мне представляется, означает, что есть некоторые ошибки в username поле, но не распространяются на ошибки список.

Вместо того, чтобы создавать errors[field] стол для всех fieldС его создавать лениво:

if not is_valid then
has_errors = true
place_error(errors, field, err)
end

и помощник:

local function place_error(error_list, field, template)
if error_list[field] == nil then
error_list[field] = {}
end
--- can also write
-- error_list[field] = error_list[field] or {}
table.insert(error_list[field], template:format(field))
end


Имя функции extract_rule_arg надо только извлечь аргумент из правила, но он возвращает 2 значения, который является анти-паттерн ИМО. Переименуйте его, например. parse_rule или просто get_rule.


На основе существующих моделей анализа, вы рассматриваете |min_max_2134(12) чтобы быть таким же, как |min(12). Вместо этого она должна вызвать ошибку то же самое.

local name, arg = rule:match "[^(]+%s*%((.+)%)"

и для required и другие подобные правила, в следующей строке:

if rules[rule] then name = rule end

должно быть достаточно. Хотя, в этот метод, вы бы снова отбрасывая другой мнимой без аргументов, на основе правила: a|min(x) так rules['a'] не существует. Если вы действительно хотите поднять ошибкам в таких случаях, вы можете выбрать, чтобы rule чтобы стать имени:

name = name or rule

положить, что все вместе:

local function parse_rule(rule)
local name, arg = rule:match "([^(]+)%s*%((.+)%)"
if rules[rule] then name = rule end
name = name or rule
arg = tonumber(arg) or arg
return name, arg
-- or you may choose to return the function if you rename
-- the function as `get_rule` for eg.
-- return rules[name], arg
end

1
ответ дан 8 апреля 2018 в 05:04 Источник Поделиться