Schematist now covers the whole Draft 2020-12 vocabulary. 1.0 emitted Draft 2020-12 documents but couldn't express every schema the spec allows. This closes the five places where it fell short.
A schema does not have to be an object
A type with a name declares a property. Without a name it declares what the schema itself is.
class Tags < Schematist::Schema
array of: :string, unique: true # the whole schema is an array
end
class Id < Schematist::Schema
one_of do # the whole schema is a choice
string
integer
end
end
class Person < Schematist::Schema
raw({ "$ref" => "https://example.com/person.json" })
endIt works inside define too, so a definition can be a reusable string, a shared enum, an array, or a union, not just an object:
define :status do
string enum: %w[draft sent paid]
endObjects also take min_properties, max_properties, unevaluated_properties and unevaluated_items at the root, alongside additional_properties. Closes #59 and #61.
Conditionals hold any schema
if, then and else each hold any schema in Draft 2020-12. A branch was stuck with a required list plus nine validation keys, so it couldn't ask for a nested object, an array, a reference, or any composition. A branch is backed by a schema class now, so the whole DSL works inside one:
given kind: "business" do
requires :vat_id
object :tax_details do
string :vat_number
end
otherwise do
validates :vat_id, type: :string
end
endgiven still matches on property values, and takes a schema outright when the condition is something else:
given({ required: %w[tax_id] }) { requires :summary }requires and validates stay as shorthands. A dependency that only lists fields still emits dependentRequired; one that describes a schema upgrades to dependentSchemas. Closes #60.
Open ended tuples
A tuple is exactly its prefix unless you give it somewhere to put the rest. That covers both of the usual Draft 2020-12 array shapes:
tuple :event, of: :string do # prefixItems, then a typed tail
string
integer
end
tuple :pair, unevaluated_items: false do # closed after the prefix
string
string
endunevaluated_items: is on the composition builders too, alongside unevaluated_properties:. Closes #62.
Wider keywords
enum and const belong to the validation vocabulary and apply to any type, so enum works on boolean and null now. format isn't type restricted either, so integer :id, format: "int64" works. Closes #63.
const on null stays out on purpose. {"type":"null"} and {"type":"null","const":null} accept and reject the same things, so it buys you nothing, and raw covers it if you want the exact bytes.
Fixed
- Cycle detection only walked a definition's properties, so a
$refburied in a union was invisible. It walks the whole thing now, which matters more now that definitions can be unions. - An object with no block and no
of:schema raisedwrong number of arguments (given 0, expected 1..3). It says what's wrong now.
Also
ruby_llm-schema 1.0.0 is on RubyGems. It depends on Schematist and aliases RubyLLM::Schema, so the old constant keeps resolving while you migrate. It warns on load, and it's the last release of that name.