feat(domain): add field definition types (FieldType, FieldDefinition)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
use crate::{AuthorityKind, FieldDefinitionId, LocalizedLabel, VocabularyId};
|
||||
|
||||
/// The type of a flexible field, carrying its binding where applicable.
|
||||
///
|
||||
/// Type-driven: a `Term` always names its vocabulary; a non-term never carries one.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FieldType {
|
||||
Text,
|
||||
LocalizedText,
|
||||
Integer,
|
||||
Date,
|
||||
Boolean,
|
||||
Term { vocabulary_id: VocabularyId },
|
||||
Authority { kind: Option<AuthorityKind> },
|
||||
}
|
||||
|
||||
impl FieldType {
|
||||
/// The stored discriminant string.
|
||||
pub fn kind_str(&self) -> &'static str {
|
||||
match self {
|
||||
FieldType::Text => "text",
|
||||
FieldType::LocalizedText => "localized_text",
|
||||
FieldType::Integer => "integer",
|
||||
FieldType::Date => "date",
|
||||
FieldType::Boolean => "boolean",
|
||||
FieldType::Term { .. } => "term",
|
||||
FieldType::Authority { .. } => "authority",
|
||||
}
|
||||
}
|
||||
|
||||
/// Decompose into the three stored columns: `(data_type, vocabulary_id, authority_kind)`.
|
||||
pub fn to_parts(&self) -> (&'static str, Option<VocabularyId>, Option<AuthorityKind>) {
|
||||
match self {
|
||||
FieldType::Term { vocabulary_id } => ("term", Some(*vocabulary_id), None),
|
||||
FieldType::Authority { kind } => ("authority", None, *kind),
|
||||
other => (other.kind_str(), None, None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstruct from the stored columns. `None` for an unknown or inconsistent combo
|
||||
/// (e.g. `term` without a vocabulary).
|
||||
pub fn from_parts(
|
||||
data_type: &str,
|
||||
vocabulary_id: Option<VocabularyId>,
|
||||
authority_kind: Option<AuthorityKind>,
|
||||
) -> Option<Self> {
|
||||
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 {
|
||||
kind: authority_kind,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A registered flexible field, with its multilingual display labels.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FieldDefinition {
|
||||
pub id: FieldDefinitionId,
|
||||
pub key: String,
|
||||
pub field_type: FieldType,
|
||||
pub required: bool,
|
||||
pub group_key: Option<String>,
|
||||
pub labels: Vec<LocalizedLabel>,
|
||||
}
|
||||
|
||||
/// A field definition to be created.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NewFieldDefinition {
|
||||
pub key: String,
|
||||
pub field_type: FieldType,
|
||||
pub required: bool,
|
||||
pub group_key: Option<String>,
|
||||
pub labels: Vec<LocalizedLabel>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn field_type_round_trips_through_parts() {
|
||||
let v = VocabularyId::new();
|
||||
let cases = [
|
||||
FieldType::Text,
|
||||
FieldType::LocalizedText,
|
||||
FieldType::Integer,
|
||||
FieldType::Date,
|
||||
FieldType::Boolean,
|
||||
FieldType::Term { vocabulary_id: v },
|
||||
FieldType::Authority {
|
||||
kind: Some(AuthorityKind::Person),
|
||||
},
|
||||
FieldType::Authority { kind: None },
|
||||
];
|
||||
for ft in cases {
|
||||
let (data_type, vocabulary_id, authority_kind) = ft.to_parts();
|
||||
assert_eq!(
|
||||
FieldType::from_parts(data_type, vocabulary_id, authority_kind),
|
||||
Some(ft)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn term_without_vocabulary_is_invalid() {
|
||||
assert_eq!(FieldType::from_parts("term", None, None), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_type_is_none() {
|
||||
assert_eq!(FieldType::from_parts("blob", None, None), None);
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,10 @@ id_newtype!(
|
||||
/// Identifier for a catalogue object (or group of objects).
|
||||
ObjectId
|
||||
);
|
||||
id_newtype!(
|
||||
/// Identifier for a flexible-field definition.
|
||||
FieldDefinitionId
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
mod audit;
|
||||
mod authority;
|
||||
mod field_definition;
|
||||
mod id;
|
||||
mod label;
|
||||
mod object;
|
||||
@@ -9,7 +10,8 @@ mod vocabulary;
|
||||
|
||||
pub use audit::{AuditAction, AuditActor, AuditEntry, FieldChange, NewAuditEvent};
|
||||
pub use authority::{Authority, AuthorityKind, AuthorityRef, NewAuthority};
|
||||
pub use id::{AuthorityId, ObjectId, OrgId, TermId, VocabularyId};
|
||||
pub use field_definition::{FieldDefinition, FieldType, NewFieldDefinition};
|
||||
pub use id::{AuthorityId, FieldDefinitionId, ObjectId, OrgId, TermId, VocabularyId};
|
||||
pub use label::{LocalizedLabel, pick_label};
|
||||
pub use object::{CatalogueObject, ObjectInput, Visibility};
|
||||
pub use vocabulary::{NewTerm, Term, TermRef, Vocabulary};
|
||||
|
||||
Reference in New Issue
Block a user