use anyhow::anyhow;
use knf::{
IntegerOutOfRange, InterpError, LoadError, MergeError, NonFiniteFloat, NullInToml, PathError,
Problem, RuleError, RuleErrors, TomlError,
};
pub fn explain_pipeline(err: anyhow::Error) -> anyhow::Error {
let err = match err.downcast::<LoadError>() {
Ok(err) => return explain_load(err),
Err(err) => err,
};
let err = match err.downcast::<MergeError>() {
Ok(err) => return name_the_flag(err),
Err(err) => err,
};
let err = match err.downcast::<InterpError>() {
Ok(err) => return explain_interp(err),
Err(err) => err,
};
let err = match err.downcast::<NonFiniteFloat>() {
Ok(report) => return explain_non_finite(report),
Err(err) => err,
};
match err.downcast::<TomlError>() {
Ok(TomlError::Null(report)) => explain_null(report),
Ok(TomlError::Integer(report)) => explain_integer(report),
Ok(err @ TomlError::Datetime(_)) => err.into(),
Err(err) => err,
}
}
fn explain_null(err: NullInToml) -> anyhow::Error {
anyhow!("{err}\nhelp: emit JSON with -f json, substitute with --null-as, or remove the null")
}
fn explain_integer(err: IntegerOutOfRange) -> anyhow::Error {
anyhow!("{err}\nhelp: TOML integers are signed 64-bit; emit JSON with -f json")
}
fn explain_non_finite(err: NonFiniteFloat) -> anyhow::Error {
anyhow!("{err}\nhelp: emit TOML with -f toml, which can represent inf and nan")
}
fn explain_load(err: LoadError) -> anyhow::Error {
match &err {
LoadError::StdinNeedsFormat | LoadError::UnknownExtension { .. } => {
anyhow!("{err}: pass --input-format json or --input-format toml")
}
LoadError::Directory { path } => anyhow!(
"{err}\nhelp: `knf {}/*.toml` merges its files as layers",
path.display()
),
}
}
pub fn name_the_rule_flag(err: PathError, flag: &str) -> anyhow::Error {
match err {
PathError::IndexInKeyPath { .. } => {
anyhow!("{err}\nhelp: {flag} takes a key path; a rule cannot name an array element")
}
other => other.into(),
}
}
pub fn name_the_set_flag(err: PathError) -> anyhow::Error {
match err {
PathError::IndexInKeyPath { .. } => anyhow!(
"{err}\nhelp: --set takes KEY.PATH=VALUE; an index like servers[0] can be read\n \
by a ${{...}} reference but never written — put the value in a file instead"
),
other => other.into(),
}
}
pub fn explain_rules(errors: RuleErrors) -> anyhow::Error {
const FLAGS: &str = "--append, --replace and --fail";
let mut help = String::new();
if errors
.errors()
.iter()
.any(|e| matches!(e, RuleError::Conflict { .. }))
{
help.push_str(&format!(
"\nhelp: a path may be named by only one of {FLAGS}"
));
}
if errors
.errors()
.iter()
.any(|e| matches!(e, RuleError::Unreachable { .. }))
{
help.push_str(&format!(
"\nhelp: {FLAGS} take the whole value at their path, so a rule below one can never fire"
));
}
anyhow!("{errors}{help}")
}
fn name_the_flag(err: MergeError) -> anyhow::Error {
let help = match err {
MergeError::Locked { .. } => {
"help: --fail pins a path to the first layer that sets it; drop the flag or the later value"
}
MergeError::AppendKind { .. } => "help: --append needs an array on both sides",
MergeError::TypeConflict { .. } => return err.into(),
};
anyhow!("{err}\n{help}")
}
fn explain_interp(err: InterpError) -> anyhow::Error {
let mut help = String::new();
match &err {
InterpError::Cycle(_) => {
help.push_str("\nhelp: a reference may not resolve, directly or indirectly, to itself")
}
InterpError::Problems(problems) => {
let has = |f: fn(&Problem) -> bool| problems.iter().any(f);
let syntax = has(|p| matches!(p, Problem::Syntax { .. }));
let unresolved = has(|p| matches!(p, Problem::Unresolved { .. }));
if syntax {
help.push_str(
"\nhelp: a reference is `${key.path}` (with `[n]` for array elements) or `${env:NAME}`; write `$$` for a literal `$`",
);
}
if unresolved {
help.push_str(
"\nhelp: `${key.path}` names a key in the merged document, `${env:NAME}` an environment variable",
);
}
if has(|p| matches!(p, Problem::NotStringifiable { .. })) {
help.push_str(
"\nhelp: an object or array reference must be the whole string, not embedded in one",
);
}
if syntax || unresolved {
help.push_str("\nhelp: drop --interpolate to pass `${...}` through untouched");
}
}
}
anyhow!("{err}{help}")
}