--- Validators for form inputs. --- --- A validator is a function `fun(value): string|nil`. It receives the --- current input value and returns a non-empty error message string when --- invalid, or `nil` / `""` when valid. --- --- This module exposes factory functions for common validators plus a --- `chain` combinator that runs several validators in order and returns the --- first error. --- ---@tag input-form.validators local M = {} --- Require the field to have a non-empty value. ---@param msg string|nil Override error message. ---@return function function M.non_empty(msg) msg = msg or "This field is required" return function(value) if value == nil then return msg end if type(value) == "string" and value == "" then return msg end return nil end end --- Require the value's length to be at least `n` characters. ---@param n integer ---@param msg string|nil ---@return function function M.min_length(n, msg) return function(value) local s = type(value) == "string" and value or tostring(value or "") if vim.fn.strchars(s) < n then return msg or ("Must be at least " .. n .. " characters") end return nil end end --- Require the value's length to be at most `n` characters. ---@param n integer ---@param msg string|nil ---@return function function M.max_length(n, msg) return function(value) local s = type(value) == "string" and value or tostring(value or "") if vim.fn.strchars(s) > n then return msg or ("Must be at most " .. n .. " characters") end return nil end end --- Require the value to match a Lua pattern. ---@param pattern string Lua pattern (not PCRE). ---@param msg string|nil ---@return function function M.matches(pattern, msg) return function(value) local s = type(value) == "string" and value or tostring(value or "") if not s:match(pattern) then return msg or "Invalid format" end return nil end end --- Require the value to parse as a number. ---@param msg string|nil ---@return function function M.is_number(msg) return function(value) if value == nil or value == "" or tonumber(value) == nil then return msg or "Must be a number" end return nil end end --- Require a checkbox value to equal the given boolean. Intended for --- `checkbox` inputs ("must agree to terms", "must enable feature", ...). ---@param required boolean|nil The required value. Defaults to `true`. ---@param msg string|nil Override error message. Defaults to --- `"(must be checked)"` when `required` is `true`, otherwise --- `"(must be unchecked)"`. ---@return function function M.checked(required, msg) if required == nil then required = true end if type(msg) ~= "string" then msg = required and "(must be checked)" or "(must be unchecked)" end return function(value) if value == required then return nil end return msg end end --- Require the value to be one of the given choices (useful for text inputs --- that must match a fixed allowlist; select inputs should use their --- `options` list instead). ---@param choices table List of allowed values. ---@param msg string|nil ---@return function function M.one_of(choices, msg) return function(value) for _, c in ipairs(choices) do if c == value then return nil end end return msg or "Value is not allowed" end end --- Wrap a predicate `fun(value): boolean` as a validator. ---@param predicate function ---@param msg string Error message to return when the predicate is false. ---@return function function M.custom(predicate, msg) return function(value) if predicate(value) then return nil end return msg end end --- Combine multiple validators. Runs them in order and returns the first --- non-empty error. Accepts either a list table or a varargs list. ---@param ... function|table ---@return function function M.chain(...) local validators = { ... } if #validators == 1 and type(validators[1]) == "table" and type(validators[1][1]) == "function" then validators = validators[1] end return function(value) for _, v in ipairs(validators) do local err = v(value) if err and err ~= "" then return err end end return nil end end return M