use fig::Value;
use fig_schema::{Cardinality, Term, Validate, Validation, validate_enum};
pub type FieldRule = fig_schema::FieldRule<Constraint>;
pub type Schema = fig_schema::Schema<Constraint>;
#[derive(Debug, Clone)]
pub enum Constraint {
Enum {
values: Vec<Term>,
closed: bool,
},
Reference {
relation: String,
cardinality: Cardinality,
spanning: bool,
},
}
impl Validate for Constraint {
fn validate(&self, value: &Value) -> Validation {
match self {
Constraint::Enum { values, closed } => validate_enum(values, *closed, value),
Constraint::Reference { .. } => Validation::Ok,
}
}
}
pub trait FieldRuleExt {
fn enum_constraint(&self) -> Option<(&[Term], bool)>;
fn reference(&self) -> Option<&str>;
}
impl FieldRuleExt for FieldRule {
fn enum_constraint(&self) -> Option<(&[Term], bool)> {
match &self.constraint {
Some(Constraint::Enum { values, closed }) => Some((values, *closed)),
_ => None,
}
}
fn reference(&self) -> Option<&str> {
match &self.constraint {
Some(Constraint::Reference { relation, .. }) => Some(relation),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tree::Seg;
use fig_schema::{FieldType, Issue, PathPat};
#[test]
fn closed_enum_rejects_unknown_accepts_known() {
let rule = FieldRule::new(PathPat::each_item_of("audience"))
.ty(FieldType::Str)
.constraint(Constraint::Enum {
values: vec![Term::value("public"), Term::value("private")],
closed: true,
});
assert_eq!(rule.validate(&Value::Str("public".into())), Validation::Ok);
assert!(matches!(
rule.validate(&Value::Str("familly".into())),
Validation::Reject(_)
));
}
#[test]
fn reference_constraint_is_not_checked_here() {
let rule = FieldRule::new(PathPat::key("part_of"))
.ty(FieldType::Ref)
.constraint(Constraint::Reference {
relation: "part_of".into(),
cardinality: Cardinality::One,
spanning: true,
});
assert_eq!(
rule.validate(&Value::Str("anything".into())),
Validation::Ok
);
assert_eq!(rule.reference(), Some("part_of"));
}
#[test]
fn schema_rule_for_matches_by_path() {
let schema = Schema::new(vec![FieldRule::new(PathPat::key("status")).constraint(
Constraint::Enum {
values: vec![Term::value("active"), Term::value("archived").retired(true)],
closed: true,
},
)]);
let rule = schema.rule_for(&[Seg::Key("status".into())]).unwrap();
assert_eq!(rule.validate(&Value::Str("active".into())), Validation::Ok);
assert_eq!(
rule.validate(&Value::Str("archived".into())),
Validation::Warn(Issue::retired("archived"))
);
let unknown = rule.validate(&Value::Str("activ".into()));
assert!(unknown.is_reject());
assert_eq!(
unknown.issue().and_then(|i| i.suggestion.as_deref()),
Some("active")
);
}
}