apimock_server/
parsed_request.rs1use apimock_config::config::log_config::verbose_config::VerboseConfig;
12use apimock_routing::{ParsedRequest, util::http::normalize_url_path};
13use console::style;
14use http_body_util::BodyExt;
15use hyper::header::ORIGIN;
16use hyper::{Version, body::Incoming};
17use serde_json::{Value, to_string_pretty};
18
19use std::time::{SystemTime, UNIX_EPOCH};
20
21use crate::http_util::content_type_is_application_json;
22
23pub async fn parsed_request_from(
35 request: hyper::Request<Incoming>,
36) -> Result<ParsedRequest, String> {
37 let (component_parts, body) = request.into_parts();
38
39 let body_bytes = match body.boxed().collect().await {
40 Ok(x) => Some(x.to_bytes()),
41 Err(err) => {
42 log::warn!("failed to collect request incoming body: {}", err);
43 None
44 }
45 };
46
47 let has_body = body_bytes.as_ref().map(|b| !b.is_empty()).unwrap_or(false);
48
49 let body_json = if has_body {
50 let bytes = body_bytes
51 .as_ref()
52 .expect("body_bytes presence checked by has_body");
53 let raw_body_json = serde_json::from_slice::<Option<Value>>(bytes);
54
55 match (
56 content_type_is_application_json(&component_parts.headers),
57 raw_body_json,
58 ) {
59 (Some(true), Err(err)) => {
61 return Err(format!(
62 "failed to get json value from request body: {}",
63 err
64 ));
65 }
66 (Some(true), Ok(v)) => v,
67 (_, Ok(v)) => {
68 if matches!(
69 content_type_is_application_json(&component_parts.headers),
70 Some(false)
71 ) {
72 log::warn!("request has body but its content-type is not application/json");
73 } else if content_type_is_application_json(&component_parts.headers).is_none() {
74 log::warn!("request has body but doesn't have content-type");
75 }
76 v
77 }
78 (_, Err(_)) => None,
79 }
80 } else {
81 None
82 };
83
84 let url_path = normalize_url_path(component_parts.uri.path(), None);
85
86 Ok(ParsedRequest {
87 url_path,
88 component_parts,
89 body_json,
90 })
91}
92
93pub fn capture_in_log(request: &ParsedRequest, verbose: VerboseConfig) {
95 let now = SystemTime::now()
96 .duration_since(UNIX_EPOCH)
97 .map(|d| d.as_secs())
98 .unwrap_or_default();
99 let hours = (now / 3600) % 24;
100 let minutes = (now / 60) % 60;
101 let seconds = now % 60;
102 let timestamp = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);
103
104 let version = match request.component_parts.version {
105 Version::HTTP_3 => "HTTP/3",
106 Version::HTTP_2 => "HTTP/2",
107 Version::HTTP_11 => "HTTP/1.1",
108 _ => "HTTP/1.0 or earlier, or HTTP/4 or later",
109 };
110
111 let origin = request
112 .component_parts
113 .headers
114 .get(ORIGIN)
115 .and_then(|v| v.to_str().ok());
116
117 let mut printed = format!(
118 "<- {}\n [{}]",
119 style(request.url_path.as_str()).yellow(),
120 request.component_parts.method,
121 );
122 if let Some(origin) = origin {
123 printed.push_str(&format!(" [ORIGIN {}]", origin));
124 }
125 printed.push_str(&format!(
126 " [{}] request received (at {} UTC)",
127 version, timestamp
128 ));
129
130 if verbose.header || verbose.body {
131 printed.push_str("\n");
132 }
133 if verbose.header {
134 let headers = request
135 .component_parts
136 .headers
137 .iter()
138 .map(|(name, value)| format!("\n{}: {}", name, value.to_str().unwrap_or("<non-utf8>")))
139 .collect::<String>();
140 printed.push_str(&format!(
141 " [request.headers]{}\n",
142 style(headers).magenta()
143 ));
144 }
145
146 let mut is_verbose_body = false;
147 if verbose.body {
148 let query = request.component_parts.uri.query();
149 if let Some(query) = query {
150 printed.push_str(&format!(" [request.query] {}\n", query));
151 is_verbose_body = true;
152 }
153
154 if let Some(request_body_json_value) = &request.body_json {
155 printed.push_str(" [request.body.json]\n");
156
157 let body_str = match to_string_pretty(request_body_json_value) {
158 Ok(x) => x,
159 Err(err) => {
160 log::warn!(
161 "failed to prettify JSON: {} ({})",
162 request_body_json_value,
163 err
164 );
165 request_body_json_value.to_string()
166 }
167 };
168 let styled_body_str = body_str
169 .split("\n")
170 .map(|s| style(s).green().to_string())
171 .collect::<Vec<String>>()
172 .join("\n");
173 printed.push_str(styled_body_str.as_str());
174
175 is_verbose_body = true;
176 }
177 }
178 if verbose.header || is_verbose_body {
179 printed.push_str("\n");
180 }
181
182 log::info!("{}", printed);
183}