fix(domain): make FieldType::from_parts a strict inverse; reject stray bindings

This commit is contained in:
2026-06-02 10:15:07 +02:00
parent 2242ff5ef1
commit 6e27288f43
+46 -11
View File
@@ -10,8 +10,14 @@ pub enum FieldType {
Integer,
Date,
Boolean,
Term { vocabulary_id: VocabularyId },
Authority { kind: Option<AuthorityKind> },
Term {
vocabulary_id: VocabularyId,
},
/// An authority reference. `kind: None` accepts any authority kind;
/// `Some(kind)` constrains to that kind.
Authority {
kind: Option<AuthorityKind>,
},
}
impl FieldType {
@@ -37,21 +43,30 @@ impl FieldType {
}
}
/// Reconstruct from the stored columns. `None` for an unknown or inconsistent combo
/// (e.g. `term` without a vocabulary).
/// Reconstruct from the stored columns. Returns `None` for an unknown
/// 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(
data_type: &str,
vocabulary_id: Option<VocabularyId>,
authority_kind: Option<AuthorityKind>,
) -> Option<Self> {
let scalar = vocabulary_id.is_none() && authority_kind.is_none();
match data_type {
"text" => Some(FieldType::Text),
"localized_text" => Some(FieldType::LocalizedText),
"integer" => Some(FieldType::Integer),
"date" => Some(FieldType::Date),
"boolean" => Some(FieldType::Boolean),
"term" => vocabulary_id.map(|vocabulary_id| FieldType::Term { vocabulary_id }),
"authority" => Some(FieldType::Authority {
"text" if scalar => Some(FieldType::Text),
"localized_text" if scalar => Some(FieldType::LocalizedText),
"integer" if scalar => Some(FieldType::Integer),
"date" if scalar => Some(FieldType::Date),
"boolean" if scalar => Some(FieldType::Boolean),
"term" => match vocabulary_id {
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,
}),
_ => None,
@@ -117,4 +132,24 @@ mod tests {
fn unknown_type_is_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);
}
}