uqa_client/
http_engine_error.rs1use std::fmt;
8use std::io;
9
10use reqwest::StatusCode;
11use thiserror::Error;
12
13#[derive(Error)]
15pub enum HttpEngineError {
16 #[error("UQA data-plane URL is invalid")]
17 InvalidBaseURL,
18 #[error("plain HTTP UQA URLs must resolve to loopback")]
19 InsecureRemoteURL,
20 #[error("UQA project token must not be empty")]
21 InvalidCredential,
22 #[error("required UQA connection environment variable {0} is missing")]
23 MissingEnvironmentVariable(&'static str),
24 #[error("UQA project name must not be empty")]
25 EmptyProjectName,
26 #[error("UQA organization name must not be empty")]
27 EmptyOrganizationName,
28 #[error("uqa CLI could not be started; install it or provide its executable path")]
29 CLIUnavailable(#[source] io::Error),
30 #[error("uqa CLI connection lookup failed")]
31 CLIExecution(#[source] io::Error),
32 #[error("uqa CLI connection lookup timed out")]
33 CLITimedOut,
34 #[error("uqa CLI connection output exceeded the client safety limit")]
35 CLIOutputTooLarge,
36 #[error("uqa CLI could not resolve the requested project; run the matching connection command for details")]
37 CLIConnectionFailed,
38 #[error("uqa CLI returned an invalid connection response")]
39 InvalidCLIResponse(#[source] serde_json::Error),
40 #[error("SQL text must not be empty")]
41 EmptySQL,
42 #[error("SQL parameter cannot be represented by the HTTP protocol")]
43 InvalidParameter,
44 #[error("UQA HTTP client could not be initialized")]
45 BuildClient(#[source] reqwest::Error),
46 #[error("UQA HTTP transport failed")]
47 Transport(#[source] reqwest::Error),
48 #[error("UQA returned {status} with code {code}")]
49 Server {
50 status: StatusCode,
51 code: String,
52 message: String,
53 request_id: Option<String>,
54 },
55 #[error("UQA response exceeded the client safety limit")]
56 ResponseTooLarge,
57 #[error("UQA response content type is invalid")]
58 UnexpectedContentType,
59 #[error("UQA response is missing its request ID")]
60 MissingRequestId,
61 #[error("UQA response request IDs do not match")]
62 ResponseRequestIdMismatch,
63 #[error("UQA response body is not valid JSON")]
64 InvalidResponse(#[source] serde_json::Error),
65 #[error("UQA NDJSON stream frame exceeded the client safety limit")]
66 StreamFrameTooLarge,
67 #[error("UQA NDJSON stream frame order is invalid")]
68 InvalidStreamSequence,
69 #[error("UQA NDJSON stream ended before a terminal frame")]
70 TruncatedStream,
71 #[error("UQA NDJSON stream request ID does not match its HTTP response")]
72 StreamRequestIdMismatch,
73}
74
75impl HttpEngineError {
76 pub(crate) fn build_client(error: reqwest::Error) -> Self {
77 Self::BuildClient(error.without_url())
78 }
79
80 pub(crate) fn transport(error: reqwest::Error) -> Self {
81 Self::Transport(error.without_url())
82 }
83}
84
85impl fmt::Debug for HttpEngineError {
86 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match self {
88 Self::Server {
89 status,
90 code,
91 request_id,
92 ..
93 } => formatter
94 .debug_struct("HttpEngineError::Server")
95 .field("status", status)
96 .field("code", code)
97 .field("request_id", request_id)
98 .finish(),
99 Self::InvalidBaseURL => formatter.write_str("HttpEngineError::InvalidBaseURL"),
100 Self::InsecureRemoteURL => formatter.write_str("HttpEngineError::InsecureRemoteURL"),
101 Self::InvalidCredential => formatter.write_str("HttpEngineError::InvalidCredential"),
102 Self::MissingEnvironmentVariable(name) => formatter
103 .debug_tuple("HttpEngineError::MissingEnvironmentVariable")
104 .field(name)
105 .finish(),
106 Self::EmptyProjectName => formatter.write_str("HttpEngineError::EmptyProjectName"),
107 Self::EmptyOrganizationName => {
108 formatter.write_str("HttpEngineError::EmptyOrganizationName")
109 }
110 Self::CLIUnavailable(_) => {
111 formatter.write_str("HttpEngineError::CLIUnavailable([REDACTED])")
112 }
113 Self::CLIExecution(_) => {
114 formatter.write_str("HttpEngineError::CLIExecution([REDACTED])")
115 }
116 Self::CLITimedOut => formatter.write_str("HttpEngineError::CLITimedOut"),
117 Self::CLIOutputTooLarge => formatter.write_str("HttpEngineError::CLIOutputTooLarge"),
118 Self::CLIConnectionFailed => {
119 formatter.write_str("HttpEngineError::CLIConnectionFailed")
120 }
121 Self::InvalidCLIResponse(_) => {
122 formatter.write_str("HttpEngineError::InvalidCLIResponse([REDACTED])")
123 }
124 Self::EmptySQL => formatter.write_str("HttpEngineError::EmptySQL"),
125 Self::InvalidParameter => formatter.write_str("HttpEngineError::InvalidParameter"),
126 Self::BuildClient(_) => formatter.write_str("HttpEngineError::BuildClient([REDACTED])"),
127 Self::Transport(_) => formatter.write_str("HttpEngineError::Transport([REDACTED])"),
128 Self::ResponseTooLarge => formatter.write_str("HttpEngineError::ResponseTooLarge"),
129 Self::UnexpectedContentType => {
130 formatter.write_str("HttpEngineError::UnexpectedContentType")
131 }
132 Self::MissingRequestId => formatter.write_str("HttpEngineError::MissingRequestId"),
133 Self::ResponseRequestIdMismatch => {
134 formatter.write_str("HttpEngineError::ResponseRequestIdMismatch")
135 }
136 Self::InvalidResponse(_) => {
137 formatter.write_str("HttpEngineError::InvalidResponse([REDACTED])")
138 }
139 Self::StreamFrameTooLarge => {
140 formatter.write_str("HttpEngineError::StreamFrameTooLarge")
141 }
142 Self::InvalidStreamSequence => {
143 formatter.write_str("HttpEngineError::InvalidStreamSequence")
144 }
145 Self::TruncatedStream => formatter.write_str("HttpEngineError::TruncatedStream"),
146 Self::StreamRequestIdMismatch => {
147 formatter.write_str("HttpEngineError::StreamRequestIdMismatch")
148 }
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn server_debug_output_omits_customer_message() {
159 let secret = "customer SQL and result";
160 let error = HttpEngineError::Server {
161 status: StatusCode::BAD_REQUEST,
162 code: "SQL_EXECUTION_FAILED".to_owned(),
163 message: secret.to_owned(),
164 request_id: Some("qry_test".to_owned()),
165 };
166
167 let debug = format!("{error:?}");
168 assert!(!debug.contains(secret));
169 assert!(debug.contains("SQL_EXECUTION_FAILED"));
170 }
171}