use std::sync::Arc;
use indexmap::IndexMap;
use crate::ast::{Node, NodeType, Value};
use crate::validate::schema::{FunctionTransformHook, FunctionValidateHook, Schema};
use crate::validate::{SchemaAttribute, ValidationType};
pub type Variables = IndexMap<String, Value>;
#[derive(Clone, Default)]
pub struct Config<'a> {
pub nodes: Arc<IndexMap<NodeType, Schema>>,
pub tags: Arc<IndexMap<String, Schema>>,
pub variables: Option<Variables>,
pub functions: Arc<IndexMap<String, ConfigFunction>>,
pub partials: Arc<IndexMap<String, Node<'a>>>,
pub validation: ValidationOptions<'a>,
}
impl<'a> Config<'a> {
#[must_use]
pub fn new() -> Config<'a> {
Config::default()
}
pub fn nodes_mut(&mut self) -> &mut IndexMap<NodeType, Schema> {
Arc::make_mut(&mut self.nodes)
}
pub fn tags_mut(&mut self) -> &mut IndexMap<String, Schema> {
Arc::make_mut(&mut self.tags)
}
pub fn functions_mut(&mut self) -> &mut IndexMap<String, ConfigFunction> {
Arc::make_mut(&mut self.functions)
}
pub fn partials_mut(&mut self) -> &mut IndexMap<String, Node<'a>> {
Arc::make_mut(&mut self.partials)
}
#[must_use]
pub fn find_schema(&self, node: &Node<'_>) -> Option<&Schema> {
match &node.tag {
Some(tag) => self.tags.get(tag.as_str()),
None => self.nodes.get(&node.node_type),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ValidationOptions<'a> {
pub parents: Vec<&'a Node<'a>>,
pub validate_functions: bool,
pub environment: Option<String>,
}
#[derive(Clone, Default)]
pub struct ConfigFunction {
pub returns: Option<ValidationType>,
pub parameters: Option<IndexMap<String, SchemaAttribute>>,
pub transform: Option<FunctionTransformHook>,
pub validate: Option<FunctionValidateHook>,
}
impl std::fmt::Debug for Config<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Config")
.field("nodes", &self.nodes.keys().collect::<Vec<_>>())
.field("tags", &self.tags.keys().collect::<Vec<_>>())
.field("variables", &self.variables)
.field("functions", &self.functions.keys().collect::<Vec<_>>())
.field("partials", &self.partials.keys().collect::<Vec<_>>())
.field("validation", &self.validation)
.finish()
}
}
impl std::fmt::Debug for ConfigFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConfigFunction")
.field("returns", &self.returns)
.field("parameters", &self.parameters)
.field("transform", &self.transform.is_some())
.field("validate", &self.validate.is_some())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Node;
#[test]
fn a_tag_is_looked_up_by_name_and_a_node_by_type() {
let mut config = Config::new();
config
.tags_mut()
.insert("callout".to_string(), Schema::default());
config
.nodes_mut()
.insert(NodeType::Heading, Schema::default());
let mut tag = Node::new(NodeType::Tag);
tag.tag = Some("callout".to_string());
assert!(config.find_schema(&tag).is_some());
assert!(config.find_schema(&Node::new(NodeType::Heading)).is_some());
assert!(
config
.find_schema(&Node::new(NodeType::Paragraph))
.is_none()
);
let mut unknown = Node::new(NodeType::Tag);
unknown.tag = Some("nope".to_string());
assert!(config.find_schema(&unknown).is_none());
}
#[test]
fn a_config_with_nothing_borrowed_outlives_any_document() {
let config: Config<'static> = Config::new();
let source = String::from("# hi\n");
let node = Node::new(NodeType::Document);
let _ = source;
assert!(config.find_schema(&node).is_none());
}
}