// A field that can be absent, present-and-null, or present with a value.
//
// Three fields in the whole in-scope corpus declare `nullable`, and only one of
// them is a client's to set -- but that one carries real meaning:
// `putRecord`'s `swapRecord` is an optimistic-concurrency check where
//
// absent don't care whether the record already exists
// null it must NOT already exist
// a CID it must exist AND be exactly this version
//
// An `Option` cannot say that. Collapsing the two empty cases -- which is what
// rsky does, its serde structs having nowhere to put the distinction -- turns
// "create only if absent" into "overwrite whatever is there".
//
// Used only where the Lexicon says `nullable`. Everywhere else an absent field
// is an `Option`, and encoding omits it.
///|
pub(all) enum Nullable[T] {
Absent
Null
Value(T)
} derive(Eq, Debug)
///|
/// The value, if there is one. Both empty cases collapse here, which is fine
/// for a reader -- it is the WRITER that needs all three.
pub fn[T] Nullable::value(self : Self[T]) -> T? {
match self {
Value(v) => Some(v)
_ => None
}
}
///|
pub fn[T] Nullable::is_absent(self : Self[T]) -> Bool {
self is Absent
}
///|
pub fn[T] Nullable::is_null(self : Self[T]) -> Bool {
self is Null
}
///|
/// Reads a nullable field, consuming it. `Absent` when the key is not there,
/// `Null` when it is there and null.
pub fn[T] take_nullable(
rest : Map[String, LexValue],
key : String,
path? : String = "",
parse : (LexValue, String) -> T raise DecodeError,
) -> Nullable[T] raise DecodeError {
match rest.get(key) {
None => Absent
Some(Null) => {
rest.remove(key)
Null
}
Some(value) => {
rest.remove(key)
Value(parse(value, field_path(path, key)))
}
}
}
///|
/// Writes a nullable field. `Absent` writes nothing; `Null` writes an explicit
/// null, which is the whole point -- this is the one place in the library where
/// emitting a null is correct rather than a bug.
pub fn[T] put_nullable(
out : Map[String, LexValue],
key : String,
value : Nullable[T],
encode : (T) -> LexValue,
) -> Unit {
match value {
Absent => ()
Null => out[key] = LexValue::Null
Value(v) => out[key] = encode(v)
}
}