std/encoding
Encode, Decode and Describe, the traits a format implements, EncodedValue and Structure, Format for streaming, and RFC 4648's Base64, Base32 and hexadecimal for bytes as text (hexEncoded(), hexDecoded(), base64Decoded() methods).
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Die generierte Referenz von std/encoding zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.
std/encoding is what other languages need reflection for: serialization, configuration mapping, database rows,
schemas. A value is its constructor call, and the compiler offers that call in three forms - written (Encode), read
(Decode) and described without a value (Describe). A format implements Encoder, Decoder or Describer and
never sees a type, so there is no tree in between. All three are generated for a type whose constructor is usable from
outside, over exactly its parameters; the generated code is a straight line of the steps this package declares, and
could be written by hand. The traits, DecodeError, EncodedValue and rendered are in scope through the prelude.
This package also has the plainer kind of encoding, unrelated to the three forms above: bytes as text and back, RFC
4648's Base64, Base32 and hexadecimal - for a format whose wire is itself text (a JWT, a !!binary YAML scalar, a
TOTP secret), not for a value's own Encoder.bytes. Hexadecimal and Base64 decoding are methods: bytes.hexEncoded(),
text.hexDecoded(), text.base64Decoded(). A file names the methods it uses, written with the type they extend:
use Buffer.hexEncoded, String.hexDecoded, String.base64Decoded from "std/encoding".
Import#
use Encode, Decode, Describe, DecodeError, Format, rendered from "std/encoding"
use Encoder, Decoder, Describer, EncodedValue, Structure, structureOf, Naming from "std/encoding"
use Base64Alphabet, Base32Alphabet, EncodingError from "std/encoding"
use Buffer.hexEncoded, String.hexDecoded, String.base64Decoded from "std/encoding"use structureOf from "std/encoding"
type Point {
x: Int
y: Int
}
const point = Point x: 1, y: 2
print rendered(point)
print EncodedValue.of(point)
print structureOf<Point>()Declarations#
Encode, Decode, Describe#
trait Encode {
fn encode<Target: Encoder>(var target: Target)
}
trait Decode {
static fn decode<Source: Decoder>(var source: Source): Result<Self, DecodeError>
}
trait Describe {
static fn describe<Target: Describer>(var target: Target)
}encode writes self into whatever format target is; decode is a static fn through Type.decode(source) and
answers the failure a format's decoder produced; describe walks the structure without a value. The format is a type
parameter, so every call is monomorphized per format and direct. A type whose constructor is closed from outside (a
capsule) is written, read and described as the source of its conversion pair (the one with String where there are several).
DecodeError#
type DecodeError with Show, Error {
message: String
path: List<String> = []
problems: List<DecodeError> = []
static fn collecting(problems: List<DecodeError>): Self
fn inside(segment: String): Self
fn each(): List<DecodeError>
fn at(position: Int): Self
fn show(): String
}error.inside("email"): a DecodeError collects the field chain on its way out of nested records, sequences and maps,
so show() renders "user.email: a text is needed" rather than losing where the field was.
Encoder, Decoder, Describer#
trait Encoder {
var fn nothing()
var fn bool(value: Bool)
var fn int(value: Int64)
var fn unsigned(value: UInt64)
var fn float(value: Float64)
var fn decimal(value: Decimal)
var fn string(value: String)
var fn bytes(value: Bytes)
var fn sequence(length: Int?)
var fn map(length: Int?)
var fn record(typeName: String)
var fn variant(typeName: String, name: String)
var fn field(name: String)
var fn finish()
fn writesPrivateFields(): Bool
fn flagsAsNames(): Bool
}
trait Decoder {
var fn nothing(): Bool
var fn bool(): Result<Bool, DecodeError>
var fn int(): Result<Int64, DecodeError>
var fn unsigned(): Result<UInt64, DecodeError>
var fn float(): Result<Float64, DecodeError>
var fn decimal(): Result<Decimal, DecodeError>
var fn string(): Result<String, DecodeError>
var fn bytes(): Result<Bytes, DecodeError>
var fn sequence(): Result<Int?, DecodeError>
var fn map(): Result<Int?, DecodeError>
var fn hasNext(): Result<Bool, DecodeError>
var fn record(typeName: String): Result<Void, DecodeError>
var fn variant(typeName: String): Result<String, DecodeError>
var fn field(name: String): Result<Bool, DecodeError>
var fn finish(): Result<Void, DecodeError>
fn readsPrivateFields(): Bool
fn flagsAsNames(): Bool
}
trait Describer {
var fn bool()
var fn int()
var fn unsigned()
var fn float()
var fn decimal()
var fn string()
var fn bytes()
var fn optional()
var fn sequence()
var fn map()
var fn record(typeName: String): Bool
var fn variant(typeName: String): Bool
var fn variantCase(name: String)
var fn field(description: FieldDescription)
var fn finish()
}The methods are named after the types of the language, and a narrow number is widened before it is written, so nothing
about a format's own numbers is lost. sequence, map, record and variant open, and finish closes the innermost
of them; a record announces each field with field and writes its value right after. A record that announces no field
is a wrapper around its one value. Every method is free of type parameters and closures, so a format is a straight-line
state machine. typeName is the declaration's qualified name - package, module path, type - which is the key a format
finds a mapping for one type by.
Reading is driven by the type: it asks the decoder for what it expects and gets a DecodeError if something else is
there. nothing() is how Option decides between None and Some, and field(name) answers false for a field
the input does not have, so the field's default applies. Describer.record answers false for a type the target has
seen already, which is what makes a type that contains itself describe exactly once.
flagsAsNames() is how a Flags asks its format how to look on the wire. A format answers false unless it is told
otherwise, and then a Flags is its mask, a number. With the option set (Json(flagsAsNames: true),
Yaml(flagsAsNames: true)) a Flags is the sequence of its case names, lowest bit first, each written by the case's
own encode, and read back through the case's own decode: a name that is no case, and a value that is not a
sequence, are a DecodeError. The query is an option of the format, like naming, and not something a type decides.
EncodedValue and the shared decoder#
type EncodedValue with Show {
case Nothing
case Bool(value: Bool)
case Int(value: Int64)
case Unsigned(value: UInt64)
case Float(value: Float64)
case Exact(value: Decimal)
case String(value: String)
case Bytes(value: Bytes)
case Sequence(items: List<Self>)
case Mapping(entries: List<Self>)
case Record(typeName: String, fields: List<EncodedField>)
case Variant(typeName: String, name: String, fields: List<EncodedField>)
static fn of<Value: Encode>(value: Value, privateFields: Bool = false): Self
fn show(): String
fn plain(): String
}
extend EncodedValue with Encode
fn rendered<Value: Encode>(value: Value): String
type Values with Encoder {
privateFields: Bool = false
fn writesPrivateFields(): Bool
fn result(): EncodedValue
var fn nothing()
var fn bool(value: Bool)
var fn int(value: Int64)
var fn unsigned(value: UInt64)
var fn float(value: Float64)
var fn decimal(value: Decimal)
var fn string(value: String)
var fn bytes(value: Bytes)
var fn sequence(length: Int?)
var fn map(length: Int?)
var fn record(typeName: String)
var fn variant(typeName: String, name: String)
var fn field(name: String)
var fn finish()
}
type ValueDecoder with Decoder {
static fn of(value: EncodedValue, lenient: Bool = false, naming: Naming = Naming.Unchanged, strict: Bool = false, privateFields: Bool = false, flagsAsNames: Bool = false, exactAsText: Bool = false): Self
fn readsPrivateFields(): Bool
fn flagsAsNames(): Bool
var fn nothing(): Bool
var fn bool(): Result<Bool, DecodeError>
var fn int(): Result<Int64, DecodeError>
var fn unsigned(): Result<UInt64, DecodeError>
var fn float(): Result<Float64, DecodeError>
var fn decimal(): Result<Decimal, DecodeError>
var fn string(): Result<String, DecodeError>
var fn bytes(): Result<Bytes, DecodeError>
var fn sequence(): Result<Int?, DecodeError>
var fn map(): Result<Int?, DecodeError>
var fn hasNext(): Result<Bool, DecodeError>
var fn record(typeName: String): Result<Void, DecodeError>
var fn variant(typeName: String): Result<String, DecodeError>
var fn field(name: String): Result<Bool, DecodeError>
var fn finish(): Result<Void, DecodeError>
}EncodedValue is any Encode value held without its type: a quotation's captures, a field default in a schema, the
text of rendered(value). It is built by Values, an ordinary Encoder, and it writes itself into any other one. An
exact Decimal is an EncodedValue.Exact with every digit and its scale: 19.90 stays 19.90. ValueDecoder is the Decoder every buffering format shares: a text format with
unordered fields parses into an EncodedValue and reads a typed value out of it, with the options lenient (numbers
from text), naming, strict, flagsAsNames and exactAsText (a Decimal from a text, for a format without a decimal
type, such as TOML) of the format. A Decimal field that meets a text refuses it unless one of lenient and
exactAsText is set. JSON and YAML read a number that a Float64 does not print back as an Exact, so 19.90 and a
value of 40 digits keep their digits in a document without a type.
Structure#
fn structureOf<Value: Describe>(): Structure
type Structure with Show {
case Boolean
case Integer
case Natural
case Floating
case Exact
case Text
case Binary
case Optional(of: Self)
case Sequence(of: Self)
case Mapping(key: Self, value: Self)
case Record(typeName: String, fields: List<StructureField>)
case Wrapper(typeName: String, of: Self)
case Variant(typeName: String, cases: List<StructureCase>)
case Reference(typeName: String)
fn recordFields(): List<StructureField>
fn declaredName(): String
fn show(): String
}
type StructureField {
description: FieldDescription
of: Structure
fn name(): String
fn isRequired(): Bool
}
type FieldDescription {
name: String
documentation: String = ""
default: FieldDefault = FieldDefault.Required
}
type FieldDefault {
case Required
case Computed
case Constant(value: EncodedValue)
}
type Structures with Describer {
fn result(): Structure
var fn bool()
var fn int()
var fn unsigned()
var fn float()
var fn decimal()
var fn string()
var fn bytes()
var fn optional()
var fn sequence()
var fn map()
var fn record(typeName: String): Bool
var fn variant(typeName: String): Bool
var fn variantCase(name: String)
var fn field(description: FieldDescription)
var fn finish()
}structureOf<Order>() is a type's description as a tree, which is what a schema format wants to walk: a record with its
fields in declaration order, each with its doc comment and whether it is required, has a default that is a constant, or
has one computed on the way in. A type that is already being described further up is a Reference to its name.
Naming#
type Naming {
case Unchanged
case SnakeCase
fn applied(name: String): String
}How a format spells the field names a type declares. It is an option of a format (Json(naming: .SnakeCase)) and never
a word on the type.
The steps a derived form is made of#
encodeField, decodeRequired, decodeHasField, describeField and the rest of src/derived.trb are what the
compiler generates a derived encode, decode and describe out of: one call per field, instantiated for the field's
type and the format. They are public so that what a derived form does can be read, and so that a hand-written form can
be made of the same pieces.
Format#
trait Format<Failure> {
static fn encodeAll<Value: Encode>(value: Value): Bytes
static fn decodeAll<Value: Decode>(bytes: Bytes): Result<Value, Failure>
static fn items<Item: Decode>(): Stage<Bytes, Result<Item, Failure>>
static fn encoded<Item: Encode>(): Stage<Item, Bytes>
}What a whole format is, once: bytes in both directions, plus the two Stages (see std/iteration) that
make it work on a Source of std/stream - body.through(Json.items<User>()).checked() reads one User
at a time out of a byte stream, with memory of one value. Failure is a type parameter rather than an associated type,
because the language has no associated types: Json is a Format<JsonError>. The members are static and use the
format's default options. Encode/Decode and Encoder/Decoder stay synchronous either way; streaming happens one
level up, at the element, through a resumable framer that finds where one element ends while the element itself
decodes through the ordinary Decode.
Base64Alphabet, base64Decoded()#
type EncodingError with Show, Error {
reason: String
offset: Int
fn show(): String
}
type Base64Alphabet with Show, Equals, Hash {
case Standard
case UrlSafe
fn encoded(bytes: Bytes, padded: Bool = true): String
}
extend String {
fn hexDecoded(): Result<Bytes, EncodingError>
fn base64Decoded(): Result<Bytes, EncodingError>
}
extend String with Encode, Decode, DescribeBase64 (RFC 4648 sections 4 and 5): Base64Alphabet.Standard (+, /) for everywhere nothing says otherwise, and
Base64Alphabet.UrlSafe (-, _) for a JSON Web Token's segments and a JSON Web Key's modulus - each with or without
the = padding. text.base64Decoded() reads either alphabet, padded or not, in the same call: the two agree on 62 of their
64 characters, so nothing is lost by not asking which one a text was written in. Whitespace inside the text is
skipped, the shape a wrapped MIME block needs.
use Base64Alphabet from "std/encoding"
use String.base64Decoded from "std/encoding"
const encoded = Base64Alphabet.UrlSafe.encoded "hi".bytes(), padded: false
print encoded
print encoded.base64Decoded()Base32Alphabet#
type Base32Alphabet with Show, Equals, Hash {
case Standard
case Extended
fn encoded(bytes: Bytes, padded: Bool = true): String
fn decoded(text: String): Result<Bytes, EncodingError>
}Base32 (RFC 4648 sections 6 and 7): Base32Alphabet.Standard (A-Z, 2-7), what NATS NKeys and a TOTP secret are
written in, and Base32Alphabet.Extended (0-9, A-V, "base32hex"), which sorts the same as the bytes it stands
for. Unlike Base64's two alphabets, these are not read together - A means 0 in one and 10 in the other - so decoding
takes the alphabet the text was written in.
use Base32Alphabet from "std/encoding"
print Base32Alphabet.Standard.encoded("foobar".bytes())Hexadecimal: hexEncoded(), hexDecoded()#
extend Bytes {
fn hexEncoded(uppercase: Bool = false): String
}Hexadecimal (RFC 4648 section 8): two digits a byte, most significant nibble first. bytes.hexEncoded() writes lower case
unless told otherwise; text.hexDecoded() reads either case, mixed or not. Digest.hex() of std/digest is this
in one direction only, fixed to lower case, for a hash's own printing.
use Buffer.hexEncoded, String.hexDecoded from "std/encoding"
print "hi".bytes().hexEncoded()
print "6869".hexDecoded()Related#
- std/json -
Json, the one format the standard library implements. - Encode and Decode - the rule the three forms are derived by.
- std/iteration -
Stage, whichFormat.itemsandFormat.encodedanswer. - std/digest -
Digest.hex(), andHmac/Hmac.pbkdf2over SHA-256, SHA-1 and MD5. - The standard library - the other packages.