1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use std::{error, fmt};
4#[cfg(feature = "reqwest")]
5use std::{future::Future, pin::Pin};
6
7use serde::Deserialize;
8
9type Result<T, E> = std::result::Result<T, TorCheckError<E>>;
11#[cfg(feature = "reqwest")]
13type FutureResult<T, E> = Pin<Box<dyn Future<Output = Result<T, E>>>>;
14
15#[derive(Debug, Deserialize)]
17struct TorCheckStatus {
18 #[serde(rename = "IsTor")]
19 is_tor: bool,
20}
21
22impl TorCheckStatus {
23 const API_URL: &str = "https://check.torproject.org/api/ip";
24
25 fn result<E: error::Error>(&self) -> Result<(), E> {
26 if self.is_tor {
27 return Ok(());
28 }
29
30 Err(TorCheckError::YouAreNotUsingTor)
31 }
32}
33
34pub trait TorCheck {
36 type Result;
37
38 fn tor_check(self) -> Self::Result;
42}
43
44#[derive(Debug, PartialEq)]
48pub enum TorCheckError<E> {
49 HttpClient(E),
51 YouAreNotUsingTor,
53}
54
55#[cfg(feature = "reqwest")]
56impl TorCheckError<reqwest::Error> {
57 pub fn is_decode(&self) -> bool {
60 if let Self::HttpClient(err) = self {
61 return err.is_decode();
62 }
63
64 false
65 }
66}
67
68#[cfg(feature = "ureq")]
69impl TorCheckError<ureq::Error> {
70 pub fn is_decode(&self) -> bool {
73 matches!(self, Self::HttpClient(ureq::Error::Json(_)))
74 }
75}
76
77impl<E: error::Error> fmt::Display for TorCheckError<E> {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 Self::HttpClient(err) => write!(f, "{err}"),
81 Self::YouAreNotUsingTor => write!(f, "You are not using Tor"),
82 }
83 }
84}
85
86impl<E: error::Error> error::Error for TorCheckError<E> {}
87
88#[cfg(feature = "ureq")]
89impl From<ureq::Error> for TorCheckError<ureq::Error> {
90 fn from(err: ureq::Error) -> Self {
91 Self::HttpClient(err)
92 }
93}
94
95#[cfg(feature = "reqwest")]
96impl From<reqwest::Error> for TorCheckError<reqwest::Error> {
97 fn from(err: reqwest::Error) -> Self {
98 Self::HttpClient(err)
99 }
100}
101
102#[cfg(feature = "ureq")]
103impl TorCheck for ureq::Agent {
104 type Result = Result<Self, ureq::Error>;
105
106 fn tor_check(self) -> Self::Result {
107 self.get(TorCheckStatus::API_URL)
108 .call()?
109 .body_mut()
110 .read_json::<TorCheckStatus>()?
111 .result()
112 .map(move |_| self)
113 }
114}
115
116#[cfg(feature = "reqwest")]
117impl TorCheck for reqwest::Client {
118 type Result = FutureResult<Self, reqwest::Error>;
119
120 fn tor_check(self) -> Self::Result {
121 Box::pin(async move {
122 self.get(TorCheckStatus::API_URL)
123 .send()
124 .await?
125 .json::<TorCheckStatus>()
126 .await?
127 .result()
128 .map(move |_| self)
129 })
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::{TorCheckError as Error, *};
136
137 #[derive(Debug, PartialEq)]
138 struct MockError;
139
140 impl fmt::Display for MockError {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "Mock error")
143 }
144 }
145
146 impl error::Error for MockError {}
147
148 #[test]
149 fn test_tor_check_result() {
150 let failure = serde_json::from_str::<TorCheckStatus>(
151 r#"{"IsTor":false,"IP":"192.0.2.1"}"#,
152 )
153 .unwrap();
154 let success = serde_json::from_str::<TorCheckStatus>(
155 r#"{"IsTor":true,"IP":"192.0.2.1"}"#,
156 )
157 .unwrap();
158 let expected_error = Error::YouAreNotUsingTor;
159
160 assert_eq!(failure.result::<MockError>(), Err(expected_error));
161 assert_eq!(success.result::<MockError>(), Ok(()));
162 }
163}