openapi_nexus/parser/
parser.rs1use std::fs;
4use std::path::Path;
5
6use tracing::{debug, error};
7
8use super::error::ParseError;
9use super::serde_error::SerdeErrorExtractor;
10use crate::ParsedSpec;
11use crate::spec::{OpenApiV30Spec, OpenApiV31Spec, OpenApiV32Spec};
12
13fn extract_error_context(content: &str, error_msg: &str) -> Vec<String> {
14 let (line, column) = SerdeErrorExtractor::new(error_msg).extract_location();
15
16 if line > 0 {
17 debug!("Error at line {}, column {}", line, column);
18
19 let lines: Vec<&str> = content.lines().collect();
20 if line <= lines.len() {
21 let error_line_idx = line - 1;
22 let start_line = error_line_idx.saturating_sub(5);
23 let end_line = (error_line_idx + 10).min(lines.len());
24
25 debug!(
26 "Raw content around error (lines {} to {}):",
27 start_line + 1,
28 end_line
29 );
30 for (i, line_content) in lines.iter().enumerate().take(end_line).skip(start_line) {
31 let line_num = i + 1;
32 let is_error_line = line_num == line;
33 let marker = if is_error_line { ">>>" } else { " " };
34 debug!("{} {} | {}", marker, line_num, line_content);
35 }
36
37 if error_line_idx + 1 < lines.len() {
38 let next_line = lines[error_line_idx + 1];
39 debug!("Next line after error: {}", next_line);
40 debug!(
41 "Next line indentation: {} spaces",
42 next_line.chars().take_while(|c| *c == ' ').count()
43 );
44 }
45
46 let error_line = lines[line - 1];
47 vec![
48 format!("Error at line {}: {}", line, error_line),
49 format!("Column: {}", column),
50 ]
51 } else {
52 vec![format!("Error: {}", error_msg)]
53 }
54 } else {
55 vec![format!("Error: {}", error_msg)]
56 }
57}
58
59fn detect_version_json(value: &serde_json::Value) -> Result<&str, ParseError> {
61 value
62 .get("openapi")
63 .and_then(|v| v.as_str())
64 .ok_or(ParseError::MissingVersionField)
65}
66
67fn detect_version_yaml(value: &serde_norway::Value) -> Result<String, ParseError> {
69 let mapping = value.as_mapping().ok_or(ParseError::MissingVersionField)?;
70 for (k, v) in mapping {
71 if k.as_str() == Some("openapi") {
72 if let Some(s) = v.as_str() {
73 return Ok(s.to_string());
74 }
75 if let Some(f) = v.as_f64() {
77 let s = format!("{f}");
79 if s.contains('.') {
80 return Ok(s);
81 }
82 return Ok(format!("{f:.1}"));
83 }
84 }
85 }
86 Err(ParseError::MissingVersionField)
87}
88
89fn classify_version(version: &str) -> Result<OpenApiMajorMinor, ParseError> {
91 if version.starts_with("3.0") {
92 Ok(OpenApiMajorMinor::V3_0)
93 } else if version.starts_with("3.1") {
94 Ok(OpenApiMajorMinor::V3_1)
95 } else if version.starts_with("3.2") {
96 Ok(OpenApiMajorMinor::V3_2)
97 } else {
98 Err(ParseError::UnsupportedVersion {
99 version: version.to_string(),
100 })
101 }
102}
103
104enum OpenApiMajorMinor {
105 V3_0,
106 V3_1,
107 V3_2,
108}
109
110pub fn parse_content_json(content: &str) -> Result<ParsedSpec, ParseError> {
111 let value: serde_json::Value = serde_json::from_str(content).map_err(|e| {
113 let error_msg = e.to_string();
114 debug!("Serde error message: {}", error_msg);
115 let context = extract_error_context(content, &error_msg);
116 for line in &context {
117 error!("{}", line);
118 }
119 ParseError::JsonParse { source: e, context }
120 })?;
121
122 let version = detect_version_json(&value)?;
123 match classify_version(version)? {
124 OpenApiMajorMinor::V3_0 => {
125 let spec: OpenApiV30Spec = serde_json::from_value(value).map_err(|e| {
126 debug!("parse error: {}", e);
127 ParseError::OpenApiDeserializeJson { source: e }
128 })?;
129 Ok(ParsedSpec::V30(Box::new(spec)))
130 }
131 OpenApiMajorMinor::V3_1 => {
132 let spec: OpenApiV31Spec = serde_json::from_value(value).map_err(|e| {
133 debug!("parse error: {}", e);
134 ParseError::OpenApiDeserializeJson { source: e }
135 })?;
136 Ok(ParsedSpec::V31(Box::new(spec)))
137 }
138 OpenApiMajorMinor::V3_2 => {
139 let spec: OpenApiV32Spec = serde_json::from_value(value).map_err(|e| {
140 debug!("parse error: {}", e);
141 ParseError::OpenApiDeserializeJson { source: e }
142 })?;
143 Ok(ParsedSpec::V32(Box::new(spec)))
144 }
145 }
146}
147
148pub fn parse_content_yaml(content: &str) -> Result<ParsedSpec, ParseError> {
149 let value: serde_norway::Value = serde_norway::from_str(content).map_err(|e| {
151 let error_msg = e.to_string();
152 debug!("Serde error message: {}", error_msg);
153 let context = extract_error_context(content, &error_msg);
154 for line in &context {
155 error!("{}", line);
156 }
157 ParseError::YamlParse { source: e, context }
158 })?;
159
160 let version = detect_version_yaml(&value)?;
161 match classify_version(&version)? {
162 OpenApiMajorMinor::V3_0 => {
163 let spec: OpenApiV30Spec = serde_norway::from_value(value).map_err(|e| {
164 debug!("parse error: {}", e);
165 ParseError::OpenApiDeserializeYaml { source: e }
166 })?;
167 Ok(ParsedSpec::V30(Box::new(spec)))
168 }
169 OpenApiMajorMinor::V3_1 => {
170 let spec: OpenApiV31Spec = serde_norway::from_value(value).map_err(|e| {
171 debug!("parse error: {}", e);
172 ParseError::OpenApiDeserializeYaml { source: e }
173 })?;
174 Ok(ParsedSpec::V31(Box::new(spec)))
175 }
176 OpenApiMajorMinor::V3_2 => {
177 let spec: OpenApiV32Spec = serde_norway::from_value(value).map_err(|e| {
178 debug!("parse error: {}", e);
179 ParseError::OpenApiDeserializeYaml { source: e }
180 })?;
181 Ok(ParsedSpec::V32(Box::new(spec)))
182 }
183 }
184}
185
186pub fn parse_file(path: &Path) -> Result<ParsedSpec, ParseError> {
188 let content = fs::read_to_string(path).map_err(|e| ParseError::FileRead {
189 path: path.to_string_lossy().to_string(),
190 source: e,
191 })?;
192
193 let file_extension = path.extension().and_then(|ext| ext.to_str());
194
195 match file_extension {
196 Some("json") => parse_content_json(&content),
197 Some("yaml") | Some("yml") => parse_content_yaml(&content),
198 Some(ext) => Err(ParseError::UnsupportedFormat {
199 format: ext.to_string(),
200 }),
201 None => Err(ParseError::UnsupportedFormat {
202 format: "<unknown>".to_string(),
203 }),
204 }
205}
206
207pub fn parse_content_yaml_v31(content: &str) -> Result<OpenApiV31Spec, ParseError> {
209 let _value: serde_norway::Value = serde_norway::from_str(content).map_err(|e| {
210 let error_msg = e.to_string();
211 let context = extract_error_context(content, &error_msg);
212 ParseError::YamlParse { source: e, context }
213 })?;
214
215 serde_norway::from_str::<OpenApiV31Spec>(content)
216 .map_err(|e| ParseError::OpenApiDeserializeYaml { source: e })
217}
218
219pub fn parse_content_json_v31(content: &str) -> Result<OpenApiV31Spec, ParseError> {
221 let _value: serde_json::Value = serde_json::from_str(content).map_err(|e| {
222 let error_msg = e.to_string();
223 let context = extract_error_context(content, &error_msg);
224 ParseError::JsonParse { source: e, context }
225 })?;
226
227 serde_json::from_str::<OpenApiV31Spec>(content)
228 .map_err(|e| ParseError::OpenApiDeserializeJson { source: e })
229}