chore: cleanup core::error and app::error

* Remove `app` specific portions in core that were unused.
* Remove unused error-context and sentry integration which was unused everywhere.
This commit is contained in:
Sebastian Thiel 2024-03-31 17:42:48 +02:00
parent f614f739ae
commit 136a998623
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
5 changed files with 5 additions and 851 deletions

View File

@ -1,320 +1,5 @@
#[cfg(feature = "sentry")]
mod sentry;
pub use legacy::*;
pub mod gb {
#[cfg(feature = "error-context")]
pub use error_context::*;
#[cfg(feature = "error-context")]
mod error_context {
use std::collections::BTreeMap;
use backtrace::Backtrace;
use super::{ErrorKind, Result, WithContext};
#[derive(Debug)]
pub struct Context {
pub backtrace: Backtrace,
pub caused_by: Option<Box<ErrorContext>>,
pub vars: BTreeMap<String, String>,
}
impl Default for Context {
fn default() -> Self {
Self {
backtrace: Backtrace::new_unresolved(),
caused_by: None,
vars: BTreeMap::default(),
}
}
}
#[derive(Debug)]
pub struct ErrorContext {
error: ErrorKind,
context: Context,
}
impl core::fmt::Display for ErrorContext {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.error.fmt(f)
}
}
impl std::error::Error for ErrorContext {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.context
.caused_by
.as_ref()
.map(|e| e as &dyn std::error::Error)
}
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
if request.would_be_satisfied_by_ref_of::<Backtrace>() {
request.provide_ref(&self.context.backtrace);
}
}
}
impl ErrorContext {
#[inline]
pub fn error(&self) -> &ErrorKind {
&self.error
}
#[inline]
pub fn context(&self) -> &Context {
&self.context
}
pub fn into_owned(self) -> (ErrorKind, Context) {
(self.error, self.context)
}
}
impl<E: Into<ErrorContext>> WithContext<ErrorContext> for E {
fn add_err_context<K: Into<String>, V: Into<String>>(
self,
name: K,
value: V,
) -> ErrorContext {
let mut e = self.into();
e.context.vars.insert(name.into(), value.into());
e
}
fn wrap_err<K: Into<ErrorKind>>(self, error: K) -> ErrorContext {
let mut new_err = ErrorContext {
error: error.into(),
context: Context::default(),
};
new_err.context.caused_by = Some(Box::new(self.into()));
new_err
}
}
impl<T, E> WithContext<Result<T>> for std::result::Result<T, E>
where
E: Into<ErrorKind>,
{
#[inline]
fn add_err_context<K: Into<String>, V: Into<String>>(
self,
name: K,
value: V,
) -> Result<T> {
self.map_err(|e| {
ErrorContext {
error: e.into(),
context: Context::default(),
}
.add_err_context(name, value)
})
}
#[inline]
fn wrap_err<K: Into<ErrorKind>>(self, error: K) -> Result<T> {
self.map_err(|e| {
ErrorContext {
error: e.into(),
context: Context::default(),
}
.wrap_err(error)
})
}
}
#[cfg(feature = "error-context")]
impl serde::Serialize for ErrorContext {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(None)?;
let mut current = Some(self);
while let Some(err) = current {
seq.serialize_element(&err.error)?;
current = err.context.caused_by.as_deref();
}
seq.end()
}
}
impl From<ErrorKind> for ErrorContext {
fn from(error: ErrorKind) -> Self {
Self {
error,
context: Context::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_context() {
fn low_level_io() -> std::result::Result<(), std::io::Error> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
}
fn app_level_io() -> Result<()> {
low_level_io().add_err_context("foo", "bar")?;
unreachable!();
}
use std::error::Error;
let r = app_level_io();
assert!(r.is_err());
let e = r.unwrap_err();
assert_eq!(e.context().vars.get("foo"), Some(&"bar".to_string()));
assert!(e.source().is_none());
assert!(e.to_string().starts_with("io.other-error:"));
}
}
}
pub trait WithContext<R> {
fn add_err_context<K: Into<String>, V: Into<String>>(self, name: K, value: V) -> R;
fn wrap_err<E: Into<ErrorKind>>(self, error: E) -> R;
}
#[cfg(not(feature = "error-context"))]
pub struct Context;
pub trait ErrorCode {
fn code(&self) -> String;
fn message(&self) -> String;
}
#[derive(Debug, thiserror::Error)]
pub enum ErrorKind {
Io(#[from] ::std::io::Error),
Git(#[from] ::git2::Error),
CommonDirNotAvailable(String),
}
impl ErrorCode for std::io::Error {
fn code(&self) -> String {
slug::slugify(self.kind().to_string())
}
fn message(&self) -> String {
self.to_string()
}
}
impl ErrorCode for git2::Error {
fn code(&self) -> String {
slug::slugify(format!("{:?}", self.class()))
}
fn message(&self) -> String {
self.to_string()
}
}
impl ErrorCode for ErrorKind {
fn code(&self) -> String {
match self {
ErrorKind::Io(e) => format!("io.{}", <std::io::Error as ErrorCode>::code(e)),
ErrorKind::Git(e) => format!("git.{}", <git2::Error as ErrorCode>::code(e)),
ErrorKind::CommonDirNotAvailable(_) => "no-common-dir".to_string(),
}
}
fn message(&self) -> String {
match self {
ErrorKind::Io(e) => <std::io::Error as ErrorCode>::message(e),
ErrorKind::Git(e) => <git2::Error as ErrorCode>::message(e),
ErrorKind::CommonDirNotAvailable(s) => format!("{s} is not available"),
}
}
}
impl core::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
format!(
"{}: {}",
<Self as ErrorCode>::code(self),
<Self as ErrorCode>::message(self)
)
.fmt(f)
}
}
#[cfg(not(feature = "error-context"))]
pub type Error = ErrorKind;
#[cfg(feature = "error-context")]
pub type Error = ErrorContext;
pub type Result<T> = ::std::result::Result<T, Error>;
#[cfg(not(feature = "error-context"))]
impl ErrorKind {
#[inline]
pub fn error(&self) -> &Error {
self
}
#[inline]
pub fn context(&self) -> Option<&Context> {
None
}
}
#[cfg(not(feature = "error-context"))]
impl WithContext<ErrorKind> for ErrorKind {
#[inline]
fn add_err_context<K: Into<String>, V: Into<String>>(self, _name: K, _value: V) -> Error {
self
}
#[inline]
fn wrap_err(self, _error: Error) -> Error {
self
}
}
#[cfg(not(feature = "error-context"))]
impl<T, E> WithContext<std::result::Result<T, E>> for std::result::Result<T, E> {
#[inline]
fn add_err_context<K: Into<String>, V: Into<String>>(
self,
_name: K,
_value: V,
) -> std::result::Result<T, E> {
self
}
#[inline]
fn wrap_err(self, _error: Error) -> std::result::Result<T, E> {
self
}
}
#[cfg(feature = "error-context")]
impl serde::Serialize for ErrorKind {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeTuple;
let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&self.code())?;
seq.serialize_element(&self.message())?;
seq.end()
}
}
}
//#[deprecated(
// note = "the types in the error::legacy::* module are deprecated; use error::gb::Error and error::gb::Result instead"
//)]

View File

@ -1,91 +0,0 @@
use std::collections::BTreeMap;
use sentry::{
protocol::{value::Map, Event, Exception, Value},
types::Uuid,
};
use crate::error::gb::{ErrorCode, ErrorContext};
pub trait SentrySender {
fn send_to_sentry(self) -> Uuid;
}
impl<E: Into<ErrorContext>> SentrySender for E {
fn send_to_sentry(self) -> Uuid {
let sentry_event = self.into().into();
sentry::capture_event(sentry_event)
}
}
trait PopulateException {
fn populate_exception(
self,
exceptions: &mut Vec<Exception>,
vars: &mut BTreeMap<String, Value>,
);
}
impl PopulateException for ErrorContext {
fn populate_exception(
self,
exceptions: &mut Vec<Exception>,
vars: &mut BTreeMap<String, Value>,
) {
let (error, mut context) = self.into_owned();
let mut exc = Exception {
ty: error.code(),
value: Some(error.message()),
..Exception::default()
};
if let Some(cause) = context.caused_by {
cause.populate_exception(exceptions, vars);
}
// We don't resolve at capture time because it can DRASTICALLY
// slow down the application (can take up to 0.5s to resolve
// a *single* frame). We do it here, only when a Sentry event
// is being created.
context.backtrace.resolve();
exc.stacktrace =
sentry::integrations::backtrace::backtrace_to_stacktrace(&context.backtrace);
::backtrace::clear_symbol_cache();
vars.insert(
error.code(),
Value::Object(
context
.vars
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
),
);
exceptions.push(exc);
}
}
impl From<ErrorContext> for Event<'_> {
fn from(error_context: ErrorContext) -> Self {
let mut sentry_event = Event {
message: Some(format!(
"{}: {}",
error_context.error().code(),
error_context.error().message()
)),
..Event::default()
};
let mut vars = BTreeMap::new();
error_context.populate_exception(&mut sentry_event.exception.values, &mut vars);
sentry_event
.extra
.insert("context_vars".into(), Value::Object(Map::from_iter(vars)));
sentry_event
}
}

View File

@ -67,12 +67,12 @@ correctness = "deny"
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is a URL
default = ["custom-protocol", "sentry", "error-context"]
default = ["custom-protocol", "sentry"]
# this feature enables devtools
devtools = ["tauri/devtools"]
# this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]
sentry = ["dep:sentry", "error-context"]
sentry = ["dep:sentry"]
error-context = ["dep:backtrace", "sentry/backtrace"]

View File

@ -1,363 +1,14 @@
#[cfg(feature = "sentry")]
mod sentry;
pub(crate) use legacy::*;
pub(crate) mod gb {
#[cfg(feature = "error-context")]
pub use error_context::*;
#[cfg(feature = "error-context")]
mod error_context {
use std::collections::BTreeMap;
use backtrace::Backtrace;
use super::{ErrorKind, Result, WithContext};
#[derive(Debug)]
pub struct Context {
pub backtrace: Backtrace,
pub caused_by: Option<Box<ErrorContext>>,
pub vars: BTreeMap<String, String>,
}
impl Default for Context {
fn default() -> Self {
Self {
backtrace: Backtrace::new_unresolved(),
caused_by: None,
vars: BTreeMap::default(),
}
}
}
#[derive(Debug)]
pub struct ErrorContext {
error: ErrorKind,
context: Context,
}
impl core::fmt::Display for ErrorContext {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.error.fmt(f)
}
}
impl std::error::Error for ErrorContext {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.context
.caused_by
.as_ref()
.map(|e| e as &dyn std::error::Error)
}
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
if request.would_be_satisfied_by_ref_of::<Backtrace>() {
request.provide_ref(&self.context.backtrace);
}
}
}
impl ErrorContext {
#[inline]
pub fn error(&self) -> &ErrorKind {
&self.error
}
#[inline]
pub fn context(&self) -> &Context {
&self.context
}
pub(crate) fn into_owned(self) -> (ErrorKind, Context) {
(self.error, self.context)
}
}
impl<E: Into<ErrorContext>> WithContext<ErrorContext> for E {
fn add_err_context<K: Into<String>, V: Into<String>>(
self,
name: K,
value: V,
) -> ErrorContext {
let mut e = self.into();
e.context.vars.insert(name.into(), value.into());
e
}
fn wrap_err<K: Into<ErrorKind>>(self, error: K) -> ErrorContext {
let mut new_err = ErrorContext {
error: error.into(),
context: Context::default(),
};
new_err.context.caused_by = Some(Box::new(self.into()));
new_err
}
}
impl<T, E> WithContext<Result<T>> for std::result::Result<T, E>
where
E: Into<ErrorKind>,
{
#[inline]
fn add_err_context<K: Into<String>, V: Into<String>>(
self,
name: K,
value: V,
) -> Result<T> {
self.map_err(|e| {
ErrorContext {
error: e.into(),
context: Context::default(),
}
.add_err_context(name, value)
})
}
#[inline]
fn wrap_err<K: Into<ErrorKind>>(self, error: K) -> Result<T> {
self.map_err(|e| {
ErrorContext {
error: e.into(),
context: Context::default(),
}
.wrap_err(error)
})
}
}
#[cfg(feature = "error-context")]
impl serde::Serialize for ErrorContext {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(None)?;
let mut current = Some(self);
while let Some(err) = current {
seq.serialize_element(&err.error)?;
current = err.context.caused_by.as_deref();
}
seq.end()
}
}
impl From<ErrorKind> for ErrorContext {
fn from(error: ErrorKind) -> Self {
Self {
error,
context: Context::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_context() {
fn low_level_io() -> std::result::Result<(), std::io::Error> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
}
fn app_level_io() -> Result<()> {
low_level_io().add_err_context("foo", "bar")?;
unreachable!();
}
use std::error::Error;
let r = app_level_io();
assert!(r.is_err());
let e = r.unwrap_err();
assert_eq!(e.context().vars.get("foo"), Some(&"bar".to_string()));
assert!(e.source().is_none());
assert!(e.to_string().starts_with("io.other-error:"));
}
}
}
pub trait WithContext<R> {
fn add_err_context<K: Into<String>, V: Into<String>>(self, name: K, value: V) -> R;
fn wrap_err<E: Into<ErrorKind>>(self, error: E) -> R;
}
#[cfg(not(feature = "error-context"))]
pub struct Context;
pub trait ErrorCode {
fn code(&self) -> String;
fn message(&self) -> String;
}
#[derive(Debug, thiserror::Error)]
pub enum ErrorKind {
Io(#[from] ::std::io::Error),
Git(#[from] ::git2::Error),
CommonDirNotAvailable(String),
}
impl ErrorCode for std::io::Error {
fn code(&self) -> String {
slug::slugify(self.kind().to_string())
}
fn message(&self) -> String {
self.to_string()
}
}
impl ErrorCode for git2::Error {
fn code(&self) -> String {
slug::slugify(format!("{:?}", self.class()))
}
fn message(&self) -> String {
self.to_string()
}
}
impl ErrorCode for ErrorKind {
fn code(&self) -> String {
match self {
ErrorKind::Io(e) => format!("io.{}", <std::io::Error as ErrorCode>::code(e)),
ErrorKind::Git(e) => format!("git.{}", <git2::Error as ErrorCode>::code(e)),
ErrorKind::CommonDirNotAvailable(_) => "no-common-dir".to_string(),
}
}
fn message(&self) -> String {
match self {
ErrorKind::Io(e) => <std::io::Error as ErrorCode>::message(e),
ErrorKind::Git(e) => <git2::Error as ErrorCode>::message(e),
ErrorKind::CommonDirNotAvailable(s) => format!("{s} is not available"),
}
}
}
impl core::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
format!(
"{}: {}",
<Self as ErrorCode>::code(self),
<Self as ErrorCode>::message(self)
)
.fmt(f)
}
}
#[cfg(not(feature = "error-context"))]
pub type Error = ErrorKind;
#[cfg(feature = "error-context")]
pub type Error = ErrorContext;
pub type Result<T> = ::std::result::Result<T, Error>;
#[cfg(not(feature = "error-context"))]
impl ErrorKind {
#[inline]
pub fn error(&self) -> &Error {
self
}
#[inline]
pub fn context(&self) -> Option<&Context> {
None
}
}
#[cfg(not(feature = "error-context"))]
impl WithContext<ErrorKind> for ErrorKind {
#[inline]
fn add_err_context<K: Into<String>, V: Into<String>>(self, _name: K, _value: V) -> Error {
self
}
#[inline]
fn wrap_err(self, _error: Error) -> Error {
self
}
}
#[cfg(not(feature = "error-context"))]
impl<T, E> WithContext<std::result::Result<T, E>> for std::result::Result<T, E> {
#[inline]
fn add_err_context<K: Into<String>, V: Into<String>>(
self,
_name: K,
_value: V,
) -> std::result::Result<T, E> {
self
}
#[inline]
fn wrap_err(self, _error: Error) -> std::result::Result<T, E> {
self
}
}
#[cfg(feature = "error-context")]
impl serde::Serialize for ErrorKind {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeTuple;
let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&self.code())?;
seq.serialize_element(&self.message())?;
seq.end()
}
}
}
pub use gitbutler_core::error::Code;
pub(crate) use legacy::Error;
//#[deprecated(
// note = "the types in the error::legacy::* module are deprecated; use error::gb::Error and error::gb::Result instead"
//)]
mod legacy {
use core::fmt;
use gitbutler_core::error::Code;
use gitbutler_core::project_repository;
use serde::{ser::SerializeMap, Serialize};
#[derive(Debug)]
pub enum Code {
Unknown,
Validation,
Projects,
Branches,
ProjectGitAuth,
ProjectGitRemote,
ProjectConflict,
ProjectHead,
Menu,
PreCommitHook,
CommitMsgHook,
}
impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Code::Menu => write!(f, "errors.menu"),
Code::Unknown => write!(f, "errors.unknown"),
Code::Validation => write!(f, "errors.validation"),
Code::Projects => write!(f, "errors.projects"),
Code::Branches => write!(f, "errors.branches"),
Code::ProjectGitAuth => write!(f, "errors.projects.git.auth"),
Code::ProjectGitRemote => write!(f, "errors.projects.git.remote"),
Code::ProjectHead => write!(f, "errors.projects.head"),
Code::ProjectConflict => write!(f, "errors.projects.conflict"),
//TODO: rename js side to be more precise what kind of hook error this is
Code::PreCommitHook => write!(f, "errors.hook"),
Code::CommitMsgHook => write!(f, "errors.hooks.commit.msg"),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("[{code}]: {message}")]

View File

@ -1,91 +0,0 @@
use std::collections::BTreeMap;
use sentry::{
protocol::{value::Map, Event, Exception, Value},
types::Uuid,
};
use crate::error::gb::{ErrorCode, ErrorContext};
pub trait SentrySender {
fn send_to_sentry(self) -> Uuid;
}
impl<E: Into<ErrorContext>> SentrySender for E {
fn send_to_sentry(self) -> Uuid {
let sentry_event = self.into().into();
sentry::capture_event(sentry_event)
}
}
trait PopulateException {
fn populate_exception(
self,
exceptions: &mut Vec<Exception>,
vars: &mut BTreeMap<String, Value>,
);
}
impl PopulateException for ErrorContext {
fn populate_exception(
self,
exceptions: &mut Vec<Exception>,
vars: &mut BTreeMap<String, Value>,
) {
let (error, mut context) = self.into_owned();
let mut exc = Exception {
ty: error.code(),
value: Some(error.message()),
..Exception::default()
};
if let Some(cause) = context.caused_by {
cause.populate_exception(exceptions, vars);
}
// We don't resolve at capture time because it can DRASTICALLY
// slow down the application (can take up to 0.5s to resolve
// a *single* frame). We do it here, only when a Sentry event
// is being created.
context.backtrace.resolve();
exc.stacktrace =
sentry::integrations::backtrace::backtrace_to_stacktrace(&context.backtrace);
::backtrace::clear_symbol_cache();
vars.insert(
error.code(),
Value::Object(
context
.vars
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
),
);
exceptions.push(exc);
}
}
impl From<ErrorContext> for Event<'_> {
fn from(error_context: ErrorContext) -> Self {
let mut sentry_event = Event {
message: Some(format!(
"{}: {}",
error_context.error().code(),
error_context.error().message()
)),
..Event::default()
};
let mut vars = BTreeMap::new();
error_context.populate_exception(&mut sentry_event.exception.values, &mut vars);
sentry_event
.extra
.insert("context_vars".into(), Value::Object(Map::from_iter(vars)));
sentry_event
}
}