Skip to content

Commit dcfb975

Browse files
committed
Move input module to input/yaml submodule
1 parent e0177ef commit dcfb975

File tree

2 files changed

+220
-0
lines changed

2 files changed

+220
-0
lines changed

src/input/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//! Input processing functionality for Sprocket.
2+
3+
pub mod yaml;
4+
5+
// Re-export the main functionality from the yaml module
6+
pub use yaml::InputFormat;
7+
pub use yaml::get_json_file_path;
8+
pub use yaml::get_json_string;
9+
pub use yaml::parse_input_file;

src/input/yaml.rs

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//! YAML input processing functionality for Sprocket.
2+
3+
use std::fs;
4+
use std::path::PathBuf;
5+
6+
use anyhow::Context;
7+
use anyhow::Result;
8+
use anyhow::bail;
9+
use tracing::debug;
10+
11+
/// Identifies the format of an input file.
12+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13+
pub enum InputFormat {
14+
/// JSON format
15+
Json,
16+
/// YAML format
17+
Yaml,
18+
}
19+
20+
/// Parses an input file as either JSON or YAML.
21+
///
22+
/// Tries to parse as JSON first, then YAML if JSON parsing fails.
23+
/// Returns the parsed data as a JSON value along with the detected format.
24+
pub fn parse_input_file(path: &PathBuf) -> Result<(InputFormat, serde_json::Value)> {
25+
let content = fs::read_to_string(path)
26+
.with_context(|| format!("Failed to read file: {}", path.display()))?;
27+
28+
// Try parsing as JSON first
29+
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&content) {
30+
debug!("File parsed as JSON: {}", path.display());
31+
return Ok((InputFormat::Json, json_value));
32+
}
33+
34+
// Then try parsing as YAML
35+
if let Ok(yaml_value) = serde_yaml::from_str::<serde_yaml::Value>(&content) {
36+
debug!("File parsed as YAML: {}", path.display());
37+
// Convert YAML to JSON
38+
let json_value = serde_json::to_value(&yaml_value)
39+
.context("Failed to convert YAML value to JSON value")?;
40+
return Ok((InputFormat::Yaml, json_value));
41+
}
42+
43+
// If neither worked, return an error
44+
bail!(
45+
"File is neither valid JSON nor valid YAML: {}",
46+
path.display()
47+
)
48+
}
49+
50+
/// Gets a JSON string representation of the input file.
51+
pub fn get_json_string(input_path: PathBuf) -> Result<String> {
52+
let (format, json_value) = parse_input_file(&input_path)?;
53+
54+
match format {
55+
InputFormat::Json => {
56+
debug!("Using JSON input directly");
57+
serde_json::to_string_pretty(&json_value)
58+
.context("Failed to convert JSON value to string")
59+
}
60+
InputFormat::Yaml => {
61+
debug!("Using YAML input converted to JSON");
62+
serde_json::to_string_pretty(&json_value)
63+
.context("Failed to convert YAML-derived JSON value to string")
64+
}
65+
}
66+
}
67+
68+
/// Gets a path to a JSON file from an input file which may be in either JSON or
69+
/// YAML format.
70+
///
71+
/// For JSON inputs, returns the original path directly.
72+
/// For YAML inputs, converts to JSON and creates a temporary file.
73+
pub fn get_json_file_path(input_path: PathBuf) -> Result<PathBuf> {
74+
let (format, json_value) = parse_input_file(&input_path)?;
75+
76+
match format {
77+
InputFormat::Json => {
78+
debug!("Input is already JSON, using original file directly");
79+
Ok(input_path)
80+
}
81+
InputFormat::Yaml => {
82+
debug!("Converting YAML to JSON and creating temporary file");
83+
let json_string = serde_json::to_string_pretty(&json_value)
84+
.context("Failed to convert YAML-derived JSON value to string")?;
85+
86+
let temp_dir = std::env::temp_dir();
87+
let temp_file = temp_dir.join("sprocket_temp_input.json");
88+
89+
fs::write(&temp_file, json_string).context("Failed to write temporary JSON file")?;
90+
91+
Ok(temp_file)
92+
}
93+
}
94+
}
95+
96+
#[cfg(test)]
97+
mod tests {
98+
use std::io::Write;
99+
100+
use tempfile::NamedTempFile;
101+
102+
use super::*;
103+
104+
#[test]
105+
fn test_parse_json_file() {
106+
// Create a temporary JSON file
107+
let mut file = NamedTempFile::new().unwrap();
108+
file.write_all(b"{\"key\": \"value\", \"number\": 42}")
109+
.unwrap();
110+
111+
// Test parsing
112+
let (format, json) = parse_input_file(&file.path().to_path_buf()).unwrap();
113+
assert_eq!(format, InputFormat::Json);
114+
assert_eq!(json["key"], "value");
115+
assert_eq!(json["number"], 42);
116+
}
117+
118+
#[test]
119+
fn test_parse_yaml_file() {
120+
// Create a temporary YAML file
121+
let mut file = NamedTempFile::new().unwrap();
122+
file.write_all(b"key: value\nnumber: 42").unwrap();
123+
124+
// Test parsing
125+
let (format, json) = parse_input_file(&file.path().to_path_buf()).unwrap();
126+
assert_eq!(format, InputFormat::Yaml);
127+
assert_eq!(json["key"], "value");
128+
assert_eq!(json["number"], 42);
129+
}
130+
131+
#[test]
132+
fn test_invalid_format_parsing() {
133+
// Create a temporary file with invalid content
134+
// This content is invalid in both JSON and YAML:
135+
// - For JSON, it's missing quotes and braces
136+
// - For YAML, it has invalid indentation and structure
137+
let mut file = NamedTempFile::new().unwrap();
138+
file.write_all(b"key: value\n - invalid indent\n ]broken: [structure")
139+
.unwrap();
140+
141+
// Test parsing should fail
142+
let result = parse_input_file(&file.path().to_path_buf());
143+
assert!(result.is_err());
144+
}
145+
146+
#[test]
147+
fn test_get_json_string_with_json() {
148+
// Create a temporary JSON file
149+
let mut file = NamedTempFile::new().unwrap();
150+
file.write_all(b"{\"key\": \"value\", \"number\": 42}")
151+
.unwrap();
152+
153+
// Get JSON string
154+
let json_string = get_json_string(file.path().to_path_buf()).unwrap();
155+
156+
// Parse back to verify
157+
let json: serde_json::Value = serde_json::from_str(&json_string).unwrap();
158+
assert_eq!(json["key"], "value");
159+
assert_eq!(json["number"], 42);
160+
}
161+
162+
#[test]
163+
fn test_get_json_string_with_yaml() {
164+
// Create a temporary YAML file
165+
let mut file = NamedTempFile::new().unwrap();
166+
file.write_all(b"key: value\nnested:\n inner: 42\nlist:\n - item1\n - item2")
167+
.unwrap();
168+
169+
// Get JSON string
170+
let json_string = get_json_string(file.path().to_path_buf()).unwrap();
171+
172+
// Parse back to verify
173+
let json: serde_json::Value = serde_json::from_str(&json_string).unwrap();
174+
assert_eq!(json["key"], "value");
175+
assert_eq!(json["nested"]["inner"], 42);
176+
assert_eq!(json["list"][0], "item1");
177+
assert_eq!(json["list"][1], "item2");
178+
}
179+
180+
#[test]
181+
fn test_get_json_file_path_with_json() {
182+
// Create a temporary JSON file
183+
let mut file = NamedTempFile::new().unwrap();
184+
file.write_all(b"{\"key\": \"value\", \"number\": 42}")
185+
.unwrap();
186+
let path = file.path().to_path_buf();
187+
188+
// For JSON input, should return the original path
189+
let result_path = get_json_file_path(path.clone()).unwrap();
190+
assert_eq!(result_path, path);
191+
}
192+
193+
#[test]
194+
fn test_get_json_file_path_with_yaml() {
195+
// Create a temporary YAML file
196+
let mut file = NamedTempFile::new().unwrap();
197+
file.write_all(b"key: value\nnested:\n inner: 42").unwrap();
198+
199+
// For YAML input, should create a new JSON file
200+
let result_path = get_json_file_path(file.path().to_path_buf()).unwrap();
201+
202+
// Result should be a different path
203+
assert_ne!(result_path, file.path());
204+
205+
// Verify the content of the new file
206+
let content = fs::read_to_string(&result_path).unwrap();
207+
let json: serde_json::Value = serde_json::from_str(&content).unwrap();
208+
assert_eq!(json["key"], "value");
209+
assert_eq!(json["nested"]["inner"], 42);
210+
}
211+
}

0 commit comments

Comments
 (0)