{"id":19212350,"url":"https://github.com/leafo/tableshape","last_synced_at":"2026-03-17T00:02:47.168Z","repository":{"id":46230867,"uuid":"50267485","full_name":"leafo/tableshape","owner":"leafo","description":"Test the shape or structure of a Lua table, inspired by React.PropTypes \u0026 LPeg","archived":false,"fork":false,"pushed_at":"2023-02-23T03:30:29.000Z","size":388,"stargazers_count":115,"open_issues_count":5,"forks_count":9,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-06-08T11:51:26.369Z","etag":null,"topics":["lua"],"latest_commit_sha":null,"homepage":"","language":"MoonScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leafo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2016-01-24T01:40:24.000Z","updated_at":"2025-05-27T06:56:32.000Z","dependencies_parsed_at":"2024-11-09T13:46:44.148Z","dependency_job_id":null,"html_url":"https://github.com/leafo/tableshape","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/leafo/tableshape","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Ftableshape","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Ftableshape/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Ftableshape/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Ftableshape/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leafo","download_url":"https://codeload.github.com/leafo/tableshape/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Ftableshape/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278240197,"owners_count":25954138,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-03T02:00:06.070Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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"],"created_at":"2024-11-09T13:46:39.656Z","updated_at":"2026-03-17T00:02:47.139Z","avatar_url":"https://github.com/leafo.png","language":"MoonScript","readme":"\n# tableshape\n\n![test](https://github.com/leafo/tableshape/workflows/test/badge.svg)\n\nA Lua library for verifying the shape (schema, structure, etc.) of a table, and\ntransforming it if necessary. The type checking syntax is inspired by the\n[PropTypes module of\nReact](https://facebook.github.io/react/docs/reusable-components.html#prop-validation).\nComplex types \u0026amp; value transformations can be expressed using an operator\noverloading syntax similar to [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg/).\n\n### Install\n\n```bash\n$ luarocks install tableshape\n```\n\n### Quick usage\n\n```lua\nlocal types = require(\"tableshape\").types\n\n-- define the shape of our player object\nlocal player_shape = types.shape{\n  class = types.one_of{\"player\", \"enemy\"},\n  name = types.string,\n  position = types.shape{\n    x = types.number,\n    y = types.number,\n  },\n  inventory = types.array_of(types.shape{\n    name = types.string,\n    id = types.integer\n  }):is_optional()\n}\n\n\n\n-- create a valid object to test the shape with\nlocal player = {\n  class = \"player\",\n  name = \"Lee\",\n  position = {\n    x = 2.8,\n    y = 8.5\n  },\n}\n\n-- verify that it matches the shape\nassert(player_shape(player))\n\n-- let's break the shape to see the error message:\nplayer.position.x = \"heck\"\nassert(player_shape(player))\n\n-- error: field `position`: field `x`: got type `string`, expected `number`\n```\n\n#### Transforming\n\nA malformed value can be repaired to the expected shape by using the\ntransformation operator and method. The input value is cloned and modified\nbefore being returned.\n\n\n```lua\nlocal types = require(\"tableshape\").types\n\n-- a type checker that will coerce a value into a number from a string or return 0\nlocal number = types.number + types.string / tonumber + types.any / 0\n\nnumber:transform(5) --\u003e 5\nnumber:transform(\"500\") --\u003e 500\nnumber:transform(\"hi\") --\u003e 0\nnumber:transform({}) --\u003e 0\n```\n\nBecause type checkers are composable objects, we can build more complex types\nout of existing types we've written:\n\n```lua\n\n-- here we reference our transforming number type from above\nlocal coordinate = types.shape {\n  x = number,\n  y = number\n}\n\n-- a compound type checker that can fill in missing values\nlocal player_shape = types.shape({\n  name = types.string + types.any / \"unknown\",\n  position = coordinate\n})\n\nlocal bad_player = {\n  position = {\n    x = \"234\",\n    y = false\n  }\n}\n\nlocal fixed_player = player_shape:transform(bad_player)\n\n-- fixed_player --\u003e {\n--   name = \"unknown\",\n--   position = {\n--     x = 234,\n--     y = 0\n--   }\n-- }\n```\n\n## Tutorial\n\nTo load the library `require` it. The most important part of the library is the\n`types` table, which will give you acess to all the type checkers\n\n```lua\nlocal types = require(\"tableshape\").types\n```\n\nYou can use the types table to check the types of simple values, not just\ntables. Calling the type checker like a function will test a value to see if it\nmatches the shape or type. It returns `true` on a match, or `nil` and the error\nmessage if it fails. (This is done with the `__call` metamethod, you can also\nuse the `check_value` method directly)\n\n```lua\ntypes.string(\"hello!\") --\u003e true\ntypes.string(777)      --\u003e nil, expected type \"string\", got \"number\"\n```\n\nYou can see the full list of the available types below in the reference.\n\nThe real power of `tableshape` comes from the ability to describe complex types\nby nesting the type checkers.\n\nHere we test for an array of numbers by using `array_of`:\n\n```lua\nlocal numbers_shape = types.array_of(types.number)\n\nassert(numbers_shape({1,2,3}))\n\n-- error: item 2 in array does not match: got type `string`, expected `number`\nassert(numbers_shape({1,\"oops\",3}))\n```\n\n\u003e **Note:** The type checking is strict, a string that looks like a number,\n\u003e `\"123\"`, is not a number and will trigger an error!\n\nThe structure of a generic table can be tested with `types.shape`. It takes a\nmapping table where the key is the field to check, and the value is the type\nchecker:\n\n```lua\nlocal object_shape = types.shape{\n  id = types.number,\n  name = types.string:is_optional(),\n}\n\n-- success\nassert(object_shape({\n  id = 1234,\n  name = \"hello world\"\n}))\n\n-- sucess, optional field is not there\nassert(object_shape({\n  id = 1235,\n}))\n\n\n-- error: field `id`: got type `nil`, expected `number`\nassert(object_shape({\n  name = 424,\n}))\n```\n\nThe `is_optional` method can be called on any type checker to return a new type\nchecker that can also accept `nil` as a value. (It is equivalent to `t + types['nil']`)\n\nIf multiple fields fail the type check in a shape, the error message will\ncontain all the failing fields\n\nYou can also use a literal value to match it directly: (This is equivalent to using `types.literal(v)`)\n\n```lua\nlocal object_shape = types.shape{\n  name = \"Cowcat\"\n}\n\n-- error: field `name` expected `Cowcat`\nassert(object_shape({\n  name = \"Cowdog\"\n}))\n```\n\nThe `one_of` type constructor lets you specify a list of types, and will\nsucceed if one of them matches. (It works the same as the `+` operator)\n\n\n```lua\nlocal func_or_bool = types.one_of { types.func, types.boolean }\n\nassert(func_or_bool(function() end))\n\n-- error: expected type \"function\", or type \"boolean\"\nassert(func_or_bool(2345))\n```\n\nIt can also be used with literal values as well:\n\n```lua\nlocal limbs = types.one_of{\"foot\", \"arm\"}\n\nassert(limbs(\"foot\")) -- success\nassert(limbs(\"arm\")) -- success\n\n-- error: expected \"foot\", or \"arm\"\nassert(limbs(\"baseball\"))\n```\n\nThe `pattern` type can be used to test a string with a Lua pattern\n\n```lua\nlocal no_spaces = types.pattern \"^[^%s]*$\"\n\nassert(no_spaces(\"hello!\"))\n\n-- error: doesn't match pattern `^[^%s]*$`\nassert(no_spaces(\"oh no!\"))\n```\n\nThese examples only demonstrate some of the type checkers provided.  You can\nsee all the other type checkers in the reference below.\n\n### Type operators\n\nType checker objects have the operators `*`, `+`, and `/` overloaded to provide\na quick way to make composite types.\n\n* `*` — The **all of (and)** operator, both operands must match.\n* `+` — The **first of (or)** operator, the operands are checked against the value from left to right\n* `/` — The **transform** operator, when using the `transform` method, the value will be converted by what's to the right of the operator\n* `%` — The **transform with state** operator, same as transform, but state is passed as second argument\n\n#### The 'all of' operator\n\nThe **all of** operator checks if a value matches multiple types. Types are\nchecked from left to right, and type checking will abort on the first failed\ncheck. It works the same as `types.all_of`.\n\n```lua\nlocal s = types.pattern(\"^hello\") * types.pattern(\"world$\")\n\ns(\"hello 777 world\")   --\u003e true\ns(\"good work\")         --\u003e nil, \"doesn't match pattern `^hello`\"\ns(\"hello, umm worldz\") --\u003e nil, \"doesn't match pattern `world$`\"\n```\n\n#### The 'first of' operator\n\nThe **first of** operator checks if a value matches one of many types. Types\nare checked from left to right, and type checking will succeed on the first\nmatched type. It works the same as `types.one_of`.\n\nOnce a type has been matched, no additional types are checked. If you use a\ngreedy type first, like `types.any`, then it will not check any additional\nones. This is important to realize if your subsequent types have any side\neffects like transformations or tags.\n\n\n```lua\nlocal s = types.number + types.string\n\ns(44)            --\u003e true\ns(\"hello world\") --\u003e true\ns(true)          --\u003e nil, \"no matching option (got type `boolean`, expected `number`; got type `boolean`, expected `string`)\"\n```\n\n#### The 'transform' operator\n\nIn type matching mode, the transform operator has no effect. When using the\n`transform` method, however, the value will be modified by a callback or\nchanged to a fixed value.\n\nThe following syntax is used: `type / transform_callback --\u003e transformable_type`\n\n```lua\nlocal t = types.string + types.any / \"unknown\"\n```\n\nThe proceeding type can be read as: \"Match any string, or for any other type,\ntransform it into the string 'unknown'\".\n\n```lua\nt:transform(\"hello\") --\u003e \"hello\"\nt:transform(5)       --\u003e \"unknown\"\n```\n\nBecause this type checker uses `types.any`, it will pass for whatever value is\nhanded to it. A transforming type can fail also fail, here's an example:\n\n```lua\nlocal n = types.number + types.string / tonumber\n\nn:transform(\"5\") --\u003e 5\nn:transform({})  --\u003e nil, \"no matching option (got type `table`, expected `number`; got type `table`, expected `string`)\"\n```\n\nThe transform callback can either be a function, or a literal value. If a\nfunction is used, then the function is called with the current value being\ntransformed, and the result of the transformation should be returned. If a\nliteral value is used, then the transformation always turns the value into the\nspecified value.\n\nA transform function is not a predicate, and can't directly cause the type\nchecking to fail. Returning `nil` is valid and will change the value to `nil`.\nIf you wish to fail based on a function you can use the `custom` type or chain\nanother type checker after the transformation:\n\n\n```lua\n-- this will fail unless `tonumber` returns a number\nlocal t = (types.string / tonumber) * types.number\nt:transform(\"nothing\") --\u003e nil, \"got type `nil`, expected `number`\"\n```\n\nA common pattern for repairing objects involves testing for the types you know\nhow to fix followed by ` + types.any`, followed by a type check of the final\ntype you want:\n\nHere we attempt to repair a value to the expected format for an x,y coordinate:\n\n\n```lua\nlocal types = require(\"tableshape\").types\n\nlocal str_to_coord = types.string / function(str)\n  local x,y = str:match(\"(%d+)[^%d]+(%d+)\")\n  if not x then return end\n  return {\n    x = tonumber(x),\n    y = tonumber(y)\n  }\nend\n\nlocal array_to_coord = types.shape{types.number, types.number} / function(a)\n  return {\n    x = a[1],\n    y = a[2]\n  }\nend\n\nlocal cord = (str_to_coord + array_to_coord + types.any) * types.shape {\n  x = types.number,\n  y = types.number\n}\n\ncord:transform(\"100,200\")        --\u003e { x = 100, y = 200}\ncord:transform({5, 23})          --\u003e { x = 5, y = 23}\ncord:transform({ x = 9, y = 10}) --\u003e { x = 9, y = 10}\n```\n\n### Tags\n\nTags can be used to extract values from a type as it's checked. A tag is only\nsaved if the type it wraps matches. If a tag type wraps type checker that\ntransforms a value, then the tag will store the result of the transformation\n\n\n```lua\nlocal t = types.shape {\n  a = types.number:tag(\"x\"),\n  b = types.number:tag(\"y\"),\n} + types.shape {\n  types.number:tag(\"x\"),\n  types.number:tag(\"y\"),\n}\n\nt({1,2})          --\u003e { x = 1, y = 2}\nt({a = 3, b = 9}) --\u003e { x = 3, y = 9}\n```\n\nThe values captured by tags are stored in the *state* object, a table that is\npassed throughout the entire type check. When invoking a type check, on success\nthe return value will be the state object if any state is used, via tags or any\nof the state APIs listed below. If no state is used, `true` is returned on a\nsuccessful check.\n\nIf a tag name ends in `\"[]\"` (eg. `\"items[]\"`), then repeated use of the tag\nname will cause each value to accumulate into an array. Otherwise, re-use of a\ntag name will cause the value to be overwritten at that name.\n\n### Scopes\n\nYou can use scopes to nest state objects (which includes the result of tags). A\nscope can be created with `types.scope`. A scope works by pushing a new state\non the state stack. After the scope is completed, it is assigned to the\nprevious scope at the specified tag name.\n\n```lua\nlocal obj = types.shape {\n  id = types.string:tag(\"name\"),\n  age = types.number\n}\n\nlocal many = types.array_of(types.scope(obj, { tag = \"results[]\"}))\n\nmany({\n  { id = \"leaf\", age = 2000 },\n  { id = \"amos\", age = 15 }\n}) --\u003e { results = {name = \"leaf\"}, {name = \"amos\"}}\n```\n\n\u003e Note: In this example, we use the special `[]` syntax in the tag name to accumulate\n\u003e all values that are tagged into an array. If the `[]` was left out, then each\n\u003e tagged value would overwrite the previous.\n\nIf the tag of the `types.scope` is left out, then an anonymous scope is\ncreated.  An anonymous scope is thrown away after the scope is exited. This\nstyle is useful if you use state for a local transformation, and don't need\nthose values to affect the enclosing state object.\n\n### Transforming\n\nThe `transform` method on a type object is a special way to invoke a type check\nthat allows the value to be changed into something else during the type\nchecking process. This can be usful for repairing or normalizing input into an\nexpected shape.\n\nThe simplest way to tranform a value is using the transform operator, `/`:\n\nFor example, we can type checker for URLs that will either accept a valid url,\nor convert any other string into a valid URL:\n\n```lua\nlocal url_shape = types.pattern(\"^https?://\") + types.string / function(val)\n  return \"http://\" .. val\nend\n```\n\n```lua\nurl_shape:transform(\"https://itch.io\") --\u003e https://itch.io\nurl_shape:transform(\"leafo.net\")       --\u003e http://leafo.net\nurl_shape:transform({})                --\u003e nil, \"no matching option (expected string for value; got type `table`)\"\n```\n\nWe can compose transformable type checkers. Now that we know how to fix a URL,\nwe can fix an array of URLs:\n\n```lua\nlocal urls_array = types.array_of(url_shape + types.any / nil)\n\nlocal fixed_urls = urls_array:transform({\n  \"https://itch.io\",\n  \"leafo.net\",\n  {}\n  \"www.streak.club\",\n})\n\n-- will return:\n-- {\n--   \"https://itch.io\",\n--   \"http://leafo.net\",\n--   \"http://www.streak.club\"\n-- }\n```\n\nThe `transform` method of the `array_of` type will transform each value of the\narray. A special property of the `array_of` transform is to exclude any values\nthat get turned into `nil` in the final output. You can use this to filter out\nany bad data without having holes in your array. (You can override this with\nthe `keep_nils` option.\n\nNote how we add the `types.any / nil` alternative after the URL shape. This\nwill ensure any unrecognized values are turned to `nil` so that they can be\nfiltered out from the `array_of` shape. If this was not included, then the URL\nshape will fail on invalid values and the the entire transformation would be\naborted.\n\n### Transformation and mutable objects\n\nSpecial care must be made when writing a transformation function when working\nwith mutable objects like tables. You should never modify the object, instead\nmake a clone of it, make the changes, then return the new object.\n\nBecause types can be deeply nested, it's possible that transformation may be\ncalled on a value, but the type check later fails. If you mutated the input\nvalue then there's no way to undo that change, and you've created a side effect\nthat may break your program.\n\n**Never do this:**\n\n```lua\nlocal types = require(\"tableshape\").types\n\n-- WARNING: READ CAREFULLY\nlocal add_id = types.table / function(t)\n  -- NEVER DO THIS\n  t.id = 100\n  -- I repeat, don't do what's in the line above\n  return t\nend\n\n-- This is why, imagine we create a new compund type:\n\nlocal array_of_add_id = types.array_of(add_id)\n\n-- And now we pass in the following malformed object:\n\nlocal items = {\n  { entry = 1},\n  \"entry2\",\n  { entry = 3},\n}\n\n\n-- And attempt to verify it by transforming it:\n\nlocal result,err = array_of_add_id:transform(items)\n\n-- This will fail because there's an value in items that will fail validation for\n-- add_id. Since types are processed incrementally, the first entry would have\n-- been permanently changed by the transformation. Even though the check failed,\n-- the data is partially modified and may result in a hard-to-catch bug.\n\nprint items[1] --\u003e = { id = 100, entry = 1}\nprint items[3] --\u003e = { entry = 3}\n```\n\nLuckily, tableshape provides a helper type that is designed to clone objects,\n`types.clone`. Here's the correct way to write the transformation:\n\n```lua\nlocal types = require(\"tableshape\").types\n\nlocal add_id = types.table / function(t)\n  local new_t = assert(types.clone:transform(t))\n  new_t.id = 100\n  return new_t\nend\n```\n\n\u003e **Advanced users only:** Since `types.clone` is a type itself, you can chain\n\u003e it before any *dirty* functions you may have to ensure that mutations don't\n\u003e cause side effects to persist during type validation: `types.table * types.clone / my_messy_function`\n\nThe built in composite types that operate on objects will automatically clone\nan object if any of the nested types have transforms that return new values.\nThis includes composite type constructors like `types.shape`, `types.array_of`,\n`types.map_of`, etc. You only need to be careful about mutations when using\ncustom transformation functions.\n\n## Reference\n\n```lua\nlocal types = require(\"tableshape\").types\n```\n\n### Type constructors\n\nType constructors build a type checker configured by the parameters you pass.\nHere are all the available ones, full documentation is below.\n\n* `types.shape` - checks the shape of a table\n* `types.partial` - shorthand for an *open* `types.shape`\n* `types.one_of` - checks if value matches one of the types provided\n* `types.pattern` - checks if Lua pattern matches value\n* `types.array_of` - checks if value is array containing a type\n* `types.array_contains` - checks if value is an array that contains a type (short circuits by default)\n* `types.map_of` - checks if value is table that matches key and value types\n* `types.literal` - checks if value matches the provided value with `==`\n* `types.custom` - lets you provide a function to check the type\n* `types.equivalent` - checks if values deeply compare to one another\n* `types.range` - checks if value is between two other values\n* `types.proxy` - dynamically load a type checker\n\n#### `types.shape(table_dec, options={})`\n\nReturns a type checker tests for a table where every key in `table_dec` has a\ntype matching the associated value. The associated value can also be a literal\nvalue.\n\n```lua\nlocal t = types.shape{\n  category = \"object\", -- matches the literal value `\"object\"`\n  id = types.number,\n  name = types.string\n}\n```\n\nThe following options are supported:\n\n* `open` \u0026mdash; The shape will accept any additional fields without failing\n* `extra_fields` \u0026mdash; A type checker for use with extra keys. For each extra field in the table, the value `{key = value}` is passed to the `extra_fields` type checker. During transformation, the table can be transformed to change either the key or value. Transformers that return `nil` will clear the field. See below for examples. The extra keys shape can also use tags.\n\nExamples with `extra_fields`:\n\nBasic type test for extra fields:\n\n```lua\nlocal t = types.shape({\n  name = types.string\n}, {\n  extra_fields = types.map_of(types.string, types.number)\n})\n\nt({\n  name = \"lee\",\n  height = \"10cm\",\n  friendly = false,\n}) --\u003e nil, \"field `height` value in table does not match: got type `string`, expected `number`\"\n\n```\n\nA transform can be used on `extra_fields` as well. In this example all extra fields are removed:\n\n```lua\nlocal t = types.shape({\n  name = types.string\n}, {\n  extra_fields = types.any / nil\n})\n\nt:transform({\n  name = \"amos\",\n  color = \"blue\",\n  1,2,3\n}) --\u003e { name = \"amos\"}\n```\n\nModifying the extra keys using a transform:\n\n```lua\nlocal types = require(\"tableshape\").types\n\nlocal t = types.shape({\n  name = types.string\n}, {\n  extra_fields = types.map_of(\n    -- prefix all extra keys with _\n    types.string / function(str) return \"_\" .. str end,\n\n    -- leave values as is\n    types.any\n  )\n})\n\nt:transform({\n  name = \"amos\",\n  color = \"blue\"\n}) --\u003e { name = \"amos\", _color = \"blue\" }\n```\n\n#### `types.partial(table_dec, options={})`\n\nThe same as `types.shape` but sets `open = true` by default. This alias\nfunction was added because open shape objects are common when using tableshape.\n\n```lua\nlocal types = require(\"tableshape\").types\n\nlocal t = types.partial {\n  name = types.string:tag(\"player_name\")\n}\n\nt({\n  t: \"character\"\n  name: \"Good Friend\"\n}) --\u003e { player_name: \"Good Friend\" }\n```\n\n#### `types.array_of(item_type, options={})`\n\nReturns a type checker that tests if the value is an array where each item\nmatches the provided type.\n\n```lua\nlocal t = types.array_of(types.shape{\n  id = types.number\n})\n```\n\nThe following options are supported:\n\n* `keep_nils` \u0026mdash; By default, if a value is transformed into a nil then it won't be kept in the output array. If you need to keep these holes then set this option to `true`\n* `length` \u0026mdash; Provide a type checker to be used on the length of the array. The length is calculated with the `#` operator. It's typical to use `types.range` to test for a range\n\n\n#### `types.array_contains(item_type, options={})`\n\nReturns a type checker that tests if `item_type` exists in the array. By\ndefault, `short_circuit` is enabled. It will search until it finds the first\ninstance of `item_type` in the array then stop with a success. This impacts\ntransforming types, as only the first match will be transformed by default. To\nprocess every entry in the array, set `short_circuit = false` in the options.\n\n\n```lua\nlocal t = types.array_contains(types.number)\n\nt({\"one\", \"two\", 3, \"four\"}) --\u003e true\nt({\"hello\", true}) --\u003e fails\n```\n\nThe following options are supported:\n\n* `short_circuit` \u0026mdash; (default `true`) Will stop scanning over the array if a single match is found\n* `keep_nils` \u0026mdash; By default, if a value is transformed into a nil then it won't be kept in the output array. If you need to keep these holes then set this option to `true`\n\n\n#### `types.map_of(key_type, value_type)`\n\nReturns a type checker that tests for a table where every key and value matches\nthe respective type checkers provided as arguments.\n\n```lua\nlocal t = types.map_of(types.string, types.any)\n```\n\nWhen transforming a `map_of`, you can remove fields from the table by\ntransforming either the key or value to `nil`.\n\n```lua\n-- this will remove all fields with non-string keys\nlocal t = types.map_of(types.string + types.any / nil, types.any)\n\nt:transform({\n  1,2,3,\n  hello = \"world\"\n}) --\u003e { hello = \"world\" }\n```\n\n#### `types.one_of({type1, type2, ...})`\n\nReturns a type checker that tests if the value matches one of the provided\ntypes. A literal value can also be passed as a type.\n\n```lua\nlocal t = types.one_of{\"none\", types.number}\n```\n\n#### `types.pattern(lua_pattern)`\n\nReturns a type checker that tests if a string matches the provided Lua pattern\n\n```lua\nlocal t = types.pattern(\"^#[a-fA-F%d]+$\")\n```\n\n#### `types.literal(value)`\n\nReturns a type checker that checks if value is equal to the one provided. When\nusing shape this is normally unnecessary since non-type checker values will be\nchecked literally with `==`. This lets you attach a repair function to a\nliteral check.\n\n```lua\nlocal t = types.literal \"hello world\"\nassert(t(\"hello world\") == true)\nassert(t(\"jello world\") == false)\n```\n\n#### `types.custom(fn)`\n\nReturns a type checker that calls the function provided to verify the value.\nThe function will receive the value being tested as the first argument, and any\nexisting state object as the second.\n\nThe function should return true if the value passes, or `nil` and an error\nmessage if it fails.\n\n```lua\nlocal is_even = types.custom(function(val)\n  if type(val) == \"number\" then\n    if val % 2 == 0 then\n      return true\n    else\n      return nil, \"number is not even\"\n    end\n  else\n    return nil, \"expected number\"\n  end\nend)\n```\n\n#### types.equivalent(val)\n\nReturns a type checker that will do a deep compare between val and the input.\n\n```lua\nlocal t = types.equivalent {\n  color = {255,100,128},\n  name = \"leaf\"\n}\n\n-- although we're testing a different instance of the table, the structure is\n-- the same so it passes\nt {\n  name = \"leaf\"\n  color = {255,100,128},\n} --\u003e true\n\n```\n\n#### types.range(left, right)\n\nCreates a type checker that will check if a value is beween `left` and `right`\ninclusive. The type of the value is checked before doing the comparison:\npassing a string to a numeric type checker will fail up front.\n\n```lua\nlocal nums = types.range 1, 20\nlocal letters = types.range \"a\", \"f\"\n\nnums(4)    --\u003e true\nletters(\"c\")  --\u003e true\nletters(\"n\")  --\u003e true\n```\n\nThis checker works well with the length checks for strings and arrays.\n\n#### types.proxy(fn)\n\nThe proxy type checker will execute the provided function, `fn`, when called\nand use the return value as the type checker.  The `fn` function must return a\nvalid tableshape type checker object.\n\nThis can be used to have types that circularly depend on one another, or handle\nrecursive types. `fn` is called every time the proxy checks a value, if you\nwant to optimize for performance then you are responsible for caching type\nchecker that is returned.\n\n\nAn example recursive type checker:\n\n```lua\nlocal entity_type = types.shape {\n  name = types.string,\n  child = types['nil'] + types.proxy(function() return entity_type end)\n}\n```\n\nA proxy is needed above because the value of `entity_type` is `nil` while the\ntype checker is being constructed. By using the proxy, we can create a closure\nto the variable that will eventually hold the `entity_type` checker.\n\n### Built in types\n\nBuilt in types can be used directly without being constructed.\n\n* `types.string` - checks for `type(val) == \"string\"`\n* `types.number` - checks for `type(val) == \"number\"`\n* `types['function']` - checks for `type(val) == \"function\"`\n* `types.func` - alias for `types['function']`\n* `types.boolean` - checks for `type(val) == \"boolean\"`\n* `types.userdata` - checks for `type(val) == \"userdata\"`\n* `types.table` - checks for `type(val) == \"table\"`\n* `types['nil']` - checks for `type(val) == \"nil\"`\n* `types.null` - alias for `types['nil']`\n* `types.array` - checks for table of numerically increasing indexes\n* `types.integer` - checks for a number with no decimal component\n* `types.clone` - creates a shallow copy of the input, fails if value is not cloneable (eg. userdata, function)\n\nAdditionally there's the special *any* type:\n\n* `types.any` - succeeds no matter value is passed, including `nil`\n\n### Type methods\n\nEvery type checker has the follow methods:\n\n#### `type(value)` or `type:check_value(value)`\n\nCalling `check_value` is equivalent to calling the type checker object like a\nfunction. The `__call` metamethod is provided on all type checker objects to\nallow you easily test a value by treating them like a function.\n\nTests `value` against the type checker. Returns `true` (or the current state\nobject) if the value passes the check. Returns `nil` and an error message as a\nstring if there is a mismatch. The error message will identify where the\nmismatch happened as best it can.\n\n`check_value` will abort on the first error found, and only that error message is returned.\n\n\u003e Note: Under the hood, checking a value will always execute the full\n\u003e transformation, but the resulting object is thrown away, and only the state\n\u003e is returned. Keep this in mind because there is no performance benefit to\n\u003e calling `check_value` over `transform`\n\n#### `type:transform(value, initial_state=nil)`\n\nWill apply transformation to the `value` with the provided type. If the type\ndoes not include any transformations then the same object will be returned\nassuming it matches the type check. If transformations take place then a new\nobject will be returned with all other fields copied over.\n\n\u003e You can use the *transform operator* (`/`) to specify how values are transformed.\n\nA second argument can optionally be provided for the initial state. This should\nbe a Lua table.\n\nIf no state is provided, an empty Lua table will automatically will\nautomatically be created if any of the type transformations make changes to the\nstate.\n\n\u003e The state object is used to store the result of any tagged types. The state\n\u003e object can also be used to store data across the entire type checker for more\n\u003e advanced functionality when using the custom state operators and types.\n\n```lua\nlocal t = types.number + types.string / tonumber\n\nt:transform(10) --\u003e 10\nt:transform(\"15\") --\u003e 15\n```\n\nOn success, this method will return the resulting value and the resulting\nstate. If no state is used, then no state will be returned. On failure, the\nmethod will return `nil` and a string error message.\n\n#### `type:repair(value)`\n\n\u003e This method is deprecated, use the `type:transform` instead\n\nAn alias for `type:transform(value)`\n\n#### `type:is_optional()`\n\nReturns a new type checker that matches the same type, or `nil`. This is\neffectively the same as using the expression:\n\n\n```lua\nlocal optional_my_type = types[\"nil\"] + my_type\n````\n\nInternally, though, `is_optional` creates new *OptionalType* node in the type\nhierarchy to make printing summaries and error messages more clear.\n\n#### `type:describe(description)`\n\nReturns a wrapped type checker that will use `description` to describe the type\nwhen an error message is returned. `description` can either be a string\nliteral, or a function. When using a function, it must return the description\nof the type as a string.\n\n\n#### `type:tag(name_or_fn)`\n\nCauses the type checker to save matched values into the state object. If\n`name_or_fn` is a string, then the tested value is stored into the state with\nkey `name_or_fn`.\n\nIf `name_or_fn` is a function, then you provide a callback to control how the\nstate is updated. The function takes as arguments the state object and the\nvalue that matched:\n\n```lua\n-- an example tag function that accumulates an array\ntypes.number:tag(function(state, value)\n  -- nested objects should be treated as read only, so modifications are done to a copy\n  if state.numbers then\n    state.numbers = { unpack state.numbers }\n  else\n    state.numbers = { }\n  end\n\n  table.insert(state.numbers, value)\nend)\n```\n\n\u003e This is illustrative example. If you need to accumulate a list of values then\n\u003e use the `[]` syntax for tag names.\n\nYou can mutate the `state` argument with any changes. The return value of this\nfunction is ignored.\n\nNote that state objects are generally immutable. Whenever a state modifying\noperation takes place, the modification is done to a copy of the state object.\nThis is to prevent changes to the  state object from being kept around when a\nfailing type is tested.\n\nA `function` tag gets a copy of the current state as its first argument ready\nfor editing. The copy is a shallow copy. If you have any nested objects then\nit's necessary to clone them before making any modifications, as seen in the\nexample above.\n\n#### `shape_type:is_open()`\n\n\u003e This method is deprecated, use the `open = true` constructor option on shapes instead\n\nThis method is only available on a type checker generated by `types.shape`.\n\nReturns a new shape type checker that won't fail if there are extra fields not\nspecified.\n\n#### `type:on_repair(func)`\n\nAn alias for the transform pattern:\n\n```lua\ntype + types.any / func * type\n```\n\nIn English, this will let a value that matches `type` pass through, otherwise\nfor anything else call `func(value)` and let the return value pass through if\nit matches `type`, otherwise fail.\n\n## Changelog\n\n**Jan 25 2021** - 2.2.0\n\n* Fixed bug where state could be overidden when tagging in `array_contains`\n* Expose (and add docs for) for `types.proxy`\n* Add experimental `Annotated` type\n* Update test suite to GitHub Actions\n\n**Oct 19 2019** - 2.1.0\n\n* Add `types.partial` alias for open shape\n* Add `types.array_contains`\n* Add `not` type, and unary minus operator\n* Add MoonScript module: `class_type`, `instance_type`, `instance_type` checkers\n\n**Aug 09 2018** - 2.0.0\n\n* Add overloaded operators to compose types\n* Add transformation interface\n* Add support for tagging\n* Add `state` parameter that's passed through type checks\n* Replace repair interface with simple transform\n* Error messages will never re-output the value\n* Type objects have a new interface to describe their shape\n\n**Feb 10 2016** - 1.2.1\n\n* Fix bug where literal fields with no dot operator could not be checked\n* Better failure message when field doesn't match literal value\n* Add `types.nil`\n\n**Feb 04 2016** - 1.2.0\n\n* Add the repair interface\n\n**Jan 25 2016** - 1.1.0\n\n* Add `types.map_of`\n* Add `types.any`\n\n**Jan 24 2016**\n\n* Initial release\n\n## License (MIT)\n\nCopyright (C) 2022 by Leaf Corcoran\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafo%2Ftableshape","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleafo%2Ftableshape","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafo%2Ftableshape/lists"}