pingora_error/
lib.rs

1// Copyright 2025 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![warn(clippy::all)]
16//! The library to provide the struct to represent errors in pingora.
17
18pub use std::error::Error as ErrorTrait;
19use std::fmt;
20use std::fmt::Debug;
21use std::result::Result as StdResult;
22
23mod immut_str;
24pub use immut_str::ImmutStr;
25
26/// The boxed [Error], the desired way to pass [Error]
27pub type BError = Box<Error>;
28/// Syntax sugar for `std::Result<T, BError>`
29pub type Result<T, E = BError> = StdResult<T, E>;
30
31/// The struct that represents an error
32#[derive(Debug)]
33pub struct Error {
34    /// the type of error
35    pub etype: ErrorType,
36    /// the source of error: from upstream, downstream or internal
37    pub esource: ErrorSource,
38    /// if the error is retry-able
39    pub retry: RetryType,
40    /// chain to the cause of this error
41    pub cause: Option<Box<(dyn ErrorTrait + Send + Sync)>>,
42    /// an arbitrary string that explains the context when the error happens
43    pub context: Option<ImmutStr>,
44}
45
46/// The source of the error
47#[derive(Debug, PartialEq, Eq, Clone)]
48pub enum ErrorSource {
49    /// The error is caused by the remote server
50    Upstream,
51    /// The error is caused by the remote client
52    Downstream,
53    /// The error is caused by the internal logic
54    Internal,
55    /// Error source unknown or to be set
56    Unset,
57}
58
59/// Whether the request can be retried after encountering this error
60#[derive(Debug, PartialEq, Eq, Clone, Copy)]
61pub enum RetryType {
62    Decided(bool),
63    ReusedOnly, // only retry when the error is from a reused connection
64}
65
66impl RetryType {
67    pub fn decide_reuse(&mut self, reused: bool) {
68        if matches!(self, RetryType::ReusedOnly) {
69            *self = RetryType::Decided(reused);
70        }
71    }
72
73    pub fn retry(&self) -> bool {
74        match self {
75            RetryType::Decided(b) => *b,
76            RetryType::ReusedOnly => {
77                panic!("Retry is not decided")
78            }
79        }
80    }
81}
82
83impl From<bool> for RetryType {
84    fn from(b: bool) -> Self {
85        RetryType::Decided(b)
86    }
87}
88
89impl ErrorSource {
90    /// for displaying the error source
91    pub fn as_str(&self) -> &str {
92        match self {
93            Self::Upstream => "Upstream",
94            Self::Downstream => "Downstream",
95            Self::Internal => "Internal",
96            Self::Unset => "",
97        }
98    }
99}
100
101/// Predefined type of errors
102#[derive(Debug, PartialEq, Eq, Clone)]
103pub enum ErrorType {
104    // connect errors
105    ConnectTimedout,
106    ConnectRefused,
107    ConnectNoRoute,
108    TLSWantX509Lookup,
109    TLSHandshakeFailure,
110    TLSHandshakeTimedout,
111    InvalidCert,
112    HandshakeError, // other handshake
113    ConnectError,   // catch all
114    BindError,
115    AcceptError,
116    SocketError,
117    ConnectProxyFailure,
118    // protocol errors
119    InvalidHTTPHeader,
120    H1Error,     // catch all
121    H2Error,     // catch all
122    H2Downgrade, // Peer over h2 requests to downgrade to h1
123    InvalidH2,   // Peer sends invalid h2 frames to us
124    // IO error on established connections
125    ReadError,
126    WriteError,
127    ReadTimedout,
128    WriteTimedout,
129    ConnectionClosed,
130    // application error, will return HTTP status code
131    HTTPStatus(u16),
132    // file related
133    FileOpenError,
134    FileCreateError,
135    FileReadError,
136    FileWriteError,
137    // other errors
138    InternalError,
139    // catch all
140    UnknownError,
141    /// Custom error with static string.
142    /// this field is to allow users to extend the types of errors. If runtime generated string
143    /// is needed, it is more likely to be treated as "context" rather than "type".
144    Custom(&'static str),
145    /// Custom error with static string and code.
146    /// this field allows users to extend error further with error codes.
147    CustomCode(&'static str, u16),
148}
149
150impl ErrorType {
151    /// create a new type of error. Users should try to make `name` unique.
152    pub const fn new(name: &'static str) -> Self {
153        ErrorType::Custom(name)
154    }
155
156    /// create a new type of error. Users should try to make `name` unique.
157    pub const fn new_code(name: &'static str, code: u16) -> Self {
158        ErrorType::CustomCode(name, code)
159    }
160
161    /// for displaying the error type
162    pub fn as_str(&self) -> &str {
163        match self {
164            ErrorType::ConnectTimedout => "ConnectTimedout",
165            ErrorType::ConnectRefused => "ConnectRefused",
166            ErrorType::ConnectNoRoute => "ConnectNoRoute",
167            ErrorType::ConnectProxyFailure => "ConnectProxyFailure",
168            ErrorType::TLSWantX509Lookup => "TLSWantX509Lookup",
169            ErrorType::TLSHandshakeFailure => "TLSHandshakeFailure",
170            ErrorType::TLSHandshakeTimedout => "TLSHandshakeTimedout",
171            ErrorType::InvalidCert => "InvalidCert",
172            ErrorType::HandshakeError => "HandshakeError",
173            ErrorType::ConnectError => "ConnectError",
174            ErrorType::BindError => "BindError",
175            ErrorType::AcceptError => "AcceptError",
176            ErrorType::SocketError => "SocketError",
177            ErrorType::InvalidHTTPHeader => "InvalidHTTPHeader",
178            ErrorType::H1Error => "H1Error",
179            ErrorType::H2Error => "H2Error",
180            ErrorType::InvalidH2 => "InvalidH2",
181            ErrorType::H2Downgrade => "H2Downgrade",
182            ErrorType::ReadError => "ReadError",
183            ErrorType::WriteError => "WriteError",
184            ErrorType::ReadTimedout => "ReadTimedout",
185            ErrorType::WriteTimedout => "WriteTimedout",
186            ErrorType::ConnectionClosed => "ConnectionClosed",
187            ErrorType::FileOpenError => "FileOpenError",
188            ErrorType::FileCreateError => "FileCreateError",
189            ErrorType::FileReadError => "FileReadError",
190            ErrorType::FileWriteError => "FileWriteError",
191            ErrorType::HTTPStatus(_) => "HTTPStatus",
192            ErrorType::InternalError => "InternalError",
193            ErrorType::UnknownError => "UnknownError",
194            ErrorType::Custom(s) => s,
195            ErrorType::CustomCode(s, _) => s,
196        }
197    }
198}
199
200impl Error {
201    /// Simply create the error. See other functions that provide less verbose interfaces.
202    #[inline]
203    pub fn create(
204        etype: ErrorType,
205        esource: ErrorSource,
206        context: Option<ImmutStr>,
207        cause: Option<Box<dyn ErrorTrait + Send + Sync>>,
208    ) -> BError {
209        let retry = if let Some(c) = cause.as_ref() {
210            if let Some(e) = c.downcast_ref::<BError>() {
211                e.retry
212            } else {
213                false.into()
214            }
215        } else {
216            false.into()
217        };
218        Box::new(Error {
219            etype,
220            esource,
221            retry,
222            cause,
223            context,
224        })
225    }
226
227    #[inline]
228    fn do_new(e: ErrorType, s: ErrorSource) -> BError {
229        Self::create(e, s, None, None)
230    }
231
232    /// Create an error with the given type
233    #[inline]
234    pub fn new(e: ErrorType) -> BError {
235        Self::do_new(e, ErrorSource::Unset)
236    }
237
238    /// Create an error with the given type, a context string and the causing error.
239    /// This method is usually used when there the error is caused by another error.
240    /// ```
241    /// use pingora_error::{Error, ErrorType, Result};
242    ///
243    /// fn b() -> Result<()> {
244    ///     // ...
245    ///     Ok(())
246    /// }
247    /// fn do_something() -> Result<()> {
248    ///     // a()?;
249    ///     b().map_err(|e| Error::because(ErrorType::InternalError, "b failed after a", e))
250    /// }
251    /// ```
252    /// Choose carefully between simply surfacing the causing error versus Because() here.
253    /// Only use Because() when there is extra context that is not capture by
254    /// the causing error itself.
255    #[inline]
256    pub fn because<S: Into<ImmutStr>, E: Into<Box<dyn ErrorTrait + Send + Sync>>>(
257        e: ErrorType,
258        context: S,
259        cause: E,
260    ) -> BError {
261        Self::create(
262            e,
263            ErrorSource::Unset,
264            Some(context.into()),
265            Some(cause.into()),
266        )
267    }
268
269    /// Short for Err(Self::because)
270    #[inline]
271    pub fn e_because<T, S: Into<ImmutStr>, E: Into<Box<dyn ErrorTrait + Send + Sync>>>(
272        e: ErrorType,
273        context: S,
274        cause: E,
275    ) -> Result<T> {
276        Err(Self::because(e, context, cause))
277    }
278
279    /// Create an error with context but no direct causing error
280    #[inline]
281    pub fn explain<S: Into<ImmutStr>>(e: ErrorType, context: S) -> BError {
282        Self::create(e, ErrorSource::Unset, Some(context.into()), None)
283    }
284
285    /// Short for Err(Self::explain)
286    #[inline]
287    pub fn e_explain<T, S: Into<ImmutStr>>(e: ErrorType, context: S) -> Result<T> {
288        Err(Self::explain(e, context))
289    }
290
291    /// The new_{up, down, in} functions are to create new errors with source
292    /// {upstream, downstream, internal}
293    #[inline]
294    pub fn new_up(e: ErrorType) -> BError {
295        Self::do_new(e, ErrorSource::Upstream)
296    }
297
298    #[inline]
299    pub fn new_down(e: ErrorType) -> BError {
300        Self::do_new(e, ErrorSource::Downstream)
301    }
302
303    #[inline]
304    pub fn new_in(e: ErrorType) -> BError {
305        Self::do_new(e, ErrorSource::Internal)
306    }
307
308    /// Create a new custom error with the static string
309    #[inline]
310    pub fn new_str(s: &'static str) -> BError {
311        Self::do_new(ErrorType::Custom(s), ErrorSource::Unset)
312    }
313
314    // the err_* functions are the same as new_* but return a Result<T>
315    #[inline]
316    pub fn err<T>(e: ErrorType) -> Result<T> {
317        Err(Self::new(e))
318    }
319
320    #[inline]
321    pub fn err_up<T>(e: ErrorType) -> Result<T> {
322        Err(Self::new_up(e))
323    }
324
325    #[inline]
326    pub fn err_down<T>(e: ErrorType) -> Result<T> {
327        Err(Self::new_down(e))
328    }
329
330    #[inline]
331    pub fn err_in<T>(e: ErrorType) -> Result<T> {
332        Err(Self::new_in(e))
333    }
334
335    pub fn etype(&self) -> &ErrorType {
336        &self.etype
337    }
338
339    pub fn esource(&self) -> &ErrorSource {
340        &self.esource
341    }
342
343    pub fn retry(&self) -> bool {
344        self.retry.retry()
345    }
346
347    pub fn set_retry(&mut self, retry: bool) {
348        self.retry = retry.into();
349    }
350
351    pub fn reason_str(&self) -> &str {
352        self.etype.as_str()
353    }
354
355    pub fn source_str(&self) -> &str {
356        self.esource.as_str()
357    }
358
359    /// The as_{up, down, in} functions are to change the current errors with source
360    /// {upstream, downstream, internal}
361    pub fn as_up(&mut self) {
362        self.esource = ErrorSource::Upstream;
363    }
364
365    pub fn as_down(&mut self) {
366        self.esource = ErrorSource::Downstream;
367    }
368
369    pub fn as_in(&mut self) {
370        self.esource = ErrorSource::Internal;
371    }
372
373    /// The into_{up, down, in} are the same as as_* but takes `self` and also return `self`
374    pub fn into_up(mut self: BError) -> BError {
375        self.as_up();
376        self
377    }
378
379    pub fn into_down(mut self: BError) -> BError {
380        self.as_down();
381        self
382    }
383
384    pub fn into_in(mut self: BError) -> BError {
385        self.as_in();
386        self
387    }
388
389    pub fn into_err<T>(self: BError) -> Result<T> {
390        Err(self)
391    }
392
393    pub fn set_cause<C: Into<Box<dyn ErrorTrait + Send + Sync>>>(&mut self, cause: C) {
394        self.cause = Some(cause.into());
395    }
396
397    pub fn set_context<T: Into<ImmutStr>>(&mut self, context: T) {
398        self.context = Some(context.into());
399    }
400
401    /// Create a new error from self, with the same type and source and put self as the cause
402    /// ```
403    /// use pingora_error::Result;
404    ///
405    ///  fn b() -> Result<()> {
406    ///     // ...
407    ///     Ok(())
408    /// }
409    ///
410    /// fn do_something() -> Result<()> {
411    ///     // a()?;
412    ///     b().map_err(|e| e.more_context("b failed after a"))
413    /// }
414    /// ```
415    /// This function is less verbose than `Because`. But it only work for [Error] while
416    /// `Because` works for all types of errors who implement [std::error::Error] trait.
417    pub fn more_context<T: Into<ImmutStr>>(self: BError, context: T) -> BError {
418        let esource = self.esource.clone();
419        let retry = self.retry;
420        let mut e = Self::because(self.etype.clone(), context, self);
421        e.esource = esource;
422        e.retry = retry;
423        e
424    }
425
426    // Display error but skip the duplicate elements from the error in previous hop
427    fn chain_display(&self, previous: Option<&Error>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        if previous.map(|p| p.esource != self.esource).unwrap_or(true) {
429            write!(f, "{}", self.esource.as_str())?
430        }
431        if previous.map(|p| p.etype != self.etype).unwrap_or(true) {
432            write!(f, " {}", self.etype.as_str())?
433        }
434
435        if let Some(c) = self.context.as_ref() {
436            write!(f, " context: {}", c)?;
437        }
438        if let Some(c) = self.cause.as_ref() {
439            if let Some(e) = c.downcast_ref::<BError>() {
440                write!(f, " cause: ")?;
441                e.chain_display(Some(self), f)
442            } else {
443                write!(f, " cause: {}", c)
444            }
445        } else {
446            Ok(())
447        }
448    }
449
450    /// Return the ErrorType of the root Error
451    pub fn root_etype(&self) -> &ErrorType {
452        self.cause.as_ref().map_or(&self.etype, |c| {
453            // Stop the recursion if the cause is not Error
454            c.downcast_ref::<BError>()
455                .map_or(&self.etype, |e| e.root_etype())
456        })
457    }
458
459    pub fn root_cause(&self) -> &(dyn ErrorTrait + Send + Sync + 'static) {
460        self.cause.as_deref().map_or(self, |c| {
461            c.downcast_ref::<BError>().map_or(c, |e| e.root_cause())
462        })
463    }
464}
465
466impl fmt::Display for Error {
467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468        self.chain_display(None, f)
469    }
470}
471
472impl ErrorTrait for Error {}
473
474/// Helper trait to add more context to a given error
475pub trait Context<T> {
476    /// Wrap the `Err(E)` in [Result] with more context, the existing E will be the cause.
477    ///
478    /// This is a shortcut for map_err() + more_context()
479    fn err_context<C: Into<ImmutStr>, F: FnOnce() -> C>(self, context: F) -> Result<T, BError>;
480}
481
482impl<T> Context<T> for Result<T, BError> {
483    fn err_context<C: Into<ImmutStr>, F: FnOnce() -> C>(self, context: F) -> Result<T, BError> {
484        self.map_err(|e| e.more_context(context()))
485    }
486}
487
488/// Helper trait to chain errors with context
489pub trait OrErr<T, E> {
490    /// Wrap the E in [Result] with new [ErrorType] and context, the existing E will be the cause.
491    ///
492    /// This is a shortcut for map_err() + because()
493    fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError>
494    where
495        E: Into<Box<dyn ErrorTrait + Send + Sync>>;
496
497    /// Similar to or_err(), but takes a closure, which is useful for constructing String.
498    fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
499        self,
500        et: ErrorType,
501        context: F,
502    ) -> Result<T, BError>
503    where
504        E: Into<Box<dyn ErrorTrait + Send + Sync>>;
505
506    /// Replace the E in [Result] with a new [Error] generated from the current error
507    ///
508    /// This is useful when the current error cannot move out of scope. This is a shortcut for map_err() + explain().
509    fn explain_err<C: Into<ImmutStr>, F: FnOnce(E) -> C>(
510        self,
511        et: ErrorType,
512        context: F,
513    ) -> Result<T, BError>;
514
515    /// Similar to or_err() but just to surface errors that are not [Error] (where `?` cannot be used directly).
516    ///
517    /// or_err()/or_err_with() are still preferred because they make the error more readable and traceable.
518    fn or_fail(self) -> Result<T>
519    where
520        E: Into<Box<dyn ErrorTrait + Send + Sync>>;
521}
522
523impl<T, E> OrErr<T, E> for Result<T, E> {
524    fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError>
525    where
526        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
527    {
528        self.map_err(|e| Error::because(et, context, e))
529    }
530
531    fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
532        self,
533        et: ErrorType,
534        context: F,
535    ) -> Result<T, BError>
536    where
537        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
538    {
539        self.map_err(|e| Error::because(et, context(), e))
540    }
541
542    fn explain_err<C: Into<ImmutStr>, F: FnOnce(E) -> C>(
543        self,
544        et: ErrorType,
545        exp: F,
546    ) -> Result<T, BError> {
547        self.map_err(|e| Error::explain(et, exp(e)))
548    }
549
550    fn or_fail(self) -> Result<T, BError>
551    where
552        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
553    {
554        self.map_err(|e| Error::because(ErrorType::InternalError, "", e))
555    }
556}
557
558/// Helper trait to convert an [Option] to an [Error] with context.
559pub trait OkOrErr<T> {
560    fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError>;
561
562    fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
563        self,
564        et: ErrorType,
565        context: F,
566    ) -> Result<T, BError>;
567}
568
569impl<T> OkOrErr<T> for Option<T> {
570    /// Convert the [Option] to a new [Error] with [ErrorType] and context if None, Ok otherwise.
571    ///
572    /// This is a shortcut for .ok_or(Error::explain())
573    fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError> {
574        self.ok_or(Error::explain(et, context))
575    }
576
577    /// Similar to to_err(), but takes a closure, which is useful for constructing String.
578    fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
579        self,
580        et: ErrorType,
581        context: F,
582    ) -> Result<T, BError> {
583        self.ok_or_else(|| Error::explain(et, context()))
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use super::*;
590
591    #[test]
592    fn test_chain_of_error() {
593        let e1 = Error::new(ErrorType::InternalError);
594        let mut e2 = Error::new(ErrorType::HTTPStatus(400));
595        e2.set_cause(e1);
596        assert_eq!(format!("{}", e2), " HTTPStatus cause:  InternalError");
597        assert_eq!(e2.root_etype().as_str(), "InternalError");
598
599        let e3 = Error::new(ErrorType::InternalError);
600        let e4 = Error::because(ErrorType::HTTPStatus(400), "test", e3);
601        assert_eq!(
602            format!("{}", e4),
603            " HTTPStatus context: test cause:  InternalError"
604        );
605        assert_eq!(e4.root_etype().as_str(), "InternalError");
606    }
607
608    #[test]
609    fn test_error_context() {
610        let mut e1 = Error::new(ErrorType::InternalError);
611        e1.set_context(format!("{} {}", "my", "context"));
612        assert_eq!(format!("{}", e1), " InternalError context: my context");
613    }
614
615    #[test]
616    fn test_context_trait() {
617        let e1: Result<(), BError> = Err(Error::new(ErrorType::InternalError));
618        let e2 = e1.err_context(|| "another");
619        assert_eq!(
620            format!("{}", e2.unwrap_err()),
621            " InternalError context: another cause: "
622        );
623    }
624
625    #[test]
626    fn test_cause_trait() {
627        let e1: Result<(), BError> = Err(Error::new(ErrorType::InternalError));
628        let e2 = e1.or_err(ErrorType::HTTPStatus(400), "another");
629        assert_eq!(
630            format!("{}", e2.unwrap_err()),
631            " HTTPStatus context: another cause:  InternalError"
632        );
633    }
634
635    #[test]
636    fn test_option_some_ok() {
637        let m = Some(2);
638        let o = m.or_err(ErrorType::InternalError, "some is not an error!");
639        assert_eq!(2, o.unwrap());
640
641        let o = m.or_err_with(ErrorType::InternalError, || "some is not an error!");
642        assert_eq!(2, o.unwrap());
643    }
644
645    #[test]
646    fn test_option_none_err() {
647        let m: Option<i32> = None;
648        let e1 = m.or_err(ErrorType::InternalError, "none is an error!");
649        assert_eq!(
650            format!("{}", e1.unwrap_err()),
651            " InternalError context: none is an error!"
652        );
653
654        let e1 = m.or_err_with(ErrorType::InternalError, || "none is an error!");
655        assert_eq!(
656            format!("{}", e1.unwrap_err()),
657            " InternalError context: none is an error!"
658        );
659    }
660
661    #[test]
662    fn test_into() {
663        fn other_error() -> Result<(), &'static str> {
664            Err("oops")
665        }
666
667        fn surface_err() -> Result<()> {
668            other_error().or_fail()?; // can return directly but want to showcase ?
669            Ok(())
670        }
671
672        let e = surface_err().unwrap_err();
673        assert_eq!(format!("{}", e), " InternalError context:  cause: oops");
674    }
675}