wasmer_wasix/runtime/resolver/
utils.rs

1use std::{
2    cmp::Ordering,
3    path::{Path, PathBuf},
4};
5
6use anyhow::Error;
7use http::{HeaderMap, StatusCode};
8use semver::Version;
9use url::Url;
10
11use crate::http::{HttpResponse, USER_AGENT};
12
13/// Compare optional package versions by SemVer *precedence*, i.e. ignoring build
14/// metadata as the spec requires (`1.0.0+a` and `1.0.0+b` rank equally). `None`
15/// orders below any `Some`, matching [`Option`]'s own ordering.
16pub(crate) fn cmp_version_precedence(left: Option<&Version>, right: Option<&Version>) -> Ordering {
17    match (left, right) {
18        (Some(left), Some(right)) => left.cmp_precedence(right),
19        (left, right) => left.is_some().cmp(&right.is_some()),
20    }
21}
22
23/// Polyfill for [`Url::from_file_path()`] that works on `wasm32-unknown-unknown`.
24pub(crate) fn url_from_file_path(path: impl AsRef<Path>) -> Option<Url> {
25    let path = path.as_ref();
26
27    if !path.is_absolute() {
28        return None;
29    }
30
31    let mut buffer = String::new();
32
33    for component in path {
34        if !buffer.ends_with('/') {
35            buffer.push('/');
36        }
37
38        buffer.push_str(component.to_str()?);
39    }
40
41    buffer.insert_str(0, "file://");
42
43    buffer.parse().ok()
44}
45
46pub(crate) fn webc_headers() -> HeaderMap {
47    let mut headers = HeaderMap::new();
48    headers.insert("Accept", "application/webc".parse().unwrap());
49    headers.insert("User-Agent", USER_AGENT.parse().unwrap());
50    headers
51}
52
53pub(crate) fn http_error(response: &HttpResponse) -> Error {
54    let status = response.status;
55
56    if status == StatusCode::SERVICE_UNAVAILABLE
57        && let Some(retry_after) = response
58            .headers
59            .get("Retry-After")
60            .and_then(|retry_after| retry_after.to_str().ok())
61    {
62        tracing::debug!(
63            %retry_after,
64            "Received 503 Service Unavailable while looking up a package. The backend may still be generating the *.webc file.",
65        );
66        return anyhow::anyhow!("{status} (Retry After: {retry_after})");
67    }
68
69    Error::msg(status)
70}
71
72pub(crate) fn file_path_from_url(url: &Url) -> Result<PathBuf, Error> {
73    debug_assert_eq!(url.scheme(), "file");
74
75    // Note: The Url::to_file_path() method is platform-specific
76    cfg_if::cfg_if! {
77        if #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] {
78            use anyhow::Context;
79
80            if let Ok(path) = url.to_file_path() {
81                return Ok(path);
82            }
83
84            // Sometimes we'll get a UNC-like path (e.g.
85            // "file:///?\\C:/\\/path/to/file.txt") and Url::to_file_path()
86            // won't be able to handle the "\\?" so we try to "massage" the URL
87            // a bit.
88            // See <https://github.com/servo/rust-url/issues/450> for more.
89            let modified = url.as_str().replace(r"\\?", "").replace("//?", "").replace('\\', "/");
90            Url::parse(&modified)
91                .ok()
92                .and_then(|url| url.to_file_path().ok())
93                .context("Unable to extract the file path")
94        } else {
95            anyhow::bail!("Url::to_file_path() is not supported on this platform");
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    #[allow(unused_imports)]
103    use super::*;
104
105    #[test]
106    #[cfg(unix)]
107    fn from_file_path_behaviour_is_identical() {
108        let inputs = [
109            "/",
110            "/path",
111            "/path/to/file.txt",
112            "./path/to/file.txt",
113            ".",
114            "",
115        ];
116
117        for path in inputs {
118            let got = url_from_file_path(path);
119            let expected = Url::from_file_path(path).ok();
120            assert_eq!(got, expected, "Mismatch for \"{path}\"");
121        }
122    }
123
124    #[test]
125    #[cfg(windows)]
126    fn to_file_path_can_handle_unc_paths() {
127        let path = Path::new(env!("CARGO_MANIFEST_DIR"))
128            .canonicalize()
129            .unwrap();
130        let url = Url::from_file_path(&path).unwrap();
131
132        let got = file_path_from_url(&url).unwrap();
133
134        assert_eq!(got.canonicalize().unwrap(), path);
135    }
136}