Forest Logo
search
package_2

schema

By @post-ill

Roblox

Mirrored

schema

schema is a minimal, highly composable runtime type checker for Roblox.

Aim

  • Clean implementation: Everything results in a function
  • Fast ship: The library is a single file at src/schema.luau
  • Readable API: The developer can figure out what each schema is about by just looking at its name

Installation

schema is available on wally, so on pesde. You can also just go to the latest release, pick the script and drop it into your project ;)

Usage

An schema is simply a function that expects a single argument and returns an error message in case something bad happens.

local s = require(path.to.schema)

local yourType = s.whatever
local err = yourType(yourValue)

if not err then
   print("Your value matches your schema")
   return
end

print(`Schema error: {err}`)

API

1. Primitives

typeaccepts
s.booleantrue or false
s.integernumbers without decimals
s.numberall numbers
s.stringall forms of string
s.anyanything

2. Constraints

typeacceptsexample
s.min(n)numbers, strings or tables with a minimum size of n (inclusive)s.min(5)
s.max(n)numbers, strings or tables with a maximum size of n (inclusive)s.max(10)
s.range(n, m)numbers, strings or tables with a size between n and m (both inclusive)s.range(5, 10)
s.size(n)strings or tables with a size of ns.size(10)
s.unsignednumbers greater or equal to 0s.unsigned(0.5)

3. Combinators

typeacceptsexample
s.array(t)tables with consecutive integer keys whose elements match t schemas.array(s.integer)
s.set(t, i?)values matching s.array(t) schema with no duplicates based on i identity function. The callback defaults to function(value) return value end. Identity function allows storing complex objects while associating an unique keys.set(s.string)
s.map(kt, vt)regular tables whose keys matches kt schema and values vt schemas.map(s.string, s.boolean)
s.object(o)tables matching at least all o keys and valuess.object({ id = s.integer, vip = s.boolean })
s.shape(o)tables matching s.object(o) schema containing only o entriess.shape({ key = s.string, value = s.number })
s.union(...t)values matching at least one of the ...t schemass.union(s.literal("r"), s.literal("g"), s.literal("b"))
s.intersection(...t)values matching all ...t schemass.intersection(s.integer, s.unsigned)
s.optional(t)values matching t or nils.optional(s.string)

4. Roblox types

typeacceptsexample
s.enum(e)enum items of es.enum(Enum.HumanoidRigType)
s.dataType(dt)data objects matching dts.dataType("Vector3")
s.instance(c)instances that match or inherit c classs.instance("GuiObject")
s.class(c)instance with class cs.class("Script")

Complex examples

-- Number arrays with a max. size of 10 elements
s.intersection(s.array(s.number), s.max(10))

-- Uniquely identified shape based on its 'id' field
local shape = s.shape({
   id = s.integer,
   name = s.string,
   interests = s.set(s.string)
})
local function identity(value)
   return value.id
end
s.intersection(s.set(shape, identity), s.min(5))

-- Maps with combined key
s.map(s.intersection(s.string, s.size(1)), s.number)

-- Tagged union
s.union(
   s.shape({
      type = s.literal("red"),
      hex = s.string
   }),
   s.shape({
      type = s.literal("green"),
      hex = s.string
   }),
   s.shape({
      type = s.literal("blue"),
      hex = s.string
   })
)

Future plans

schema ended up looking more like t than I expected. This is not a bad thing at all, it's just something that happened. In a principle, it was supposed to have mixed explicit and implicit typing (by explicit types meaning literals) using a builder schema.build function for instance.

local builtType = s.build({ -- This being a shape
   primitiveLiteral = 3.14,
   tuple = { s.number, s.integer, s.string },
   array = { s.number },
   set = { s.unique(s.number) }, -- Not really a thing, at that point just use s.set(type) lol
   map = { [s.string] = s.boolean },
   enum = Enum.HumanoidRigType,
   object = s.object({}) -- Objects could only be specified through this way
})

For the sake of consistency, combinators should also have to use this build function internally for the receiving types. At the time I decided to keep literals out. If the library gets to somewhere I could maybe start thinking more about adding it as a v2.

Issues

Feel free to open an issue if you find out that the library is not behaving as expected or maybe you want a feature that would fit in.

Package Details

Install command (Click to copy)


Version

1.0.1

License

Unlicense

check_circle

Safe for commercial use

infoThe package archive does not include its license text; the license is declared in its manifest metadata.

Automated license review — not legal advice.