tableshape

验证和转换 Lua 表格的结构

$ opm get leafo/tableshape

tableshape

!测试

一个用于验证表格形状(模式、结构等)并根据需要进行转换的 Lua 库。类型检查语法受到 [React 的 PropTypes 模块](https://fbdocs.cn/react/docs/reusable-components.html#prop-validation) 的启发。复杂类型和值转换可以使用类似于 LPeg 的运算符重载语法来表达。

安装

    $ luarocks install tableshape

快速使用

    local types = require("tableshape").types
    
    -- define the shape of our player object
    local player_shape = types.shape{
      class = types.one_of{"player", "enemy"},
      name = types.string,
      position = types.shape{
        x = types.number,
        y = types.number,
      },
      inventory = types.array_of(types.shape{
        name = types.string,
        id = types.integer
      }):is_optional()
    }
    
    
    
    -- create a valid object to test the shape with
    local player = {
      class = "player",
      name = "Lee",
      position = {
        x = 2.8,
        y = 8.5
      },
    }
    
    -- verify that it matches the shape
    assert(player_shape(player))
    
    -- let's break the shape to see the error message:
    player.position.x = "heck"
    assert(player_shape(player))
    
    -- error: field `position`: field `x`: got type `string`, expected `number`

转换

可以通过使用转换运算符和方法将格式错误的值修复为预期形状。输入值会在返回之前被克隆和修改。

    local types = require("tableshape").types
    
    -- a type checker that will coerce a value into a number from a string or return 0
    local number = types.number + types.string / tonumber + types.any / 0
    
    number:transform(5) --> 5
    number:transform("500") --> 500
    number:transform("hi") --> 0
    number:transform({}) --> 0

因为类型检查器是可组合的对象,所以我们可以用我们已经编写的现有类型构建更复杂的类型。

    -- here we reference our transforming number type from above
    local coordinate = types.shape {
      x = number,
      y = number
    }
    
    -- a compound type checker that can fill in missing values
    local player_shape = types.shape({
      name = types.string + types.any / "unknown",
      position = coordinate
    })
    
    local bad_player = {
      position = {
        x = "234",
        y = false
      }
    }
    
    local fixed_player = player_shape:transform(bad_player)
    
    -- fixed_player --> {
    --   name = "unknown",
    --   position = {
    --     x = 234,
    --     y = 0
    --   }
    -- }

教程

要加载库,请使用 require。库中最重要的部分是 types 表格,它将为您提供访问所有类型检查器的权限。

    local types = require("tableshape").types

您可以使用 types 表格来检查简单值的类型,而不仅仅是表格。像函数一样调用类型检查器将测试一个值以查看它是否匹配形状或类型。如果匹配,它将返回 true,否则返回 nil 和错误消息。(这是通过 __call 元方法完成的,您也可以直接使用 check_value 方法)

    types.string("hello!") --> true
    types.string(777)      --> nil, expected type "string", got "number"

您可以在下面的参考中查看可用类型的完整列表。

tableshape 的真正强大之处在于能够通过嵌套类型检查器来描述复杂类型。

这里我们使用 array_of 测试数字数组。

    local numbers_shape = types.array_of(types.number)
    
    assert(numbers_shape({1,2,3}))
    
    -- error: item 2 in array does not match: got type `string`, expected `number`
    assert(numbers_shape({1,"oops",3}))

> 注意:类型检查很严格,看起来像数字的字符串,例如 "123",不是数字,会触发错误!

通用表格的结构可以使用 types.shape 进行测试。它接受一个映射表格,其中键是要检查的字段,值是类型检查器。

    local object_shape = types.shape{
      id = types.number,
      name = types.string:is_optional(),
    }
    
    -- success
    assert(object_shape({
      id = 1234,
      name = "hello world"
    }))
    
    -- sucess, optional field is not there
    assert(object_shape({
      id = 1235,
    }))
    
    
    -- error: field `id`: got type `nil`, expected `number`
    assert(object_shape({
      name = 424,
    }))

is_optional 方法可以应用于任何类型检查器,以返回一个新的类型检查器,该检查器还可以接受 nil 作为值。(它等效于 t + types['nil']

如果形状中的多个字段类型检查失败,错误消息将包含所有失败的字段。

您还可以使用字面量值直接匹配它:(这等效于使用 types.literal(v)

    local object_shape = types.shape{
      name = "Cowcat"
    }
    
    -- error: field `name` expected `Cowcat`
    assert(object_shape({
      name = "Cowdog"
    }))

one_of 类型构造函数允许您指定一个类型列表,如果其中一个类型匹配,则将成功。(它与 + 运算符的工作方式相同)

    local func_or_bool = types.one_of { types.func, types.boolean }
    
    assert(func_or_bool(function() end))
    
    -- error: expected type "function", or type "boolean"
    assert(func_or_bool(2345))

它也可以与字面量值一起使用。

    local limbs = types.one_of{"foot", "arm"}
    
    assert(limbs("foot")) -- success
    assert(limbs("arm")) -- success
    
    -- error: expected "foot", or "arm"
    assert(limbs("baseball"))

pattern 类型可用于使用 Lua 模式测试字符串。

    local no_spaces = types.pattern "^[^%s]*$"
    
    assert(no_spaces("hello!"))
    
    -- error: doesn't match pattern `^[^%s]*$`
    assert(no_spaces("oh no!"))

这些示例仅演示了一些提供的类型检查器。您可以在下面的参考中查看所有其他类型检查器。

类型运算符

类型检查器对象重载了 *+/ 运算符,以提供一种快速创建复合类型的方法。

  • *全部(与)运算符,两个操作数都必须匹配。

  • +第一个(或)运算符,从左到右对操作数进行值检查。

  • /转换运算符,当使用 transform 方法时,值将被运算符右侧的内容转换。

  • %带状态的转换运算符,与转换相同,但状态作为第二个参数传递。

“全部”运算符

全部运算符检查值是否匹配多个类型。类型从左到右检查,类型检查将在第一次检查失败时中止。它的工作方式与 types.all_of 相同。

    local s = types.pattern("^hello") * types.pattern("world$")
    
    s("hello 777 world")   --> true
    s("good work")         --> nil, "doesn't match pattern `^hello`"
    s("hello, umm worldz") --> nil, "doesn't match pattern `world$`"

“第一个”运算符

第一个运算符检查值是否匹配多种类型中的一种。类型从左到右检查,类型检查将在第一个匹配类型时成功。它的工作方式与 types.one_of 相同。

一旦匹配了一个类型,就不会再检查其他类型。如果您首先使用贪婪类型,例如 types.any,那么它将不会检查任何其他类型。如果您后续的类型有任何副作用,例如转换或标记,则需要了解这一点。

    local s = types.number + types.string
    
    s(44)            --> true
    s("hello world") --> true
    s(true)          --> nil, "no matching option (got type `boolean`, expected `number`; got type `boolean`, expected `string`)"

“转换”运算符

在类型匹配模式下,转换运算符无效。但是,当使用 transform 方法时,值将由回调修改或更改为固定值。

使用以下语法:type / transform_callback --> transformable_type

    local t = types.string + types.any / "unknown"

前面的类型可以理解为:“匹配任何字符串,或者对于任何其他类型,将其转换为字符串 'unknown'”。

    t:transform("hello") --> "hello"
    t:transform(5)       --> "unknown"

因为此类型检查器使用 types.any,所以它将通过传递给它的任何值。转换类型也可能失败,以下是一个示例。

    local n = types.number + types.string / tonumber
    
    n:transform("5") --> 5
    n:transform({})  --> nil, "no matching option (got type `table`, expected `number`; got type `table`, expected `string`)"

转换回调可以是函数或字面量值。如果使用函数,则使用正在转换的当前值调用该函数,并且应返回转换的结果。如果使用字面量值,则转换始终将值转换为指定的值。

转换函数不是谓词,不能直接导致类型检查失败。返回 nil 是有效的,并将值更改为 nil。如果您希望根据函数失败,可以使用 custom 类型或在转换后链接另一个类型检查器。

    -- this will fail unless `tonumber` returns a number
    local t = (types.string / tonumber) * types.number
    t:transform("nothing") --> nil, "got type `nil`, expected `number`"

修复对象的常见模式包括测试您知道如何修复的类型,然后是 + types.any,然后是您想要的最终类型的类型检查。

这里我们尝试将值修复为 x,y 坐标的预期格式。

    local types = require("tableshape").types
    
    local str_to_coord = types.string / function(str)
      local x,y = str:match("(%d+)[^%d]+(%d+)")
      if not x then return end
      return {
        x = tonumber(x),
        y = tonumber(y)
      }
    end
    
    local array_to_coord = types.shape{types.number, types.number} / function(a)
      return {
        x = a[1],
        y = a[2]
      }
    end
    
    local cord = (str_to_coord + array_to_coord + types.any) * types.shape {
      x = types.number,
      y = types.number
    }
    
    cord:transform("100,200")        --> { x = 100, y = 200}
    cord:transform({5, 23})          --> { x = 5, y = 23}
    cord:transform({ x = 9, y = 10}) --> { x = 9, y = 10}

标记

标记可用于在检查类型时从类型中提取值。仅当它包装的类型匹配时才保存标记。如果一个标记类型包装了一个转换值的类型检查器,则该标记将存储转换的结果。

    local t = types.shape {
      a = types.number:tag("x"),
      b = types.number:tag("y"),
    } + types.shape {
      types.number:tag("x"),
      types.number:tag("y"),
    }
    
    t({1,2})          --> { x = 1, y = 2}
    t({a = 3, b = 9}) --> { x = 3, y = 9}

标记捕获的值存储在 state 对象中,该对象是一个在整个类型检查过程中传递的表格。当调用类型检查时,如果成功,如果使用了任何状态(通过标记或下面列出的任何状态 API),返回值将是状态对象。如果未使用任何状态,则在检查成功时返回 true

如果标记名称以 "[]" 结尾(例如 "items[]"),则重复使用标记名称会导致每个值累积到数组中。否则,重复使用标记名称会导致该名称的值被覆盖。

范围

您可以使用范围嵌套状态对象(包括标记的结果)。可以使用 types.scope 创建范围。范围的工作原理是将新状态推送到状态栈上。范围完成后,它将分配给指定标记名称的前一个范围。

    local obj = types.shape {
      id = types.string:tag("name"),
      age = types.number
    }
    
    local many = types.array_of(types.scope(obj, { tag = "results[]"}))
    
    many({
      { id = "leaf", age = 2000 },
      { id = "amos", age = 15 }
    }) --> { results = {name = "leaf"}, {name = "amos"}}

> 注意:在此示例中,我们在标记名称中使用特殊的 [] 语法来累积 > 所有标记到数组中的值。如果省略了 [],则每个 > 标记的值将覆盖前一个值。

如果省略了 types.scope 的标记,则将创建一个匿名范围。匿名范围在范围退出后将被丢弃。如果您将状态用于本地转换并且不需要这些值影响封闭状态对象,则此样式很有用。

转换

类型对象上的 transform 方法是一种特殊的调用类型检查的方法,它允许在类型检查过程中将值更改为其他内容。这对于修复或规范化输入到预期形状很有用。

转换值的简单方法是使用转换运算符 /

例如,我们可以为 URL 创建一个类型检查器,它要么接受有效的 URL,要么将任何其他字符串转换为有效的 URL。

    local url_shape = types.pattern("^https?://") + types.string / function(val)
      return "http://" .. val
    end


    url_shape:transform("https://itch.io") --> https://itch.io
    url_shape:transform("leafo.net")       --> http://leafo.net
    url_shape:transform({})                --> nil, "no matching option (expected string for value; got type `table`)"

我们可以组合可转换的类型检查器。现在我们知道了如何修复 URL,我们可以修复 URL 数组。

    local urls_array = types.array_of(url_shape + types.any / nil)
    
    local fixed_urls = urls_array:transform({
      "https://itch.io",
      "leafo.net",
      {}
      "www.streak.club",
    })
    
    -- will return:
    -- {
    --   "https://itch.io",
    --   "http://leafo.net",
    --   "http://www.streak.club"
    -- }

array_of 类型的 transform 方法将转换数组的每个值。array_of 转换的一个特殊属性是排除最终输出中变为 nil 的任何值。您可以使用它来过滤掉任何不良数据,而不会在数组中出现空洞。(您可以使用 keep_nils 选项覆盖此功能。

请注意,我们在 URL 形状之后添加了 types.any / nil 备选方案。这将确保任何无法识别的值都将转换为 nil,以便可以从 array_of 形状中过滤掉它们。如果没有包含此项,则 URL 形状将在无效值上失败,并且整个转换将中止。

转换和可变对象

在处理表格等可变对象时编写转换函数时必须格外小心。您永远不应该修改对象,而是应该克隆它,进行更改,然后返回新对象。

因为类型可以深度嵌套,所以转换可能会在值上被调用,但类型检查后来失败了。如果您修改了输入值,则无法撤消该更改,并且您创建了一个可能破坏程序的副作用。

永远不要这样做。

    local types = require("tableshape").types
    
    -- WARNING: READ CAREFULLY
    local add_id = types.table / function(t)
      -- NEVER DO THIS
      t.id = 100
      -- I repeat, don't do what's in the line above
      return t
    end
    
    -- This is why, imagine we create a new compund type:
    
    local array_of_add_id = types.array_of(add_id)
    
    -- And now we pass in the following malformed object:
    
    local items = {
      { entry = 1},
      "entry2",
      { entry = 3},
    }
    
    
    -- And attempt to verify it by transforming it:
    
    local result,err = array_of_add_id:transform(items)
    
    -- This will fail because there's an value in items that will fail validation for
    -- add_id. Since types are processed incrementally, the first entry would have
    -- been permanently changed by the transformation. Even though the check failed,
    -- the data is partially modified and may result in a hard-to-catch bug.
    
    print items[1] --> = { id = 100, entry = 1}
    print items[3] --> = { entry = 3}

幸运的是,tableshape 提供了一个旨在克隆对象的辅助类型 types.clone。以下是编写转换的正确方法。

    local types = require("tableshape").types
    
    local add_id = types.table / function(t)
      local new_t = assert(types.clone:transform(t))
      new_t.id = 100
      return new_t
    end

> 仅供高级用户使用:由于 types.clone 本身就是一个类型,因此您可以在 > 任何您可能需要确保突变不会 > 导致副作用在类型验证期间持续存在的 函数之前链接它:types.table * types.clone / my_messy_function

如果任何嵌套类型具有返回新值的转换,则操作对象的内置复合类型将自动克隆对象。这包括复合类型构造函数,例如 types.shapetypes.array_oftypes.map_of 等。仅当使用自定义转换函数时,您才需要小心突变。

参考

    local types = require("tableshape").types

类型构造函数

类型构造函数构建一个由您传递的参数配置的类型检查器。以下列出了所有可用的构造函数,完整文档如下所示。

  • types.shape - 检查表格的形状

  • types.partial - 开放 types.shape 的简写

  • types.one_of - 检查值是否匹配提供的类型之一

  • types.pattern - 检查 Lua 模式是否匹配值

  • types.array_of - 检查值是否是包含特定类型的数组

  • types.array_contains - 检查值是否是包含特定类型的数组(默认情况下短路)

  • types.map_of - 检查值是否是键和值类型都匹配的表格

  • types.literal - 使用 == 检查值是否匹配提供的数值

  • types.custom - 允许您提供一个函数来检查类型

  • types.equivalent - 检查值是否深度比较相等

  • types.range - 检查值是否在两个其他值之间

  • types.proxy - 动态加载类型检查器

types.shape(table_dec, options={})

返回一个类型检查器,用于测试表格,其中 table_dec 中的每个键都具有与关联值匹配的类型。关联值也可以是字面量值。

    local t = types.shape{
      category = "object", -- matches the literal value `"object"`
      id = types.number,
      name = types.string
    }

支持以下选项

  • open — 形状将接受任何其他字段而不会失败

  • extra_fields — 用于额外键的类型检查器。对于表中的每个额外字段,值 {key = value} 将传递给 extra_fields 类型检查器。在转换过程中,可以转换表以更改键或值。返回 nil 的转换器将清除该字段。请参见下面的示例。额外键的形状也可以使用标签。

extra_fields 示例

额外字段的基本类型测试

    local t = types.shape({
      name = types.string
    }, {
      extra_fields = types.map_of(types.string, types.number)
    })
    
    t({
      name = "lee",
      height = "10cm",
      friendly = false,
    }) --> nil, "field `height` value in table does not match: got type `string`, expected `number`"
    

extra_fields 也可以使用转换。在此示例中,所有额外字段都将被移除

    local t = types.shape({
      name = types.string
    }, {
      extra_fields = types.any / nil
    })
    
    t:transform({
      name = "amos",
      color = "blue",
      1,2,3
    }) --> { name = "amos"}

使用转换修改额外键

    local types = require("tableshape").types
    
    local t = types.shape({
      name = types.string
    }, {
      extra_fields = types.map_of(
        -- prefix all extra keys with _
        types.string / function(str) return "_" .. str end,
    
        -- leave values as is
        types.any
      )
    })
    
    t:transform({
      name = "amos",
      color = "blue"
    }) --> { name = "amos", _color = "blue" }

types.partial(table_dec, options={})

types.shape 相同,但默认情况下将 open = true。添加此别名函数是因为在使用 tableshape 时,开放形状对象很常见。

    local types = require("tableshape").types
    
    local t = types.partial {
      name = types.string\tag "player_name"
    }
    
    t({
      t: "character"
      name: "Good Friend"
    }) --> { player_name: "Good Friend" }

types.array_of(item_type, options={})

返回一个类型检查器,用于测试值是否为数组,其中每个项目都与提供的类型匹配。

    local t = types.array_of(types.shape{
      id = types.number
    })

支持以下选项

  • keep_nils — 默认情况下,如果某个值被转换为 nil,则它不会保留在输出数组中。如果需要保留这些空洞,则将此选项设置为 true

  • length — 提供一个用于数组长度的类型检查器。长度使用 # 运算符计算。通常使用 types.range 来测试范围

types.array_contains(item_type, options={})

返回一个类型检查器,用于测试 item_type 是否存在于数组中。默认情况下,启用 short_circuit。它将搜索直到找到数组中 item_type 的第一个实例,然后停止并成功。这会影响转换类型,因为默认情况下只有第一个匹配项会被转换。要处理数组中的每个条目,请在选项中将 short_circuit = false

    local t = types.array_contains(types.number)
    
    t({"one", "two", 3, "four"}) --> true
    t({"hello", true}) --> fails

支持以下选项

  • short_circuit — (默认值 true) 如果找到单个匹配项,则停止扫描数组

  • keep_nils — 默认情况下,如果某个值被转换为 nil,则它不会保留在输出数组中。如果需要保留这些空洞,则将此选项设置为 true

types.map_of(key_type, value_type)

返回一个类型检查器,用于测试表,其中每个键和值都与作为参数提供的相应类型检查器匹配。

    local t = types.map_of(types.string, types.any)

在转换 map_of 时,可以通过将键或值转换为 nil 来从表中删除字段。

    -- this will remove all fields with non-string keys
    local t = types.map_of(types.string + types.any / nil, types.any)
    
    t:transform({
      1,2,3,
      hello = "world"
    }) --> { hello = "world" }

types.one_of({type1, type2, ...})

返回一个类型检查器,用于测试值是否与提供的类型之一匹配。文字值也可以作为类型传递。

    local t = types.one_of{"none", types.number}

types.pattern(lua_pattern)

返回一个类型检查器,用于测试字符串是否与提供的 Lua 模式匹配

    local t = types.pattern("^#[a-fA-F%d]+$")

types.literal(value)

返回一个类型检查器,用于检查值是否等于提供的那个值。在使用 shape 时,这通常是不必要的,因为非类型检查器值将使用 == 进行文字检查。这允许您将修复函数附加到文字检查。

    local t = types.literal "hello world"
    assert(t("hello world") == true)
    assert(t("jello world") == false)

types.custom(fn)

返回一个类型检查器,该检查器调用提供的函数来验证值。该函数将接收被测试的值作为第一个参数,并将类型检查器作为第二个参数。

如果值通过,则该函数应返回 true;如果失败,则返回 nil 和错误消息。

    local is_even = types.custom(function(val)
      if type(val) == "number" then
        if val % 2 == 0 then
          return true
        else
          return nil, "number is not even"
        end
      else
        return nil, "expected number"
      end
    end)

types.equivalent(val)

返回一个类型检查器,它将在 val 和输入之间进行深度比较。

    local t = types.equivalent {
      color = {255,100,128},
      name = "leaf"
    }
    
    -- although we're testing a different instance of the table, the structure is
    -- the same so it passes
    t {
      name = "leaf"
      color = {255,100,128},
    } --> true
    

types.range(left, right)

创建一个类型检查器,用于检查值是否在 leftright(包含)之间。在进行比较之前会检查值的类型:将字符串传递给数值类型检查器将提前失败。

    local nums = types.range 1, 20
    local letters = types.range "a", "f"
    
    nums(4)    --> true
    letters("c")  --> true
    letters("n")  --> true

此检查器与字符串和数组的长度检查配合使用效果很好。

types.proxy(fn)

代理类型检查器将在被调用时执行提供的函数 fn,并将返回值用作类型检查器。fn 函数必须返回一个有效的 tableshape 类型检查器对象。

这可用于具有循环依赖关系的类型或处理递归类型。每次代理检查值时都会调用 fn,如果要优化性能,则您负责缓存返回的类型检查器。

递归类型检查器示例

    local entity_type = types.shape {
      name = types.string,
      child = types['nil'] + types.proxy(function() return entity_type end)
    }

上面需要代理,因为在构造类型检查器时 entity_type 的值为 nil。通过使用代理,我们可以创建一个到最终将保存 entity_type 检查器的变量的闭包。

内置类型

内置类型可以直接使用,无需构造。

  • types.string - 检查 type(val) == "string"

  • types.number - 检查 type(val) == "number"

  • types['function'] - 检查 type(val) == "function"

  • types.func - types['function'] 的别名

  • types.boolean - 检查 type(val) == "boolean"

  • types.userdata - 检查 type(val) == "userdata"

  • types.table - 检查 type(val) == "table"

  • types['nil'] - 检查 type(val) == "nil"

  • types.null - types['nil'] 的别名

  • types.array - 检查具有按数字递增索引的表

  • types.integer - 检查没有小数部分的数字

  • types.clone - 创建输入的浅拷贝,如果值不可克隆(例如 userdata、函数),则失败

此外还有特殊的 any 类型

  • types.any - 无论传递什么值(包括 nil)都成功

类型方法

每个类型检查器都具有以下方法

type(value)type:check_value(value)

调用 check_value 等效于将类型检查器对象像函数一样调用。所有类型检查器对象上都提供了 __call 元方法,允许您通过将它们视为函数来轻松测试值。

根据类型检查器测试 value。如果值通过检查,则返回 true(或当前状态对象)。如果存在不匹配,则返回 nil 和错误消息(作为字符串)。错误消息将尽可能识别不匹配发生的位置。

check_value 将在找到第一个错误时中止,并且只返回该错误消息。

> 注意:在幕后,检查值将始终执行完整的转换,但结果对象会被丢弃,并且只返回状态。请记住这一点,因为调用 check_value 相比于 transform 没有任何性能优势

type:transform(value, initial_state=nil)

将使用提供的类型对 value 应用转换。如果类型不包含任何转换,则假设它与类型检查匹配,将返回相同的对象。如果发生转换,则将返回一个新对象,其中所有其他字段都将被复制。

> 您可以使用转换运算符/)来指定值的转换方式。

可以可选地为初始状态提供第二个参数。这应该是一个 Lua 表格。

如果没有提供状态,如果任何类型转换对状态进行更改,则将自动创建一个空的 Lua 表格。

> 状态对象用于存储任何标记类型的结果。状态对象还可以用于在整个类型检查器中存储数据,以便在使用自定义状态运算符和类型时实现更高级的功能。

    local t = types.number + types.string / tonumber
    
    t:transform(10) --> 10
    t:transform("15") --> 15

成功时,此方法将返回结果值和结果状态。如果未使用状态,则不会返回状态。失败时,该方法将返回 nil 和字符串错误消息。

type:repair(value)

> 此方法已弃用,请改用 type:transform

type:transform(value) 的别名

type:is_optional()

返回一个匹配相同类型的新类型检查器,或 nil。这实际上与使用表达式相同

    local optional_my_type = types["nil"] + my_type
    ````
    
    Internally, though, `is_optional` creates new *OptionalType* node in the type
    hierarchy to make printing summaries and error messages more clear.
    
    #### `type:describe(description)`
    
    Returns a wrapped type checker that will use `description` to describe the type
    when an error message is returned. `description` can either be a string
    literal, or a function. When using a function, it must return the description
    of the type as a string.
    
    
    #### `type:tag(name_or_fn)`
    
    Causes the type checker to save matched values into the state object. If
    `name_or_fn` is a string, then the tested value is stored into the state with
    key `name_or_fn`.
    
    If `name_or_fn` is a function, then you provide a callback to control how the
    state is updated. The function takes as arguments the state object and the
    value that matched:
    
    ```lua
    -- an example tag function that accumulates an array
    types.number:tag(function(state, value)
      -- nested objects should be treated as read only, so modifications are done to a copy
      if state.numbers then
        state.numbers = { unpack state.numbers }
      else
        state.numbers = { }
      end
    
      table.insert(state.numbers, value)
    end)

> 这是一个说明性示例。如果需要累积值的列表,则使用标签名称的 [] 语法。

您可以使用任何更改来更改 state 参数。此函数的返回值被忽略。

请注意,状态对象通常是不可变的。每当发生状态修改操作时,修改都会对状态对象的副本进行。这是为了防止在测试失败的类型时保留对状态对象的更改。

function 标签会获取当前状态的副本作为其第一个参数,以便进行编辑。该副本是浅拷贝。如果存在任何嵌套对象,则需要在进行任何修改之前克隆它们,如上例所示。

type:scope(name)

在堆栈顶部推送一个新的状态对象。在范围类型匹配后,它创建的状态将使用键 name 分配给先前的范围。

它等效于使用 types.scope 构造函数,如下所示

    -- The following two lines are equivalent
    type:scope(name)                  --> scoped type
    types.scope(type, { tag = name }) --> scoped type

shape_type:is_open()

> 此方法已弃用,请改用形状上的 open = true 构造函数选项

此方法仅适用于 types.shape 生成的类型检查器。

返回一个新的形状类型检查器,如果存在未指定的额外字段,则不会失败。

type:on_repair(func)

转换模式的别名

    type + types.any / func * type

用英语来说,这将允许与 type 匹配的值通过,否则对于任何其他值,调用 func(value) 并让返回值通过(如果它与 type 匹配),否则失败。

变更日志

2021年1月25日 - 2.2.0

  • 修复了在 array_contains 中标记时状态可能被覆盖的错误

  • 公开(并添加 types.proxy 的文档)

  • 添加实验性 Annotated 类型

  • 将测试套件更新到 GitHub Actions

2019年10月19日 - 2.1.0

  • 为开放形状添加 types.partial 别名

  • 添加 types.array_contains

  • 添加 not 类型和一元减运算符

  • 添加 MoonScript 模块:class_typeinstance_typeinstance_type 检查器

2018年8月9日 - 2.0.0

  • 添加重载运算符以组合类型

  • 添加转换接口

  • 添加对标记的支持

  • 添加通过类型检查传递的 state 参数

  • 用简单的转换替换修复接口

  • 错误消息将永远不会重新输出值

  • 类型对象具有描述其形状的新接口

2016年2月10日 - 1.2.1

  • 修复了没有点运算符的文字字段无法检查的错误

  • 当字段与文字值不匹配时,提供更好的错误消息

  • 添加 types.nil

2016年2月4日 - 1.2.0

  • 添加修复接口

2016年1月25日 - 1.1.0

  • 添加 types.map_of

  • 添加 types.any

2016年1月24日

  • 初始版本

许可证 (MIT)

版权所有 (C) 2022 Leaf Corcoran

特此免费授予获得此软件和相关文档文件(“软件”)副本的任何人无限制地使用软件的权利,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本的权利,并允许软件被提供给的人员这样做,但须符合以下条件

上述版权声明和本许可声明应包含在所有副本或实质部分的软件中。

软件“按原样”提供,没有任何形式的明示或暗示保证,包括但不限于适销性、特定用途的适用性和不侵权的保证。在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是在合同、侵权或其他方面产生的、源于或与软件或软件的使用或其他交易有关。

作者

Leaf Corcoran (leafo)

许可证

mit

版本