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, Presentation};
#[test]
fn closed_enum_rejects_unknown_accepts_known() {
let rule = FieldRule {
at: PathPat::each_item_of("audience"),
ty: Some(FieldType::Str),
constraint: Some(Constraint::Enum {
values: vec![Term::value("public"), Term::value("private")],
closed: true,
}),
present: Presentation::default(),
};
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 {
at: PathPat::key("part_of"),
ty: Some(FieldType::Ref),
constraint: Some(Constraint::Reference {
relation: "part_of".into(),
cardinality: Cardinality::One,
spanning: true,
}),
present: Presentation::default(),
};
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 {
at: PathPat::key("status"),
ty: None,
constraint: Some(Constraint::Enum {
values: vec![
Term::value("active"),
Term {
retired: true,
..Term::value("archived")
},
],
closed: true,
}),
present: Presentation::default(),
}]);
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")
);
}
}