1use std::collections::HashMap;
2use std::fmt::Write;
3use std::future::Future;
4
5use serde::Serialize;
6
7use tracing::error;
8
9use tosca::device::DeviceEnvironment;
10use tosca::hazards::Hazards;
11use tosca::parameters::{ParameterValue, ParametersData, ParametersValues};
12use tosca::response::{ResponseKind, SERIALIZATION_ERROR};
13use tosca::route::{RestKind, RouteConfig, RouteConfigs};
14
15use crate::error::{Error, ErrorKind};
16use crate::response::{InfoResponseParser, OkResponseParser, Response, SerialResponseParser};
17
18fn slash_end(s: &str) -> &str {
19 if s.len() > 1 && s.ends_with('/') {
20 &s[..s.len() - 1]
21 } else {
22 s
23 }
24}
25
26fn slash_start(s: &str) -> &str {
27 if s.len() > 1 && s.starts_with('/') {
28 &s[1..]
29 } else {
30 s
31 }
32}
33
34fn slash_start_end(s: &str) -> &str {
35 slash_start(slash_end(s))
36}
37
38fn compare_values_with_params_data(
39 parameter_values: &ParametersValues,
40 parameters_data: &ParametersData,
41) -> Result<(), Error> {
42 for (name, parameter_value) in parameter_values {
43 let Some(parameter_kind) = parameters_data.get(name) else {
44 return Err(parameter_error(format!("`{name}` does not exist")));
45 };
46
47 if !parameter_value.match_kind(parameter_kind) {
48 return Err(parameter_error(format!(
49 "Found type `{}` for `{name}`, expected type `{}`",
50 parameter_value.as_type(),
51 parameter_kind.as_type(),
52 )));
53 }
54 }
55 Ok(())
56}
57
58fn parameter_error(message: String) -> Error {
59 error!(message);
60 Error::new(ErrorKind::InvalidParameter, message)
61}
62
63#[derive(Debug, PartialEq)]
64struct RequestData {
65 request: String,
66 parameters: HashMap<String, String>,
67}
68
69impl RequestData {
70 const fn new(request: String, parameters: HashMap<String, String>) -> Self {
71 Self {
72 request,
73 parameters,
74 }
75 }
76}
77
78pub(crate) fn create_requests(
79 route_configs: RouteConfigs,
80 complete_address: &str,
81 main_route: &str,
82 environment: DeviceEnvironment,
83) -> HashMap<String, Request> {
84 route_configs
85 .into_iter()
86 .map(|route| {
87 (
88 route.data.path.to_string(),
89 Request::new(complete_address, main_route, environment, route),
90 )
91 })
92 .collect()
93}
94
95pub struct RequestInfo<'device> {
100 pub route: &'device str,
102 pub description: Option<&'device str>,
104 pub rest_kind: RestKind,
106 pub hazards: &'device Hazards,
108 pub parameters_data: &'device ParametersData,
112 pub response_kind: ResponseKind,
114}
115
116impl<'device> RequestInfo<'device> {
117 pub(crate) fn new(route: &'device str, request: &'device Request) -> Self {
118 Self {
119 route,
120 description: request.description.as_deref(),
121 rest_kind: request.kind,
122 hazards: &request.hazards,
123 parameters_data: &request.parameters_data,
124 response_kind: request.response_kind,
125 }
126 }
127}
128
129#[derive(Debug, PartialEq, Serialize)]
134pub struct Request {
135 pub(crate) kind: RestKind,
136 pub(crate) hazards: Hazards,
137 pub(crate) route: String,
138 pub(crate) description: Option<String>,
139 pub(crate) parameters_data: ParametersData,
140 pub(crate) response_kind: ResponseKind,
141 pub(crate) device_environment: DeviceEnvironment,
142}
143
144impl Request {
145 #[must_use]
147 pub fn hazards(&self) -> &Hazards {
148 &self.hazards
149 }
150
151 #[must_use]
153 pub fn kind(&self) -> RestKind {
154 self.kind
155 }
156
157 #[must_use]
162 pub fn parameters_data(&self) -> Option<&ParametersData> {
163 self.parameters_data
164 .is_empty()
165 .then_some(&self.parameters_data)
166 }
167
168 pub(crate) fn new(
169 address: &str,
170 main_route: &str,
171 device_environment: DeviceEnvironment,
172 route_config: RouteConfig,
173 ) -> Self {
174 let kind = route_config.rest_kind;
175 let route = format!(
176 "{}/{}/{}",
177 slash_end(address),
178 slash_start_end(main_route),
179 slash_start_end(&route_config.data.path)
180 );
181 let hazards = route_config.data.hazards;
182 let parameters_data = route_config.data.parameters;
183 let response_kind = route_config.response_kind;
184
185 Self {
186 kind,
187 hazards,
188 route,
189 description: route_config.data.description.map(|s| s.to_string()),
190 parameters_data,
191 response_kind,
192 device_environment,
193 }
194 }
195
196 pub(crate) async fn retrieve_response<F, Fut>(
197 &self,
198 skip: bool,
199 retrieve_response: F,
200 ) -> Result<Response, Error>
201 where
202 F: FnOnce() -> Fut,
203 Fut: Future<Output = Result<reqwest::Response, Error>>,
204 {
205 if skip {
206 return Ok(Response::Skipped);
207 }
208
209 let response = retrieve_response().await?;
210
211 Ok(match self.response_kind {
212 ResponseKind::Ok => Response::OkBody(OkResponseParser::new(response)),
213 ResponseKind::Serial => Response::SerialBody(SerialResponseParser::new(response)),
214 ResponseKind::Info => Response::InfoBody(InfoResponseParser::new(response)),
215 #[cfg(feature = "stream")]
216 ResponseKind::Stream => {
217 Response::StreamBody(crate::response::StreamResponse::new(response))
218 }
219 })
220 }
221
222 pub(crate) async fn plain_send(&self) -> Result<reqwest::Response, Error> {
223 let request_data =
224 self.request_data(|| self.axum_get_plain(), || self.create_params_plain());
225
226 self.parameters_send(request_data).await
227 }
228
229 pub(crate) async fn create_response(
230 &self,
231 parameters: &ParametersValues<'_>,
232 ) -> Result<reqwest::Response, Error> {
233 let request_data = self.create_request(parameters)?;
234 self.parameters_send(request_data).await
235 }
236
237 async fn parameters_send(&self, request_data: RequestData) -> Result<reqwest::Response, Error> {
238 let RequestData {
239 request,
240 parameters,
241 } = request_data;
242
243 let client = reqwest::Client::new();
244
245 let request_builder = match self.kind {
246 RestKind::Get => client.get(request),
247 RestKind::Post => client.post(request),
248 RestKind::Put => client.put(request),
249 RestKind::Delete => client.delete(request),
250 };
251
252 let request_builder = if self.kind != RestKind::Get && !parameters.is_empty() {
253 request_builder.json(¶meters)
254 } else {
255 request_builder
256 };
257
258 let response = request_builder.header("Connection", "close").send().await?;
260
261 if response.headers().contains_key(SERIALIZATION_ERROR) {
279 match response.text().await {
280 Ok(serial_error) => {
281 error!("Serialization error encountered on the device side: {serial_error}");
282 return Err(Error::new(ErrorKind::Request, serial_error));
283 }
284 Err(err) => {
285 error!("Error occurred while converting the request into text: {err}");
286 return Err(Error::new(ErrorKind::Request, err.to_string()));
287 }
288 }
289 }
290
291 Ok(response)
292 }
293
294 fn request_data<A, F>(&self, axum_get: A, params: F) -> RequestData
295 where
296 A: FnOnce() -> String,
297 F: FnOnce() -> HashMap<String, String>,
298 {
299 let request =
300 if self.kind == RestKind::Get && self.device_environment == DeviceEnvironment::Os {
301 axum_get()
302 } else {
303 self.route.clone()
304 };
305
306 let parameters = params();
307
308 RequestData::new(request, parameters)
309 }
310
311 fn create_request(&self, parameters: &ParametersValues) -> Result<RequestData, Error> {
312 compare_values_with_params_data(parameters, &self.parameters_data)?;
314
315 Ok(self.request_data(
316 || self.axum_get(parameters),
317 || self.create_params(parameters),
318 ))
319 }
320
321 fn axum_get_plain(&self) -> String {
322 let mut route = self.route.clone();
323 for (_, parameter_kind) in &self.parameters_data {
324 if let Err(e) = write!(
326 route,
327 "/{}",
328 ParameterValue::from_parameter_kind(parameter_kind)
329 ) {
330 error!("Error in adding a path to a route : {e}");
331 break;
332 }
333 }
334 route
335 }
336
337 fn create_params_plain(&self) -> HashMap<String, String> {
338 let mut params = HashMap::new();
339 for (name, parameter_kind) in &self.parameters_data {
340 params.insert(
341 name.clone(),
342 format!("{}", ParameterValue::from_parameter_kind(parameter_kind)),
343 );
344 }
345 params
346 }
347
348 fn axum_get(&self, parameters: &ParametersValues) -> String {
351 let mut route = String::from(&self.route);
352 for (name, parameter_kind) in &self.parameters_data {
353 let value = if let Some(value) = parameters.get(name) {
354 format!("{value}")
355 } else {
356 format!("{}", ParameterValue::from_parameter_kind(parameter_kind))
357 };
358 if let Err(e) = write!(route, "/{value}") {
360 error!("Error in adding a path to a route : {e}");
361 break;
362 }
363 }
364
365 route
366 }
367
368 fn create_params(&self, parameters: &ParametersValues<'_>) -> HashMap<String, String> {
369 let mut params = HashMap::new();
370 for (name, parameter_kind) in &self.parameters_data {
371 let (name, value) = if let Some(value) = parameters.get(name) {
372 (name, format!("{value}"))
373 } else {
374 (
375 name,
376 format!("{}", ParameterValue::from_parameter_kind(parameter_kind)),
377 )
378 };
379 params.insert(name.clone(), value);
380 }
381 params
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use std::collections::HashMap;
388
389 use tosca::device::DeviceEnvironment;
390 use tosca::hazards::{Hazard, Hazards};
391 use tosca::parameters::{ParameterKind, Parameters, ParametersData, ParametersValues};
392 use tosca::route::{RestKind, Route, RouteConfig};
393
394 use super::{Request, RequestData, ResponseKind, parameter_error};
395
396 const ADDRESS_ROUTE: &str = "http://tosca.local/";
397 const ADDRESS_ROUTE_WITHOUT_SLASH: &str = "http://tosca.local/";
398 const COMPLETE_ROUTE: &str = "http://tosca.local/light/route";
399
400 fn plain_request(route: Route, kind: RestKind, hazards: Hazards) {
401 let route = route.serialize_data();
402 let description = route
403 .data
404 .description
405 .as_ref()
406 .map(std::string::ToString::to_string);
407
408 let request = Request::new(ADDRESS_ROUTE, "light/", DeviceEnvironment::Os, route);
409
410 assert_eq!(
411 request,
412 Request {
413 kind,
414 hazards,
415 route: COMPLETE_ROUTE.into(),
416 description,
417 parameters_data: ParametersData::new(),
418 response_kind: ResponseKind::Ok,
419 device_environment: DeviceEnvironment::Os,
420 }
421 );
422 }
423
424 fn request_with_parameters(route: Route, kind: RestKind, hazards: &Hazards) {
425 let route = route
426 .with_parameters(
427 Parameters::new()
428 .rangeu64_with_default("rangeu64", (0, 20, 1), 5)
429 .rangef64("rangef64", (0., 20., 0.1)),
430 )
431 .serialize_data();
432 let description = route
433 .data
434 .description
435 .as_ref()
436 .map(std::string::ToString::to_string);
437
438 let parameters_data = ParametersData::new()
439 .insert(
440 "rangeu64".into(),
441 ParameterKind::RangeU64 {
442 min: 0,
443 max: 20,
444 step: 1,
445 default: 5,
446 },
447 )
448 .insert(
449 "rangef64".into(),
450 ParameterKind::RangeF64 {
451 min: 0.,
452 max: 20.,
453 step: 0.1,
454 default: 0.,
455 },
456 );
457
458 let request = Request::new(ADDRESS_ROUTE, "light/", DeviceEnvironment::Os, route);
459
460 assert_eq!(
461 request,
462 Request {
463 kind,
464 hazards: hazards.clone(),
465 route: COMPLETE_ROUTE.into(),
466 description,
467 parameters_data,
468 response_kind: ResponseKind::Ok,
469 device_environment: DeviceEnvironment::Os,
470 }
471 );
472
473 assert_eq!(
475 request.create_request(ParametersValues::new().u64("wrong", 0)),
476 Err(parameter_error("`wrong` does not exist".into()))
477 );
478
479 assert_eq!(
481 request.create_request(ParametersValues::new().f64("rangeu64", 0.)),
482 Err(parameter_error(
483 "Found type `f64` for `rangeu64`, expected type `u64`".into()
484 ))
485 );
486
487 let mut parameters = HashMap::with_capacity(2);
488 parameters.insert("rangeu64".into(), "3".into());
489 parameters.insert("rangef64".into(), "0".into());
490
491 assert_eq!(
492 request.create_request(ParametersValues::new().u64("rangeu64", 3)),
493 Ok(RequestData {
494 request: if kind == RestKind::Get {
495 format!("{COMPLETE_ROUTE}/3/0")
496 } else {
497 COMPLETE_ROUTE.into()
498 },
499 parameters,
500 })
501 );
502 }
503
504 fn request_builder(
505 route: &str,
506 main_route: &str,
507 device_environment: DeviceEnvironment,
508 route_config: RouteConfig,
509 ) {
510 assert_eq!(
511 Request::new(route, main_route, device_environment, route_config),
512 Request {
513 kind: RestKind::Put,
514 hazards: Hazards::new(),
515 route: COMPLETE_ROUTE.into(),
516 description: None,
517 parameters_data: ParametersData::new(),
518 response_kind: ResponseKind::Ok,
519 device_environment: DeviceEnvironment::Os,
520 }
521 );
522 }
523
524 #[test]
525 fn check_request_builder() {
526 let route = Route::put("Route", "/route").serialize_data();
527 let environment = DeviceEnvironment::Os;
528
529 request_builder(ADDRESS_ROUTE, "light/", environment, route.clone());
530 request_builder(ADDRESS_ROUTE_WITHOUT_SLASH, "light", environment, route);
531 }
532
533 #[test]
534 fn create_plain_get_request() {
535 let route = Route::get("Route", "/route").description("A GET route.");
536 plain_request(route, RestKind::Get, Hazards::new());
537 }
538
539 #[test]
540 fn create_plain_post_request() {
541 let route = Route::post("Route", "/route").description("A POST route.");
542 plain_request(route, RestKind::Post, Hazards::new());
543 }
544
545 #[test]
546 fn create_plain_put_request() {
547 let route = Route::put("Route", "/route").description("A PUT route.");
548 plain_request(route, RestKind::Put, Hazards::new());
549 }
550
551 #[test]
552 fn create_plain_delete_request() {
553 let route = Route::delete("Route", "/route").description("A DELETE route.");
554 plain_request(route, RestKind::Delete, Hazards::new());
555 }
556
557 #[test]
558 fn create_plain_get_request_with_hazards() {
559 let hazards = Hazards::new()
560 .insert(Hazard::FireHazard)
561 .insert(Hazard::AirPoisoning);
562 plain_request(
563 Route::get("Route", "/route")
564 .description("A GET route.")
565 .with_hazards(hazards.clone()),
566 RestKind::Get,
567 hazards,
568 );
569 }
570
571 #[test]
572 fn create_get_request_with_parameters() {
573 request_with_parameters(
574 Route::get("Route", "/route").description("A GET route."),
575 RestKind::Get,
576 &Hazards::new(),
577 );
578 }
579
580 #[test]
581 fn create_post_request_with_parameters() {
582 let route = Route::post("Route", "/route").description("A POST route.");
583 request_with_parameters(route, RestKind::Post, &Hazards::new());
584 }
585
586 #[test]
587 fn create_put_request_with_parameters() {
588 let route = Route::put("Route", "/route").description("A PUT route.");
589 request_with_parameters(route, RestKind::Put, &Hazards::new());
590 }
591
592 #[test]
593 fn create_delete_request_with_parameters() {
594 let route = Route::delete("Route", "/route").description("A DELETE route.");
595 request_with_parameters(route, RestKind::Delete, &Hazards::new());
596 }
597
598 #[test]
599 fn create_get_request_with_hazards_and_parameters() {
600 let hazards = Hazards::new()
601 .insert(Hazard::FireHazard)
602 .insert(Hazard::AirPoisoning);
603
604 request_with_parameters(
605 Route::get("Route", "/route")
606 .description("A GET route.")
607 .with_hazards(hazards.clone()),
608 RestKind::Get,
609 &hazards,
610 );
611 }
612}