1use reqwest::header;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5pub struct Client {
8 client: reqwest::Client,
9}
10
11#[derive(Serialize, Deserialize)]
12pub struct Error {
13 code: i32,
14 message: String,
15}
16
17impl fmt::Display for Error {
18 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19 write!(f, "{}", self.message)
20 }
21}
22
23impl std::error::Error for Error {}
24
25impl fmt::Debug for Error {
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 write!(
28 f,
29 "AppError {{ code: {}, message: {} }}",
30 self.code, self.message
31 )
32 }
33}
34
35pub struct Point {
36 pub longitude: f64,
37 pub latitude: f64,
38}
39
40pub enum Type {
41 Car,
42 Motorcycle,
43}
44
45#[derive(Debug, Serialize, Deserialize)]
46pub struct Routes {
47 pub routes: Vec<Route>,
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51pub struct Route {
52 pub legs: Vec<Leg>,
53}
54
55#[derive(Debug, Serialize, Deserialize)]
56pub struct Leg {
57 pub summary: String,
58 pub duration: Duration,
59 pub distance: Distance,
60}
61
62#[derive(Debug, Serialize, Deserialize)]
63pub struct PostalAddress {
64 pub formatted_address: String,
65 pub route_name: String,
66 pub neighbourhood: Option<String>,
67 pub city: String,
68 pub state: String,
69 pub place: Option<String>,
70 pub municipality_zone: Option<String>,
71 pub in_traffic_zone: bool,
72 pub in_odd_even_zone: bool,
73}
74
75#[derive(Debug, Serialize, Deserialize)]
77pub struct Distance {
78 pub value: f64,
79 pub text: String,
80}
81
82#[derive(Debug, Serialize, Deserialize)]
84pub struct Duration {
85 pub value: f64,
86 pub text: String,
87}
88
89impl fmt::Display for Type {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 Type::Car => f.write_str("car"),
93 Type::Motorcycle => f.write_str("motorcycle"),
94 }
95 }
96}
97
98impl Client {
99 pub fn new(api_key: &str) -> Client {
101 let mut headers = header::HeaderMap::new();
102 headers.insert("Api-Key", header::HeaderValue::from_str(api_key).unwrap());
103
104 let client = reqwest::Client::builder()
105 .user_agent("neshan-rs")
106 .default_headers(headers)
107 .build()
108 .unwrap();
109
110 Client { client }
111 }
112
113 pub async fn route(
119 &self,
120 vehicle: Type,
121 origin: Point,
122 destination: Point,
123 avoid_traffic_zone: bool,
124 avoid_odd_even_zone: bool,
125 alternative_paths: bool,
126 ) -> Result<Routes, Box<dyn std::error::Error>> {
127 let res = self
128 .client
129 .get("https://api.neshan.org/v3/direction")
130 .query(&[
131 ("type", vehicle.to_string()),
132 (
133 "origin",
134 format!("{},{}", origin.latitude, origin.longitude),
135 ),
136 (
137 "destination",
138 format!("{},{}", destination.latitude, destination.longitude),
139 ),
140 ("avoid_traffic_zone", avoid_traffic_zone.to_string()),
141 ("avoid_odd_event_zone", avoid_odd_even_zone.to_string()),
142 ("alternative", alternative_paths.to_string()),
143 ])
144 .send()
145 .await?;
146
147 if !res.status().is_success() {
148 let err = res.json::<Error>().await?;
149
150 return Err(Box::new(err));
151 }
152
153 let routes = res.json::<Routes>().await?;
154
155 Ok(routes)
156 }
157
158 pub async fn reverse_geocode(
161 &self,
162 point: Point,
163 ) -> Result<PostalAddress, Box<dyn std::error::Error>> {
164 let res = self
165 .client
166 .get("https://api.neshan.org/v2/reverse")
167 .query(&[
168 ("lat", point.latitude.to_string()),
169 ("lng", point.longitude.to_string()),
170 ])
171 .send()
172 .await?;
173
174 if !res.status().is_success() {
175 let err = res.json::<Error>().await?;
176
177 return Err(Box::new(err));
178 }
179
180 let postal_address = res.json::<PostalAddress>().await?;
181
182 Ok(postal_address)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 #[tokio::test]
189 async fn routes() {
190 let api_key = std::env::var("NESHAN_RS_API_KEY").unwrap();
191
192 let client = super::Client::new(&api_key);
193 let routes = client
194 .route(
195 super::Type::Car,
196 super::Point {
197 latitude: 35.731984409609694,
198 longitude: 51.392684661470156,
199 },
200 super::Point {
201 latitude: 35.723680037006304,
202 longitude: 50.953103738230396,
203 },
204 true,
205 true,
206 false,
207 )
208 .await
209 .unwrap();
210
211 println!("{:?}", routes);
212 }
213
214 #[tokio::test]
215 async fn reverse_geocode() {
216 let api_key = std::env::var("NESHAN_RS_API_KEY").unwrap();
217
218 let client = super::Client::new(&api_key);
219 let postal_address = client
220 .reverse_geocode(super::Point {
221 latitude: 35.731984409609694,
222 longitude: 51.392684661470156,
223 })
224 .await
225 .unwrap();
226
227 assert_eq!(postal_address.neighbourhood.as_ref().unwrap(), "قزل قلعه");
228 assert_eq!(postal_address.municipality_zone.as_ref().unwrap(), "6");
229 assert_eq!(postal_address.city, "تهران");
230
231 println!("{:?}", postal_address);
232 }
233}