Skip to main content

turbomcp_openapi/
parser.rs

1//! OpenAPI specification parsing.
2
3use std::path::Path;
4
5use openapiv3::OpenAPI;
6
7use crate::error::{OpenApiError, Result};
8
9/// Parse an OpenAPI specification from a string.
10///
11/// Automatically detects JSON or YAML format based on content.
12pub fn parse_spec(content: &str) -> Result<OpenAPI> {
13    // Try JSON first (faster)
14    if content.trim_start().starts_with('{') {
15        return serde_json::from_str(content).map_err(Into::into);
16    }
17
18    // Try YAML
19    serde_yaml::from_str(content).map_err(Into::into)
20}
21
22/// Load an OpenAPI specification from a file.
23pub fn load_from_file(path: &Path) -> Result<OpenAPI> {
24    let content = std::fs::read_to_string(path)?;
25    parse_spec(&content)
26}
27
28/// Fetch an OpenAPI specification from a URL.
29pub async fn fetch_from_url(url: &str) -> Result<OpenAPI> {
30    let response = reqwest::get(url).await?;
31
32    if !response.status().is_success() {
33        return Err(OpenApiError::ApiError(format!(
34            "HTTP {} fetching OpenAPI spec",
35            response.status()
36        )));
37    }
38
39    let content = response.text().await?;
40    parse_spec(&content)
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    const SIMPLE_SPEC_JSON: &str = r#"{
48        "openapi": "3.0.0",
49        "info": {
50            "title": "Test API",
51            "version": "1.0.0"
52        },
53        "paths": {
54            "/users": {
55                "get": {
56                    "summary": "List users",
57                    "responses": {
58                        "200": {
59                            "description": "Success"
60                        }
61                    }
62                }
63            }
64        }
65    }"#;
66
67    const SIMPLE_SPEC_YAML: &str = r#"
68openapi: "3.0.0"
69info:
70  title: Test API
71  version: "1.0.0"
72paths:
73  /users:
74    get:
75      summary: List users
76      responses:
77        "200":
78          description: Success
79"#;
80
81    #[test]
82    fn test_parse_json() {
83        let spec = parse_spec(SIMPLE_SPEC_JSON).unwrap();
84        assert_eq!(spec.info.title, "Test API");
85        assert!(spec.paths.paths.contains_key("/users"));
86    }
87
88    #[test]
89    fn test_parse_yaml() {
90        let spec = parse_spec(SIMPLE_SPEC_YAML).unwrap();
91        assert_eq!(spec.info.title, "Test API");
92        assert!(spec.paths.paths.contains_key("/users"));
93    }
94
95    #[test]
96    fn test_invalid_spec() {
97        let result = parse_spec("not valid openapi");
98        assert!(result.is_err());
99    }
100}