Skip to main content

axum_error_sets/
api_error.rs

1use crate::{IntoResponseWith, StatusWrapper};
2use axum_core::response::{IntoResponse, Response};
3use http::StatusCode;
4use std::{fmt::Debug, hash::Hash, marker::PhantomData};
5use type_sets::{Contains, SupersetOf};
6
7/// An error defined by a set of possible status codes.
8///
9/// The parameter `T` is the actual value of the error, and must implement
10/// [`IntoResponseWith`].
11///
12/// The parameter `E` is a type-level set of possible status codes that this error
13/// can have. e.g. `(NotFound, InternalServerError)` means that this error can either
14/// be a 404 or a 500.
15pub struct ErrorSet<T, E> {
16    value: T,
17    code: StatusCode,
18    _e: PhantomData<fn() -> E>,
19}
20
21impl<T, E> ErrorSet<T, E> {
22    /// Create a new [`ApiError`] with the given wrapper-type.
23    ///
24    /// Checks at compile time that the status code is part of the set `E`.
25    pub fn new_with<R: StatusWrapper>(value: T) -> Self
26    where
27        E: Contains<R>,
28    {
29        Self::new_unchecked(value, R::STATUS_CODE)
30    }
31
32    /// Create a new [`ApiError`] with the given wrapper from a type that implements
33    /// [`StatusWrapper`]. (e.g. [`NotFound<YourValue>`](crate::code::NotFound))
34    ///
35    /// Checks at compile time that the status code is part of the set `E`.
36    pub fn new<R: StatusWrapper>(value: R) -> Self
37    where
38        E: Contains<R::Pure>,
39        R::Inner: Into<T>,
40    {
41        Self::new_with::<R::Pure>(value.into_inner().into())
42    }
43
44    /// Create a new [`ApiError`] with the given value and status code.
45    ///
46    /// This does not check that the status code is part of the set `E`. Be careful when
47    /// using this method, as it can lead to invalid [`ApiError`]s.
48    pub fn new_unchecked(value: T, code: StatusCode) -> Self {
49        ErrorSet {
50            value,
51            code,
52            _e: PhantomData,
53        }
54    }
55
56    /// Convert this [`ApiError`] into a tuple of the value and the status code.
57    pub fn into_parts(self) -> (T, StatusCode) {
58        (self.value, self.code)
59    }
60
61    /// Convert this [`ApiError`] into a new [`ApiError`] with a superset of the original
62    /// set of status codes.
63    pub fn into_superset<E2>(self) -> ErrorSet<T, E2>
64    where
65        E2: SupersetOf<E>,
66    {
67        ErrorSet::new_unchecked(self.value, self.code)
68    }
69
70    /// Convert this [`ApiError`] into a new [`ApiError`] with a different value type.
71    pub fn map_value<F, U>(self, f: F) -> ErrorSet<U, E>
72    where
73        F: FnOnce(T) -> U,
74    {
75        ErrorSet::new_unchecked(f(self.value), self.code)
76    }
77
78    /// Get a reference to the value of this [`ApiError`].
79    pub fn value(&self) -> &T {
80        &self.value
81    }
82
83    /// Get a mutable reference to the value of this [`ApiError`].
84    pub fn value_mut(&mut self) -> &mut T {
85        &mut self.value
86    }
87
88    /// Get the status code of this [`ApiError`].
89    pub fn status_code(&self) -> StatusCode {
90        self.code
91    }
92}
93
94impl<T: IntoResponseWith, R> IntoResponse for ErrorSet<T, R> {
95    fn into_response(self) -> Response {
96        self.value.into_response_with(self.code)
97    }
98}
99
100impl<T: Debug, R> Debug for ErrorSet<T, R> {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.debug_struct("ApiError")
103            .field("value", &self.value)
104            .field("code", &self.code)
105            .finish()
106    }
107}
108
109impl<T: Clone, R> Clone for ErrorSet<T, R> {
110    fn clone(&self) -> Self {
111        Self {
112            value: self.value.clone(),
113            code: self.code,
114            _e: PhantomData,
115        }
116    }
117}
118
119impl<T: Hash, R> Hash for ErrorSet<T, R> {
120    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
121        self.value.hash(state);
122        self.code.hash(state);
123    }
124}
125
126impl<T: PartialEq, R> PartialEq for ErrorSet<T, R> {
127    fn eq(&self, other: &Self) -> bool {
128        self.value == other.value && self.code == other.code
129    }
130}
131
132impl<T: Eq, R> Eq for ErrorSet<T, R> {}