fix(domain): make FieldType::from_parts a strict inverse; reject stray bindings
This commit is contained in:
@@ -10,8 +10,14 @@ pub enum FieldType {
|
|||||||
Integer,
|
Integer,
|
||||||
Date,
|
Date,
|
||||||
Boolean,
|
Boolean,
|
||||||
Term { vocabulary_id: VocabularyId },
|
Term {
|
||||||
Authority { kind: Option<AuthorityKind> },
|
vocabulary_id: VocabularyId,
|
||||||
|
},
|
||||||
|
/// An authority reference. `kind: None` accepts any authority kind;
|
||||||
|
/// `Some(kind)` constrains to that kind.
|
||||||
|
Authority {
|
||||||
|
kind: Option<AuthorityKind>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldType {
|
impl FieldType {
|
||||||
@@ -37,21 +43,30 @@ impl FieldType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconstruct from the stored columns. `None` for an unknown or inconsistent combo
|
/// Reconstruct from the stored columns. Returns `None` for an unknown
|
||||||
/// (e.g. `term` without a vocabulary).
|
/// discriminant or an inconsistent combination — a scalar type carrying any
|
||||||
|
/// binding, a `term` without a vocabulary (or with an authority kind), or an
|
||||||
|
/// `authority` carrying a vocabulary.
|
||||||
pub fn from_parts(
|
pub fn from_parts(
|
||||||
data_type: &str,
|
data_type: &str,
|
||||||
vocabulary_id: Option<VocabularyId>,
|
vocabulary_id: Option<VocabularyId>,
|
||||||
authority_kind: Option<AuthorityKind>,
|
authority_kind: Option<AuthorityKind>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
|
let scalar = vocabulary_id.is_none() && authority_kind.is_none();
|
||||||
|
|
||||||
match data_type {
|
match data_type {
|
||||||
"text" => Some(FieldType::Text),
|
"text" if scalar => Some(FieldType::Text),
|
||||||
"localized_text" => Some(FieldType::LocalizedText),
|
"localized_text" if scalar => Some(FieldType::LocalizedText),
|
||||||
"integer" => Some(FieldType::Integer),
|
"integer" if scalar => Some(FieldType::Integer),
|
||||||
"date" => Some(FieldType::Date),
|
"date" if scalar => Some(FieldType::Date),
|
||||||
"boolean" => Some(FieldType::Boolean),
|
"boolean" if scalar => Some(FieldType::Boolean),
|
||||||
"term" => vocabulary_id.map(|vocabulary_id| FieldType::Term { vocabulary_id }),
|
"term" => match vocabulary_id {
|
||||||
"authority" => Some(FieldType::Authority {
|
Some(vocabulary_id) if authority_kind.is_none() => {
|
||||||
|
Some(FieldType::Term { vocabulary_id })
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
"authority" if vocabulary_id.is_none() => Some(FieldType::Authority {
|
||||||
kind: authority_kind,
|
kind: authority_kind,
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -117,4 +132,24 @@ mod tests {
|
|||||||
fn unknown_type_is_none() {
|
fn unknown_type_is_none() {
|
||||||
assert_eq!(FieldType::from_parts("blob", None, None), None);
|
assert_eq!(FieldType::from_parts("blob", None, None), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stray_binding_on_scalar_type_is_rejected() {
|
||||||
|
let v = VocabularyId::new();
|
||||||
|
assert_eq!(FieldType::from_parts("text", Some(v), None), None);
|
||||||
|
assert_eq!(
|
||||||
|
FieldType::from_parts("boolean", None, Some(AuthorityKind::Person)),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn term_or_authority_with_wrong_binding_is_rejected() {
|
||||||
|
let v = VocabularyId::new();
|
||||||
|
assert_eq!(
|
||||||
|
FieldType::from_parts("term", Some(v), Some(AuthorityKind::Person)),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(FieldType::from_parts("authority", Some(v), None), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user