trino_rust_client/
error.rs1use reqwest::header::HeaderName;
2use reqwest::StatusCode;
3use serde::Deserialize;
4use thiserror::Error;
5
6use crate::models::QueryError;
7
8#[derive(Error, Debug)]
9#[non_exhaustive]
10pub enum Error {
11 #[error("duplicate header")]
12 DuplicateHeader(HeaderName),
13 #[error("invalid empty auth")]
14 EmptyAuth,
15 #[error("forbidden: {message}")]
16 Forbidden { message: String },
17 #[error("basic auth can not be used with http")]
18 BasicAuthWithHttp,
19 #[error("http error, reason: {0}")]
20 HttpError(#[source] Box<reqwest::Error>),
21 #[error("http not ok, code: {0}, reason: {1}")]
22 HttpNotOk(StatusCode, String),
23 #[error("query error [{}]: {}", .0.error_name, .0.message)]
28 Query(#[source] Box<QueryError>),
29 #[error("decode error: {0}")]
31 Decode(String),
32 #[error("tls error: {0}")]
34 Tls(String),
35 #[error("protocol error: {0}")]
39 Protocol(String),
40 #[error("transaction error: {0}")]
44 Transaction(String),
45 #[error("oauth2 error: {0}")]
48 OAuth2(String),
49 #[error("inconsistent data")]
50 InconsistentData,
51 #[error("reach max attempt: {0}")]
52 ReachMaxAttempt(usize),
53 #[error("invalid host: {0}")]
54 InvalidHost(String),
55 #[error("internal error: {0}")]
57 InternalError(String),
58}
59
60impl From<reqwest::Error> for Error {
61 fn from(err: reqwest::Error) -> Self {
62 Error::HttpError(Box::new(err))
63 }
64}
65
66impl From<QueryError> for Error {
67 fn from(err: QueryError) -> Self {
68 if err.error_code == 4 {
70 Error::Forbidden {
71 message: err.message,
72 }
73 } else {
74 Error::Query(Box::new(err))
75 }
76 }
77}
78
79pub type Result<T> = std::result::Result<T, Error>;
80
81#[derive(Debug, Deserialize)]
82pub struct TrinoRetryResult {
83 pub id: String,
84 #[serde(rename = "infoUri")]
85 pub info_uri: String,
86 pub stats: TrinoStats,
87 pub error: Option<QueryError>,
88 #[serde(rename = "updateType")]
89 pub update_type: Option<String>,
90 #[serde(rename = "updateCount")]
91 pub update_count: Option<u64>,
92}
93
94#[derive(Debug, Deserialize)]
95pub struct TrinoStats {
96 pub state: String,
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::models::QueryError;
103
104 fn query_error(error_code: i32, error_name: &str) -> QueryError {
105 QueryError {
106 message: "boom".into(),
107 sql_state: None,
108 error_code,
109 error_name: error_name.into(),
110 error_type: "USER_ERROR".into(),
111 error_location: None,
112 failure_info: None,
113 }
114 }
115
116 #[test]
120 fn permission_denied_maps_to_forbidden() {
121 match Error::from(query_error(4, "PERMISSION_DENIED")) {
123 Error::Forbidden { message } => assert_eq!(message, "boom"),
124 other => panic!("expected Forbidden, got {other:?}"),
125 }
126 }
127
128 #[test]
129 fn other_failures_map_to_structured_query() {
130 match Error::from(query_error(1, "SYNTAX_ERROR")) {
131 Error::Query(q) => {
132 assert_eq!(q.error_name, "SYNTAX_ERROR");
133 assert_eq!(q.error_code, 1);
134 assert_eq!(q.error_type, "USER_ERROR");
135 }
136 other => panic!("expected Query, got {other:?}"),
137 }
138 }
139
140 #[test]
141 fn query_error_preserves_source_chain() {
142 use std::error::Error as _;
143
144 let err = Error::from(query_error(1, "SYNTAX_ERROR"));
145 assert_eq!(err.to_string(), "query error [SYNTAX_ERROR]: boom");
147 let source = err.source().expect("Query should expose a source");
150 assert!(source.to_string().contains("boom"));
151 }
152}