// FR-34 — ExecutionDecision: one word for "may the caller act on this value".
//
// Until now a caller had to read three fields (`exactness`, `ambiguous`, and
// whether `repairs` is empty) and know that `for_execution()` refuses rather
// than repairs. The decision is that reading, made once, in the library, with
// the risk order fixed:
//
// ambiguous -> lossy -> cosmetic -> safe
//
// The order is not arbitrary. A repair with a second reading is worse than one
// that merely invented content, because the caller cannot even tell what was
// invented: `["a" "b"]` may have become `["a","b"]` or `"ab"`. That is why
// `Ambiguous` outranks `LossyRepair`, and why `Options::for_execution()` raises
// `E011` before it raises `E010` (FR-27).
///|
/// What a caller may do with a result (FR-34).
pub(all) enum ExecutionDecision {
/// Valid JSON that needed nothing.
Safe
/// Only the notation was normalised: the value is byte-for-byte the value the
/// input already denoted, so the result may be executed.
CosmeticRepair
/// A repair invented, dropped, truncated or replaced a value
/// (`Severity::Warning`). Correct for a display channel, not for an effect.
LossyRepair
/// A repair admits a second reading (FR-27).
Ambiguous
/// No value was produced: the input could not be repaired under the options,
/// or the value does not satisfy the schema it was checked against.
Invalid
}
///|
/// Explicit `Eq` implementation for `ExecutionDecision`.
pub impl Eq for ExecutionDecision with fn equal(
self : ExecutionDecision,
other : ExecutionDecision,
) -> Bool {
self.name() == other.name()
}
///|
/// Explicit `Eq` extension for `ExecutionDecision`.
pub extend ExecutionDecision with Eq::{not_equal, equal}
///|
/// Stable lowercase name, e.g. `lossy_repair`.
pub fn ExecutionDecision::name(self : ExecutionDecision) -> String {
match self {
Safe => "safe"
CosmeticRepair => "cosmetic_repair"
LossyRepair => "lossy_repair"
Ambiguous => "ambiguous"
Invalid => "invalid"
}
}
///|
/// Whether the caller may execute: `Safe` and `CosmeticRepair` only.
pub fn ExecutionDecision::is_safe(self : ExecutionDecision) -> Bool {
match self {
Safe | CosmeticRepair => true
LossyRepair | Ambiguous | Invalid => false
}
}
///|
/// Upper-case label for a human, e.g. `SAFE TO EXECUTE`.
pub fn ExecutionDecision::label(self : ExecutionDecision) -> String {
match self {
Safe => "SAFE TO EXECUTE"
CosmeticRepair => "SAFE TO EXECUTE (notation normalised)"
LossyRepair => "REJECT (a value was changed)"
Ambiguous => "BLOCKED (two readings)"
Invalid => "REJECT (not repairable)"
}
}
///|
/// The decision for a result that was produced: `repairs` is the number of
/// recorded records, because `exactness` alone cannot tell a clean document
/// from one that needed a cosmetic repair (both are `Exact`).
pub fn decide(
repairs : Int32,
exactness : Exactness,
ambiguous : Bool,
) -> ExecutionDecision {
if ambiguous {
Ambiguous
} else if exactness is Lossy {
LossyRepair
} else if repairs > 0 || exactness is Cosmetic {
CosmeticRepair
} else {
Safe
}
}
///|
/// The decision for a value that was refused: `E011` is an ambiguity, every
/// other refusal of a repair (`E010`) is a lossy one, and a fatal error that is
/// not a repair refusal is `Invalid`.
pub fn decide_error(code : String) -> ExecutionDecision {
match code {
"E011" => Ambiguous
"E010" => LossyRepair
_ => Invalid
}
}
///|
/// The risk of a decision, used to order two decisions (FR-34): `Safe` 0,
/// `CosmeticRepair` 1, `LossyRepair` 2, `Ambiguous` 3, `Invalid` 4.
pub fn ExecutionDecision::rank(self : ExecutionDecision) -> Int32 {
match self {
Safe => 0
CosmeticRepair => 1
LossyRepair => 2
Ambiguous => 3
Invalid => 4
}
}
///|
/// The riskier of two decisions. Used to combine the parse decision with the
/// schema verdict without inventing a sixth variant.
pub fn ExecutionDecision::worse_of(
self : ExecutionDecision,
other : ExecutionDecision,
) -> ExecutionDecision {
if other.rank() > self.rank() {
other
} else {
self
}
}