kex docs Standard Library 0.4.0-alpha kex.run ↗

Binaryprelude

type Binary

An opaque, immutable sequence of bytes. A Binary never implicitly becomes text.

[Byte] is the materialized list of bytes, while Binary is the storage form. A file, an HTTP body, or a digest is a Binary. On the BEAM it is a native binary, so slicing shares storage instead of copying. None of the operations here walks a character list.

let data = Binary.fromHex("00686900").try
data.length            # => 4
data.at(1)             # => Just(104)
data.take(2).hex       # => "0068"
data.showValue         # => "#Binary<4 bytes>"

The type is opaque on purpose: there is no field to reach through, so text operations cannot be run on bytes by accident. Conversions are always explicit in both directions, and both directions can fail. Runtime conversions through to always return an Optional<T>:

"árvíz".to(Binary)                  #  => Just(#Binary<7 bytes>) : Binary?
Binary.fromBytes([255]).to(String)  # => None

Showable and Inspectable deliberately render the length alone. Neither ever decodes or interpolates the payload, so printing a binary cannot leak its contents or fail on bytes that are not text. Use hex, base64, or to(String) when you actually want to see it.

module Binary

constant empty Binary

The binary holding no bytes.

Backed by Binary.fromBytes([]), this is the natural starting value this library uses, just like (Headers.empty).

function fromBytes

Builds a binary from a list of bytes.

Total: every Byte is in 0..255 by construction, so there is no rejection case. Binary.fromBytes([]) is the empty binary.

fromBytes(values) : [Byte] -> Binary
Parameters
values [Byte]
the bytes to store

Returns: Binary — the bytes, in binary storage

Examples
Binary.fromBytes([104, 105])   # => #Binary<2 bytes>
Binary.fromBytes([])           # => #Binary<0 bytes>

function fromHex

Decodes lowercase hexadecimal text.

Strict, so that decoding is the exact inverse of hex: uppercase digits, an odd length, whitespace, a 0x prefix, and any non-hex character all answer None rather than being repaired.

fromHex(text) : String -> Binary?
Parameters
text String
the hexadecimal text

Returns: Binary? — the bytes, or None when the text is not canonical hex

Examples
Binary.fromHex("0068")   # => Just(#Binary<2 bytes>)
Binary.fromHex("0x68")   # => None
Binary.fromHex("AB")     # => None
Binary.fromHex("a")      # => None

Reading a compact identifier from configuration

let key = env.get("CACHE_KEY").flatMap(~Binary.fromHex)
let cacheEnabled? = key.present?

function fromBase64

Decodes standard base64 text (RFC 4648).

Strict, so that decoding is the exact inverse of base64: the URL-safe alphabet, missing or malformed padding, whitespace, and any other noncanonical encoding all answer None.

fromBase64(text) : String -> Binary?
Parameters
text String
the base64 text

Returns: Binary? — the bytes, or None when the text is not canonical

Examples
Binary.fromBase64("AGhp/w==")   # => Just(#Binary<4 bytes>)
Binary.fromBase64("AGhp_w==")   # => None
Binary.fromBase64("AGhp/w")     # => None

Loading binary key material from an environment variable

let signingKey = ENV.get("SIGNING_KEY").flatMap(~Binary.fromBase64)

make Binary implements Showable, Inspectable

at

The byte at index, counting from zero.

Answers None for a negative or out-of-range index, so an index you did not check is something you handle rather than something that stops the program.

at(index)
Parameters
index Integer
the position to read

Returns: Byte? — the byte, or None when the index is outside the binary

Examples
let data = Binary.fromBytes([104, 105])
data.at(0)    # => Just(104)
data.at(2)    # => None
data.at(-1)   # => None

take

The first count bytes.

Clamped at both ends, like the list operations: a count of zero or less answers the empty binary, and an oversized one answers the whole binary.

take(count)
Parameters
count Integer
how many bytes to keep

Returns: Binary — the leading bytes

Examples
let data = Binary.fromBytes([104, 105])
data.take(1).bytes     # => [104]
data.take(0).bytes     # => []
data.take(999) == data # => true

drop

Everything after the first count bytes.

Clamped at both ends: a count of zero or less answers the whole binary, and an oversized one answers the empty binary. take(n) and drop(n) therefore always concatenate back to the original.

drop(count)
Parameters
count Integer
how many bytes to skip

Returns: Binary — the remaining bytes

Examples
let data = Binary.fromBytes([104, 105])
data.drop(1).bytes                    # => [105]
(data.take(1) + data.drop(1)) == data # => true

+

Joins two binaries end to end.

+(other)
Parameters
other Binary
the bytes to append

Returns: Binary — the two payloads, in order

Examples
Binary.fromBytes([104]) + Binary.fromBytes([105])   # => #Binary<2 bytes>

inspectValue

The length-only rendering: never the payload. The same as showValue.

inspectValue(_)

Returns: String#Binary<N bytes>

to

Decodes the payload as UTF-8, or answers None when the bytes are not valid text.

This deliberately differs from showValue, which reveals only the byte count. Converting asks to inspect the actual payload and therefore makes the possibility of invalid text explicit.

to(String)

Returns: String? — the decoded text, or None for invalid UTF-8

Examples

Decoding a text response while handling a binary one

match response.body.to(String) do
  Just(text) => IO.printLine(text)
  None       => IO.printLine("received ${response.body.length} bytes")
end