scraper_trail/request/
mod.rs1use bounded_static::{IntoBoundedStatic, ToBoundedStatic};
2use chrono::{DateTime, Utc};
3use http::{
4 Method,
5 header::{HeaderMap, HeaderName, HeaderValue},
6};
7use indexmap::IndexMap;
8use serde_field_attributes::{represented_as_str, timestamp_millis_str};
9use std::borrow::Cow;
10use url::Url;
11
12pub mod params;
13
14#[derive(Debug, thiserror::Error)]
15pub enum HeaderError {
16 #[error("Invalid header name")]
17 Name(#[from] http::header::InvalidHeaderName),
18 #[error("Invalid header value")]
19 Value(#[from] http::header::InvalidHeaderValue),
20}
21
22#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
23pub struct Request<'a> {
24 pub url: Url,
25 #[serde(rename = "timestamp_ms", with = "timestamp_millis_str")]
26 pub timestamp: DateTime<Utc>,
27 #[serde(
28 with = "represented_as_str",
29 default,
30 skip_serializing_if = "is_method_get"
31 )]
32 pub method: Method,
33 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
34 pub headers: IndexMap<Cow<'a, str>, Cow<'a, str>>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub body: Option<Cow<'a, str>>,
37}
38
39impl<'a> Request<'a> {
40 pub fn new<
41 U: AsRef<str>,
42 K: Into<Cow<'a, str>>,
43 V: Into<Cow<'a, str>>,
44 I: IntoIterator<Item = (K, V)>,
45 B: Into<Cow<'a, str>>,
46 >(
47 url: U,
48 timestamp: Option<DateTime<Utc>>,
49 method: Option<Method>,
50 headers: Option<I>,
51 body: Option<B>,
52 ) -> Result<Self, url::ParseError> {
53 Ok(Self {
54 url: url.as_ref().parse()?,
55 timestamp: timestamp.unwrap_or_else(Utc::now),
56 method: method.unwrap_or_default(),
57 headers: headers
58 .map(|headers| {
59 headers
60 .into_iter()
61 .map(|(key, value)| (key.into(), value.into()))
62 .collect()
63 })
64 .unwrap_or_default(),
65 body: body.map(std::convert::Into::into),
66 })
67 }
68
69 pub fn header_map(&self) -> Result<HeaderMap, HeaderError> {
70 self.headers
71 .iter()
72 .map(|(name, value)| {
73 Ok((
74 HeaderName::try_from(name.as_ref())?,
75 HeaderValue::try_from(value.as_ref())?,
76 ))
77 })
78 .collect()
79 }
80}
81
82impl IntoBoundedStatic for Request<'_> {
83 type Static = Request<'static>;
84
85 fn into_static(self) -> Self::Static {
86 Self::Static {
87 url: self.url,
88 timestamp: self.timestamp,
89 method: self.method,
90 headers: self
91 .headers
92 .into_iter()
93 .map(|(key, value)| (key.into_static(), value.into_static()))
94 .collect(),
95 body: self
96 .body
97 .map(bounded_static::IntoBoundedStatic::into_static),
98 }
99 }
100}
101
102impl ToBoundedStatic for Request<'_> {
103 type Static = Request<'static>;
104
105 fn to_static(&self) -> Self::Static {
106 Self::Static {
107 url: self.url.clone(),
108 timestamp: self.timestamp,
109 method: self.method.clone(),
110 headers: self
111 .headers
112 .iter()
113 .map(|(key, value)| (key.to_static(), value.to_static()))
114 .collect(),
115 body: self
116 .body
117 .as_ref()
118 .map(bounded_static::ToBoundedStatic::to_static),
119 }
120 }
121}
122
123fn is_method_get(method: &Method) -> bool {
124 method == Method::GET
125}