Skip to main content

openapi_nexus_parser/
parser.rs

1//! OpenAPI specification parser
2
3use std::fs;
4use std::path::Path;
5
6use tracing::{debug, error};
7
8use crate::error::ParseError;
9use crate::serde_error::SerdeErrorExtractor;
10use openapi_nexus_spec::OpenApiV31Spec;
11
12fn extract_error_context(content: &str, error_msg: &str) -> Vec<String> {
13    let (line, column) = SerdeErrorExtractor::new(error_msg).extract_location();
14
15    if line > 0 {
16        debug!("Error at line {}, column {}", line, column);
17
18        let lines: Vec<&str> = content.lines().collect();
19        if line <= lines.len() {
20            let error_line_idx = line - 1;
21            let start_line = error_line_idx.saturating_sub(5);
22            let end_line = (error_line_idx + 10).min(lines.len());
23
24            debug!(
25                "Raw content around error (lines {} to {}):",
26                start_line + 1,
27                end_line
28            );
29            for (i, line_content) in lines.iter().enumerate().take(end_line).skip(start_line) {
30                let line_num = i + 1;
31                let is_error_line = line_num == line;
32                let marker = if is_error_line { ">>>" } else { "   " };
33                debug!("{} {} | {}", marker, line_num, line_content);
34            }
35
36            if error_line_idx + 1 < lines.len() {
37                let next_line = lines[error_line_idx + 1];
38                debug!("Next line after error: {}", next_line);
39                debug!(
40                    "Next line indentation: {} spaces",
41                    next_line.chars().take_while(|c| *c == ' ').count()
42                );
43            }
44
45            let error_line = lines[line - 1];
46            vec![
47                format!("Error at line {}: {}", line, error_line),
48                format!("Column: {}", column),
49            ]
50        } else {
51            vec![format!("Error: {}", error_msg)]
52        }
53    } else {
54        vec![format!("Error: {}", error_msg)]
55    }
56}
57
58pub fn parse_content_json(content: &str) -> Result<OpenApiV31Spec, ParseError> {
59    // First try to parse as JSON to get proper error context for JSON syntax errors
60    let _value: serde_json::Value = serde_json::from_str(content).map_err(|e| {
61        let error_msg = e.to_string();
62        debug!("Serde error message: {}", error_msg);
63        let context = extract_error_context(content, &error_msg);
64
65        for line in &context {
66            error!("{}", line);
67        }
68
69        ParseError::JsonParse { source: e, context }
70    })?;
71
72    // Deserialize as OpenAPI spec
73    serde_json::from_str::<OpenApiV31Spec>(content).map_err(|e| {
74        let error_msg = e.to_string();
75        debug!("parse error: {}", error_msg);
76        let context = extract_error_context(content, &error_msg);
77
78        for line in &context {
79            error!("{}", line);
80        }
81
82        ParseError::OpenApiDeserializeJson { source: e }
83    })
84}
85
86pub fn parse_content_yaml(content: &str) -> Result<OpenApiV31Spec, ParseError> {
87    // First try to parse as YAML to get proper error context for YAML syntax errors
88    let _value: serde_norway::Value = serde_norway::from_str(content).map_err(|e| {
89        let error_msg = e.to_string();
90        debug!("Serde error message: {}", error_msg);
91        let context = extract_error_context(content, &error_msg);
92
93        for line in &context {
94            error!("{}", line);
95        }
96
97        ParseError::YamlParse { source: e, context }
98    })?;
99
100    // Deserialize as OpenAPI spec
101    serde_norway::from_str::<OpenApiV31Spec>(content).map_err(|e| {
102        let error_msg = e.to_string();
103        debug!("parse error: {}", error_msg);
104        let context = extract_error_context(content, &error_msg);
105
106        for line in &context {
107            error!("{}", line);
108        }
109
110        ParseError::OpenApiDeserializeYaml { source: e }
111    })
112}
113
114/// Parse an OpenAPI specification from a file
115pub fn parse_file(path: &Path) -> Result<OpenApiV31Spec, ParseError> {
116    let content = fs::read_to_string(path).map_err(|e| ParseError::FileRead {
117        path: path.to_string_lossy().to_string(),
118        source: e,
119    })?;
120
121    let file_extension = path.extension().and_then(|ext| ext.to_str());
122
123    match file_extension {
124        Some("json") => parse_content_json(&content),
125        Some("yaml") | Some("yml") => parse_content_yaml(&content),
126        Some(ext) => Err(ParseError::UnsupportedFormat {
127            format: ext.to_string(),
128        }),
129        None => Err(ParseError::UnsupportedFormat {
130            format: "<unknown>".to_string(),
131        }),
132    }
133}