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,
Err(e) => match e {
update::error::PrivacyAllowList::Overlapping(overlap) =>anyhow::bail!("`--allow` and `--disallow` must not overlap: {overlap:?}"),
update::error::PrivacyAllowList::PublicVisibility => return Err(Error::WithHint {
err:
update::error::PrivacyAllowList::PublicVisibility => return Err(Error::with_hint(
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`",
}.into())
"use `--visibility private` to make the repository private, or perhaps you meant to use `--delegate`/`--rescind`")
.into())
}
};
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 issue = issues
.get(&id)
.map_err(|e| Error::WithHint {
err: e.into(),
hint: "reset the cache with `rad issue cache` and try again",
.map_err(|e| {
Error::with_hint(e, "reset the cache with `rad issue cache` and try again")
})?
.context("No issue with the given ID exists")?;
term::issue::show(&issue, &id, format, args.verbose, &profile)?;

View File

@ -33,10 +33,9 @@ pub fn run(
workdir: Option<&git::raw::Repository>,
) -> anyhow::Result<()> {
let patches = term::cob::patches(profile, stored)?;
let Some(patch) = patches.get(patch_id).map_err(|e| Error::WithHint {
err: e.into(),
hint: "reset the cache with `rad patch cache` and try again",
})?
let Some(patch) = patches
.get(patch_id)
.map_err(|e| Error::with_hint(e, "reset the cache with `rad patch cache` and try again"))?
else {
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();
if doc.is_public() {
return Err(term::Error::WithHint {
err: anyhow!("repository is already public"),
hint: "to announce the repository to the network, run `rad sync --inventory`",
}
return Err(term::Error::with_hint(
anyhow!("repository is already public"),
"to announce the repository to the network, run `rad sync --inventory`",
)
.into());
}
if !doc.is_delegate(&profile.id().into()) {
return Err(anyhow!("only the repository delegate can publish it"));
}
if doc.delegates().len() > 1 {
return Err(term::Error::WithHint {
err: anyhow!(
"only repositories with a single delegate can be published with this command"
),
hint: "see `rad id --help` to publish repositories with more than one delegate",
}
return Err(term::Error::with_hint(
anyhow!("only repositories with a single delegate can be published with this command"),
"see `rad id --help` to publish repositories with more than one delegate",
)
.into());
}
let signer = profile.signer()?;

View File

@ -45,10 +45,10 @@ impl Context for DefaultContext {
fn profile(&self) -> Result<Profile, anyhow::Error> {
match Profile::load() {
Ok(profile) => Ok(profile),
Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::WithHint {
err: anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
hint: "To setup your radicle profile, run `rad auth`.",
}
Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::with_hint(
anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
"To setup your radicle profile, run `rad auth`.",
)
.into()),
Err(radicle::profile::Error::LoadConfig(e)) => Err(e.into()),
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 {
/// An error with a hint.
#[error("{err}")]
WithHint {
err: anyhow::Error,
hint: &'static str,
},
WithHint { err: anyhow::Error, hint: String },
}
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

View File

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