tor_dirclient/
response.rs1use std::str;
4
5use tor_linkspec::{LoggedChanTarget, OwnedChanTarget};
6use tor_proto::circuit::UniqId;
7
8use crate::{RequestError, RequestFailedError};
9
10#[derive(Debug, Clone)]
13#[must_use = "You need to check whether the response was successful."]
14pub struct DirResponse {
15 status: u16,
17 status_message: Option<String>,
19 output: Vec<u8>,
21 error: Option<RequestError>,
23 source: Option<SourceInfo>,
25}
26
27#[derive(Debug, Clone, derive_more::Display)]
32#[display("{} via {}", cache_id, tunnel)]
33pub struct SourceInfo {
34 tunnel: UniqId,
36 cache_id: LoggedChanTarget,
38}
39
40impl DirResponse {
41 pub(crate) fn new(
43 status: u16,
44 status_message: Option<String>,
45 error: Option<RequestError>,
46 output: Vec<u8>,
47 source: Option<SourceInfo>,
48 ) -> Self {
49 DirResponse {
50 status,
51 status_message,
52 output,
53 error,
54 source,
55 }
56 }
57
58 pub fn from_body(body: impl AsRef<[u8]>) -> Self {
60 Self::new(200, None, None, body.as_ref().to_vec(), None)
61 }
62
63 pub fn status_code(&self) -> u16 {
65 self.status
66 }
67
68 pub fn is_partial(&self) -> bool {
70 self.error.is_some()
71 }
72
73 pub fn error(&self) -> Option<&RequestError> {
75 self.error.as_ref()
76 }
77
78 pub fn output_unchecked(&self) -> &[u8] {
82 &self.output
83 }
84
85 pub fn output(&self) -> Result<&[u8], RequestFailedError> {
87 self.check_ok()?;
88 Ok(self.output_unchecked())
89 }
90
91 pub fn output_string(&self) -> Result<&str, RequestFailedError> {
94 let output = self.output()?;
95 let s = str::from_utf8(output).map_err(|_| RequestFailedError {
96 error: String::from_utf8(output.to_owned())
99 .expect_err("was bad, now good")
100 .into(),
101 source: self.source.clone(),
102 })?;
103 Ok(s)
104 }
105
106 pub fn into_output_unchecked(self) -> Vec<u8> {
110 self.output
111 }
112
113 pub fn into_output(self) -> Result<Vec<u8>, RequestFailedError> {
115 self.check_ok()?;
116 Ok(self.into_output_unchecked())
117 }
118
119 pub fn into_output_string(self) -> Result<String, RequestFailedError> {
122 self.check_ok()?;
123 let s = String::from_utf8(self.output).map_err(|error| RequestFailedError {
124 error: error.into(),
125 source: self.source.clone(),
126 })?;
127 Ok(s)
128 }
129
130 pub fn source(&self) -> Option<&SourceInfo> {
132 self.source.as_ref()
133 }
134
135 fn check_ok(&self) -> Result<(), RequestFailedError> {
137 let wrap_err = |error| {
138 Err(RequestFailedError {
139 error,
140 source: self.source.clone(),
141 })
142 };
143 if let Some(error) = &self.error {
144 return wrap_err(error.clone());
145 }
146 assert!(!self.is_partial(), "partial but no error?");
147 if self.status_code() != 200 {
148 let msg = match &self.status_message {
149 Some(m) => m.clone(),
150 None => "".to_owned(),
151 };
152 return wrap_err(RequestError::HttpStatus(self.status_code(), msg));
153 }
154 Ok(())
155 }
156}
157
158impl SourceInfo {
159 pub fn from_tunnel(
164 tunnel: impl AsRef<tor_proto::ClientTunnel>,
165 ) -> tor_proto::Result<Option<Self>> {
166 let tunnel = tunnel.as_ref();
167 match tunnel.last_hop_info()? {
168 None => Ok(None),
169 Some(last_hop) => Ok(Some(SourceInfo {
170 tunnel: tunnel.unique_id(),
171 cache_id: last_hop.into(),
172 })),
173 }
174 }
175
176 pub fn unique_circ_id(&self) -> &UniqId {
179 &self.tunnel
180 }
181
182 pub fn cache_id(&self) -> &OwnedChanTarget {
184 self.cache_id.as_inner()
185 }
186}
187
188#[cfg(test)]
189mod test {
190 #![allow(clippy::bool_assert_comparison)]
192 #![allow(clippy::clone_on_copy)]
193 #![allow(clippy::dbg_macro)]
194 #![allow(clippy::mixed_attributes_style)]
195 #![allow(clippy::print_stderr)]
196 #![allow(clippy::print_stdout)]
197 #![allow(clippy::single_char_pattern)]
198 #![allow(clippy::unwrap_used)]
199 #![allow(clippy::unchecked_duration_subtraction)]
200 #![allow(clippy::useless_vec)]
201 #![allow(clippy::needless_pass_by_value)]
202 use super::*;
204
205 #[test]
206 fn errors() {
207 let mut response = DirResponse::new(200, None, None, vec![b'Y'], None);
208
209 assert_eq!(response.output().unwrap(), b"Y");
210 assert_eq!(response.clone().into_output().unwrap(), b"Y");
211
212 let expect_error = |response: &DirResponse, error: RequestError| {
213 let error = RequestFailedError {
214 error,
215 source: None,
216 };
217 let error = format!("{:?}", error);
218
219 assert_eq!(error, format!("{:?}", response.output().unwrap_err()));
220 assert_eq!(
221 error,
222 format!("{:?}", response.clone().into_output().unwrap_err())
223 );
224 };
225
226 let with_error = |response: &DirResponse| {
227 let mut response = response.clone();
228 response.error = Some(RequestError::DirTimeout);
229 expect_error(&response, RequestError::DirTimeout);
230 };
231
232 with_error(&response);
233
234 response.status = 404;
235 response.status_message = Some("Not found".into());
236 expect_error(&response, RequestError::HttpStatus(404, "Not found".into()));
237
238 with_error(&response);
239 }
240}