use serde::{Deserialize, Serialize};
use super::error::SchemaError;
use super::field::FieldKind;
use super::properties::FieldProperties;
use super::value::FieldValue;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FieldDef {
pub name: String,
pub kind: FieldKind,
pub properties: FieldProperties,
}
impl FieldDef {
pub fn new(name: impl Into<String>, kind: FieldKind) -> Self {
Self {
name: name.into(),
kind,
properties: FieldProperties::default(),
}
}
pub fn validate(&self) -> Result<(), SchemaError> {
validate_object_name(&self.name)?;
self.kind.validate()?;
self.properties.validate_for(&self.kind)?;
Ok(())
}
pub fn validate_value(&self, value: &FieldValue) -> Result<(), SchemaError> {
match self.kind.validate_value(value) {
Err(SchemaError::EngineAssigned { field: None }) => Err(SchemaError::EngineAssigned {
field: Some(self.name.clone()),
}),
other => {
if matches!(value, FieldValue::Null) {
if self.properties.required {
return Err(SchemaError::NullNotAllowed {
field: self.name.clone(),
});
}
return Ok(());
}
if let FieldValue::Text(s) = value
&& s.is_empty()
&& !self.properties.allow_zero_length
&& matches!(self.kind, FieldKind::Text { .. } | FieldKind::Memo)
{
return Err(SchemaError::EmptyStringNotAllowed {
field: self.name.clone(),
});
}
other
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TableDef {
pub name: String,
pub fields: Vec<FieldDef>,
}
impl TableDef {
pub fn access_default(name: impl Into<String>) -> Self {
Self::new(
name,
vec![
FieldDef::new("ID", FieldKind::Number(super::NumericKind::LongInteger)),
FieldDef::new("Field1", FieldKind::Text { max_len: 255 }),
],
)
}
pub fn new(name: impl Into<String>, fields: Vec<FieldDef>) -> Self {
Self {
name: name.into(),
fields,
}
}
pub fn validate(&self) -> Result<(), SchemaError> {
validate_object_name(&self.name)?;
let mut seen: Vec<String> = Vec::with_capacity(self.fields.len());
for field in &self.fields {
field.validate()?;
let key = field.name.to_lowercase();
if seen.contains(&key) {
return Err(SchemaError::DuplicateName {
name: field.name.clone(),
});
}
seen.push(key);
}
Ok(())
}
}
pub(crate) fn validate_object_name(name: &str) -> Result<(), SchemaError> {
if name.trim().is_empty() {
Err(SchemaError::InvalidName {
detail: "names must not be empty or whitespace-only".to_owned(),
})
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::schema::NumericKind;
use super::{FieldKind, TableDef};
#[test]
fn access_default_is_id_plus_one_text_field() {
let table = TableDef::access_default("New");
assert_eq!(table.name, "New");
assert_eq!(table.fields.len(), 2);
assert_eq!(table.fields[0].name, "ID");
assert_eq!(
table.fields[0].kind,
FieldKind::Number(NumericKind::LongInteger)
);
assert_eq!(table.fields[1].name, "Field1");
assert_eq!(table.fields[1].kind, FieldKind::Text { max_len: 255 });
}
}