use std::collections::BTreeSet;
use super::field::FormFieldKind;
use super::validation::{validate_identifier, validate_text};
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FormTypeId(String);
impl FormTypeId {
pub fn new(value: impl AsRef<str>) -> Result<Self> {
let value = value.as_ref();
if value.len() != 36 || !value.chars().all(|ch| ch == '-' || ch.is_ascii_hexdigit()) {
return Err(Error::InvalidInput(format!(
"invalid form type id: {value}"
)));
}
Ok(Self(value.to_ascii_lowercase()))
}
pub(crate) fn new_random() -> Result<Self> {
Ok(Self(crate::LockboxId::new_random()?.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for FormTypeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormDefinition {
pub type_id: FormTypeId,
pub alias: String,
pub revision: u32,
pub name: String,
pub description: String,
pub fields: Vec<FormFieldDefinition>,
}
impl FormDefinition {
pub(crate) fn validated_alias(value: &str) -> Result<String> {
validate_identifier(value, "form alias")
}
pub(crate) fn validated_name(value: &str) -> Result<String> {
validate_text(value, "form name")?;
Ok(value.to_string())
}
pub(crate) fn validated_description(value: &str) -> Result<String> {
validate_text(value, "form description")?;
Ok(value.to_string())
}
pub(crate) fn validated(
type_id: FormTypeId,
alias: String,
revision: u32,
name: &str,
description: &str,
fields: Vec<FormFieldDefinition>,
) -> Result<Self> {
if fields.is_empty() {
return Err(Error::InvalidInput(
"form definition requires at least one field".to_string(),
));
}
let mut seen = BTreeSet::new();
let mut validated_fields = Vec::with_capacity(fields.len());
for field in fields {
let id = FormFieldDefinition::validated_id(&field.id)?;
if !seen.insert(id.clone()) {
return Err(Error::InvalidInput(format!(
"duplicate form field id: {id}"
)));
}
validated_fields.push(FormFieldDefinition {
id,
label: FormFieldDefinition::validated_label(&field.label)?,
kind: field.kind,
required: field.required,
});
}
Ok(Self {
type_id,
alias: Self::validated_alias(&alias)?,
revision,
name: Self::validated_name(name)?,
description: Self::validated_description(description)?,
fields: validated_fields,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormFieldDefinition {
pub id: String,
pub label: String,
pub kind: FormFieldKind,
pub required: bool,
}
impl FormFieldDefinition {
pub(crate) fn validated_id(value: &str) -> Result<String> {
validate_identifier(value, "form field id")
}
pub(crate) fn validated_label(value: &str) -> Result<String> {
validate_text(value, "form field label")?;
Ok(value.to_string())
}
}