mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-01 12:26:02 +03:00
review error docs to fit the latest changes
This commit is contained in:
parent
567a077582
commit
83893a9db3
@ -2,74 +2,115 @@
|
||||
//!
|
||||
//! This is a primer on how to use the types provided here.
|
||||
//!
|
||||
//! Generally, if you do not care about attaching an error code for error-classification or
|
||||
//! and/or messages that may show up in the user interface, read no further. Just use `anyhow`
|
||||
//! or `thiserror` like before.
|
||||
//! **tl;dr** - use `anyhow::Result` by direct import so a typical function looks like this:
|
||||
//!
|
||||
//! ### Adding Context
|
||||
//!
|
||||
//! The [`Context`] type is the richest context we may attach to either `anyhow` errors or `thiserror`,
|
||||
//! albeit using a different mechanism. This context is maintained as the error propagates and the
|
||||
//! context higest up the error chain, the one most recently added, can be used by higher layers
|
||||
//! of the GitButler application. Currently, a [`Context::message`] is shown in the user-interface,
|
||||
//! whereas [`Context::code`] can be provided to help the user interface to make decisions, as it uses
|
||||
//! the code for classifying errors.
|
||||
//!
|
||||
//! #### With `anyhow`
|
||||
//!
|
||||
//! The basis is an error without context, just by using `anyhow` in any way.
|
||||
//!
|
||||
//!```rust
|
||||
//!# use anyhow::bail;
|
||||
//! fn f() -> anyhow::Result<()> {
|
||||
//! bail!("internal information")
|
||||
//! ```rust
|
||||
//!# use anyhow::{Result, bail};
|
||||
//! fn f() -> Result<()> {
|
||||
//! bail!("this went wrong")
|
||||
//! }
|
||||
//!```
|
||||
//! ```
|
||||
//!
|
||||
//! Adding context is as easy as using the `context()` method on any `Result` or [`anyhow::Error`].
|
||||
//! This can be a [`Code`], which automatically uses the message provided previously in the
|
||||
//! frontend (note, though, that this is an implementation detail, which may change).
|
||||
//! It serves as marker to make these messages show up, even if the code is [`Code::Unknown`].
|
||||
//! ### Providing Context
|
||||
//!
|
||||
//!```rust
|
||||
//!# use anyhow::{anyhow};
|
||||
//! To inform about what you were trying to do when it went wrong, assign some [`context`](anyhow::Context::context)
|
||||
//! directly to [results](Result), to [`options`](Option) or to `anyhow` errors.
|
||||
//!
|
||||
//! ```rust
|
||||
//!# use anyhow::{anyhow, Result, bail, Context};
|
||||
//! fn maybe() -> Option<()> {
|
||||
//! None
|
||||
//! }
|
||||
//!
|
||||
//! fn a() -> Result<()> {
|
||||
//! maybe().context("didn't get it at this time")
|
||||
//! }
|
||||
//!
|
||||
//! fn b() -> Result<()> {
|
||||
//! a().context("an operation couldn't be performed")
|
||||
//! }
|
||||
//!
|
||||
//! fn c() -> Result<()> {
|
||||
//! b().map_err(|err| err.context("sometimes useful"))
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! assert_eq!(format!("{:#}", c().unwrap_err()),
|
||||
//! "sometimes useful: an operation couldn't be performed: didn't get it at this time");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Frontend Interactions
|
||||
//!
|
||||
//! We don't know anything about frontends here, but we also have to know something to be able to control
|
||||
//! which error messages show up. Sometimes the frontend needs to decide what to do based on a particular
|
||||
//! error that happened, hence it has to classify errors and it shouldn't do that by matching strings.
|
||||
//!
|
||||
//! #### Meet the `Code`
|
||||
//!
|
||||
//! The [`Code`] is a classifier for errors, and it can be attached as [`anyhow context`](anyhow::Context)
|
||||
//! to be visible to `tauri`, which looks at the error chain to obtain such metadata.
|
||||
//!
|
||||
//! By default, the frontend will show the stringified root error if a `tauri` command fails.
|
||||
//! However, **sometimes we want to cut that short and display a particular message**.
|
||||
//!
|
||||
//! ```rust
|
||||
//!# use anyhow::{Result, Context};
|
||||
//!# use gitbutler_core::error::Code;
|
||||
//! fn f() -> anyhow::Result<()> {
|
||||
//! return Err(anyhow!("user information").context(Code::Unknown))
|
||||
//!
|
||||
//! fn do_io() -> std::io::Result<()> {
|
||||
//! Err(std::io::Error::new(std::io::ErrorKind::Other, "this didn't work"))
|
||||
//! }
|
||||
//!```
|
||||
//!
|
||||
//! Finally, it's also possible to specify the user-message by using a [`Context`].
|
||||
//!
|
||||
//!```rust
|
||||
//!# use anyhow::{anyhow};
|
||||
//!# use gitbutler_core::error::{Code, Context};
|
||||
//! fn f() -> anyhow::Result<()> {
|
||||
//! return Err(anyhow!("internal information").context(Context::new_static(Code::Unknown, "user information")))
|
||||
//! fn a() -> Result<()> {
|
||||
//! do_io()
|
||||
//! .context("whatever comes before a `Code` context shows in frontend, so THIS")
|
||||
//! .context(Code::Unknown)
|
||||
//! }
|
||||
//!```
|
||||
//!
|
||||
//! #### Backtraces and `anyhow`
|
||||
//! fn main() {
|
||||
//! assert_eq!(format!("{:#}", a().unwrap_err()),
|
||||
//! "errors.unknown: whatever comes before a `Code` context shows in frontend, so THIS: this didn't work",
|
||||
//! "however, that Code also shows up in the error chain in logs - context is just like an Error for anyhow");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! #### Tuning error chains
|
||||
//!
|
||||
//! The style above was most convenient and can be used without hesitation, but if for some reason it's important
|
||||
//! for `Code` not to show up in the error chain, one can use the [`error::Context`](Context) directly.
|
||||
//!
|
||||
//! ```rust
|
||||
//!# use anyhow::{Result, Context};
|
||||
//!# use gitbutler_core::error;
|
||||
//!
|
||||
//! fn do_io() -> std::io::Result<()> {
|
||||
//! Err(std::io::Error::new(std::io::ErrorKind::Other, "this didn't work"))
|
||||
//! }
|
||||
//!
|
||||
//! fn a() -> Result<()> {
|
||||
//! do_io().context(error::Context::new("This message is shown and only this meessage")
|
||||
//! .with_code(error::Code::Validation))
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! assert_eq!(format!("{:#}", a().unwrap_err()),
|
||||
//! "This message is shown and only this meessage: this didn't work",
|
||||
//! "now the added context just looks like an error, even though it also contains a `Code` which can be queried");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Backtraces and `anyhow`
|
||||
//!
|
||||
//! Backtraces are automatically collected when `anyhow` errors are instantiated, as long as the
|
||||
//! `RUST_BACKTRACE` variable is set.
|
||||
//!
|
||||
//! #### With `thiserror`
|
||||
//!
|
||||
//! `thiserror` doesn't have a mechanism for generic context, and if it's needed the error must be converted to `anyhow::Error`.
|
||||
//! `thiserror` doesn't have a mechanism for generic context, and if it's needed the error can be converted
|
||||
//! to `anyhow::Error`.
|
||||
//!
|
||||
//! By default, `thiserror` instances have no context.
|
||||
//!
|
||||
//! ### Assuring Context
|
||||
//!
|
||||
//! Currently, the consumers of errors with context are quite primitive and thus rely on `anyhow`
|
||||
//! to collect and find context hidden in the error chain.
|
||||
//! To make that work, it's important that `thiserror` based errors never silently convert into
|
||||
//! `anyhow::Error`, as the context-consumers wouldn't find the context anymore.
|
||||
//!
|
||||
//! To prevent issues around this, make sure that relevant methods use the [`Error`] type provided
|
||||
//! here. It is made to only automatically convert from types that have context information.
|
||||
//! Those who have not will need to be converted by hand using [`Error::from_err()`].
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
@ -589,7 +589,7 @@ impl Repository {
|
||||
|
||||
/// Returns a list of remotes
|
||||
///
|
||||
/// Returns Vec<String> instead of StringArray because StringArray cannot safly be sent between threads
|
||||
/// Returns `Vec<String>` instead of StringArray because StringArray cannot safly be sent between threads
|
||||
pub fn remotes(&self) -> Result<Vec<String>> {
|
||||
self.0
|
||||
.remotes()
|
||||
|
@ -35,7 +35,7 @@ pub struct Snapshot {
|
||||
|
||||
/// The payload of a snapshot commit
|
||||
///
|
||||
/// This is persisted as a commit message in the title, body and trailers format (https://git-scm.com/docs/git-interpret-trailers)
|
||||
/// This is persisted as a commit message in the title, body and trailers format (<https://git-scm.com/docs/git-interpret-trailers>)
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SnapshotDetails {
|
||||
@ -196,7 +196,7 @@ impl FromStr for Version {
|
||||
}
|
||||
|
||||
/// Represents a key value pair stored in a snapshot, like `key: value\n`
|
||||
/// Using the git trailer format (https://git-scm.com/docs/git-interpret-trailers)
|
||||
/// Using the git trailer format (<https://git-scm.com/docs/git-interpret-trailers>)
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Trailer {
|
||||
|
@ -1,6 +1,12 @@
|
||||
//! ## How-To
|
||||
//! Utilities to control which errors show in the frontend.
|
||||
//!
|
||||
//! This is a primer on how to use the [`Error`] provided here.
|
||||
//! ## How to use this
|
||||
//!
|
||||
//! Just make sure this [`Error`] type is used for each provided `tauri` command. The rest happens automatically
|
||||
//! such that:
|
||||
//!
|
||||
//! * The frontend shows the root error as string by default…
|
||||
//! * …or it shows the provided [`Context`](gitbutler_core::error::Context) as controlled by the `core` crate.
|
||||
//!
|
||||
//! ### Interfacing with `tauri` using [`Error`]
|
||||
//!
|
||||
@ -10,13 +16,6 @@
|
||||
//!
|
||||
//! The values in these fields are controlled by attaching context, please [see the `core` docs](gitbutler_core::error))
|
||||
//! on how to do this.
|
||||
//!
|
||||
//! To assure context is picked up correctly to be made available to the UI if present, use
|
||||
//! [`Error`] in the `tauri` commands. Due to technical limitations, it will only auto-convert
|
||||
//! from `anyhow::Error`, or [core::Error](gitbutler_core::error::Error).
|
||||
//! Errors that are known to have context can be converted using [`Error::from_error_with_context`].
|
||||
//! If there is an error without context, one would have to convert to `anyhow::Error` as intermediate step,
|
||||
//! typically by adding `.context()`.
|
||||
pub(crate) use frontend::Error;
|
||||
|
||||
mod frontend {
|
||||
@ -35,25 +34,12 @@ mod frontend {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Convert an error without context to our type.
|
||||
///
|
||||
/// For now, we avoid using a conversion as it would be so general, we'd miss errors with context
|
||||
/// which need [`from_error_with_context`](Self::from_error_with_context) for the context to be
|
||||
/// picked up.
|
||||
pub fn from_error_without_context(
|
||||
err: impl std::error::Error + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let ctx = self.0.custom_context().unwrap_or_default();
|
||||
let ctx = self.0.custom_context_or_root_cause();
|
||||
|
||||
let mut map = serializer.serialize_map(Some(2))?;
|
||||
map.serialize_entry("code", &ctx.code.to_string())?;
|
||||
@ -79,7 +65,7 @@ mod frontend {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_context_or_code() {
|
||||
fn no_context_or_code_shows_root_error() {
|
||||
let err = anyhow!("err msg");
|
||||
assert_eq!(
|
||||
format!("{:#}", err),
|
||||
@ -88,8 +74,8 @@ mod frontend {
|
||||
);
|
||||
assert_eq!(
|
||||
json(err),
|
||||
"{\"code\":\"errors.unknown\",\"message\":\"Something went wrong\"}",
|
||||
"if there is no explicit error code or context, the original error message isn't shown"
|
||||
"{\"code\":\"errors.unknown\",\"message\":\"err msg\"}",
|
||||
"if there is no explicit error code or context, the original error message is shown"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -107,8 +107,10 @@ impl TestProject {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// ```text
|
||||
/// git add -A
|
||||
/// git reset --hard <oid>
|
||||
/// ```
|
||||
pub fn reset_hard(&self, oid: Option<git::Oid>) {
|
||||
let mut index = self.local_repository.index().expect("failed to get index");
|
||||
index
|
||||
|
Loading…
Reference in New Issue
Block a user