silk.schema.codec.

Two-way conversions between a value and its form on the wire, and checks that refine them.

When to use#

Implement Codec for a conversion that a format cannot do by itself, such as a port number written as text. Run several steps in order with Codecs.chain2 to Codecs.chain6, and attach a check to a step with Codecs.check, so a multi-step conversion is written once and runs in both directions.

Details#

A Codec<A, I> decodes a wire value I into a value A, and encodes A back into I. Decoding can fail with an Issue; encoding cannot, because a value that exists already passed its checks. A chain is an ordinary value whose type records each step, such as Chain3<Trim, Refine<NumberText<u16>, ...>, PortOf, ...>, so decoding is direct calls with no codec object left at runtime.

Decoding runs the steps from the wire form to the value, and encoding runs them the other way. A check added with Codecs.check runs on decode only, on the value its step produced.

Gotchas#

A step of a chain must not be another chain, and the codec inside Codecs.check must not be a chain: the compiler rejects the nested specialization (SEM0053). Use one longer chain instead.

Examples#

Read a port from text#

silk
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.schema.codec { Between, Codec, Codecs, NumberText, Trim }
import silk.schema.issue { Issue }
import silk.text.string { String }

effect fn port(text: string) -> u16 ! Issue | OutOfMemoryError ? &mut Allocator {
  let codec = Codecs.chain2(Trim {}, Codecs.check(NumberText<u16>.make(),
    Between<u16> {low: 1, high: 65535, message: "between 1 and 65535"}))
  return run Codec.decode(&codec, run String.copy(text))
}

pub effect fn main() -> i32 ! OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let parsed = run Effect.result(port(" 8080 ")) |> Effect.provideMut<Allocator>(&mut allocator)
  return match move parsed {
    Result<u16, Issue | OutOfMemoryError>.Success {value} => if value == 8080 { 0 } else { 1 }
    Result<u16, Issue | OutOfMemoryError>.Failure {..} => 2
  }
}

Import as Codec with import silk.schema.codec { Codec }.

Public declarations: 15.

Codec#

silk
pub interface Codec<A, I>

A two-way conversion between a value A and its wire form I.

When to use#

Implement it for a conversion step, such as text to a number. Combine steps with Codecs.chain2 and Codecs.check rather than writing one large implementation.

Details#

decode takes the wire value by value, so owned text moves through a chain without copies. encode borrows the value. A codec type usually has no fields, or holds the static settings of its step, such as the bounds of a range.

Operation decode#

silk
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut Allocator

Converts the wire value input into a value.

Errors#

  • Issue if input does not hold an acceptable value.

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

Operation encode#

silk
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut Allocator

Converts value back into its wire form.

Errors#

  • OutOfMemoryError if the wire form needs memory that the Allocator cannot supply.

Check#

silk
pub interface Check<T>

A test that a decoded value must pass, with the message a person sees when it fails.

When to use#

Implement it for a rule a type cannot express, such as a length limit, and attach it to a codec with Codecs.check.

Operation test#

silk
fn test(self: &Self, value: &T) -> bool

Returns whether value passes.

Operation message#

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

Returns what the check expects, as one line for a person, such as "between 1 and 64".

Chain2#

silk
pub struct Chain2<S1, S2, A, M1, I>

Two codecs run one after the other, from the wire form I to the value A.

Details#

Build it with Codecs.chain2. Decoding runs step1 first; encoding runs step2 first.

Field step1#

silk
pub step1: S1

Step 1, next to the wire form.

Field step2#

silk
pub step2: S2

Step 2, next to the value.

Field value#

silk
pub value: [A; 0]

Empty. It names the value type A.

Field between1#

silk
pub between1: [M1; 0]

Empty. It names the type between step 1 and step 2.

Field wire#

silk
pub wire: [I; 0]

Empty. It names the wire form I.

Implementation Codec<A, I> for Chain2<S1, S2, A, M1, I>#

silk
impl<A, M1, I, S1: Codec<M1, I>, S2: Codec<A, M1>> Codec<A, I> for Chain2<S1, S2, A, M1, I>

Operation decode#

silk
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut Allocator

Decodes with each step, from step1 to step2.

Operation encode#

silk
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut Allocator

Encodes with each step, from step2 to step1.

Chain3#

silk
pub struct Chain3<S1, S2, S3, A, M1, M2, I>

Three codecs run one after the other, from the wire form I to the value A.

Details#

Build it with Codecs.chain3. Decoding runs step1 first; encoding runs step3 first.

Field step1#

silk
pub step1: S1

Step 1, next to the wire form.

Field step2#

silk
pub step2: S2

Step 2.

Field step3#

silk
pub step3: S3

Step 3, next to the value.

Field value#

silk
pub value: [A; 0]

Empty. It names the value type A.

Field between1#

silk
pub between1: [M1; 0]

Empty. It names the type between step 1 and step 2.

Field between2#

silk
pub between2: [M2; 0]

Empty. It names the type between step 2 and step 3.

Field wire#

silk
pub wire: [I; 0]

Empty. It names the wire form I.

Implementation Codec<A, I> for Chain3<S1, S2, S3, A, M1, M2, I>#

silk
impl<A, M1, M2, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<A, M2>> Codec<A, I> for Chain3<S1, S2, S3, A, M1, M2, I>

Operation decode#

silk
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut Allocator

Decodes with each step, from step1 to step3.

Operation encode#

silk
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut Allocator

Encodes with each step, from step3 to step1.

Chain4#

silk
pub struct Chain4<S1, S2, S3, S4, A, M1, M2, M3, I>

Four codecs run one after the other, from the wire form I to the value A.

Details#

Build it with Codecs.chain4. Decoding runs step1 first; encoding runs step4 first.

Field step1#

silk
pub step1: S1

Step 1, next to the wire form.

Field step2#

silk
pub step2: S2

Step 2.

Field step3#

silk
pub step3: S3

Step 3.

Field step4#

silk
pub step4: S4

Step 4, next to the value.

Field value#

silk
pub value: [A; 0]

Empty. It names the value type A.

Field between1#

silk
pub between1: [M1; 0]

Empty. It names the type between step 1 and step 2.

Field between2#

silk
pub between2: [M2; 0]

Empty. It names the type between step 2 and step 3.

Field between3#

silk
pub between3: [M3; 0]

Empty. It names the type between step 3 and step 4.

Field wire#

silk
pub wire: [I; 0]

Empty. It names the wire form I.

Implementation Codec<A, I> for Chain4<S1, S2, S3, S4, A, M1, M2, M3, I>#

silk
impl<A, M1, M2, M3, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<M3, M2>, S4: Codec<A, M3>> Codec<A, I> for Chain4<S1, S2, S3, S4, A, M1, M2, M3, I>

Operation decode#

silk
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut Allocator

Decodes with each step, from step1 to step4.

Operation encode#

silk
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut Allocator

Encodes with each step, from step4 to step1.

Chain5#

silk
pub struct Chain5<S1, S2, S3, S4, S5, A, M1, M2, M3, M4, I>

Five codecs run one after the other, from the wire form I to the value A.

Details#

Build it with Codecs.chain5. Decoding runs step1 first; encoding runs step5 first.

Field step1#

silk
pub step1: S1

Step 1, next to the wire form.

Field step2#

silk
pub step2: S2

Step 2.

Field step3#

silk
pub step3: S3

Step 3.

Field step4#

silk
pub step4: S4

Step 4.

Field step5#

silk
pub step5: S5

Step 5, next to the value.

Field value#

silk
pub value: [A; 0]

Empty. It names the value type A.

Field between1#

silk
pub between1: [M1; 0]

Empty. It names the type between step 1 and step 2.

Field between2#

silk
pub between2: [M2; 0]

Empty. It names the type between step 2 and step 3.

Field between3#

silk
pub between3: [M3; 0]

Empty. It names the type between step 3 and step 4.

Field between4#

silk
pub between4: [M4; 0]

Empty. It names the type between step 4 and step 5.

Field wire#

silk
pub wire: [I; 0]

Empty. It names the wire form I.

Implementation Codec<A, I> for Chain5<S1, S2, S3, S4, S5, A, M1, M2, M3, M4, I>#

silk
impl<A, M1, M2, M3, M4, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<M3, M2>, S4: Codec<M4, M3>, S5: Codec<A, M4>> Codec<A, I> for Chain5<S1, S2, S3, S4, S5, A, M1, M2, M3, M4, I>

Operation decode#

silk
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut Allocator

Decodes with each step, from step1 to step5.

Operation encode#

silk
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut Allocator

Encodes with each step, from step5 to step1.

Chain6#

silk
pub struct Chain6<S1, S2, S3, S4, S5, S6, A, M1, M2, M3, M4, M5, I>

Six codecs run one after the other, from the wire form I to the value A.

Details#

Build it with Codecs.chain6. Decoding runs step1 first; encoding runs step6 first.

Field step1#

silk
pub step1: S1

Step 1, next to the wire form.

Field step2#

silk
pub step2: S2

Step 2.

Field step3#

silk
pub step3: S3

Step 3.

Field step4#

silk
pub step4: S4

Step 4.

Field step5#

silk
pub step5: S5

Step 5.

Field step6#

silk
pub step6: S6

Step 6, next to the value.

Field value#

silk
pub value: [A; 0]

Empty. It names the value type A.

Field between1#

silk
pub between1: [M1; 0]

Empty. It names the type between step 1 and step 2.

Field between2#

silk
pub between2: [M2; 0]

Empty. It names the type between step 2 and step 3.

Field between3#

silk
pub between3: [M3; 0]

Empty. It names the type between step 3 and step 4.

Field between4#

silk
pub between4: [M4; 0]

Empty. It names the type between step 4 and step 5.

Field between5#

silk
pub between5: [M5; 0]

Empty. It names the type between step 5 and step 6.

Field wire#

silk
pub wire: [I; 0]

Empty. It names the wire form I.

Implementation Codec<A, I> for Chain6<S1, S2, S3, S4, S5, S6, A, M1, M2, M3, M4, M5, I>#

silk
impl<A, M1, M2, M3, M4, M5, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<M3, M2>, S4: Codec<M4, M3>, S5: Codec<M5, M4>, S6: Codec<A, M5>> Codec<A, I> for Chain6<S1, S2, S3, S4, S5, S6, A, M1, M2, M3, M4, M5, I>

Operation decode#

silk
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut Allocator

Decodes with each step, from step1 to step6.

Operation encode#

silk
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut Allocator

Encodes with each step, from step6 to step1.

Refine#

silk
pub struct Refine<C, K, A, I>

A codec whose decoded values must also pass a check.

Details#

Build it with Codecs.check. Encoding does not run the check.

Field codec#

silk
pub codec: C

The codec that produces the value.

Field check#

silk
pub check: K

The check the decoded value must pass.

Field value#

silk
pub value: [A; 0]

Empty. It names the value type A; wire names the wire form.

Field wire#

silk
pub wire: [I; 0]

Empty.

Implementation Codec<A, I> for Refine<C, K, A, I>#

silk
impl<A, I, C: Codec<A, I>, K: Check<A>> Codec<A, I> for Refine<C, K, A, I>

Operation decode#

silk
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut Allocator

Decodes with the codec, then fails with Issue.checkFailed if the check rejects the value.

Operation encode#

silk
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut Allocator

Encodes with the codec. The check does not run.

Identity#

silk
pub struct Identity

The codec that leaves a value as it is. Use it to start a chain that only checks.

Implementation Codec<T, T> for Identity#

silk
impl<T: Copy> Codec<T, T> for Identity

Operation decode#

silk
effect fn decode(self: &Self, input: T) -> T ! Issue | OutOfMemoryError ? &mut Allocator

Returns input.

Operation encode#

silk
effect fn encode(self: &Self, value: &T) -> T ! OutOfMemoryError ? &mut Allocator

Returns a copy of value.

Trim#

silk
pub struct Trim

The codec that removes white space at both ends of text when decoding. Encoding copies the text unchanged.

Implementation Codec<String, String> for Trim#

silk
impl Codec<String, String> for Trim

Operation decode#

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

Returns input without white space at either end.

Operation encode#

silk
effect fn encode(self: &Self, value: &String) -> String ! OutOfMemoryError ? &mut Allocator

Returns a copy of value.

NumberText#

silk
pub struct NumberText<T>

The codec between an integer and its decimal text, such as "8080" and 8080.

Details#

It is implemented for u8, u16, u32, u64, usize, i8, i16, i32 and i64. Decoding accepts an optional sign and decimal digits, and fails with IssueReason.InvalidType for other text and IssueReason.InvalidValue for a number that does not fit.

Field kind#

silk
pub kind: [T; 0]

Empty. It names the integer type.

Implementation NumberText<T>#

silk
impl<T> NumberText<T>

Associated function make#

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

Returns the codec for the integer type T.

Implementation Codec<u8, String> for NumberText<u8>#

silk
impl Codec<u8, String> for NumberText<u8>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> u8 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text.

Operation encode#

silk
effect fn encode(self: &Self, value: &u8) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits.

Implementation Codec<u16, String> for NumberText<u16>#

silk
impl Codec<u16, String> for NumberText<u16>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> u16 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text.

Operation encode#

silk
effect fn encode(self: &Self, value: &u16) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits.

Implementation Codec<u32, String> for NumberText<u32>#

silk
impl Codec<u32, String> for NumberText<u32>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> u32 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text.

Operation encode#

silk
effect fn encode(self: &Self, value: &u32) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits.

Implementation Codec<u64, String> for NumberText<u64>#

silk
impl Codec<u64, String> for NumberText<u64>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> u64 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text.

Operation encode#

silk
effect fn encode(self: &Self, value: &u64) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits.

Implementation Codec<usize, String> for NumberText<usize>#

silk
impl Codec<usize, String> for NumberText<usize>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> usize ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text.

Operation encode#

silk
effect fn encode(self: &Self, value: &usize) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits.

Implementation Codec<i8, String> for NumberText<i8>#

silk
impl Codec<i8, String> for NumberText<i8>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> i8 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text with an optional sign.

Operation encode#

silk
effect fn encode(self: &Self, value: &i8) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits, with - for a negative value.

Implementation Codec<i16, String> for NumberText<i16>#

silk
impl Codec<i16, String> for NumberText<i16>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> i16 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text with an optional sign.

Operation encode#

silk
effect fn encode(self: &Self, value: &i16) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits, with - for a negative value.

Implementation Codec<i32, String> for NumberText<i32>#

silk
impl Codec<i32, String> for NumberText<i32>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> i32 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text with an optional sign.

Operation encode#

silk
effect fn encode(self: &Self, value: &i32) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits, with - for a negative value.

Implementation Codec<i64, String> for NumberText<i64>#

silk
impl Codec<i64, String> for NumberText<i64>

Operation decode#

silk
effect fn decode(self: &Self, input: String) -> i64 ! Issue | OutOfMemoryError ? &mut Allocator

Parses decimal text with an optional sign.

Operation encode#

silk
effect fn encode(self: &Self, value: &i64) -> String ! OutOfMemoryError ? &mut Allocator

Writes the decimal digits, with - for a negative value.

Between#

silk
pub struct Between<T>

The check that a value lies between low and high, both included.

Field low#

silk
pub low: T

The smallest accepted value.

Field high#

silk
pub high: T

The largest accepted value.

Field message#

silk
pub message: string<'static>

What a person sees when the check fails, such as "between 1 and 65535".

Implementation Check<T> for Between<T>#

silk
impl<T: Order> Check<T> for Between<T>

Operation test#

silk
fn test(self: &Self, value: &T) -> bool

Returns whether low <= value <= high.

Operation message#

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

Returns message.

Length#

silk
pub struct Length

The check that text has between min and max bytes of UTF-8, both included.

Field min#

silk
pub min: usize

The smallest accepted length in bytes.

Field max#

silk
pub max: usize

The largest accepted length in bytes.

Field message#

silk
pub message: string<'static>

What a person sees when the check fails, such as "1 to 64 bytes".

Implementation Check<String> for Length#

silk
impl Check<String> for Length

Operation test#

silk
fn test(self: &Self, value: &String) -> bool

Returns whether the byte length of value is between min and max.

Operation message#

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

Returns message.

Items#

silk
pub struct Items<T>

The check that a list of T has between min and max elements, both included.

When to use#

Build it with Items.between, such as Items<String>.between(1, 8, "1 to 8 tags").

Implementation Items<T>#

silk
impl<T> Items<T>

Associated function between#

silk
pub fn between(min: usize, max: usize, message: string<'static>) -> Items<T>

Returns the check for min to max elements. message is what a person sees when it fails.

Implementation Check<List<T>> for Items<T>#

silk
impl<T> Check<List<T>> for Items<T>

Operation test#

silk
fn test(self: &Self, value: &List<T>) -> bool

Returns whether value has between min and max elements.

Operation message#

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

Returns the message.

Codecs#

silk
pub struct Codecs

The owner of the functions that combine codecs. It has no fields and is never constructed.

Implementation Codecs#

silk
impl Codecs

Associated function chain2#

silk
pub fn chain2<A, M1, I, S1: Codec<M1, I>, S2: Codec<A, M1>>(step1: S1, step2: S2) -> Chain2<S1, S2, A, M1, I>

Returns the codec that runs two steps in order: decoding from step1 to step2, and encoding the other way.

Details#

Each step's value must be the next step's wire form. A mismatch is a type error at the call.

Associated function chain3#

silk
pub fn chain3<A, M1, M2, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<A, M2>>(step1: S1, step2: S2, step3: S3) -> Chain3<S1, S2, S3, A, M1, M2, I>

Returns the codec that runs three steps in order: decoding from step1 to step3, and encoding the other way.

Details#

Each step's value must be the next step's wire form. A mismatch is a type error at the call.

Associated function chain4#

silk
pub fn chain4<A, M1, M2, M3, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<M3, M2>, S4: Codec<A, M3>>(step1: S1, step2: S2, step3: S3, step4: S4) -> Chain4<S1, S2, S3, S4, A, M1, M2, M3, I>

Returns the codec that runs four steps in order: decoding from step1 to step4, and encoding the other way.

Details#

Each step's value must be the next step's wire form. A mismatch is a type error at the call.

Associated function chain5#

silk
pub fn chain5<A, M1, M2, M3, M4, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<M3, M2>, S4: Codec<M4, M3>, S5: Codec<A, M4>>(step1: S1, step2: S2, step3: S3, step4: S4, step5: S5) -> Chain5<S1, S2, S3, S4, S5, A, M1, M2, M3, M4, I>

Returns the codec that runs five steps in order: decoding from step1 to step5, and encoding the other way.

Details#

Each step's value must be the next step's wire form. A mismatch is a type error at the call.

Associated function chain6#

silk
pub fn chain6<A, M1, M2, M3, M4, M5, I, S1: Codec<M1, I>, S2: Codec<M2, M1>, S3: Codec<M3, M2>, S4: Codec<M4, M3>, S5: Codec<M5, M4>, S6: Codec<A, M5>>(step1: S1, step2: S2, step3: S3, step4: S4, step5: S5, step6: S6) -> Chain6<S1, S2, S3, S4, S5, S6, A, M1, M2, M3, M4, M5, I>

Returns the codec that runs six steps in order: decoding from step1 to step6, and encoding the other way.

Details#

Each step's value must be the next step's wire form. A mismatch is a type error at the call.

Associated function check#

silk
pub fn check<A, I, C: Codec<A, I>, K: Check<A>>(codec: C, check: K) -> Refine<C, K, A, I>

Returns the codec that decodes with codec and fails if check rejects the value.

Details#

Encoding uses codec alone, because a value that exists already passed the check.

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