// Decoding a value that is already in hand, rather than a field of an object.
//
// The `take_*` family reads a named field; these read the value itself, which
// is what an array element and a union member are. Generated code passes them
// as decoders: `@data.take_array(rest, "langs", path~, @data.expect_string)`.
//
// Unlike `take_*`, a wrong type here RAISES rather than answering `None`. There
// is no `extra` to fall back into -- an array element that is not what the
// schema says it is has nowhere to go, and dropping it would silently shorten
// the list.
///|
pub fn expect_string(
value : LexValue,
path : String,
) -> String raise DecodeError {
match value.as_string() {
Some(s) => s
None =>
raise DecodeError(path~, reason="expected string, got \{value.kind()}")
}
}
///|
pub fn expect_int(value : LexValue, path : String) -> Int64 raise DecodeError {
match value.as_int() {
Some(i) => i
None =>
raise DecodeError(path~, reason="expected integer, got \{value.kind()}")
}
}
///|
pub fn expect_bool(value : LexValue, path : String) -> Bool raise DecodeError {
match value.as_bool() {
Some(b) => b
None =>
raise DecodeError(path~, reason="expected boolean, got \{value.kind()}")
}
}
///|
pub fn expect_bytes(value : LexValue, path : String) -> Bytes raise DecodeError {
match value.as_bytes() {
Some(b) => b
None =>
raise DecodeError(path~, reason="expected bytes, got \{value.kind()}")
}
}
///|
pub fn expect_link(
value : LexValue,
path : String,
) -> @syntax.Cid raise DecodeError {
match value.as_link() {
Some(c) => c
None =>
raise DecodeError(path~, reason="expected cid-link, got \{value.kind()}")
}
}
///|
/// A string in one of the eleven Lexicon formats.
pub fn[T] expect_format(
value : LexValue,
path : String,
parse : (String) -> T raise @syntax.SyntaxError,
) -> T raise DecodeError {
let text = expect_string(value, path)
parse(text) catch {
e => raise DecodeError(path~, reason=e.reason())
}
}
///|
/// An array, every element decoded by `parse`. All-or-nothing, for the same
/// reason `take_array` is: a half-decoded list round-trips as a shorter list.
pub fn[T] expect_array(
value : LexValue,
path : String,
parse : (LexValue, String) -> T raise DecodeError,
) -> Array[T] raise DecodeError {
guard value.as_array() is Some(items) else {
raise DecodeError(path~, reason="expected array, got \{value.kind()}")
}
let out = Array::new(capacity=items.length())
for i, item in items {
out.push(parse(item, index_path(path, i)))
}
out
}
///|
/// Adds a `$type` to an object, for a value being encoded as a union member.
///
/// The UNION owns the tag, not the member: `com.atproto.repo.strongRef` is a
/// bare `{uri, cid}` on its own and only carries a `$type` where a union needs
/// to tell it from its siblings. Tagging the member type instead -- which is
/// what rsky does, inconsistently -- puts a `$type` on values that should not
/// have one.
///
/// A non-object is returned unchanged; there is nowhere to put a tag, and a
/// union whose members are not objects could not be dispatched anyway.
pub fn tagged(value : LexValue, type_ : String) -> LexValue {
match value {
Obj(fields) => {
let out : Map[String, LexValue] = Map([])
out["$type"] = Str(type_)
for key, item in fields {
if key != "$type" {
out[key] = item
}
}
Obj(out)
}
other => other
}
}