1#![allow(clippy::missing_errors_doc)]
15
16#[cfg(feature = "legacy")]
17use crate::error::ErrorDiagnostics;
18
19#[cfg(feature = "legacy")]
20use crate::commands::schema::SchemaDef;
21#[cfg(feature = "legacy")]
22use crate::commands::{self, Command};
23#[cfg(feature = "legacy")]
24use crate::error::AamlError;
25#[cfg(feature = "legacy")]
26use crate::types::list::ListType;
27#[cfg(feature = "legacy")]
28use crate::types::{Type, resolve_builtin};
29#[cfg(feature = "legacy")]
30use std::collections::HashMap;
31#[cfg(feature = "legacy")]
32use std::fs;
33#[cfg(feature = "legacy")]
34use std::ops::{Add, AddAssign};
35#[cfg(feature = "legacy")]
36use std::path::{Path, PathBuf};
37#[cfg(feature = "legacy")]
38use std::sync::Arc;
39
40#[cfg(feature = "legacy")]
41mod lookup;
42pub mod parsing;
43#[cfg(feature = "legacy")]
44mod validation;
45
46#[cfg(all(feature = "serde", feature = "legacy"))]
47pub mod serialize;
48
49#[cfg(feature = "perf-hash")]
50#[cfg(feature = "legacy")]
51#[cfg(feature = "perf-hash")]
52type Hasher = ahash::RandomState;
53
54#[cfg(feature = "legacy")]
55#[cfg(not(feature = "perf-hash"))]
56type Hasher = std::collections::hash_map::RandomState;
57
58#[cfg(feature = "legacy")]
59type AamlString = Box<str>;
60
61#[cfg(feature = "legacy")]
75#[deprecated(since = "2.0.0", note = "please use AAM and Pipeline instead")]
76pub struct AAML {
77 map: HashMap<AamlString, AamlString, Hasher>,
78 commands: HashMap<String, Arc<dyn Command>>,
79 types: HashMap<String, Box<dyn Type>>,
80 schemas: HashMap<String, SchemaDef>,
81 pub(crate) source_dir: Option<PathBuf>,
83}
84
85#[cfg(feature = "legacy")]
86impl std::fmt::Debug for AAML {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.debug_struct("AAML")
89 .field("map", &self.map)
90 .field("commands_count", &self.commands.len())
91 .field("types_count", &self.types.len())
92 .field("schemas", &self.schemas)
93 .field("source_dir", &self.source_dir)
94 .finish()
95 }
96}
97
98#[cfg(feature = "legacy")]
99impl AAML {
100 #[must_use]
102 pub fn new() -> Self {
103 let mut instance = Self {
104 map: HashMap::with_hasher(Hasher::new()),
105 commands: HashMap::new(),
106 types: HashMap::new(),
107 schemas: HashMap::new(),
108 source_dir: None,
109 };
110 instance.register_default_commands();
111 instance
112 }
113
114 #[must_use]
115 pub const fn get_schemas(&self) -> &HashMap<String, SchemaDef> {
116 &self.schemas
117 }
118
119 #[must_use]
121 pub fn with_capacity(capacity: usize) -> Self {
122 let mut instance = Self {
123 map: HashMap::with_capacity_and_hasher(capacity, Hasher::default()),
124 commands: HashMap::new(),
125 types: HashMap::new(),
126 schemas: HashMap::new(),
127 source_dir: None,
128 };
129 instance.register_default_commands();
130 instance
131 }
132
133 pub(crate) const fn get_schemas_mut(&mut self) -> &mut HashMap<String, SchemaDef> {
136 &mut self.schemas
137 }
138
139 #[must_use]
140 pub fn get_schema(&self, name: &str) -> Option<&SchemaDef> {
141 self.schemas.get(name)
142 }
143
144 pub(crate) const fn get_map_mut(&mut self) -> &mut HashMap<AamlString, AamlString, Hasher> {
145 &mut self.map
146 }
147
148 #[allow(dead_code)]
152 pub(crate) fn get_commands(&self) -> &HashMap<String, Arc<dyn Command>> {
153 &self.commands
154 }
155
156 #[allow(dead_code)]
158 pub(crate) fn get_map_copy(&self) -> HashMap<AamlString, AamlString, Hasher> {
159 self.map.clone()
160 }
161
162 pub fn register_command<C: Command + 'static>(&mut self, command: C) {
166 self.commands
167 .insert(command.name().to_string(), Arc::new(command));
168 }
169
170 pub fn register_type<T: Type + 'static>(&mut self, name: String, type_def: T) {
172 self.types.insert(name, Box::new(type_def));
173 }
174
175 #[must_use]
177 pub fn get_type(&self, name: &str) -> Option<&dyn Type> {
178 self.types.get(name).map(AsRef::as_ref)
179 }
180
181 pub fn unregister_type(&mut self, name: &str) {
183 self.types.remove(name);
184 }
185
186 pub fn check_type(&self, type_name: &str, value: &str) -> Result<(), AamlError> {
191 self.types
192 .get(type_name)
193 .ok_or_else(|| AamlError::NotFound {
194 key: type_name.to_string(),
195 context: "type registry".to_string(),
196 diagnostics: Some(Box::new(ErrorDiagnostics::new(
197 "Type not found",
198 format!("Type '{type_name}' is not registered"),
199 "Check your @type directives or use a built-in type",
200 ))),
201 })?
202 .validate(value, self)
203 }
204
205 pub fn validate_value(&self, type_name: &str, value: &str) -> Result<(), AamlError> {
211 let make_err = |e: AamlError| AamlError::InvalidType {
212 type_name: type_name.to_string(),
213 details: e.to_string(),
214 provided: value.to_string(),
215 diagnostics: None,
216 };
217
218 if let Some(type_def) = self.types.get(type_name) {
219 return type_def.validate(value, self).map_err(make_err);
220 }
221
222 resolve_builtin(type_name)
223 .map_err(|_| AamlError::NotFound {
224 key: type_name.to_string(),
225 context: "builtin type registry".to_string(),
226 diagnostics: Some(Box::new(ErrorDiagnostics::new(
227 "Type not found",
228 format!("Type '{type_name}' is neither registered nor a built-in type"),
229 "Check the type name or register a custom type with @type",
230 ))),
231 })?
232 .validate(value, self)
233 .map_err(make_err)
234 }
235
236 pub fn merge_content(&mut self, content: &str) -> Result<(), AamlError> {
243 self.map.reserve(content.len() / 40);
244 let mut pending: Option<(String, usize)> = None;
245
246 for (i, line) in content.lines().enumerate() {
247 let line_num = i + 1;
248 if let Some(result) = self.accumulate_or_process(line, line_num, &mut pending)? {
249 self.process_line(&result.0, result.1)?;
250 }
251 }
252
253 if let Some((buf, start)) = pending {
254 self.process_line(&buf, start)?;
255 }
256 Ok(())
257 }
258
259 pub(crate) fn get_types_mut(&mut self) -> &mut HashMap<String, Box<dyn Type>> {
263 &mut self.types
264 }
265
266 fn accumulate_or_process(
270 &mut self,
271 line: &str,
272 line_num: usize,
273 pending: &mut Option<(String, usize)>,
274 ) -> Result<Option<(String, usize)>, AamlError> {
275 if let Some((buf, start)) = pending {
276 buf.push(' ');
277 buf.push_str(parsing::strip_comment(line).trim());
278 if parsing::block_is_complete(buf) {
279 let complete = buf.clone();
280 let start_line = *start;
281 *pending = None;
282 return Ok(Some((complete, start_line)));
283 }
284 return Ok(None);
285 }
286
287 let stripped = parsing::strip_comment(line).trim();
288 if parsing::needs_accumulation(stripped) {
289 *pending = Some((stripped.to_string(), line_num));
290 return Ok(None);
291 }
292
293 self.process_stripped_line(stripped, line_num)?;
294 Ok(None)
295 }
296
297 pub fn merge_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<(), AamlError> {
299 let content = fs::read_to_string(file_path)?;
300 self.merge_content(&content)
301 }
302
303 pub fn parse(content: &str) -> Result<Self, AamlError> {
305 let mut aaml = Self::new();
306 aaml.merge_content(content)?;
307 Ok(aaml)
308 }
309
310 pub fn load<P: AsRef<Path>>(file_path: P) -> Result<Self, AamlError> {
312 let file_path = file_path.as_ref();
313 let content = fs::read_to_string(file_path)?;
314 let mut aaml = Self::parse(&content)?;
315 aaml.source_dir = file_path.parent().map(Path::to_path_buf);
316 Ok(aaml)
317 }
318
319 #[must_use]
322 pub fn unwrap_quotes(s: &str) -> &str {
323 parsing::unwrap_quotes(s)
324 }
325
326 pub fn import_schema(&mut self, name: &str, source: &mut Self) -> Result<(), AamlError> {
327 if self.get_schemas().contains_key(name) {
329 return Ok(());
330 }
331
332 let schema =
333 source
334 .get_schemas_mut()
335 .remove(name)
336 .ok_or_else(|| AamlError::DirectiveError {
337 directive: "derive".to_string(),
338 message: format!("Schema '{name}' not found"),
339 diagnostics: Some(Box::new(ErrorDiagnostics::new(
340 "Schema not found for inheritance",
341 format!("Cannot derive from schema '{name}': it does not exist"),
342 "Ensure the schema is defined before using @derive",
343 ))),
344 })?;
345
346 let field_type_strings: Vec<String> = schema.fields.values().cloned().collect();
348
349 self.get_schemas_mut()
351 .entry(name.to_string())
352 .or_insert(schema);
353
354 for ty_str in &field_type_strings {
355 let ty_name = ListType::parse_inner(ty_str).unwrap_or_else(|| ty_str.clone());
356
357 if let Some(ty_def) = source.get_types_mut().remove(&ty_name) {
359 self.get_types_mut().entry(ty_name).or_insert(ty_def);
360 continue;
361 }
362
363 if source.get_schemas().contains_key(&ty_name)
366 && !self.get_schemas().contains_key(&ty_name)
367 {
368 self.import_schema(&ty_name, source)?;
369 }
370 }
371
372 Ok(())
373 }
374
375 pub fn merge_map_weak(&mut self, other_map: &mut HashMap<AamlString, AamlString, Hasher>) {
376 for (k, v) in other_map.drain() {
377 self.get_map_mut().entry(k).or_insert(v);
378 }
379 }
380
381 #[must_use]
383 pub fn keys(&self) -> Vec<&str> {
384 self.map.keys().map(AsRef::as_ref).collect()
385 }
386
387 #[must_use]
389 pub fn to_map(&self) -> HashMap<String, String> {
390 self.map
391 .iter()
392 .map(|(k, v)| (k.to_string(), v.to_string()))
393 .collect()
394 }
395
396 fn register_default_commands(&mut self) {
399 self.register_command(commands::import::ImportCommand);
400 self.register_command(commands::typecm::TypeCommand);
401 self.register_command(commands::schema::SchemaCommand);
402 self.register_command(commands::derive::DeriveCommand);
403 }
404
405 fn process_line(&mut self, raw_line: &str, line_num: usize) -> Result<(), AamlError> {
406 let line = parsing::strip_comment(raw_line).trim();
407 self.process_stripped_line(line, line_num)
408 }
409
410 fn process_stripped_line(&mut self, line: &str, line_num: usize) -> Result<(), AamlError> {
411 if line.is_empty() {
412 return Ok(());
413 }
414 if let Some(rest) = line.strip_prefix('@') {
415 return self.process_directive(rest, line_num);
416 }
417 self.process_assignment(line, line_num)
418 }
419
420 fn process_assignment(&mut self, line: &str, line_num: usize) -> Result<(), AamlError> {
421 match parsing::parse_assignment(line) {
422 Ok((key, value)) => {
423 self.validate_against_schemas(key, value)?;
424 self.map.insert(Box::from(key), Box::from(value));
425 Ok(())
426 }
427 Err(mut err) => {
428 if let AamlError::MalformedLiteral { diagnostics, .. } = &mut err
429 && diagnostics.is_none()
430 {
431 *diagnostics = Some(Box::new(ErrorDiagnostics::new(
432 "Failed to parse assignment",
433 format!("Line {line_num}: '{line}'"),
434 "Check the format: key = value",
435 )));
436 }
437 if let AamlError::InvalidValue { diagnostics, .. } = &mut err
438 && diagnostics.is_none()
439 {
440 *diagnostics = Some(Box::new(ErrorDiagnostics::new(
441 "Invalid assignment value",
442 format!("Line {line_num}: '{line}'"),
443 "Ensure key and value are properly formatted",
444 )));
445 }
446 Err(err)
447 }
448 }
449 }
450
451 fn process_directive(&mut self, content: &str, line_num: usize) -> Result<(), AamlError> {
452 let mut parts = content.splitn(2, char::is_whitespace);
453 let command_name = parts.next().unwrap_or("").trim();
454 let args = parts.next().unwrap_or("");
455
456 if command_name.is_empty() {
457 return Err(AamlError::ParseError {
458 line: line_num,
459 content: content.to_string(),
460 details: "Empty directive".to_string(),
461 diagnostics: Some(Box::new(ErrorDiagnostics::new(
462 "Empty directive",
463 "Directive name is missing after '@'",
464 "Use format: @directive_name args",
465 ))),
466 });
467 }
468
469 self.commands.get(command_name).cloned().map_or_else(
470 || {
471 Err(AamlError::ParseError {
472 line: line_num,
473 content: content.to_string(),
474 details: format!("Unknown directive: @{command_name}"),
475 diagnostics: Some(Box::new(ErrorDiagnostics::new(
476 "Unknown directive",
477 format!("Directive '@{command_name}' is not recognized"),
478 "Known directives: @import, @derive, @schema, @type",
479 ))),
480 })
481 },
482 |cmd| cmd.execute(self, args),
483 )
484 }
485}
486
487#[cfg(feature = "legacy")]
488impl Add for AAML {
489 type Output = Self;
490
491 fn add(mut self, rhs: Self) -> Self {
492 self.map.reserve(rhs.map.len());
493 self.map.extend(rhs.map);
494 self.types.extend(rhs.types);
495 self
496 }
497}
498
499#[cfg(feature = "legacy")]
500impl AddAssign for AAML {
501 fn add_assign(&mut self, rhs: Self) {
502 self.map.reserve(rhs.map.len());
503 self.map.extend(rhs.map);
504 self.types.extend(rhs.types);
505 }
506}
507
508#[cfg(feature = "legacy")]
509impl Default for AAML {
510 fn default() -> Self {
511 Self::new()
512 }
513}