apimock_server/json_path_util.rs
1//! Filesystem helpers for resolving URL paths onto JSON-compatible files.
2//!
3//! # Why these live in server, not routing
4//!
5//! Pre-5.0 these sat alongside `json_value_by_jsonpath` in
6//! `core::util::json`. But `resolve_with_json_compatible_extensions`
7//! and `is_equivalent_json_file` both touch the filesystem to answer
8//! "does this path exist as a file?". That's a server-layer activity —
9//! the routing layer stays abstract and doesn't care where bytes come
10//! from — so they moved here in 5.0.
11
12use std::path::Path;
13
14use apimock_routing::util::json::JSON_COMPATIBLE_EXTENSIONS;
15
16/// Directory index filename (same spirit as `index.html`).
17pub const ROOT_DIRECTORY_FILE_NAME: &str = "index";
18
19/// Try to resolve a user-visible URL path onto a real file on disk.
20///
21/// Resolution order (first hit wins):
22/// 1. Exact match (`unknown_path` is already a file).
23/// 2. `unknown_path.<ext>` for each extension in `JSON_COMPATIBLE_EXTENSIONS`.
24/// 3. `unknown_path/index.<ext>` for the same extensions.
25/// 4. `unknown_path/index.html` — present so the fallback responder
26/// can also serve a directory root HTML, matching common
27/// web-server conventions.
28pub fn resolve_with_json_compatible_extensions(unknown_path: &str) -> Option<String> {
29 let p = Path::new(unknown_path);
30 if p.is_file() {
31 return Some(unknown_path.to_owned());
32 }
33
34 for ext in JSON_COMPATIBLE_EXTENSIONS {
35 let with_ext = format!("{}.{}", unknown_path, ext);
36 if Path::new(&with_ext).is_file() {
37 return Some(with_ext);
38 }
39 }
40
41 for ext in JSON_COMPATIBLE_EXTENSIONS {
42 let p = Path::new(unknown_path).join(format!("{}.{}", ROOT_DIRECTORY_FILE_NAME, ext));
43 if p.is_file() {
44 return p
45 .canonicalize()
46 .ok()
47 .and_then(|c| c.to_str().map(|s| s.to_owned()));
48 }
49 }
50
51 let p = Path::new(unknown_path).join(format!("{}.html", ROOT_DIRECTORY_FILE_NAME));
52 if p.is_file() {
53 return p
54 .canonicalize()
55 .ok()
56 .and_then(|c| c.to_str().map(|s| s.to_owned()));
57 }
58
59 None
60}