use crate::aaml::AAML;
use crate::commands::Command;
use crate::error::AamlError;
use std::collections::HashMap;
use std::collections::HashSet;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SchemaDef {
pub fields: HashMap<String, String>,
pub optional_fields: HashSet<String>,
}
impl SchemaDef {
pub fn is_optional(&self, field: &str) -> bool {
self.optional_fields.contains(field)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SchemaCommand;
impl SchemaCommand {
fn parse_header(args: &str) -> Result<(&str, &str), AamlError> {
let (name_part, body_part) = args
.split_once('{')
.ok_or_else(|| AamlError::DirectiveError("schema".into(), "Expected '{'".into()))?;
let name = name_part.trim();
if name.is_empty() {
return Err(AamlError::DirectiveError(
"schema".into(),
"Schema name is empty".into(),
));
}
let body = body_part
.rsplit_once('}')
.ok_or_else(|| AamlError::DirectiveError("schema".into(), "Expected '}'".into()))?
.0;
Ok((name, body))
}
fn parse_field<'a>(
token: &'a str,
tokens: &mut impl Iterator<Item = &'a str>,
) -> Result<(String, String, bool), AamlError> {
let (field_raw, ty) = token.split_once(':').ok_or_else(|| {
AamlError::DirectiveError("schema".into(), format!("Bad field: '{token}'"))
})?;
let ty = if ty.is_empty() {
tokens.next().ok_or_else(|| {
AamlError::DirectiveError(
"schema".into(),
format!("Bad field: '{field_raw}:' has no type"),
)
})?
} else {
ty
};
let is_optional = field_raw.ends_with('*');
let field = if is_optional {
field_raw.trim_end_matches('*')
} else {
field_raw
};
if field.is_empty() || ty.is_empty() {
return Err(AamlError::DirectiveError(
"schema".into(),
format!("Bad field: '{field}: {ty}'"),
));
}
Ok((field.to_string(), ty.to_string(), is_optional))
}
fn parse(args: &str) -> Result<(String, SchemaDef), AamlError> {
let (name, body) = Self::parse_header(args.trim())?;
let normalized = body.replace(',', " ");
let mut tokens = normalized.split_whitespace();
let mut fields = HashMap::new();
let mut optional_fields = HashSet::new();
while let Some(token) = tokens.next() {
let (field, ty, is_optional) = Self::parse_field(token, &mut tokens)?;
if is_optional {
optional_fields.insert(field.clone());
}
fields.insert(field, ty);
}
Ok((
name.to_string(),
SchemaDef {
fields,
optional_fields,
},
))
}
}
impl Command for SchemaCommand {
fn name(&self) -> &str {
"schema"
}
fn execute(&self, aaml: &mut AAML, args: &str) -> Result<(), AamlError> {
let (name, schema) = Self::parse(args)?;
aaml.get_schemas_mut().insert(name, schema);
Ok(())
}
}