1use snafu::Snafu;
16
17static LOG_TARGET: &str = "pingap::core";
18
19#[derive(Debug, Snafu)]
20pub enum Error {
21 #[snafu(display("invalid error, {message}"))]
22 Invalid { message: String },
23 #[snafu(display("{category} not found"))]
24 NotFound { category: String },
25}
26
27pub fn new_internal_error(
29 status: u16,
30 message: impl ToString,
31) -> pingora::BError {
32 pingora::Error::because(
33 pingora::ErrorType::HTTPStatus(status),
34 message.to_string(),
35 pingora::Error::new(pingora::ErrorType::InternalError),
36 )
37}
38
39const UPSTREAM_STATUS: pingora::ErrorType =
42 pingora::ErrorType::Custom("UpstreamStatus");
43
44pub fn new_upstream_status_error(status: http::StatusCode) -> pingora::BError {
53 pingora::Error::because(
54 pingora::ErrorType::HTTPStatus(status.as_u16()),
55 status.canonical_reason().unwrap_or("Unknown Error"),
56 pingora::Error::new(UPSTREAM_STATUS),
57 )
58}
59
60pub fn is_upstream_status_error(e: &pingora::Error) -> bool {
62 matches!(e.etype(), pingora::ErrorType::HTTPStatus(_))
63 && e.root_etype() == &UPSTREAM_STATUS
64}
65
66pub const ACCESS_LOG_PRESETS: [&str; 5] =
70 ["combined", "common", "short", "tiny", "json"];
71
72mod ctx;
73mod http_header;
74mod http_response;
75mod identity;
76pub mod ja4;
77mod notification;
78mod plugin;
79mod proxy_protocol;
80mod service;
81mod ttl_lru_limit;
82mod util;
83
84pub use ctx::*;
85pub use http_header::*;
86pub use http_response::*;
87pub use identity::*;
88pub use ja4::{Ja4Fingerprint, is_grease};
89pub use notification::*;
90pub use pingora_limits::inflight::{Guard, Inflight};
91pub use pingora_limits::rate::Rate;
92pub use plugin::*;
93pub use proxy_protocol::*;
94pub use service::*;
95pub use tinyufo::TinyUfo;
96pub use ttl_lru_limit::*;
97pub use util::*;
98
99#[cfg(test)]
102pub(crate) async fn new_test_session(
103 headers: &[&str],
104 url: &str,
105) -> pingora::proxy::Session {
106 let headers = headers.join("\r\n");
107 let input_header = format!("GET {url} HTTP/1.1\r\n{headers}\r\n\r\n");
108 let mock_io = tokio_test::io::Builder::new()
109 .read(input_header.as_bytes())
110 .build();
111 let mut session = pingora::proxy::Session::new_h1(Box::new(mock_io));
112 session
113 .read_request()
114 .await
115 .expect("the test request must parse");
116 session
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_new_internal_error() {
125 let err = new_internal_error(500, "Internal Server Error");
126 assert_eq!(
127 err.to_string().trim(),
128 "HTTPStatus context: Internal Server Error cause: InternalError"
129 );
130 }
131
132 #[test]
135 fn test_upstream_status_error() {
136 let err = new_upstream_status_error(http::StatusCode::NOT_FOUND);
137 assert_eq!(&pingora::ErrorType::HTTPStatus(404), err.etype());
138 assert_eq!(
139 err.to_string().trim(),
140 "HTTPStatus context: Not Found cause: UpstreamStatus"
141 );
142 assert!(is_upstream_status_error(&err));
143 assert!(!err.retry());
144 assert!(is_upstream_status_error(&err.more_context("Peer: a")));
145
146 assert!(!is_upstream_status_error(&new_internal_error(404, "x")));
147 assert!(!is_upstream_status_error(&pingora::Error::new(
148 pingora::ErrorType::ConnectRefused
149 )));
150 let err =
152 new_upstream_status_error(http::StatusCode::from_u16(499).unwrap());
153 assert!(err.to_string().contains("Unknown Error"));
154 }
155
156 #[tokio::test]
157 async fn test_new_test_session() {
158 let session = new_test_session(
159 &["Host: github.com", "user-agent: pingap/0.1.1"],
160 "/",
161 )
162 .await;
163 assert_eq!(
164 b"pingap/0.1.1",
165 session.get_header("user-agent").unwrap().as_bytes()
166 );
167 assert_eq!("/", session.req_header().uri.path());
168 }
169}