src/openapi/loader.rs 2.3 K raw
1
use anyhow::{Context, Result};
2
use serde_json::Value;
3
4
use super::swagger2;
5
6
/// Load a spec from a local file path or an http(s) URL.
7
pub async fn load_spec(source: &str) -> Result<Value> {
8
    if source.starts_with("http://") || source.starts_with("https://") {
9
        let client = reqwest::Client::new();
10
        let text = client
11
            .get(source)
12
            .send()
13
            .await
14
            .with_context(|| format!("fetching {source}"))?
15
            .error_for_status()
16
            .with_context(|| format!("fetching {source}"))?
17
            .text()
18
            .await
19
            .with_context(|| format!("reading body of {source}"))?;
20
        parse_spec(&text).with_context(|| format!("parsing spec from {source}"))
21
    } else {
22
        let text = std::fs::read_to_string(source).with_context(|| format!("reading {source}"))?;
23
        parse_spec(&text).with_context(|| format!("parsing spec from {source}"))
24
    }
25
}
26
27
/// Parse spec text as JSON, falling back to YAML. A Swagger 2.0 document is
28
/// converted to its OpenAPI 3.0 equivalent here, so every caller downstream
29
/// only has to understand one dialect.
30
pub fn parse_spec(text: &str) -> Result<Value> {
31
    let v = match serde_json::from_str::<Value>(text) {
32
        Ok(v) => v,
33
        Err(_) => serde_yaml::from_str(text).context("spec is neither valid JSON nor YAML")?,
34
    };
35
    Ok(if swagger2::is_swagger2(&v) {
36
        swagger2::to_openapi3(v)
37
    } else {
38
        v
39
    })
40
}
41
42
#[cfg(test)]
43
mod tests {
44
    use super::*;
45
46
    #[test]
47
    fn parses_json_and_yaml() {
48
        let json = r#"{"openapi":"3.0.0"}"#;
49
        assert_eq!(parse_spec(json).unwrap()["openapi"], "3.0.0");
50
51
        let yaml = "openapi: 3.1.0\ninfo:\n  title: t\n";
52
        let v = parse_spec(yaml).unwrap();
53
        assert_eq!(v["openapi"], "3.1.0");
54
        assert_eq!(v["info"]["title"], "t");
55
    }
56
57
    #[test]
58
    fn converts_swagger_2_on_parse() {
59
        let yaml = "swagger: '2.0'\nhost: api.example.com\nbasePath: /v1\nschemes: [https]\n";
60
        let v = parse_spec(yaml).unwrap();
61
        assert_eq!(v["openapi"], "3.0.0");
62
        assert!(v.get("swagger").is_none());
63
        assert_eq!(v["servers"][0]["url"], "https://api.example.com/v1");
64
    }
65
66
    #[test]
67
    fn rejects_garbage() {
68
        assert!(parse_spec("\u{1}\u{2}not a spec at all: [").is_err());
69
    }
70
}