langcodec_cli/
validation.rs1use crate::formats::parse_custom_format;
2use std::path::Path;
3use unic_langid::LanguageIdentifier;
4
5pub struct ValidationContext {
7 pub input_files: Vec<String>,
8 pub output_file: Option<String>,
9 pub language_code: Option<String>,
10 pub input_format: Option<String>,
11 pub output_format: Option<String>,
12}
13
14impl Default for ValidationContext {
15 fn default() -> Self {
16 Self::new()
17 }
18}
19
20impl ValidationContext {
21 pub fn new() -> Self {
22 Self {
23 input_files: Vec::new(),
24 output_file: None,
25 language_code: None,
26 input_format: None,
27 output_format: None,
28 }
29 }
30
31 pub fn with_input_file(mut self, file: String) -> Self {
32 self.input_files.push(file);
33 self
34 }
35
36 pub fn with_output_file(mut self, file: String) -> Self {
37 self.output_file = Some(file);
38 self
39 }
40
41 pub fn with_language_code(mut self, lang: String) -> Self {
42 self.language_code = Some(lang);
43 self
44 }
45
46 pub fn with_input_format(mut self, format: String) -> Self {
47 self.input_format = Some(format);
48 self
49 }
50
51 pub fn with_output_format(mut self, format: String) -> Self {
52 self.output_format = Some(format);
53 self
54 }
55}
56
57pub fn validate_file_path(path: &str) -> Result<(), String> {
59 let path_obj = Path::new(path);
60
61 if !path_obj.exists() {
62 return Err(format!("File does not exist: {}", path));
63 }
64
65 if !path_obj.is_file() {
66 return Err(format!("Path is not a file: {}", path));
67 }
68
69 if !path_obj.metadata().map(|m| m.is_file()).unwrap_or(false) {
70 return Err(format!("Cannot read file: {}", path));
71 }
72
73 Ok(())
74}
75
76pub fn validate_output_path(path: &str) -> Result<(), String> {
78 let path_obj = Path::new(path);
79
80 if let Some(parent) = path_obj.parent()
81 && !parent.exists()
82 {
83 if let Err(e) = std::fs::create_dir_all(parent) {
85 return Err(format!("Cannot create output directory: {}", e));
86 }
87 }
88
89 Ok(())
90}
91
92pub fn validate_language_code(lang: &str) -> Result<(), String> {
94 if lang.is_empty() {
95 return Err("Language code cannot be empty".to_string());
96 }
97
98 match lang.parse::<LanguageIdentifier>() {
100 Ok(lang_id) => {
101 let lang_str = lang_id.to_string();
104 if lang_str == "invalid"
105 || lang_str == "123"
106 || lang_str.starts_with('-')
107 || lang_str.ends_with('-')
108 {
109 return Err(format!(
110 "Invalid language code format: {}. Expected valid BCP 47 language identifier",
111 lang
112 ));
113 }
114 Ok(())
115 }
116 Err(_) => Err(format!(
117 "Invalid language code format: {}. Expected valid BCP 47 language identifier",
118 lang
119 )),
120 }
121}
122
123pub fn validate_custom_format(format: &str) -> Result<(), String> {
125 if format.is_empty() {
126 return Err("Format cannot be empty".to_string());
127 }
128
129 let trimmed_format = format.trim();
131 if parse_custom_format(trimmed_format).is_err() {
132 return Err(format!(
133 "Unsupported custom format: {}. Supported formats: {}",
134 format,
135 crate::formats::get_supported_custom_formats()
136 ));
137 }
138
139 Ok(())
140}
141
142pub fn validate_standard_format(format: &str) -> Result<(), String> {
144 if format.is_empty() {
145 return Err("Format cannot be empty".to_string());
146 }
147
148 match format.trim().to_lowercase().as_str() {
150 "android" | "androidstrings" | "xml" => Ok(()),
151 "strings" => Ok(()),
152 "xcstrings" => Ok(()),
153 "csv" => Ok(()),
154 _ => Err(format!(
155 "Unsupported standard format: {}. Supported formats: android, strings, xcstrings, csv",
156 format
157 )),
158 }
159}
160
161pub fn validate_context(context: &ValidationContext) -> Result<(), String> {
163 for (i, input) in context.input_files.iter().enumerate() {
165 validate_file_path(input)
166 .map_err(|e| format!("Input file {} validation failed: {}", i + 1, e))?;
167 }
168
169 if let Some(ref output) = context.output_file {
171 validate_output_path(output).map_err(|e| format!("Output validation failed: {}", e))?;
172 }
173
174 if let Some(ref lang) = context.language_code {
176 validate_language_code(lang)
177 .map_err(|e| format!("Language code validation failed: {}", e))?;
178 }
179
180 if let Some(ref format) = context.input_format {
182 if validate_standard_format(format).is_err() {
184 validate_custom_format(format)
185 .map_err(|e| format!("Input format validation failed: {}", e))?;
186 }
187 }
188
189 if let Some(ref format) = context.output_format {
191 validate_standard_format(format)
193 .map_err(|e| format!("Output format validation failed: {}", e))?;
194 }
195
196 Ok(())
197}
198
199pub fn validate_custom_format_file(input: &str) -> Result<(), String> {
201 let input_ext = Path::new(input)
203 .extension()
204 .and_then(|ext| ext.to_str())
205 .unwrap_or("")
206 .to_lowercase();
207
208 match input_ext.as_str() {
209 "json" => {
210 validate_file_path(input)?;
212 }
213 "yaml" | "yml" => {
214 validate_file_path(input)?;
216 }
217 "langcodec" => {
218 validate_file_path(input)?;
220 }
221 _ => {
222 return Err(format!(
223 "Unsupported file extension for custom format: {}. Expected: json, yaml, yml, langcodec",
224 input_ext
225 ));
226 }
227 }
228
229 Ok(())
230}