cli: promote WithHint hint type to String

Changes the `Error::WithHint::hint` field to use a `String` type instead of
`&'static str`. This allows the caller to provide extra hint
information.

To make it easier to use an `Error::with_hint` constructor is added.
This commit is contained in:
Adrian Duke 2026-01-19 18:47:44 +00:00 committed by Fintan Halpenny
parent 67a4a712e4
commit 60959f7e83
7 changed files with 36 additions and 34 deletions

View File

@ -136,11 +136,10 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
Ok(proposal) => proposal, Ok(proposal) => proposal,
Err(e) => match e { Err(e) => match e {
update::error::PrivacyAllowList::Overlapping(overlap) =>anyhow::bail!("`--allow` and `--disallow` must not overlap: {overlap:?}"), update::error::PrivacyAllowList::Overlapping(overlap) =>anyhow::bail!("`--allow` and `--disallow` must not overlap: {overlap:?}"),
update::error::PrivacyAllowList::PublicVisibility => return Err(Error::WithHint { update::error::PrivacyAllowList::PublicVisibility => return Err(Error::with_hint(
err:
anyhow!("`--allow` and `--disallow` should only be used for private repositories"), anyhow!("`--allow` and `--disallow` should only be used for private repositories"),
hint: "use `--visibility private` to make the repository private, or perhaps you meant to use `--delegate`/`--rescind`", "use `--visibility private` to make the repository private, or perhaps you meant to use `--delegate`/`--rescind`")
}.into()) .into())
} }
}; };
let threshold = proposal.threshold; let threshold = proposal.threshold;

View File

@ -122,9 +122,8 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let id = id.resolve(&repo.backend)?; let id = id.resolve(&repo.backend)?;
let issue = issues let issue = issues
.get(&id) .get(&id)
.map_err(|e| Error::WithHint { .map_err(|e| {
err: e.into(), Error::with_hint(e, "reset the cache with `rad issue cache` and try again")
hint: "reset the cache with `rad issue cache` and try again",
})? })?
.context("No issue with the given ID exists")?; .context("No issue with the given ID exists")?;
term::issue::show(&issue, &id, format, args.verbose, &profile)?; term::issue::show(&issue, &id, format, args.verbose, &profile)?;

View File

@ -33,10 +33,9 @@ pub fn run(
workdir: Option<&git::raw::Repository>, workdir: Option<&git::raw::Repository>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let patches = term::cob::patches(profile, stored)?; let patches = term::cob::patches(profile, stored)?;
let Some(patch) = patches.get(patch_id).map_err(|e| Error::WithHint { let Some(patch) = patches
err: e.into(), .get(patch_id)
hint: "reset the cache with `rad patch cache` and try again", .map_err(|e| Error::with_hint(e, "reset the cache with `rad patch cache` and try again"))?
})?
else { else {
anyhow::bail!("Patch `{patch_id}` not found"); anyhow::bail!("Patch `{patch_id}` not found");
}; };

View File

@ -25,22 +25,20 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let doc = identity.doc(); let doc = identity.doc();
if doc.is_public() { if doc.is_public() {
return Err(term::Error::WithHint { return Err(term::Error::with_hint(
err: anyhow!("repository is already public"), anyhow!("repository is already public"),
hint: "to announce the repository to the network, run `rad sync --inventory`", "to announce the repository to the network, run `rad sync --inventory`",
} )
.into()); .into());
} }
if !doc.is_delegate(&profile.id().into()) { if !doc.is_delegate(&profile.id().into()) {
return Err(anyhow!("only the repository delegate can publish it")); return Err(anyhow!("only the repository delegate can publish it"));
} }
if doc.delegates().len() > 1 { if doc.delegates().len() > 1 {
return Err(term::Error::WithHint { return Err(term::Error::with_hint(
err: anyhow!( anyhow!("only repositories with a single delegate can be published with this command"),
"only repositories with a single delegate can be published with this command" "see `rad id --help` to publish repositories with more than one delegate",
), )
hint: "see `rad id --help` to publish repositories with more than one delegate",
}
.into()); .into());
} }
let signer = profile.signer()?; let signer = profile.signer()?;

View File

@ -45,10 +45,10 @@ impl Context for DefaultContext {
fn profile(&self) -> Result<Profile, anyhow::Error> { fn profile(&self) -> Result<Profile, anyhow::Error> {
match Profile::load() { match Profile::load() {
Ok(profile) => Ok(profile), Ok(profile) => Ok(profile),
Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::WithHint { Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::with_hint(
err: anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()), anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
hint: "To setup your radicle profile, run `rad auth`.", "To setup your radicle profile, run `rad auth`.",
} )
.into()), .into()),
Err(radicle::profile::Error::LoadConfig(e)) => Err(e.into()), Err(radicle::profile::Error::LoadConfig(e)) => Err(e.into()),
Err(e) => Err(anyhow::anyhow!("Could not load radicle profile: {e}")), Err(e) => Err(anyhow::anyhow!("Could not load radicle profile: {e}")),

View File

@ -8,10 +8,20 @@ use radicle::prelude::{Did, NodeId, RepoId};
pub(crate) enum Error { pub(crate) enum Error {
/// An error with a hint. /// An error with a hint.
#[error("{err}")] #[error("{err}")]
WithHint { WithHint { err: anyhow::Error, hint: String },
err: anyhow::Error, }
hint: &'static str,
}, impl Error {
pub fn with_hint<E, H>(err: E, hint: H) -> Self
where
E: Into<anyhow::Error>,
H: ToString,
{
Self::WithHint {
err: err.into(),
hint: hint.to_string(),
}
}
} }
/// Targets used in the `block` and `unblock` commands /// Targets used in the `block` and `unblock` commands

View File

@ -108,10 +108,7 @@ fn with_hint(e: profile::Error) -> anyhow::Error {
#[allow(clippy::wildcard_enum_match_arm)] #[allow(clippy::wildcard_enum_match_arm)]
match e { match e {
profile::Error::CobsCache(cob::cache::Error::OutOfDate) => { profile::Error::CobsCache(cob::cache::Error::OutOfDate) => {
anyhow::Error::from(terminal::args::Error::WithHint { terminal::args::Error::with_hint(e, MIGRATION_HINT).into()
err: e.into(),
hint: MIGRATION_HINT,
})
} }
e => anyhow::Error::from(e), e => anyhow::Error::from(e),
} }