Schemas
A schema pairs a Gale type with the rules for crossing a dynamic boundary. The compiler checks that a value and its schema agree on the type; the schema checks incoming data and value constraints at runtime.
Profile is a nominal struct with a JSON schema in schema_example.
module schema_example.profile
@moduledoc """`Profile` is a nominal struct with a JSON schema in `schema_example`."""
struct { name: Binary, age: Integer, email: Option<Binary>, tags: List<Binary>}Event is a tagged union. Its JSON representation carries the tag in a
"type" field; the schema dispatches on that field through one_of.
module schema_example.event
@moduledoc """`Event` is a tagged union. Its JSON representation carries the tag in a`"type"` field; the schema dispatches on that field through `one_of`."""
pub type Event = Started(name: Binary) | Stopped(reason: Binary)Schemas for the schema_example.profile.Profile struct and the schema_example.event.Event tagged union.
A schema is one explicit value that validates unknown terms, decodes JSON,
and encodes JSON, checking the same refinements in every direction. Passing
profile_schema() next to a Profile is compile-time evidence that the
pair agree on the type; no derivation or trait lookup is involved.
module schema_example
@moduledoc """Schemas for the `schema_example.profile.Profile` struct and the `schema_example.event.Event` tagged union.
A schema is one explicit value that validates unknown terms, decodes JSON,and encodes JSON, checking the same refinements in every direction. Passing`profile_schema()` next to a `Profile` is compile-time evidence that thepair agree on the type; no derivation or trait lookup is involved."""
alias gale_schema as schemaalias gale_schema.int as intalias gale_schema.text as text
@doc """A `Profile` schema with a refined field, an optional field, and a defaultedfield."""pub fn profile_schema() -> schema.JsonSchema<schema_example.profile.Profile> { schema.object4( fn(name: Binary, age: Integer, email: Option<Binary>, tags: List<Binary>) => schema_example.profile.Profile { name: name, age: age, email: email, tags: tags }, schema.field( "name", schema.string() |> text.min_length(1), fn(value: schema_example.profile.Profile) => value.name ), schema.field( "age", schema.integer() |> int.at_least(0), fn(value: schema_example.profile.Profile) => value.age ), schema.optional( "email", schema.string(), fn(value: schema_example.profile.Profile) => value.email ), schema.defaulted( "tags", schema.list(schema.string()), [], fn(value: schema_example.profile.Profile) => value.tags ) )}
@doc """Parses a profile from a JSON binary."""pub fn decode_profile( source: Binary) -> Result<schema_example.profile.Profile, gale_schema.JsonError> { schema.parse(profile_schema(), source)}
@doc """Encodes a profile to a JSON binary, refusing values that fail refinement."""pub fn encode_profile( value: schema_example.profile.Profile) -> Result<Binary, List<gale_schema.Issue>> { schema.stringify(profile_schema(), value)}
fn started_schema() -> schema.JsonSchema<schema_example.event.Event> { schema.object2( fn(kind: Binary, name: Binary) => schema_example.event.Event.Started(name: name), schema.field( "type", schema.enumeration(["started"]), fn(value: schema_example.event.Event) => event_kind(value) ), schema.field("name", schema.string(), fn(value: schema_example.event.Event) => started_name(value)) )}
fn stopped_schema() -> schema.JsonSchema<schema_example.event.Event> { schema.object2( fn(kind: Binary, reason: Binary) => schema_example.event.Event.Stopped(reason: reason), schema.field( "type", schema.enumeration(["stopped"]), fn(value: schema_example.event.Event) => event_kind(value) ), schema.field( "reason", schema.string(), fn(value: schema_example.event.Event) => stopped_reason(value) ) )}
fn event_kind(value: schema_example.event.Event) -> Binary { match value { schema_example.event.Event.Started(_) -> "started" schema_example.event.Event.Stopped(_) -> "stopped" }}
fn started_name(value: schema_example.event.Event) -> Binary { match value { schema_example.event.Event.Started(name) -> name schema_example.event.Event.Stopped(_) -> "" }}
fn stopped_reason(value: schema_example.event.Event) -> Binary { match value { schema_example.event.Event.Started(_) -> "" schema_example.event.Event.Stopped(reason) -> reason }}
@doc """An `Event` schema: decoding tries each variant, and encoding dispatches onthe constructor with an explicit match."""pub fn event_schema() -> schema.JsonSchema<schema_example.event.Event> { schema.of_json( fn(json) => schema.decode(schema.one_of([started_schema(), stopped_schema()]), json), fn(value: schema_example.event.Event) => match value { schema_example.event.Event.Started(name) -> gale_std.json.object([ ("type", gale_std.json.string("started")), ("name", gale_std.json.string(name)) ]) schema_example.event.Event.Stopped(reason) -> gale_std.json.object([ ("type", gale_std.json.string("stopped")), ("reason", gale_std.json.string(reason)) ]) } )}
@doc """Parses an event from a JSON binary."""pub fn decode_event(source: Binary) -> Result<schema_example.event.Event, gale_schema.JsonError> { schema.parse(event_schema(), source)}
@doc """Encodes an event to a JSON binary."""pub fn encode_event(value: schema_example.event.Event) -> Result<Binary, List<gale_schema.Issue>> { schema.stringify(event_schema(), value)}These three modules form one project. The source blocks above are checked for exact agreement with the example; its Mix tests check them together, including successful round trips and rejection of invalid fields.