Skip to main content

UnionSchema

Accepts a value that matches any one of several schemas. Options are tried in order and the first that succeeds decides the result, so when two options overlap the earlier one wins — put the narrower option first if you want it to be the one that parses.

local Id = v.union({ v.string():min(1), v.number():integer() })

Id:parse("player_1") --> "player_1"
Id:parse(7)          --> 7
Id:parse(true)       --> errors

A tagged shape falls out of combining LiteralSchema with ObjectSchema, which is how a union of message kinds is usually written. Annotate each tag as a singleton, since Vet.literal otherwise widens it to string and the arms become indistinguishable to Luau:

local Join: v.LiteralSchema<"join"> = v.literal("join")
local Chat: v.LiteralSchema<"chat"> = v.literal("chat")

local Event = v.union({
	v.object({ kind = Join, userId = v.number() }),
	v.object({ kind = Chat, text = v.string() }),
})

local event = Event:parse(payload)

if event.kind == "chat" then
	print(event.text) -- narrowed to the chat arm
end

Nothing matching is a failure of the union as a whole rather than of any one option, so the issue sits at the union's own path and its message names each option and why that option rejected the value.

Properties​

options​

This item is read only and cannot be modified. Read OnlyReflection
UnionSchema.options: {AnySchema<any>}

The schemas this union was built from, in the order they are tried:

local Id = v.union({ v.string(), v.number() })

#Id.options             --> 2
Id.options[1]:parse("") --> ""

The table is a frozen copy of the one passed to Vet.union, so mutating it fails loudly rather than corrupting the schema.

isOptional​

This item is read only and cannot be modified. Read OnlyReflection
UnionSchema.isOptional: boolean

true when any option accepts nil, since then the union does too. A DefaultSchema option does not count: it accepts a missing value but fills one in, so the union still never produces nil.

access​

This item is read only and cannot be modified. Read OnlyReflection
UnionSchema.access: "readwrite"

Always "readwrite", whatever the options carry. An option's marker says how that option would sit in an object, not how the union does.

Functions​

safeParse​

Parsing
UnionSchema:safeParse(value: unknown) → Result<T>

Tries each option in order and returns what the first match parsed to. When none match, the single issue reports every option's rejection.

parse​

Parsing
UnionSchema:parse(value: unknown) → T

Validates value and returns it, throwing on failure.

Errors

TypeDescription
"expected one of ..."Thrown when no option accepts `value`

optional​

Wrapper
UnionSchema:optional() → OptionalSchema<T>

Wraps this schema so that nil is also accepted.

default​

Wrapper
UnionSchema:default(
fallback: T--

Value used when parsing nil

) → DefaultSchema<T>

Wraps this schema so that nil yields fallback instead of failing. Prefer passing a function when the fallback is a table, since a constant one would be shared by every parse.

defaultWith​

Wrapper
UnionSchema:defaultWith(
produce: () → T--

Builds the value used when parsing nil

) → DefaultSchema<T>

Like :default, but calls produce on every parse instead of reusing one value.

The produced value is validated each time.

readonly​

Wrapper
UnionSchema:readonly() → ReadonlySchema<T>

Wraps this schema so that a field using it is typed read.

writeonly​

Wrapper
UnionSchema:writeonly() → WriteonlySchema<T>

Wraps this schema so that a field using it is typed write.

Show raw api
{
    "functions": [
        {
            "name": "safeParse",
            "desc": "Tries each option in order and returns what the first match parsed to.\nWhen none match, the single issue reports every option's rejection.\n\t",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "unknown"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Result<T>"
                }
            ],
            "function_type": "method",
            "tags": [
                "Parsing"
            ],
            "source": {
                "line": 3210,
                "path": "src/init.luau"
            }
        },
        {
            "name": "parse",
            "desc": "Validates `value` and returns it, throwing on failure.\n\t",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "unknown"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "T"
                }
            ],
            "function_type": "method",
            "tags": [
                "Parsing"
            ],
            "errors": [
                {
                    "lua_type": "\"expected one of ...\"",
                    "desc": "Thrown when no option accepts `value`"
                }
            ],
            "source": {
                "line": 3266,
                "path": "src/init.luau"
            }
        },
        {
            "name": "optional",
            "desc": "Wraps this schema so that `nil` is also accepted.\n\t",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "OptionalSchema<T>"
                }
            ],
            "function_type": "method",
            "tags": [
                "Wrapper"
            ],
            "source": {
                "line": 3284,
                "path": "src/init.luau"
            }
        },
        {
            "name": "default",
            "desc": "Wraps this schema so that `nil` yields `fallback` instead of failing.\nPrefer passing a function when the fallback is a table, since a constant\none would be shared by every parse.\n\t",
            "params": [
                {
                    "name": "fallback",
                    "desc": "Value used when parsing `nil`",
                    "lua_type": "T"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "DefaultSchema<T>"
                }
            ],
            "function_type": "method",
            "tags": [
                "Wrapper"
            ],
            "source": {
                "line": 3301,
                "path": "src/init.luau"
            }
        },
        {
            "name": "defaultWith",
            "desc": "Like `:default`, but calls `produce` on every parse instead of reusing\none value.\n\nThe produced value is validated each time.\n\t",
            "params": [
                {
                    "name": "produce",
                    "desc": "Builds the value used when parsing `nil`",
                    "lua_type": "() -> T"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "DefaultSchema<T>"
                }
            ],
            "function_type": "method",
            "tags": [
                "Wrapper"
            ],
            "source": {
                "line": 3321,
                "path": "src/init.luau"
            }
        },
        {
            "name": "readonly",
            "desc": "Wraps this schema so that a field using it is typed `read`.\n\t",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "ReadonlySchema<T>"
                }
            ],
            "function_type": "method",
            "tags": [
                "Wrapper"
            ],
            "source": {
                "line": 3335,
                "path": "src/init.luau"
            }
        },
        {
            "name": "writeonly",
            "desc": "Wraps this schema so that a field using it is typed `write`.\n\t",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "WriteonlySchema<T>"
                }
            ],
            "function_type": "method",
            "tags": [
                "Wrapper"
            ],
            "source": {
                "line": 3349,
                "path": "src/init.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "options",
            "desc": "The schemas this union was built from, in the order they are tried:\n\n```lua\nlocal Id = v.union({ v.string(), v.number() })\n\n#Id.options             --> 2\nId.options[1]:parse(\"\") --> \"\"\n```\n\nThe table is a frozen copy of the one passed to [Vet.union], so mutating\nit fails loudly rather than corrupting the schema.\n\t",
            "lua_type": "{ AnySchema<any> }",
            "tags": [
                "Reflection"
            ],
            "readonly": true,
            "source": {
                "line": 3164,
                "path": "src/init.luau"
            }
        },
        {
            "name": "isOptional",
            "desc": "`true` when any option accepts `nil`, since then the union does too.\nA [DefaultSchema] option does not count: it accepts a missing value but\nfills one in, so the union still never produces `nil`.\n\t",
            "lua_type": "boolean",
            "tags": [
                "Reflection"
            ],
            "readonly": true,
            "source": {
                "line": 3178,
                "path": "src/init.luau"
            }
        },
        {
            "name": "access",
            "desc": "Always `\"readwrite\"`, whatever the options carry. An option's marker\nsays how that option would sit in an object, not how the union does.\n\t",
            "lua_type": "\"readwrite\"",
            "tags": [
                "Reflection"
            ],
            "readonly": true,
            "source": {
                "line": 3198,
                "path": "src/init.luau"
            }
        }
    ],
    "types": [],
    "name": "UnionSchema",
    "desc": "Accepts a value that matches any one of several schemas. Options are tried\nin order and the first that succeeds decides the result, so when two options\noverlap the earlier one wins — put the narrower option first if you want it\nto be the one that parses.\n\n```lua\nlocal Id = v.union({ v.string():min(1), v.number():integer() })\n\nId:parse(\"player_1\") --> \"player_1\"\nId:parse(7)          --> 7\nId:parse(true)       --> errors\n```\n\nA tagged shape falls out of combining [LiteralSchema] with [ObjectSchema],\nwhich is how a union of message kinds is usually written. Annotate each tag\nas a singleton, since [Vet.literal] otherwise widens it to `string` and the\narms become indistinguishable to Luau:\n\n```lua\nlocal Join: v.LiteralSchema<\"join\"> = v.literal(\"join\")\nlocal Chat: v.LiteralSchema<\"chat\"> = v.literal(\"chat\")\n\nlocal Event = v.union({\n\tv.object({ kind = Join, userId = v.number() }),\n\tv.object({ kind = Chat, text = v.string() }),\n})\n\nlocal event = Event:parse(payload)\n\nif event.kind == \"chat\" then\n\tprint(event.text) -- narrowed to the chat arm\nend\n```\n\nNothing matching is a failure of the union as a whole rather than of any one\noption, so the issue sits at the union's own path and its message names each\noption and why that option rejected the value.",
    "source": {
        "line": 3124,
        "path": "src/init.luau"
    }
}