use std::collections::{HashMap, HashSet};
use toml::Value;
use regex::Regex;
mod constructor;
mod parse_toml;
mod schema_type;
pub use schema_type::SchemaType;
#[derive(Debug, Clone)]
pub struct TableEntry {
pub key: Regex,
pub value: TomlSchema
}
#[derive(Debug, Clone)]
pub enum TomlSchema {
Alternative(Vec<TomlSchema>),
String{regex: Regex},
Integer{min: i64, max: i64},
Date,
Bool,
Float{min: f64, max: f64, nan_ok: bool},
Table{extras: Vec<TableEntry>, min: usize, max: usize, entries: HashMap<String, (TomlSchema, Option<Value>)>},
Array{cond: Box<TomlSchema>, min: usize, max: usize},
Anything,
Exact(Value)
}
impl TryFrom<toml::Table> for TomlSchema {
type Error = String;
fn try_from(table: toml::Table) -> Result<Self,String>
{
let (schema, dv) = TomlSchema::from_table(&table)?;
if dv.is_some() {log::warn!("Tried to construct a TOML Schema with a default value in the root")}
Ok(schema)
}
}
#[derive(Clone, PartialEq)]
pub enum SchemaError<'s, 'v> {
TypeMismatch{expected: SchemaType, got: SchemaType},
RegexMiss{string: &'v str, re: &'s str},
FloatMiss{val: f64, min: f64, max: f64, nan_ok: bool},
IntMiss{val: i64, min: i64, max: i64},
ArrayCount{count: usize, min: usize, max: usize},
ArrayMiss{value: &'v Value, error: Box<SchemaError<'s,'v>>},
TableMiss{key: &'v str, value: &'v Value, errors: Vec<SchemaError<'s,'v>>},
AtKey{key: &'v String, error: Box<SchemaError<'s,'v>>},
InTableElement{val: &'v Value, error: Box<SchemaError<'s,'v>>},
TableCount{count: usize, min: usize, max: usize},
AlternativeMiss{val: &'v Value, errors: Vec<SchemaError<'s,'v>>},
}
impl<'s,'v> std::fmt::Debug for SchemaError<'s,'v> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TypeMismatch{expected, got} => write!(f, "Expected {:?} but got {:?}", expected, got),
Self::RegexMiss{string, re} => write!(f, "Regex {:?} does not match {:?}", re, string),
Self::FloatMiss { val, min, max, nan_ok } => write!(f, "Float {:?} does not match [{:?},{:?}] (nan:{:?})", val,min,max,nan_ok),
Self::IntMiss { val, min, max } => write!(f, "Int {:?} does not match [{:?},{:?}]",val,min,max),
Self::ArrayCount { count, min, max } => write!(f, "Array count {:?} does not match [{:?},{:?}]",count,min,max),
Self::ArrayMiss { value, error } => write!(f, "Child of Array {:?} does not match because {:?}", value, error),
Self::TableMiss { key, value, errors } => write!(f, "No match for (key = {:?}, value = {:?}), error list : {:?}", key, value, errors),
Self::AtKey { key, error } => write!(f, "At key '{:?}', got ({:?})", key, error),
Self::InTableElement {val, error} => write!(f, "In Array (child {:?}), got ({:?})", val, error),
Self::TableCount { count, min, max } => write!(f, "Table extra count {:?} does not match [{:?},{:?}]",count,min,max),
Self::AlternativeMiss { val, errors } => write!(f, "No Alternative matched for {:?}, error list : {:?}", val, errors)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::sync::Once;
static INIT: Once = Once::new();
fn init_test() {
INIT.call_once(|| {
})
}
#[test]
fn parse_test() {
init_test();
let schema_toml = std::fs::read_to_string("test_files/test_schema.toml").unwrap().parse::<toml::Table>().unwrap();
let schema = match TomlSchema::try_from(schema_toml) {
Ok(x) => x,
Err(e) => {panic!("{}", e.as_str());}
};
let test_file = std::fs::read_to_string("Cargo.toml").unwrap();
schema.check(
&test_file.parse().unwrap()
).unwrap();
}
#[test]
fn parse_fail_test() {
init_test();
let schema_toml = std::fs::read_to_string("test_files/test_schema.toml").unwrap().parse::<toml::Table>().unwrap();
let schema = match TomlSchema::try_from(schema_toml) {
Ok(x) => x,
Err(e) => {panic!("{}", e.as_str());}
};
let test_file = std::fs::read_to_string("test_files/test_schema.toml").unwrap();
schema.check(
&test_file.parse().unwrap()
).unwrap_err();
}
}