use std::collections::HashMap;
use std::path::Path;
use regex::Regex;
use toml::{self, Value};
use crate::definition::TemplateDefinition;
use crate::errors::{new_error, ErrorKind, Result};
use crate::utils::read_file;
pub fn validate_definition(def: &TemplateDefinition) -> Vec<String> {
let mut errs = vec![];
let mut types = HashMap::new();
for var in &def.variables {
let type_str = var.default.type_str();
types.insert(var.name.to_string(), type_str);
match var.default {
Value::String(_) | Value::Integer(_) | Value::Boolean(_) => (),
_ => {
errs.push(format!(
"Variable `{}` has a default of type {}, which isn't allowed",
var.name, type_str
));
}
}
if let Some(ref choices) = var.choices {
let mut choice_found = false;
for c in choices {
if *c == var.default {
choice_found = true;
}
}
if !choice_found {
errs.push(format!(
"Variable `{}` has `{}` as default, which isn't in the choices",
var.name, var.default
));
}
}
if let Some(ref cond) = var.only_if {
if let Some(ref t) = types.get(&cond.name) {
if **t != cond.value.type_str() {
errs.push(format!(
"Variable `{}` depends on `{}={}`, but the type of `{}` is {}",
var.name, cond.name, cond.value, cond.name, t
));
}
} else {
errs.push(format!(
"Variable `{}` depends on `{}`, which wasn't asked",
var.name, cond.name
));
}
}
if let Some(ref pattern) = var.validation {
if !var.default.is_str() {
errs.push(format!(
"Variable `{}` has a validation regex but is not a string",
var.name
));
continue;
}
match Regex::new(pattern) {
Ok(re) => {
if !re.is_match(&var.default.as_str().unwrap()) {
errs.push(format!(
"Variable `{}` has a default that doesn't pass its validation regex",
var.name
));
}
}
Err(_) => {
errs.push(format!(
"Variable `{}` has an invalid validation regex: {}",
var.name, pattern
));
}
}
}
}
errs
}
pub fn validate_file<T: AsRef<Path>>(path: T) -> Result<Vec<String>> {
let definition: TemplateDefinition = toml::from_str(&read_file(path.as_ref())?)
.map_err(|err| new_error(ErrorKind::Toml { err }))?;
Ok(validate_definition(&definition))
}
#[cfg(test)]
mod tests {
use toml;
use super::*;
#[test]
fn valid_definition_has_no_errors() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project_name"
default = "My project"
prompt = "What's the name of your project?"
[[variables]]
name = "database"
default = "postgres"
prompt = "Which database to use?"
choices = ["postgres", "mysql"]
[[variables]]
name = "pg_version"
prompt = "Which version of Postgres?"
default = "10.4"
choices = ["10.4", "9.3"]
only_if = { name = "database", value = "postgres" }
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(errs.is_empty());
}
#[test]
fn errors_default_not_in_choice() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project_name"
default = "My project"
prompt = "What's the name of your project?"
[[variables]]
name = "database"
default = "postgres"
prompt = "Which database to use?"
choices = ["postgres", "mysql"]
[[variables]]
name = "pg_version"
prompt = "Which version of Postgres?"
default = "10.5"
choices = ["10.4", "9.3"]
only_if = { name = "database", value = "postgres" }
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(!errs.is_empty());
assert_eq!(
errs[0],
"Variable `pg_version` has `\"10.5\"` as default, which isn\'t in the choices"
);
}
#[test]
fn errors_only_if_unkwnon_variable_name() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project_name"
default = "My project"
prompt = "What's the name of your project?"
[[variables]]
name = "pg_version"
prompt = "Which version of Postgres?"
default = "10.4"
choices = ["10.4", "9.3"]
only_if = { name = "database", value = true }
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(!errs.is_empty());
assert_eq!(errs[0], "Variable `pg_version` depends on `database`, which wasn\'t asked");
}
#[test]
fn errors_only_if_not_matching_type() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project_name"
default = "My project"
prompt = "What's the name of your project?"
[[variables]]
name = "database"
default = "postgres"
prompt = "Which database to use?"
choices = ["postgres", "mysql"]
[[variables]]
name = "pg_version"
prompt = "Which version of Postgres?"
default = "10.4"
choices = ["10.4", "9.3"]
only_if = { name = "database", value = true }
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(!errs.is_empty());
assert_eq!(errs[0], "Variable `pg_version` depends on `database=true`, but the type of `database` is string");
}
#[test]
fn errors_validation_regex_on_wrong_type() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project"
default = true
prompt = "What's the name of your project?"
validation = "[0-9]+"
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(!errs.is_empty());
assert_eq!(errs[0], "Variable `project` has a validation regex but is not a string");
}
#[test]
fn errors_invalid_validation_regex() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project_name"
default = "My project"
prompt = "What's the name of your project?"
validation = "**[0-9]++"
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(!errs.is_empty());
assert_eq!(errs[0], "Variable `project_name` has an invalid validation regex: **[0-9]++");
}
#[test]
fn errors_default_doesnt_match_validation_regex() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project_name"
default = "123"
prompt = "What's the name of your project?"
validation = "^([a-zA-Z][a-zA-Z0-9_-]+)$"
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(!errs.is_empty());
assert_eq!(
errs[0],
"Variable `project_name` has a default that doesn\'t pass its validation regex"
);
}
#[test]
fn errors_on_unsupported_type() {
let def: TemplateDefinition = toml::from_str(
r#"
name = "Test template"
description = "A description"
kickstart_version = 1
[[variables]]
name = "project_name"
default = 1.2
prompt = "What's the name of your project?"
validation = "^([a-zA-Z][a-zA-Z0-9_-]+)$"
"#,
)
.unwrap();
let errs = validate_definition(&def);
assert!(!errs.is_empty());
assert_eq!(
errs[0],
"Variable `project_name` has a default of type float, which isn\'t allowed"
);
}
}