src/openapi/loader.rs 1.7 K raw
1
use anyhow::{Context, Result};
2
use serde_json::Value;
3
4
/// Load a spec from a local file path or an http(s) URL.
5
pub async fn load_spec(source: &str) -> Result<Value> {
6
    if source.starts_with("http://") || source.starts_with("https://") {
7
        let client = reqwest::Client::new();
8
        let text = client
9
            .get(source)
10
            .send()
11
            .await
12
            .with_context(|| format!("fetching {source}"))?
13
            .error_for_status()
14
            .with_context(|| format!("fetching {source}"))?
15
            .text()
16
            .await
17
            .with_context(|| format!("reading body of {source}"))?;
18
        parse_spec(&text).with_context(|| format!("parsing spec from {source}"))
19
    } else {
20
        let text = std::fs::read_to_string(source).with_context(|| format!("reading {source}"))?;
21
        parse_spec(&text).with_context(|| format!("parsing spec from {source}"))
22
    }
23
}
24
25
/// Parse spec text as JSON, falling back to YAML.
26
pub fn parse_spec(text: &str) -> Result<Value> {
27
    if let Ok(v) = serde_json::from_str::<Value>(text) {
28
        return Ok(v);
29
    }
30
    let v: Value = serde_yaml::from_str(text).context("spec is neither valid JSON nor YAML")?;
31
    Ok(v)
32
}
33
34
#[cfg(test)]
35
mod tests {
36
    use super::*;
37
38
    #[test]
39
    fn parses_json_and_yaml() {
40
        let json = r#"{"openapi":"3.0.0"}"#;
41
        assert_eq!(parse_spec(json).unwrap()["openapi"], "3.0.0");
42
43
        let yaml = "openapi: 3.1.0\ninfo:\n  title: t\n";
44
        let v = parse_spec(yaml).unwrap();
45
        assert_eq!(v["openapi"], "3.1.0");
46
        assert_eq!(v["info"]["title"], "t");
47
    }
48
49
    #[test]
50
    fn rejects_garbage() {
51
        assert!(parse_spec("\u{1}\u{2}not a spec at all: [").is_err());
52
    }
53
}