apimock_server/
dyn_route.rs1use hyper::HeaderMap;
2use tokio::task;
3
4use std::{fs, path::Path};
5
6use crate::{
7 response::{
8 error_response::{internal_server_error_response, not_found_response},
9 file_response::FileResponse,
10 },
11 types::BoxBody,
12};
13use apimock_routing::util::json::JSON_COMPATIBLE_EXTENSIONS;
14
15pub async fn dyn_route_content(
32 url_path: &str,
33 fallback_respond_dir: &str,
34 request_headers: &HeaderMap,
35) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
36 let request_path =
37 Path::new(fallback_respond_dir).join(url_path.strip_prefix("/").unwrap_or_default());
38
39 let request_file_name = request_path
40 .file_name()
41 .unwrap_or_default()
42 .to_str()
43 .unwrap_or_default();
44
45 let Some(parent) = request_path.parent() else {
48 return internal_server_error_response(
49 &format!("parent dir not found: url_path = {}", url_path),
50 request_headers,
51 );
52 };
53 if !parent.exists() {
54 return not_found_response(request_headers);
55 }
56 let dir = parent.to_owned();
57
58 let dir_for_blocking_task = dir.clone();
69 let read_dir_result = task::spawn_blocking(move || -> Result<Vec<_>, String> {
70 let entries = fs::read_dir(dir_for_blocking_task.as_path()).map_err(|err| {
71 format!(
72 "failed to get dir: {} ({})",
73 dir_for_blocking_task.to_string_lossy(),
74 err
75 )
76 })?;
77 entries
78 .map(|entry| {
79 entry.map_err(|err| {
80 format!(
81 "failed to get dir entry from dir: {} ({})",
82 dir_for_blocking_task.to_string_lossy(),
83 err
84 )
85 })
86 })
87 .collect()
88 })
89 .await;
90
91 let entries = match read_dir_result {
92 Ok(Ok(v)) => v,
93 Ok(Err(err)) => {
94 return internal_server_error_response(err.as_str(), request_headers);
95 }
96 Err(err) => {
97 return internal_server_error_response(
98 &format!("failed to get dir entries (async handling: {})", err),
99 request_headers,
100 );
101 }
102 };
103
104 let mut found = entries.into_iter().find_map(|entry| {
106 let path = entry.path();
107 let name = path
108 .file_name()
109 .unwrap_or_default()
110 .to_str()
111 .unwrap_or_default()
112 .to_owned();
113 if name.eq_ignore_ascii_case(request_file_name) {
114 Some(path)
115 } else {
116 None
117 }
118 });
119
120 if found.is_none() && request_path.extension().is_none() {
122 if let Some(stem) = request_path.file_stem().and_then(|s| s.to_str()) {
123 for ext in JSON_COMPATIBLE_EXTENSIONS {
124 let file_path = dir.join(format!("{}.{}", stem, ext));
125 if file_path.exists() {
126 found = Some(file_path);
127 break;
128 }
129 }
130 }
131 }
132
133 let Some(found) = found else {
134 return not_found_response(request_headers);
135 };
136
137 let file_path = found.to_str().unwrap_or_default();
138 FileResponse::new(file_path, None, request_headers)
139 .file_content_response()
140 .await
141}