From c40e32e90d90ca907882c83593894c4d6fa59c27 Mon Sep 17 00:00:00 2001 From: Ben Wilber Date: Sat, 18 May 2024 16:56:01 -0400 Subject: [PATCH 1/5] More descriptive docs for path_or_nil and adding luacheck lints --- .github/workflows/test.yml | 7 ++- Makefile | 9 ++- README.md | 14 ++--- tests.lua | 114 ++++++++++++++++++++++++++++++++----- valid.lua | 49 ++++++++-------- 5 files changed, 145 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 362b117..b552b21 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Run Tests +name: Run lints and tests on: [push, pull_request] @@ -22,7 +22,10 @@ jobs: - uses: leafo/gh-actions-luarocks@v4 - name: Install dependencies - run: luarocks install busted + run: luarocks install luacheck busted + + - name: Run lints + run: make lint - name: Run tests run: make test diff --git a/Makefile b/Makefile index 2ca76d9..c8568ee 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,11 @@ -.PHONY: test +.PHONY: ready lint test + +ready: lint test + +lint: + luacheck valid.lua + luacheck --std=min+busted tests.lua test: busted tests.lua + diff --git a/README.md b/README.md index 5fb6862..0520ece 100644 --- a/README.md +++ b/README.md @@ -318,10 +318,10 @@ Validates maps with specific type definitions for both keys and values. local valid = require "valid" -- A map where keys are strings and values are numbers within the range 1 to 10 -local valid_string_number_map = valid.mapof({ +local valid_string_number_map = valid.mapof { valid.string(), valid.number {min = 1, max = 10} -}) +} local map_data = { one = 1, @@ -382,7 +382,7 @@ The library provides detailed error information when validation fails. When `is_ * `err`: Describes the type of validation error that occurred. * `badval`: The value that caused the validation to fail. -* `path`: The path to the invalid key or index within the data structure. +* `path`: The path to the invalid key or index within the table structure. These additional values can be used to pinpoint exactly where and why the validation failed. @@ -415,21 +415,21 @@ local person_data = { } } -local is_valid, val_or_err, badval_or_nil, path = valid_person(person_data) +local is_valid, val_or_err, badval_or_nil, path_or_nil = valid_person(person_data) print("is_valid:", is_valid) -- false print("val_or_err:", val_or_err) -- "pattern" print("badval_or_nil:", badval_or_nil) -- invalid-email.com --- path is a table like {"contact", {"email"}} -print("path:", path[1], path[2][1]) -- "contact" "email" +-- path_or_nil is a table like {"contact", {"email"}} +print("path_or_nil:", path_or_nil[1], path_or_nil[2][1]) -- "contact" "email" ``` ``` is_valid: false val_or_err: pattern badval_or_nil: invalid-email.com -path: contact email +path_or_nil: contact email ``` ## Contributing diff --git a/tests.lua b/tests.lua index fffc057..61f3cc5 100644 --- a/tests.lua +++ b/tests.lua @@ -1,5 +1,29 @@ +require "busted" local valid = require "valid" -local busted = require "busted" + +local ptable +ptable = function(tbl, indent) -- luacheck: no unused + if type(tbl) ~= "table" then + return tostring(tbl) + end + + indent = indent or 0 + local s = string.rep(" ", indent) .. "{\n" + + indent = indent + 2 + + for k, v in pairs(tbl) do + local fmt = string.rep(" ", indent) .. "[" .. tostring(k) .. "] = " + + if type(v) == "table" then + s = s .. fmt .. ptable(v, indent) + else + s = s .. fmt .. tostring(v) .. ",\n" + end + end + + return s .. string.rep(" ", indent - 2) .. "},\n" +end describe("Validation Library Tests", function() @@ -78,7 +102,7 @@ describe("Validation Library Tests", function() friend_ids = {1, 2, 3} }, badval_or_nil = nil, - key = nil + path_or_nil = nil } }, @@ -101,7 +125,7 @@ describe("Validation Library Tests", function() country = "USA" }, badval_or_nil = nil, - key = nil + path_or_nil = nil } }, @@ -124,7 +148,7 @@ describe("Validation Library Tests", function() tags = {"sale", "new"} }, badval_or_nil = nil, - key = nil + path_or_nil = nil } }, @@ -193,7 +217,7 @@ describe("Validation Library Tests", function() } }, badval_or_nil = nil, - key = nil + path_or_nil = nil } }, @@ -210,7 +234,7 @@ describe("Validation Library Tests", function() is_valid = false, val_or_err = "required", badval_or_nil = "city", - key = {"city"} + path_or_nil = {"city"} } }, @@ -232,7 +256,7 @@ describe("Validation Library Tests", function() is_valid = false, val_or_err = "pattern", badval_or_nil = "john.doeexample.com", - key = {"contact", {"email"}} + path_or_nil = {"contact", {"email"}} } }, @@ -254,7 +278,7 @@ describe("Validation Library Tests", function() is_valid = false, val_or_err = "pattern", badval_or_nil = "123-4567890", - key = {"contact", {"phone"}} + path_or_nil = {"contact", {"phone"}} } }, @@ -272,7 +296,7 @@ describe("Validation Library Tests", function() is_valid = false, val_or_err = "min", badval_or_nil = 0, - key = {"price"} + path_or_nil = {"price"} } }, @@ -313,14 +337,76 @@ describe("Validation Library Tests", function() is_valid = false, val_or_err = "min", badval_or_nil = -5.00, - key = {"products", {2, {"price"}}} + path_or_nil = {"products", {2, {"price"}}} + } + }, + { + description = "Deeply nested", + definition = valid.map { + table = { + categories = valid.arrayof( + valid.map { + table = { + widgets = valid.arrayof( + valid.map { + table = { + tags = valid.map { + table = { + name = valid.string() + } + } + } + } + ) + } + } + ) + } + }, + data = { + categories = { + { + widgets = { + { + tags = { + name = 12345 + } + } + } + } + } + }, + expected = { + is_valid = false, + val_or_err = "string", + badval_or_nil = 12345, + path_or_nil = { + "categories", + { + 1, + { + "widgets", + { + 1, + { + "tags", + { + "name" + } + } + } + } + } + } } } } for _, test in ipairs(tests) do it(test.description, function() - local is_valid, val_or_err, badval_or_nil, key = test.definition(test.data) + local is_valid, val_or_err, badval_or_nil, path_or_nil = test.definition(test.data) + + -- print(is_valid, val_or_err, ptable(badval_or_nil), ptable(path_or_nil)) assert.is_equal(test.expected.is_valid, is_valid) @@ -332,10 +418,10 @@ describe("Validation Library Tests", function() assert.is_equal(test.expected.badval_or_nil, badval_or_nil) - if type(key) == "table" then - assert.same(test.expected.key, key) + if type(path_or_nil) == "table" then + assert.same(test.expected.path_or_nil, path_or_nil) else - assert.is_equal(test.expected.key, key) + assert.is_equal(test.expected.path_or_nil, path_or_nil) end end) end diff --git a/valid.lua b/valid.lua index 76c8f57..b711f9b 100644 --- a/valid.lua +++ b/valid.lua @@ -7,14 +7,14 @@ end local function callfunc(func, val) local is_valid, val_or_err, badval_or_nil = func(val) - if not val_or_err then + if val_or_err == nil then if is_valid then val_or_err = val badval_or_nil = nil else val_or_err = "func" - if not badval_or_nil then + if badval_or_nil == nil then badval_or_nil = val end end @@ -139,7 +139,7 @@ local function vtable(opts) local is_valid local val_or_err local badval_or_nil - local sub_path + local path_or_nil for key, func_or_lit in pairs(tabledef) do if type(func_or_lit) ~= "function" then @@ -148,10 +148,10 @@ local function vtable(opts) -- we already validated the required fields if val[key] ~= nil then - is_valid, val_or_err, badval_or_nil, sub_path = func_or_lit(val[key]) + is_valid, val_or_err, badval_or_nil, path_or_nil = func_or_lit(val[key]) if not is_valid then - return false, val_or_err, badval_or_nil, {key, sub_path} + return false, val_or_err, badval_or_nil, {key, path_or_nil} end end end @@ -179,7 +179,7 @@ local function arrayof(deffunc, opts) local maxlen = opts.maxlen or math.huge local func = opts.func or defaultfunc - if opts.empty == true then + if opts.empty then empty = true -- minlen doesnt matter if empty = true @@ -213,23 +213,23 @@ local function arrayof(deffunc, opts) local is_valid local val_or_err local badval_or_nil - local sub_path + local path_or_nil for i, v in ipairs(val) do - is_valid, val_or_err, badval_or_nil, sub_path = deffunc(v) + is_valid, val_or_err, badval_or_nil, path_or_nil = deffunc(v) if not is_valid then - return false, val_or_err, badval_or_nil, {i, sub_path} + return false, val_or_err, badval_or_nil, {i, path_or_nil} end - is_valid, val_or_err, badval_or_nil, sub_path = callfunc(func, v) + is_valid, val_or_err, badval_or_nil, path_or_nil = callfunc(func, v) if not is_valid then - return false, val_or_err, badval_or_nil, {i, sub_path} + return false, val_or_err, badval_or_nil, {i, path_or_nil} end end - return true, val, nil + return true, val end end _M.arrayof = arrayof @@ -248,8 +248,8 @@ _M.map = map local function mapof(deffuncs, opts) opts = opts or {} local empty = false - local keydeffunc = deffuncs[1] - local valdeffunc = deffuncs[2] + local keydeffunc = deffuncs[1] or defaultfunc + local valdeffunc = deffuncs[2] or defaultfunc local keyfunc = defaultfunc local valfunc = defaultfunc @@ -258,7 +258,7 @@ local function mapof(deffuncs, opts) valfunc = opts.func[2] or defaultfunc end - if opts.empty == true then + if opts.empty then empty = true end @@ -267,6 +267,7 @@ local function mapof(deffuncs, opts) return false, "table", val end + -- checking for nil (not false) if next(val) == nil and not empty then return false, "empty", val end @@ -274,31 +275,31 @@ local function mapof(deffuncs, opts) local is_valid local val_or_err local badval_or_nil - local sub_path + local path_or_nil for k, v in pairs(val) do - is_valid, val_or_err, badval_or_nil, sub_path = keydeffunc(k) + is_valid, val_or_err, badval_or_nil, path_or_nil = keydeffunc(k) if not is_valid then - return false, val_or_err, badval_or_nil, {k, sub_path} + return false, val_or_err, badval_or_nil, {k, path_or_nil} end - is_valid, val_or_err, badval_or_nil, sub_path = keyfunc(k) + is_valid, val_or_err, badval_or_nil, path_or_nil = keyfunc(k) if not is_valid then - return false, val_or_err, badval_or_nil, {k, sub_path} + return false, val_or_err, badval_or_nil, {k, path_or_nil} end - is_valid, val_or_err, badval_or_nil, sub_path = valdeffunc(v) + is_valid, val_or_err, badval_or_nil, path_or_nil = valdeffunc(v) if not is_valid then - return false, val_or_err, badval_or_nil, {k, v, sub_path} + return false, val_or_err, badval_or_nil, {k, v, path_or_nil} end - is_valid, val_or_err, badval_or_nil, sub_path = valfunc(v) + is_valid, val_or_err, badval_or_nil, path_or_nil = valfunc(v) if not is_valid then - return false, val_or_err, badval_or_nil, {k, v, sub_path} + return false, val_or_err, badval_or_nil, {k, v, path_or_nil} end end From efee2a2057d70673e45df63aff17f97a90c7f9f4 Mon Sep 17 00:00:00 2001 From: Ben Wilber Date: Sat, 18 May 2024 17:11:04 -0400 Subject: [PATCH 2/5] Have to run luarocks install separately for each package --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b552b21..68735ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,8 +21,11 @@ jobs: - uses: leafo/gh-actions-luarocks@v4 - - name: Install dependencies - run: luarocks install luacheck busted + - name: Install luacheck + run: luarocks install luacheck + + - name: Install busted + run: luarocks install busted - name: Run lints run: make lint From 3864d65348e46cf6d37834857b9dd1a9a490df62 Mon Sep 17 00:00:00 2001 From: Ben Wilber Date: Sun, 19 May 2024 09:28:08 -0400 Subject: [PATCH 3/5] Adding more contributing docs --- README.md | 41 ++++++++++++++++++++++++++++++++++++++++- valid.lua | 19 +++++++++++-------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0520ece..828bcd1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ A library for Lua to validate various values and table structures. - [`valid.map`](#validmap) - [`valid.mapof`](#validmapof) - [Error Handling and Invalid Propagation](#error-handling-and-invalid-propagation) -- [Running Tests](#running-tests) - [Contributing](#contributing) - [License](#license) @@ -436,6 +435,46 @@ path_or_nil: contact email Contributions 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. +### Code Checks and Lints + +This library uses [luacheck](https://github.com/mpeterv/luacheck) for code checks and lints. It can be installed from [LuaRocks](https://luarocks.org/) with: + +```sh +$ luarocks install luacheck +``` + +#### Running Code Checks and Lints + +```sh +$ make lint +luacheck valid.lua +Checking valid.lua OK + +Total: 0 warnings / 0 errors in 1 file + +luacheck --std=min+busted tests.lua +Checking tests.lua OK + +Total: 0 warnings / 0 errors in 1 file +``` + +### Tests + +This library uses [busted](https://github.com/lunarmodules/busted) for tests. It can be installed from [LuaRocks](https://luarocks.org/) with: + +```sh +$ luarocks install busted +``` + +#### Running Tests + +```sh +$ make test +busted tests.lua +++++++++++ +10 successes / 0 failures / 0 errors / 0 pending : 0.001586 seconds +``` + ## License This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details. diff --git a/valid.lua b/valid.lua index b711f9b..9eb9053 100644 --- a/valid.lua +++ b/valid.lua @@ -1,7 +1,7 @@ local _M = {} local function defaultfunc(val) - return true, val, nil + return true, val end local function callfunc(func, val) @@ -103,15 +103,15 @@ local function vtable(opts) local func = opts.func or defaultfunc local tabledef = opts.table - if opts.array == true then + if opts.array then array = true end - if opts.map == true then + if opts.map then map = true end - if opts.empty == true then + if opts.empty then empty = true end @@ -131,6 +131,8 @@ local function vtable(opts) -- required fields for _, key in ipairs(required) do + + -- checking for nil (not false) if val[key] == nil then return false, "required", key, {key} end @@ -141,17 +143,18 @@ local function vtable(opts) local badval_or_nil local path_or_nil - for key, func_or_lit in pairs(tabledef) do + for key_or_idx, func_or_lit in pairs(tabledef) do if type(func_or_lit) ~= "function" then func_or_lit = literal(func_or_lit) end -- we already validated the required fields - if val[key] ~= nil then - is_valid, val_or_err, badval_or_nil, path_or_nil = func_or_lit(val[key]) + -- checking for nil (not false) + if val[key_or_idx] ~= nil then + is_valid, val_or_err, badval_or_nil, path_or_nil = func_or_lit(val[key_or_idx]) if not is_valid then - return false, val_or_err, badval_or_nil, {key, path_or_nil} + return false, val_or_err, badval_or_nil, {key_or_idx, path_or_nil} end end end From a47c6e38412526ec416e3b6e78a3f501064e45ff Mon Sep 17 00:00:00 2001 From: Ben Wilber Date: Sun, 19 May 2024 11:30:13 -0400 Subject: [PATCH 4/5] Adding link to valid.lua --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 828bcd1..1a98db9 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ A library for Lua to validate various values and table structures. ## Installation -Copy the `valid.lua` file to a directory in your `LUA_PATH`. +Copy the [`valid.lua`](valid.lua) file to a directory in your `LUA_PATH`. ## Basic Usage From 830cc0715b063952f6bb3845bef1c217f3d16b99 Mon Sep 17 00:00:00 2001 From: Ben Wilber Date: Sun, 19 May 2024 12:38:48 -0400 Subject: [PATCH 5/5] Adding valid.func() to check if a value is a function --- README.md | 23 +++++++++++++++++++++++ tests.lua | 28 ++++++++++++++++++++++++++++ valid.lua | 11 +++++++++++ 3 files changed, 62 insertions(+) diff --git a/README.md b/README.md index 4a21acb..ef8ec43 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ A library for Lua to validate various values and table structures. - [`valid.arrayof`](#validarrayof) - [`valid.map`](#validmap) - [`valid.mapof`](#validmapof) + - [`valid.func`](#validfunc) - [Error Handling and Invalid Propagation](#error-handling-and-invalid-propagation) - [Contributing](#contributing) - [License](#license) @@ -384,6 +385,28 @@ assert(not is_valid) -- false, "name" is required for "bob" * `empty`: Set to `true` to allow empty maps. * `func`: A table containing two custom validation functions, one for the keys and one for the values. +### `valid.func` + +Validates that a value is a function. + +#### Usage + +```lua +local valid = require "valid" + +local valid_function = valid.func() + +local is_valid = valid_function(function() end) +assert(is_valid) -- true + +local is_valid = valid_function("123") +assert(not is_valid) -- false, not a function +``` + +#### Parameters + +*(none)* + ## Error Handling and Invalid Propagation The 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: diff --git a/tests.lua b/tests.lua index a97c622..b76faf8 100644 --- a/tests.lua +++ b/tests.lua @@ -74,7 +74,35 @@ describe("Validation Library Tests", function() } } + local simple_function = function() end + local tests = { + -- Valid simple function + { + description = "Valid simple function", + definition = valid.func(), + data = simple_function, + expected = { + is_valid = true, + val_or_err = simple_function, + badval_or_nil = nil, + path_or_nil = nil + } + }, + + -- Invalid function + { + description = "Invalid function", + definition = valid.func(), + data = "123", + expected = { + is_valid = false, + val_or_err = "func", + badval_or_nil = "123", + path_or_nil = nil + } + }, + -- Valid contact data { description = "Valid contact data", diff --git a/valid.lua b/valid.lua index e79931b..ac76bf3 100644 --- a/valid.lua +++ b/valid.lua @@ -339,4 +339,15 @@ local function mapof(deffuncs, opts) end _M.mapof = mapof +local function func() + return function(val) + if type(val) ~= "function" then + return false, "func", val + end + + return true, val + end +end +_M.func = func + return _M