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#
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#
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#
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut AllocatorConverts the wire value input into a value.
Errors#
Issueifinputdoes not hold an acceptable value.OutOfMemoryErrorif the value needs memory that theAllocatorcannot supply.
Operation encode#
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut AllocatorConverts value back into its wire form.
Errors#
OutOfMemoryErrorif the wire form needs memory that theAllocatorcannot supply.
Check#
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#
fn test(self: &Self, value: &T) -> boolReturns whether value passes.
Operation message#
fn message(self: &Self) -> string<'static>Returns what the check expects, as one line for a person, such as "between 1 and 64".
Chain2#
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#
pub step1: S1Step 1, next to the wire form.
Field step2#
pub step2: S2Step 2, next to the value.
Field value#
pub value: [A; 0]Empty. It names the value type A.
Field between1#
pub between1: [M1; 0]Empty. It names the type between step 1 and step 2.
Field wire#
pub wire: [I; 0]Empty. It names the wire form I.
Implementation Codec<A, I> for Chain2<S1, S2, A, M1, I>#
impl<A, M1, I, S1: Codec<M1, I>, S2: Codec<A, M1>> Codec<A, I> for Chain2<S1, S2, A, M1, I>Operation decode#
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut AllocatorDecodes with each step, from step1 to step2.
Operation encode#
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut AllocatorEncodes with each step, from step2 to step1.
Chain3#
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#
pub step1: S1Step 1, next to the wire form.
Field step2#
pub step2: S2Step 2.
Field step3#
pub step3: S3Step 3, next to the value.
Field value#
pub value: [A; 0]Empty. It names the value type A.
Field between1#
pub between1: [M1; 0]Empty. It names the type between step 1 and step 2.
Field between2#
pub between2: [M2; 0]Empty. It names the type between step 2 and step 3.
Field wire#
pub wire: [I; 0]Empty. It names the wire form I.
Implementation Codec<A, I> for Chain3<S1, S2, S3, A, M1, M2, I>#
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#
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut AllocatorDecodes with each step, from step1 to step3.
Operation encode#
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut AllocatorEncodes with each step, from step3 to step1.
Chain4#
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#
pub step1: S1Step 1, next to the wire form.
Field step2#
pub step2: S2Step 2.
Field step3#
pub step3: S3Step 3.
Field step4#
pub step4: S4Step 4, next to the value.
Field value#
pub value: [A; 0]Empty. It names the value type A.
Field between1#
pub between1: [M1; 0]Empty. It names the type between step 1 and step 2.
Field between2#
pub between2: [M2; 0]Empty. It names the type between step 2 and step 3.
Field between3#
pub between3: [M3; 0]Empty. It names the type between step 3 and step 4.
Field wire#
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>#
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#
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut AllocatorDecodes with each step, from step1 to step4.
Operation encode#
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut AllocatorEncodes with each step, from step4 to step1.
Chain5#
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#
pub step1: S1Step 1, next to the wire form.
Field step2#
pub step2: S2Step 2.
Field step3#
pub step3: S3Step 3.
Field step4#
pub step4: S4Step 4.
Field step5#
pub step5: S5Step 5, next to the value.
Field value#
pub value: [A; 0]Empty. It names the value type A.
Field between1#
pub between1: [M1; 0]Empty. It names the type between step 1 and step 2.
Field between2#
pub between2: [M2; 0]Empty. It names the type between step 2 and step 3.
Field between3#
pub between3: [M3; 0]Empty. It names the type between step 3 and step 4.
Field between4#
pub between4: [M4; 0]Empty. It names the type between step 4 and step 5.
Field wire#
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>#
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#
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut AllocatorDecodes with each step, from step1 to step5.
Operation encode#
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut AllocatorEncodes with each step, from step5 to step1.
Chain6#
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#
pub step1: S1Step 1, next to the wire form.
Field step2#
pub step2: S2Step 2.
Field step3#
pub step3: S3Step 3.
Field step4#
pub step4: S4Step 4.
Field step5#
pub step5: S5Step 5.
Field step6#
pub step6: S6Step 6, next to the value.
Field value#
pub value: [A; 0]Empty. It names the value type A.
Field between1#
pub between1: [M1; 0]Empty. It names the type between step 1 and step 2.
Field between2#
pub between2: [M2; 0]Empty. It names the type between step 2 and step 3.
Field between3#
pub between3: [M3; 0]Empty. It names the type between step 3 and step 4.
Field between4#
pub between4: [M4; 0]Empty. It names the type between step 4 and step 5.
Field between5#
pub between5: [M5; 0]Empty. It names the type between step 5 and step 6.
Field wire#
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>#
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#
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut AllocatorDecodes with each step, from step1 to step6.
Operation encode#
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut AllocatorEncodes with each step, from step6 to step1.
Refine#
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#
pub codec: CThe codec that produces the value.
Field check#
pub check: KThe check the decoded value must pass.
Field value#
pub value: [A; 0]Empty. It names the value type A; wire names the wire form.
Field wire#
pub wire: [I; 0]Empty.
Implementation Codec<A, I> for Refine<C, K, A, I>#
impl<A, I, C: Codec<A, I>, K: Check<A>> Codec<A, I> for Refine<C, K, A, I>Operation decode#
effect fn decode(self: &Self, input: I) -> A ! Issue | OutOfMemoryError ? &mut AllocatorDecodes with the codec, then fails with Issue.checkFailed if the check rejects the value.
Operation encode#
effect fn encode(self: &Self, value: &A) -> I ! OutOfMemoryError ? &mut AllocatorEncodes with the codec. The check does not run.
Identity#
pub struct IdentityThe codec that leaves a value as it is. Use it to start a chain that only checks.
Implementation Codec<T, T> for Identity#
impl<T: Copy> Codec<T, T> for IdentityOperation decode#
effect fn decode(self: &Self, input: T) -> T ! Issue | OutOfMemoryError ? &mut AllocatorReturns input.
Operation encode#
effect fn encode(self: &Self, value: &T) -> T ! OutOfMemoryError ? &mut AllocatorReturns a copy of value.
Trim#
pub struct TrimThe codec that removes white space at both ends of text when decoding. Encoding copies the text unchanged.
Implementation Codec<String, String> for Trim#
impl Codec<String, String> for TrimOperation decode#
effect fn decode(self: &Self, input: String) -> String ! Issue | OutOfMemoryError ? &mut AllocatorReturns input without white space at either end.
Operation encode#
effect fn encode(self: &Self, value: &String) -> String ! OutOfMemoryError ? &mut AllocatorReturns a copy of value.
NumberText#
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#
pub kind: [T; 0]Empty. It names the integer type.
Implementation NumberText<T>#
impl<T> NumberText<T>Associated function make#
pub fn make() -> NumberText<T>Returns the codec for the integer type T.
Implementation Codec<u8, String> for NumberText<u8>#
impl Codec<u8, String> for NumberText<u8>Operation decode#
effect fn decode(self: &Self, input: String) -> u8 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text.
Operation encode#
effect fn encode(self: &Self, value: &u8) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits.
Implementation Codec<u16, String> for NumberText<u16>#
impl Codec<u16, String> for NumberText<u16>Operation decode#
effect fn decode(self: &Self, input: String) -> u16 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text.
Operation encode#
effect fn encode(self: &Self, value: &u16) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits.
Implementation Codec<u32, String> for NumberText<u32>#
impl Codec<u32, String> for NumberText<u32>Operation decode#
effect fn decode(self: &Self, input: String) -> u32 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text.
Operation encode#
effect fn encode(self: &Self, value: &u32) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits.
Implementation Codec<u64, String> for NumberText<u64>#
impl Codec<u64, String> for NumberText<u64>Operation decode#
effect fn decode(self: &Self, input: String) -> u64 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text.
Operation encode#
effect fn encode(self: &Self, value: &u64) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits.
Implementation Codec<usize, String> for NumberText<usize>#
impl Codec<usize, String> for NumberText<usize>Operation decode#
effect fn decode(self: &Self, input: String) -> usize ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text.
Operation encode#
effect fn encode(self: &Self, value: &usize) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits.
Implementation Codec<i8, String> for NumberText<i8>#
impl Codec<i8, String> for NumberText<i8>Operation decode#
effect fn decode(self: &Self, input: String) -> i8 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text with an optional sign.
Operation encode#
effect fn encode(self: &Self, value: &i8) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits, with - for a negative value.
Implementation Codec<i16, String> for NumberText<i16>#
impl Codec<i16, String> for NumberText<i16>Operation decode#
effect fn decode(self: &Self, input: String) -> i16 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text with an optional sign.
Operation encode#
effect fn encode(self: &Self, value: &i16) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits, with - for a negative value.
Implementation Codec<i32, String> for NumberText<i32>#
impl Codec<i32, String> for NumberText<i32>Operation decode#
effect fn decode(self: &Self, input: String) -> i32 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text with an optional sign.
Operation encode#
effect fn encode(self: &Self, value: &i32) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits, with - for a negative value.
Implementation Codec<i64, String> for NumberText<i64>#
impl Codec<i64, String> for NumberText<i64>Operation decode#
effect fn decode(self: &Self, input: String) -> i64 ! Issue | OutOfMemoryError ? &mut AllocatorParses decimal text with an optional sign.
Operation encode#
effect fn encode(self: &Self, value: &i64) -> String ! OutOfMemoryError ? &mut AllocatorWrites the decimal digits, with - for a negative value.
Between#
pub struct Between<T>The check that a value lies between low and high, both included.
Field low#
pub low: TThe smallest accepted value.
Field high#
pub high: TThe largest accepted value.
Field message#
pub message: string<'static>What a person sees when the check fails, such as "between 1 and 65535".
Implementation Check<T> for Between<T>#
impl<T: Order> Check<T> for Between<T>Operation test#
fn test(self: &Self, value: &T) -> boolReturns whether low <= value <= high.
Operation message#
fn message(self: &Self) -> string<'static>Returns message.
Length#
pub struct LengthThe check that text has between min and max bytes of UTF-8, both included.
Field min#
pub min: usizeThe smallest accepted length in bytes.
Field max#
pub max: usizeThe largest accepted length in bytes.
Field message#
pub message: string<'static>What a person sees when the check fails, such as "1 to 64 bytes".
Implementation Check<String> for Length#
impl Check<String> for LengthOperation test#
fn test(self: &Self, value: &String) -> boolReturns whether the byte length of value is between min and max.
Operation message#
fn message(self: &Self) -> string<'static>Returns message.
Items#
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>#
impl<T> Items<T>Associated function between#
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>#
impl<T> Check<List<T>> for Items<T>Operation test#
fn test(self: &Self, value: &List<T>) -> boolReturns whether value has between min and max elements.
Operation message#
fn message(self: &Self) -> string<'static>Returns the message.
Codecs#
pub struct CodecsThe owner of the functions that combine codecs. It has no fields and is never constructed.
Implementation Codecs#
impl CodecsAssociated function chain2#
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#
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#
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#
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#
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#
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.