{"id":44255590,"url":"https://github.com/benwilber/lua-valid","last_synced_at":"2026-02-10T16:25:57.973Z","repository":{"id":240301988,"uuid":"802260664","full_name":"benwilber/lua-valid","owner":"benwilber","description":"A library for Lua to validate various values and table structures.","archived":false,"fork":false,"pushed_at":"2024-08-17T15:17:31.000Z","size":104,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-18T20:13:26.260Z","etag":null,"topics":["lua","validation"],"latest_commit_sha":null,"homepage":"https://github.com/benwilber/lua-valid","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benwilber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-17T21:02:32.000Z","updated_at":"2025-03-13T18:42:02.000Z","dependencies_parsed_at":"2024-05-17T22:22:49.491Z","dependency_job_id":"0cd17558-9898-491d-a5ca-dcb927595bca","html_url":"https://github.com/benwilber/lua-valid","commit_stats":null,"previous_names":["benwilber/lua-valid"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/benwilber/lua-valid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benwilber%2Flua-valid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benwilber%2Flua-valid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benwilber%2Flua-valid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benwilber%2Flua-valid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benwilber","download_url":"https://codeload.github.com/benwilber/lua-valid/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benwilber%2Flua-valid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29307901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-10T16:09:25.305Z","status":"ssl_error","status_checked_at":"2026-02-10T16:08:52.170Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lua","validation"],"created_at":"2026-02-10T16:25:56.838Z","updated_at":"2026-02-10T16:25:57.965Z","avatar_url":"https://github.com/benwilber.png","language":"Lua","readme":"# Lua Validation Library\n\nA library for Lua to validate various values and table structures.\n\n## Table of Contents\n\n- [Features](#features)\n- [Supported Lua Versions](#supported-lua-versions)\n- [Installation](#installation)\n- [Basic Usage](#basic-usage)\n  - [Validating Simple Data Types](#validating-simple-data-types)\n  - [Validating Complex Data Types](#validating-complex-data-types)\n- [Validation Definition Functions](#validation-definition-functions)\n  - [`valid.literal`](#validliteral)\n  - [`valid.boolean`](#validboolean)\n  - [`valid.number`](#validnumber)\n  - [`valid.string`](#validstring)\n  - [`valid.table`](#validtable)\n  - [`valid.array`](#validarray)\n  - [`valid.arrayof`](#validarrayof)\n  - [`valid.map`](#validmap)\n  - [`valid.mapof`](#validmapof)\n  - [`valid.anyof`](#validanyof)\n  - [`valid.allof`](#validallof)\n  - [`valid.func`](#validfunc)\n- [Error Handling and Invalid Propagation](#error-handling-and-invalid-propagation)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- Validate literals, numbers, strings, and tables (arrays and maps).\n- Customizable validation functions.\n- Detailed error reporting with paths to invalid keys or indices.\n- Nested validations for complex table structures.\n\n## Supported Lua Versions\n\n`valid.lua` is tested with:\n\n- Lua 5.1 (including LuaJIT)\n- Lua 5.2\n- Lua 5.3\n- Lua 5.4\n\n## Installation\n\nCopy the [`valid.lua`](valid.lua) file to a directory in your `LUA_PATH`.\n\n## Basic Usage\n\n### Validating Simple Data Types\n\n```lua\nlocal valid = require \"valid\"\n\nlocal is_valid = valid.literal(\"abc\")(\"abc\")\nassert(is_valid)  -- true\n\nlocal is_valid = valid.number {min = 0, max = 10}(5)\nassert(is_valid)  -- true\n\nlocal is_valid = valid.string {pattern = \"%d%d%d\"}(\"abc\")\nassert(not is_valid)  -- false, not numerical digits\n\nlocal is_valid = valid.string {pattern = \"%d%d%d\"}(\"123\")\nassert(is_valid)  -- true\n```\n\n### Validating Complex Data Types\n\n```lua\nlocal valid = require \"valid\"\n\nlocal valid_contact = valid.map {\n    required = {\"email\"},\n    table = {\n        email = valid.string {pattern = \".+@.+%..+\"}, -- A very naive email pattern\n        phone = valid.string {pattern = \"%d%d%d%-%d%d%d%-%d%d%d%d\"},\n        address = valid.map {\n            required = {\"country\", \"zipcode\"},\n            table = {\n                street = valid.string {minlen = 5, maxlen = 50},\n                city = valid.string {minlen = 2, maxlen = 30},\n                zipcode = valid.string {pattern = \"%d%d%d%d%d\"},\n                country = \"USA\" -- shorthand for valid.literal(\"USA\")\n            }\n        }\n    }\n}\n\nlocal contact_data = {\n    email = \"john.doe@example.com\",\n    phone = \"123-456-7890\",\n    address = {\n        -- street and city aren't required\n        country = \"USA\", \n        zipcode = \"12345\"\n    }\n}\n\nlocal is_valid = valid_contact(contact_data)\nassert(is_valid)  -- true\n```\n\n## Validation Definition Functions\n\n### `valid.literal`\n\nValidates that a value matches a specific literal.\n\nThe comparison is performed using the equality operator (`==`), which means that both the value and the type must match exactly.\n\nNote: Two tables will not compare equal unless they are both references to *the same* table.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal is_valid = valid.literal(\"abc\")(\"abc\")\nassert(is_valid)  -- true\n\nlocal is_valid = valid.literal(\"abc\")(\"123\")\nassert(not is_valid)  -- false\n\nlocal is_valid = valid.literal(\"abc\", {icase = true})(\"ABC\")\nassert(is_valid)  -- true\n\nlocal price_table = {price = 1.00}\n\nlocal is_valid = valid.literal(price_table)({price = 1.00})\nassert(not is_valid) -- false, not the same table\n\nlocal is_valid = valid.literal(price_table)(price_table)\nassert(is_valid) -- true\n```\n\n#### Parameters\n\n* `opts` (optional): Table of options.\n    * `icase`: Set to `true` to allow case-insensitive validation of a string literal.\n    * `func`: A custom validation function to call after the literal check.\n\n\n### `valid.boolean`\n\nValidates that a value is a literal boolean either `true` or `false`.\n\nThis is a shorthand for `valid.anyof {true, false}`.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal is_valid = valid.boolean()(true)\nassert(is_valid)  -- true\n\nlocal is_valid = valid.boolean()(\"false\")\nassert(not is_valid)  -- false, not the literal boolean false\n```\n\n#### Parameters\n\n*(none)*\n\n\n### `valid.number`\n\nValidates that a value is a number within an optional range.\n\nNote: Values are not coerced to numbers by e.g. `tonumber()`.  The input value itself must already be a number or validation will fail.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal is_valid = valid.number {min = 0, max = 10}(5)\nassert(is_valid)  -- true\n\nlocal is_valid = valid.number {min = 0, max = 1}(5)\nassert(not is_valid)  -- false, not in range\n\nlocal is_valid = valid.number {min = -50, max = 50}(-1)\nassert(is_valid)  -- true\n\nlocal is_valid = valid.number {min = -5.5, max = 0}(-1.1111)\nassert(is_valid)  -- true\n\nlocal is_valid = valid.number {min = 0, max = 10}(\"8\")\nassert(not is_valid)  -- false, not a number\n```\n\n#### Parameters\n\n* `opts` (optional): Table of options.\n    * `min`: The minimum allowable value (inclusive).\n    * `max`: The maximum allowable value (inclusive).\n    * `func`: A custom validation function to call after the number check.\n\n### `valid.string`\n\nValidates that a value is a string with optional length and pattern constraints.\n\nNote: Values are not coerced to strings by e.g. `tostring()`.  The input value itself must already be a string or validation will fail.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal is_valid = valid.string {minlen = 3, maxlen = 5}(\"hello\")\nassert(is_valid)  -- true\n\nlocal is_valid = valid.string {minlen = 6, maxlen = 10}(\"hello\")\nassert(not is_valid)  -- false, too short\n\nlocal is_valid = valid.string {pattern = \"%d%d%d\"}(\"123\")\nassert(is_valid)  -- true\n\nlocal is_valid = valid.string {pattern = \"%d%d%d\"}(\"abc\")\nassert(not is_valid)  -- false, pattern does not match\n\nlocal is_valid = valid.string {minlen = 2, maxlen = 4, pattern = \"a+\"}(\"aaa\")\nassert(is_valid)  -- true\n\nlocal is_valid = valid.string {minlen = 2, maxlen = 4, pattern = \"a+\"}({})\nassert(not is_valid)  -- false, not a string\n```\n\n#### Parameters\n\n* `opts` (optional): Table of options.\n    * `minlen`: The minimum allowable length of the string (inclusive).\n    * `maxlen`: The maximum allowable length of the string (inclusive).\n    * `pattern`: A [Lua pattern](https://www.lua.org/pil/20.2.html)\n    * `func`: A custom validation function to call after the string check.\n\n### `valid.table`\n\nValidates that a value is a table, with optional constraints for arrays and maps.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal valid_person = valid.table {\n    required = {\"name\", \"age\"},\n    table = {\n        name = valid.string {minlen = 3, maxlen = 50},\n        age = valid.number {min = 0, max = 120},\n        email = valid.string {pattern = \".+@.+%..+\"}, -- A very naive email pattern\n        phone = valid.string {\n            pattern = \"%d%d%d%-%d%d%d%-%d%d%d%d\",\n            func = function(val)\n                if #val ~= 12 then\n                    return false, \"invalid phone format\", val\n                end\n\n                return true, val\n            end\n        }\n    }\n}\n\nlocal person_data = {\n    name = \"John Doe\",\n    age = 35,\n    email = \"john.doe@example.com\",\n    phone = \"123-456-7890\"\n}\n\nlocal is_valid = valid_person(person_data)\nassert(is_valid)  -- true\n\nlocal invalid_person_data = {\n    name = \"Jo\",\n    age = 150,\n    email = \"john.doeexample.com\",\n    phone = \"123-4567-890\"\n}\n\nlocal is_valid = valid_person(invalid_person_data)\nassert(not is_valid)  -- false, multiple validation errors\n```\n\n#### Parameters\n\n* `opts` (optional): Table of options.\n    * `array`: Set to `true` if the table should be validated as an array.\n    * `map`: Set to `true` if the table should be validated as a map.\n    * `empty`: Set to `true` to allow empty tables.\n    * `required`: An optional list of required keys for maps.  The special string `\"all\"` can be given to indicate that all keys are required without explicitly providing each of them.\n    * `func`: A custom validation function to call after the table check.\n    * `table`: A nested table definition for validating nested tables.\n\n### `valid.array`\n\nA shorthand for [`valid.table`](#validtable) with `opts.array` set to `true`.\n\n#### Parameters\n\n* `opts` (optional): Table of options.\n    * `empty`: Set to `true` to allow empty tables.\n    * `func`: A custom validation function to call after the table check.\n    * `table`: A nested table definition for validating nested tables.\n\n### `valid.arrayof`\n\nValidates that a value is an array where each element matches a given definition.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\n-- An array where each element is a number within the range 1 to 10\nlocal valid_numbers = valid.arrayof(valid.number {min = 1, max = 10})\n\nlocal numbers_data = {1, 2, 3, 4, 5}\n\nlocal is_valid = valid_numbers(numbers_data)\nassert(is_valid)  -- true\n\nlocal invalid_numbers_data = {1, 2, 11, 4, 5}\n\nlocal is_valid = valid_numbers(invalid_numbers_data)\nassert(not is_valid)  -- false, 11 is not within the range 1 to 10\n\n-- An array where each element is a valid string\nlocal valid_strings = valid.arrayof(valid.string {minlen = 2, maxlen = 5})\n\nlocal strings_data = {\"hi\", \"hello\", \"hey\"}\n\nlocal is_valid = valid_strings(strings_data)\nassert(is_valid)  -- true\n\nlocal invalid_strings_data = {\"hi\", \"hello\", \"thisiswaytoolong\"}\n\nlocal is_valid = valid_strings(invalid_strings_data)\nassert(not is_valid)  -- false, too long\n\nlocal unique_strings = valid.arrayof(valid.string(), {unique = true})\n\nlocal is_valid = unique_strings {\"a\", \"b\", \"c\"}\nassert(is_valid) -- true\n\nlocal is_valid = unique_strings {\"a\", \"b\", \"c\", \"c\"}\nassert(not is_valid) -- false, values are not unique\n```\n\n#### Parameters\n\n* `deffunc` (required): The definition function for the array elements.\n* `opts` (optional): Table of options.\n    * `minlen`: The minimum allowable length of the array, If `0` then sets `empty = true`.\n    * `maxlen`: The maximum allowable length of the array.\n    * `empty`: Set to `true` to allow empty arrays.  If `true` then sets `minlen = 0`.\n    * `unique`: Set to `true` to require that all values are unique. This is determined by adding each value of the array as a key in a table. If a conflict (duplicate key) is found during this process, the validation fails. This relies on the unique identity of each value, which is based on the value's equality and type.  Note that two tables are always considered distinct regardless of structure unless they are referencing *the same* table.\n    * `func`: A custom validation function to call after the array check.\n\n\n### `valid.map`\n\nA shorthand for [`valid.table`](#validtable) with `opts.map` set to `true`.\n\n#### Parameters\n\n* `opts` (optional): Table of options.\n    * `empty`: Set to `true` to allow empty tables.\n    * `required`: An optional list of required keys.\n    * `func`: A custom validation function to call after the table check.\n    * `table`: A nested table definition for validating nested tables.\n\n### `valid.mapof`\n\nValidates maps with specific type definitions for both keys and values.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\n-- A map where keys are strings and values are numbers within the range 1 to 10\nlocal valid_string_number_map = valid.mapof {\n    valid.string(),\n    valid.number {min = 1, max = 10}\n}\n\nlocal map_data = {\n    one = 1,\n    two = 2,\n    three = 3\n}\n\nlocal is_valid = valid_string_number_map(map_data)\nassert(is_valid)  -- true\n\nlocal invalid_map_data = {\n    one = 1,\n    two = 2,\n    three = 11\n}\n\nlocal is_valid = valid_string_number_map(invalid_map_data)\nassert(not is_valid)  -- false, 11 is not within the range 1 to 10\n\n-- Define a map where keys are strings and values are valid person objects\nlocal valid_person = valid.map {\n    required = {\"name\"},\n    table = {\n        name = valid.string {minlen = 3},\n        age = valid.number {min = 0}\n    }\n}\n\nlocal valid_people_map = valid.mapof {valid.string, valid_person}\n\nlocal people_data = {\n    alice = {name = \"Alice\", age = 30},\n    bob = {name = \"Bob\", age = 25}\n}\n\nlocal is_valid = valid_people_map(people_data)\nassert(is_valid)  -- true\n\nlocal invalid_people_data = {\n    alice = {name = \"Alice\", age = 30},\n    bob = {age = 25}  -- Missing required field \"name\"\n}\n\nlocal is_valid = valid_people_map(invalid_people_data)\nassert(not is_valid)  -- false, \"name\" is required for \"bob\"\n```\n\n#### Parameters\n\n* `deffuncs` (required): A table containing two definitions, one for the keys and one for the values.\n* `opts` (optional): Table of options.\n    * `empty`:  Set to `true` to allow empty maps.\n    * `func`: A table containing two custom validation functions, one for the keys and one for the values.\n\n\n### `valid.anyof`\n\nValidates that a value satisfies at least one of the given validation functions.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal valid_string_or_number = valid.anyof {valid.string(), valid.number()}\n\nlocal is_valid = valid_string_or_number \"123\"\nassert(is_valid) -- true\n\nlocal is_valid = valid_string_or_number {name = \"joe\"}\nassert(not is_valid) -- false, not a string or number\n```\n\n#### Parameters\n\n* `deffuncs` (required): An array table of one or more validation functions.\n\n\n### `valid.allof`\n\nValidates that a value satisfies all of the given validation functions.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal valid_word_and_number = valid.allof {\n    valid.string {pattern = \"%w\"},\n    valid.string {pattern = \"^%d+$\"}\n}\n\nlocal is_valid = valid_word_and_number(\"123\")\nassert(is_valid) -- true\n\nlocal is_valid = valid_word_and_number {name = \"joe\"}\nassert(not is_valid) -- false, not a string or number\n```\n\n#### Parameters\n\n* `deffuncs` (required): An array table of one or more validation functions.\n\n### `valid.func`\n\nValidates that a value is a function.\n\n#### Usage\n\n```lua\nlocal valid = require \"valid\"\n\nlocal valid_function = valid.func()\n\nlocal is_valid = valid_function(function() end)\nassert(is_valid) -- true\n\nlocal is_valid = valid_function(\"123\")\nassert(not is_valid) -- false, not a function\n```\n\n#### Parameters\n\n*(none)*\n\n## Error Handling and Invalid Propagation\n\nThe library provides detailed error information when validation fails. When `is_valid` is `false`, additional values are provided to help identify the nature of the validation failure:\n\n* `err`: Describes the type of validation error that occurred.\n* `badval`: The value that caused the validation to fail.\n* `path`: The path to the invalid key or index within the table structure.\n\nThese additional values can be used to pinpoint exactly where and why the validation failed.\n\n### Example\n\n```lua\nlocal valid = require \"valid\"\n\nlocal valid_contact = valid.map {\n    required = {\"email\"},\n    table = {\n        email = valid.string {pattern = \".+@.+%..+\"},  -- A very naive email pattern\n        phone = valid.string {pattern = \"%d%d%d%-%d%d%d%-%d%d%d%d\"},\n    }\n}\n\nlocal valid_person = valid.map {\n    required = {\"name\", \"contact\"},\n    table = {\n        name = valid.string {minlen = 3, maxlen = 50},\n        contact = valid_contact\n    }\n}\n\nlocal person_data = {\n    name = \"John Doe\",\n    contact = {\n        email = \"invalid-email.com\",  -- Invalid email\n        phone = \"123-456-7890\"\n    }\n}\n\nlocal is_valid, val_or_err, badval_or_nil, path_or_nil = valid_person(person_data)\n\nprint(\"is_valid:\", is_valid) -- false\nprint(\"val_or_err:\", val_or_err) -- \"pattern\"\nprint(\"badval_or_nil:\", badval_or_nil) -- invalid-email.com\n\n-- path_or_nil is a table like {\"contact\", {\"email\"}}\nprint(\"path_or_nil:\", path_or_nil[1], path_or_nil[2][1]) -- \"contact\" \"email\"\n```\n\n```\nis_valid:      false\nval_or_err:    pattern\nbadval_or_nil: invalid-email.com\npath_or_nil:   contact email\n```\n\n## Contributing\n\nContributions are welcome! If you have any ideas, suggestions, or bug reports, please open an issue on this GitHub repository. If you would like to contribute code, please fork the repository and submit a pull request. Make sure to follow the existing code style and include tests for any new features or bug fixes.\n\n### Code Checks and Lints\n\nThis library uses [luacheck](https://github.com/mpeterv/luacheck) for code checks and lints.  It can be installed from [LuaRocks](https://luarocks.org/) with:\n\n```sh\n$ luarocks install luacheck\n```\n\n#### Running Code Checks and Lints\n\n```sh\n$ make lint\nluacheck valid.lua\nChecking valid.lua                                OK\n\nTotal: 0 warnings / 0 errors in 1 file\n\nluacheck --std=min+busted tests.lua\nChecking tests.lua                                OK\n\nTotal: 0 warnings / 0 errors in 1 file\n```\n\n### Tests\n\nThis library uses [busted](https://github.com/lunarmodules/busted) for tests.  It can be installed from [LuaRocks](https://luarocks.org/) with:\n\n```sh\n$ luarocks install busted\n```\n\n#### Running Tests\n\n```sh\n$ make test\nbusted tests.lua\n++++++++++\n10 successes / 0 failures / 0 errors / 0 pending : 0.001586 seconds\n```\n\n## License\n\nThis project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenwilber%2Flua-valid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenwilber%2Flua-valid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenwilber%2Flua-valid/lists"}