silk.schema.schema.

Read and write your own types in any data format, from the types alone.

When to use#

Write impl Schema for T {} for a struct or a union of unit variants, and read or write it with a format such as silk.schema.json.SchemaJson. Give a type its own read and write when its wire form differs from its fields, such as a port written as text, and build them from a codec chain with Schemas.readVia and Schemas.writeVia.

Details#

A type is declared once, and the schema derives what to do from it. The default read and write walk the type's public fields with silk.core.reflect, so a struct is read from an object with one member per field, in any order, and written in declaration order. A union whose variants have no fields is the name of its variant, such as "Admin". A union with a variant that has fields is an object whose type member names the variant and whose other members are that variant's fields, such as {"type": "Circle", "radius": 2}; Spec.tag picks another member name. An Option field may be absent, or null, when it is None, and is left out when written.

Spec holds what the types cannot say, such as a field whose name on the wire differs. A type returns its spec from Schema.spec.

A format implements SchemaReader and SchemaWriter, so the same types read JSON today and other formats later without changes.

Gotchas#

Every field of a derived struct or variant must be public, because reflection sees only public fields. A variant field cannot be named type, or whatever Spec.tag picks.

Examples#

Read and write a struct as JSON#

silk
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.schema.issue { SchemaError }
import silk.schema.json { SchemaJson }
import silk.schema.schema { Schema, Spec }
import silk.text.string { String }

struct User {
  pub id: u64,
  pub name: String,
  pub admin: bool,
}

impl Schema for User {
  static fn spec() -> Spec<User> {
    return Spec<User>.of() |> Spec<User>.rename("id", "user_id")
  }
}

effect fn load() -> u64 ! SchemaError | OutOfMemoryError ? &mut Allocator {
  let user = run SchemaJson.decode<User>(b"{\"name\": \"Ada\", \"user_id\": 7, \"admin\": true}")
  if !user.admin { return 0 }
  return user.id
}

pub effect fn main() -> i32 ! OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let loaded = run Effect.result(load()) |> Effect.provideMut<Allocator>(&mut allocator)
  return match move loaded {
    Result<u64, SchemaError | OutOfMemoryError>.Success {value} => if value == 7 { 0 } else { 1 }
    Result<u64, SchemaError | OutOfMemoryError>.Failure {..} => 2
  }
}

Import as Schema with import silk.schema.schema { Schema }.

Public declarations: 8.

PathSegment#

silk
pub union PathSegment

One step of a SchemaPath: a struct field by its Silk name, or a list element by its index.

Field#

silk
Field

A struct field.

Field name#

silk
pub name: string<'static>

The field's name in Silk, not its name on the wire.

Index#

silk
Index

A list element.

Field at#

silk
pub at: usize

The position of the element, from 0.

Implementation Copy for PathSegment#

silk
impl Copy for PathSegment

SchemaPath#

silk
pub struct SchemaPath

The path from the root value to the part a reader is reading, such as settings.ports[2].

When to use#

A format's SchemaReader keeps one and returns it from SchemaReader.path. The derived read operations push a segment before they read a part and pop it after, so when a read fails the path still names the part that failed.

Implementation SchemaPath#

silk
impl SchemaPath

Associated function make#

silk
pub fn make() -> SchemaPath

Returns an empty path, which names the root value.

Method push#

silk
pub effect fn push(self: &mut Self, segment: PathSegment) -> () ! OutOfMemoryError ? &mut Allocator

Adds segment at the end of the path.

Errors#

  • OutOfMemoryError if the Allocator cannot grow the path.

Method pop#

silk
pub fn pop(self: &mut Self) -> ()

Removes the last segment. It does nothing to an empty path.

Method length#

silk
pub fn length(self: &Self) -> usize

Returns the number of segments.

Method text#

silk
pub effect fn text(self: &Self) -> String ! OutOfMemoryError ? &mut Allocator

Returns the path as text, such as settings.ports[2]. The root value is empty text.

Errors#

  • OutOfMemoryError if the Allocator cannot supply the text.

SchemaReader#

silk
pub interface SchemaReader

The reading side of a data format, in terms every format shares: scalars, text, lists and objects.

When to use#

Implement it to let every Schema type read a new format. Programs call a format's own entry point, such as silk.schema.json.SchemaJson.decode, rather than these operations.

Details#

The schema reads an object by calling beginObject, then member once for each field it needs, in its own order, and then endObject. member finds the member wherever it is in the object and leaves the reader at its value.

Operation path#

silk
fn path(self: &mut Self) -> &mut SchemaPath

Returns the path of the part being read.

Operation boolean#

silk
effect fn boolean(self: &mut Self) -> bool ! Issue | OutOfMemoryError ? &mut Allocator

Reads true or false.

Errors#

  • Issue with IssueReason.InvalidType for another kind of value.

Operation signed#

silk
effect fn signed(self: &mut Self) -> i64 ! Issue | OutOfMemoryError ? &mut Allocator

Reads an integer that fits in i64.

Errors#

  • Issue with IssueReason.InvalidType for another kind of value, or InvalidValue for an integer outside i64.

Operation unsigned#

silk
effect fn unsigned(self: &mut Self) -> u64 ! Issue | OutOfMemoryError ? &mut Allocator

Reads an integer that fits in u64.

Errors#

  • Issue with IssueReason.InvalidType for another kind of value, or InvalidValue for an integer outside u64.

Operation float#

silk
effect fn float(self: &mut Self) -> f64 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number as an f64.

Errors#

  • Issue with IssueReason.InvalidType for another kind of value.

Operation text#

silk
effect fn text(self: &mut Self) -> String ! Issue | OutOfMemoryError ? &mut Allocator

Reads text.

Errors#

  • Issue with IssueReason.InvalidType for another kind of value.

  • OutOfMemoryError if the Allocator cannot supply the text.

Operation null#

silk
effect fn null(self: &mut Self) -> bool ! Issue | OutOfMemoryError ? &mut Allocator

Reads past a null value and returns true, or returns false and leaves another value unread.

Operation beginList#

silk
effect fn beginList(self: &mut Self) -> () ! Issue | OutOfMemoryError ? &mut Allocator

Starts reading a list.

Errors#

  • Issue with IssueReason.InvalidType for another kind of value.

Operation nextElement#

silk
effect fn nextElement(self: &mut Self) -> bool ! Issue | OutOfMemoryError ? &mut Allocator

Returns true and leaves the reader at the next element, or returns false and reads past the end of the list.

Operation beginObject#

silk
effect fn beginObject(self: &mut Self) -> () ! Issue | OutOfMemoryError ? &mut Allocator

Starts reading an object.

Errors#

  • Issue with IssueReason.InvalidType for another kind of value.

Operation member#

silk
effect fn member(self: &mut Self, key: string) -> bool ! Issue | OutOfMemoryError ? &mut Allocator

Finds the member key of the object being read and leaves the reader at its value, or returns false if the object has no such member.

Operation endObject#

silk
effect fn endObject(self: &mut Self) -> () ! Issue | OutOfMemoryError ? &mut Allocator

Reads past the end of the object being read, whatever members were read.

Operation checkFailed#

silk
effect fn checkFailed(self: &mut Self, field: Option<string<'static>>, expected: string<'static>)
    -> () ! Issue | OutOfMemoryError ? &mut Allocator

Reports a check that failed on the value just read: on its field field, or on the whole value when field is None. The default fails with it; a reader that collects every problem records it and returns.

Errors#

  • Issue with IssueReason.CheckFailed, at the field's path.

  • OutOfMemoryError if the path cannot grow.

SchemaWriter#

silk
pub interface SchemaWriter

The writing side of a data format, in terms every format shares: scalars, text, lists and objects.

When to use#

Implement it to let every Schema type write a new format. Programs call a format's own entry point, such as silk.schema.json.SchemaJson.encode.

Details#

The schema writes a list as beginList, then element before each element, then endList. It writes an object as beginObject, then key before each member's value, then endObject.

Operation boolean#

silk
effect fn boolean(self: &mut Self, value: bool) -> () ! Issue | WriterError ? &mut Writer

Writes true or false.

Operation signed#

silk
effect fn signed(self: &mut Self, value: i64) -> () ! Issue | WriterError ? &mut Writer

Writes a signed integer.

Operation unsigned#

silk
effect fn unsigned(self: &mut Self, value: u64) -> () ! Issue | WriterError ? &mut Writer

Writes an unsigned integer.

Operation float#

silk
effect fn float(self: &mut Self, value: f64) -> () ! Issue | WriterError ? &mut Writer

Writes a number.

Errors#

  • Issue with IssueReason.InvalidValue if the format cannot hold value, such as a NaN in JSON.

Operation text#

silk
effect fn text(self: &mut Self, value: string) -> () ! Issue | WriterError ? &mut Writer

Writes text.

Operation null#

silk
effect fn null(self: &mut Self) -> () ! Issue | WriterError ? &mut Writer

Writes a null value.

Operation beginList#

silk
effect fn beginList(self: &mut Self) -> () ! Issue | WriterError ? &mut Writer

Starts a list.

Operation element#

silk
effect fn element(self: &mut Self) -> () ! Issue | WriterError ? &mut Writer

Starts the next element of the list being written.

Operation endList#

silk
effect fn endList(self: &mut Self) -> () ! Issue | WriterError ? &mut Writer

Ends the list being written.

Operation beginObject#

silk
effect fn beginObject(self: &mut Self) -> () ! Issue | WriterError ? &mut Writer

Starts an object.

Operation key#

silk
effect fn key(self: &mut Self, name: string) -> () ! Issue | WriterError ? &mut Writer

Writes the key of the next member of the object being written. Its value comes next.

Operation endObject#

silk
effect fn endObject(self: &mut Self) -> () ! Issue | WriterError ? &mut Writer

Ends the object being written.

Spec#

silk
pub struct Spec<T>

What a type's schema says beyond its declaration, such as a field whose name on the wire differs.

When to use#

Return one from Schema.spec and build it with pipes: Spec<User>.of() |> Spec<User>.rename("id", "user_id").

Details#

A spec exists only during static evaluation. Field names are checked against T where they are written, and the editor links them to the field.

Implementation Spec<T>#

silk
impl<T> Spec<T>

Associated function of#

silk
pub static fn of() -> Self

Returns the spec that keeps every field's own name.

Method rename#

silk
pub static fn rename(self: Self, field: Intrinsic.FieldName<T>, wire: string<'static>) -> Self

Returns the spec in which field is called wire in the data format.

Method tag#

silk
pub static fn tag(self: Self, name: string<'static>) -> Self

Returns the spec in which a union's variant is named by the field name, instead of type.

Details#

A union with a variant that has fields is written as an object holding the variant's name in this field next to the variant's own fields, such as {"type": "Circle", "radius": 2}.

Method tagName#

silk
pub static fn tagName(self: &Self) -> string<'static>

Returns the name of the field that holds a union's variant name.

Method wireName#

silk
pub static fn wireName(self: &Self, label: string<'static>) -> string<'static>

Returns the name of the field label in the data format.

Schema#

silk
pub interface Schema

A type that can be read from and written to any data format.

When to use#

Write impl Schema for T {} for a struct or union whose fields are all public and implement Schema. Override spec to rename fields. Override read and write for a type whose wire form is not its fields, usually with Schemas.readVia and Schemas.writeVia.

Details#

The standard library implements it for bool, the integer types, f64, String, Option<T> and List<T>.

Operation spec#

silk
static fn spec() -> Spec<Self>

Returns what the type's schema says beyond its declaration. The default renames nothing.

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> Self ! Issue | OutOfMemoryError ? &mut Allocator

Reads a value. The default reads a struct from an object, a union of unit variants from its variant name, and any other union from an object tagged with its variant name.

Errors#

  • Issue if the input does not hold a value of the type.

  • OutOfMemoryError if the Allocator cannot supply the value.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the value. The default writes a struct as an object, a union of unit variants as its variant name, and any other union as an object tagged with its variant name.

Errors#

  • Issue if the format cannot hold the value.

  • WriterError if the Writer fails.

  • OutOfMemoryError if a codec needs memory that the Allocator cannot supply.

Operation missing#

silk
effect fn missing() -> Self ! Issue

Returns the value of a field whose key is absent. The default fails, because the key is required; Option returns None.

Errors#

  • Issue with IssueReason.MissingKey.

Operation omitted#

silk
fn omitted(self: &Self) -> bool

Returns whether a field holding the value is left out when written. The default is false; Option returns true for None.

Operation check#

silk
fn check(self: &Self, checks: &mut Checks<Self>) -> ()

Checks a value after it is read. The default accepts every value.

When to use#

Override it to state what the type alone cannot, such as a port between 1 and 65535 or a name of at most 64 bytes, with Checks.field and Checks.that. A failed check becomes an Issue at the path of the field it names: the read's only one, or one of all of them when the format collects every problem, as SchemaJson.decodeAll does.

Checks#

silk
pub struct Checks<T>

The checks of one value of T, run by Schema.check after the value is read.

Examples#

silk
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.schema.codec { Between, Length }
import silk.schema.issue { IssueReason, SchemaError }
import silk.schema.json { SchemaJson }
import silk.schema.schema { Checks, Schema }
import silk.text.string { String }

struct Server {
  pub host: String,
  pub port: u16,
}

impl Schema for Server {
  fn check(self: &Self, checks: &mut Checks<Server>) -> () {
    checks.field(self, "host", Length {min: 1, max: 253, message: "a host name"})
    checks.field(self, "port", Between<u16> {low: 1, high: 65535, message: "a port"})
  }
}

effect fn load() -> Server ! SchemaError | OutOfMemoryError ? &mut Allocator {
  return run SchemaJson.decode<Server>(b"{\"host\": \"silk.dev\", \"port\": 0}")
}

pub effect fn main() -> i32 ! OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  return match run Effect.result(load()) |> Effect.provideMut<Allocator>(&mut allocator) {
    Result<Server, SchemaError | OutOfMemoryError>.Failure {error: SchemaError {path, issue}} =>
      if path.view() == "port" && issue.reason == IssueReason.CheckFailed { 0 } else { 1 }
    _ => 2
  }
}

Gotchas#

One value records at most 16 failed checks; later ones are dropped.

Implementation Checks<T>#

silk
impl<T> Checks<T>

Associated function make#

silk
pub fn make() -> Checks<T>

Returns the checks with nothing failed yet.

Method field#

silk
pub fn field<V, Owner, K: Check<V>>(self: &mut Self, owner: &Owner, static name: Intrinsic.FieldName<Owner>, check: K) -> ()

Checks the field name of owner with check, whose type says the field's type, such as Between<u16> for a u16 field.

Method that#

silk
pub fn that(self: &mut Self, holds: bool, expected: string<'static>) -> ()

Checks a condition on the whole value, such as two fields that must agree. expected says what the value must be when holds is false.

Schemas#

silk
pub struct Schemas

The owner of the functions that read and write Schema types. It has no fields and is never constructed.

Implementation Schemas#

silk
impl Schemas

Associated function read#

silk
pub effect fn read<T: Schema, R: SchemaReader>(reader: &mut R) -> T ! Issue | OutOfMemoryError ? &mut Allocator

Reads a T with its Schema implementation.

Errors#

  • Issue if the input does not hold a value of the type.

  • OutOfMemoryError if the Allocator cannot supply the value.

Associated function write#

silk
pub effect fn write<T: Schema, W: SchemaWriter>(value: &T, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes value with its Schema implementation.

Errors#

  • Issue if the format cannot hold the value.

  • WriterError if the Writer fails.

Associated function spec#

silk
pub static fn spec<T: Schema>() -> Spec<T>

Returns the spec of T.

Associated function readVia#

silk
pub effect fn readVia<A, I: Schema, C: Codec<A, I>, R: SchemaReader>(codec: &C, reader: &mut R)
    -> A ! Issue | OutOfMemoryError ? &mut Allocator

Reads the wire form I and decodes it into an A with codec.

When to use#

Call it from the read of a type whose wire form is another type, such as a port written as text.

Errors#

  • Issue if the input does not hold an I, or if codec rejects it.

  • OutOfMemoryError if the Allocator cannot supply the value.

Associated function writeVia#

silk
pub effect fn writeVia<A, I: Schema, C: Codec<A, I>, W: SchemaWriter>(codec: &C, value: &A, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Encodes value into the wire form I with codec and writes it.

Errors#

  • Issue if the format cannot hold the wire value.

  • OutOfMemoryError if codec needs memory that the Allocator cannot supply.

  • WriterError if the Writer fails.

Implementation Schema for bool#

silk
impl Schema for bool

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> bool ! Issue | OutOfMemoryError ? &mut Allocator

Reads true or false.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes true or false.

Implementation Schema for u64#

silk
impl Schema for u64

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> u64 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for i64#

silk
impl Schema for i64

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> i64 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for f64#

silk
impl Schema for f64

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> f64 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number. A format that cannot hold a NaN or an infinity fails with an Issue.

Implementation Schema for String#

silk
impl Schema for String

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> String ! Issue | OutOfMemoryError ? &mut Allocator

Reads text.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the text.

Implementation Schema for Option<T>#

silk
impl<T: Schema> Schema for Option<T>

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> Option<T> ! Issue | OutOfMemoryError ? &mut Allocator

Reads None from a null value, and Some from any other value.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the value of Some, or a null value for None. A None field is left out instead.

Operation missing#

silk
effect fn missing() -> Option<T> ! Issue

Returns None, so an Option field may be absent.

Operation omitted#

silk
fn omitted(self: &Self) -> bool

Returns true for None, so a None field is left out.

Implementation Schema for List<T>#

silk
impl<T: Schema> Schema for List<T>

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> List<T> ! Issue | OutOfMemoryError ? &mut Allocator

Reads a list, element by element.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the list, element by element.

Implementation Schema for u8#

silk
impl Schema for u8

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> u8 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number and fails with IssueReason.InvalidValue if it does not fit.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for u16#

silk
impl Schema for u16

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> u16 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number and fails with IssueReason.InvalidValue if it does not fit.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for u32#

silk
impl Schema for u32

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> u32 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number and fails with IssueReason.InvalidValue if it does not fit.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for usize#

silk
impl Schema for usize

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> usize ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number and fails with IssueReason.InvalidValue if it does not fit.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for i8#

silk
impl Schema for i8

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> i8 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number and fails with IssueReason.InvalidValue if it does not fit.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for i16#

silk
impl Schema for i16

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> i16 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number and fails with IssueReason.InvalidValue if it does not fit.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Implementation Schema for i32#

silk
impl Schema for i32

Operation read#

silk
effect fn read<R: SchemaReader>(reader: &mut R) -> i32 ! Issue | OutOfMemoryError ? &mut Allocator

Reads a number and fails with IssueReason.InvalidValue if it does not fit.

Operation write#

silk
effect fn write<W: SchemaWriter>(self: &Self, writer: &mut W)
    -> () ! Issue | WriterError | OutOfMemoryError ? &mut Writer | &mut Allocator

Writes the number.

Category: Data formats.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page