use serde::{Deserialize, Serialize};
use super::error::SchemaError;
use super::value::FieldValue;
const OLE_DATE_MIN: f64 = -657_434.0;
const OLE_DATE_MAX_EXCLUSIVE: f64 = 2_958_466.0;
impl NumericKind {
fn family_name(self) -> &'static str {
match self {
Self::Byte | Self::Integer | Self::LongInteger => "integer",
Self::Single => "single",
Self::Double => "double",
Self::Decimal { .. } => "decimal",
}
}
fn display_name(self) -> &'static str {
match self {
Self::Byte => "byte",
Self::Integer => "integer",
Self::LongInteger => "long integer",
Self::Single => "single",
Self::Double => "double",
Self::Decimal { .. } => "decimal",
}
}
pub fn validate_value(self, value: &FieldValue) -> Result<(), SchemaError> {
let mismatch = || SchemaError::KindMismatch {
expected: self.family_name(),
found: value.family(),
};
match self {
Self::Byte | Self::Integer | Self::LongInteger => match value {
FieldValue::Integer(i) => {
let (min, max) = self.integer_range().expect("integral kinds have a range");
if *i >= min && *i <= max {
Ok(())
} else {
Err(SchemaError::ValueOutOfRange {
detail: format!(
"{} integers hold {min}..={max}, found {i}",
self.display_name()
),
})
}
}
_ => Err(mismatch()),
},
Self::Single | Self::Double => match value {
FieldValue::Float(f) if f.is_finite() => Ok(()),
FieldValue::Float(_) => Err(SchemaError::ValueOutOfRange {
detail: "floating-point values must be finite".to_owned(),
}),
_ => Err(mismatch()),
},
Self::Decimal { .. } => match value {
FieldValue::Decimal(_) => Ok(()),
_ => Err(mismatch()),
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NumericKind {
Byte,
Integer,
LongInteger,
Single,
Double,
Decimal {
precision: u8,
scale: u8,
},
}
impl NumericKind {
pub fn integer_range(self) -> Option<(i64, i64)> {
match self {
Self::Byte => Some((0, 255)),
Self::Integer => Some((i64::from(i16::MIN), i64::from(i16::MAX))),
Self::LongInteger => Some((i64::from(i32::MIN), i64::from(i32::MAX))),
Self::Single | Self::Double | Self::Decimal { .. } => None,
}
}
pub fn validate(self) -> Result<(), SchemaError> {
if let Self::Decimal { precision, scale } = self {
if !(1..=28).contains(&precision) {
return Err(SchemaError::InvalidKind {
detail: format!("decimal precision must be 1..=28, found {precision}"),
});
}
if scale > precision {
return Err(SchemaError::InvalidKind {
detail: format!(
"decimal scale must not exceed precision (precision {precision}, scale {scale})"
),
});
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AutoKind {
LongInteger,
ReplicationId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BinaryKind {
OleObject,
Attachment,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MultiValueKind {
Text {
max_len: u16,
},
Number(NumericKind),
DateTime,
Currency,
Boolean {
tri_state: bool,
},
Guid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum FieldKind {
Text {
max_len: u16,
},
Memo,
Number(NumericKind),
DateTime,
Currency,
AutoNumber(AutoKind),
Boolean {
tri_state: bool,
},
Binary(BinaryKind),
MultiValue(MultiValueKind),
Complex,
Guid,
}
impl FieldKind {
pub fn family(&self) -> &'static str {
match self {
Self::Text { .. } => "short text",
Self::Memo => "memo",
Self::Number(NumericKind::Byte | NumericKind::Integer | NumericKind::LongInteger) => {
"integer"
}
Self::Number(NumericKind::Single) => "single",
Self::Number(NumericKind::Double) => "double",
Self::Number(NumericKind::Decimal { .. }) => "decimal",
Self::DateTime => "date/time",
Self::Currency => "currency",
Self::AutoNumber(_) => "autonumber",
Self::Boolean { .. } => "boolean",
Self::Binary(_) => "binary",
Self::MultiValue(_) => "multi-value",
Self::Complex => "complex",
Self::Guid => "guid",
}
}
pub fn validate(&self) -> Result<(), SchemaError> {
match self {
Self::Text { max_len } => {
if !(1..=255).contains(max_len) {
return Err(SchemaError::InvalidKind {
detail: format!("text max_len must be 1..=255, found {max_len}"),
});
}
Ok(())
}
Self::Number(kind) => kind.validate(),
Self::MultiValue(MultiValueKind::Text { max_len }) => {
if !(1..=255).contains(max_len) {
return Err(SchemaError::InvalidKind {
detail: format!(
"multi-value text max_len must be 1..=255, found {max_len}"
),
});
}
Ok(())
}
Self::MultiValue(MultiValueKind::Number(kind)) => kind.validate(),
_ => Ok(()),
}
}
pub fn validate_value(&self, value: &FieldValue) -> Result<(), SchemaError> {
if matches!(value, FieldValue::Null) {
return Ok(());
}
let mismatch = || SchemaError::KindMismatch {
expected: self.family(),
found: value.family(),
};
match self {
Self::Text { max_len } => match value {
FieldValue::Text(s) => {
let found = s.chars().count();
if found <= usize::from(*max_len) {
Ok(())
} else {
Err(SchemaError::ValueTooLong {
max_len: usize::from(*max_len),
found,
})
}
}
_ => Err(mismatch()),
},
Self::Memo => match value {
FieldValue::Text(_) => Ok(()),
_ => Err(mismatch()),
},
Self::Number(kind) => kind.validate_value(value),
Self::DateTime => match value {
FieldValue::DateTime(days) => {
if !days.is_finite() {
Err(SchemaError::ValueOutOfRange {
detail: "date/time values must be finite".to_owned(),
})
} else if !(OLE_DATE_MIN..OLE_DATE_MAX_EXCLUSIVE).contains(days) {
Err(SchemaError::ValueOutOfRange {
detail: format!(
"OLE date {days} is outside the Access-supported range \
[{OLE_DATE_MIN}, {OLE_DATE_MAX_EXCLUSIVE})"
),
})
} else {
Ok(())
}
}
_ => Err(mismatch()),
},
Self::Currency => match value {
FieldValue::Currency(_) => Ok(()),
_ => Err(mismatch()),
},
Self::AutoNumber(_) => Err(SchemaError::EngineAssigned { field: None }),
Self::Boolean { .. } => match value {
FieldValue::Boolean(_) => Ok(()),
_ => Err(mismatch()),
},
Self::Binary(BinaryKind::OleObject) => match value {
FieldValue::Binary(_) => Ok(()),
_ => Err(mismatch()),
},
Self::Binary(BinaryKind::Attachment) => match value {
FieldValue::Complex(_) => Ok(()),
_ => Err(mismatch()),
},
Self::MultiValue(kind) => match value {
FieldValue::Complex(records) => {
let element = kind.element_kind();
for record in records {
let Some(scalar) = record.scalar() else {
return Err(SchemaError::ValueOutOfRange {
detail: "multi-value record carries no scalar element".to_owned(),
});
};
element.validate_value(scalar)?;
}
Ok(())
}
_ => Err(mismatch()),
},
Self::Complex => match value {
FieldValue::Complex(_) => Ok(()),
_ => Err(mismatch()),
},
Self::Guid => match value {
FieldValue::Guid(_) => Ok(()),
_ => Err(mismatch()),
},
}
}
}
impl MultiValueKind {
pub fn element_kind(self) -> FieldKind {
match self {
Self::Text { max_len } => FieldKind::Text { max_len },
Self::Number(numeric) => FieldKind::Number(numeric),
Self::DateTime => FieldKind::DateTime,
Self::Currency => FieldKind::Currency,
Self::Boolean { tri_state } => FieldKind::Boolean { tri_state },
Self::Guid => FieldKind::Guid,
}
}
}
#[cfg(test)]
#[path = "field_tests.rs"]
mod tests;
#[cfg(test)]
mod coverage_tests {
use super::*;
#[test]
fn numeric_flavors_render_and_distinguish_ranges() {
for (kind, name) in [
(NumericKind::Byte, "byte"),
(NumericKind::Integer, "integer"),
(NumericKind::LongInteger, "long integer"),
(NumericKind::Single, "single"),
(NumericKind::Double, "double"),
(
NumericKind::Decimal {
precision: 10,
scale: 2,
},
"decimal",
),
] {
assert!(!kind.family_name().is_empty(), "{name}");
}
assert!(NumericKind::Byte.integer_range().is_some());
assert!(NumericKind::Integer.integer_range().is_some());
assert!(NumericKind::LongInteger.integer_range().is_some());
assert!(NumericKind::Single.integer_range().is_none());
assert!(NumericKind::Double.integer_range().is_none());
assert!(
NumericKind::Decimal {
precision: 10,
scale: 2
}
.integer_range()
.is_none()
);
}
#[test]
fn display_fallbacks_and_kind_mismatches_are_precise() {
assert_eq!(
FieldKind::AutoNumber(AutoKind::LongInteger).family(),
"autonumber"
);
assert_eq!(FieldKind::Complex.family(), "complex");
let error = FieldKind::Memo
.validate_value(&FieldValue::Integer(1))
.expect_err("wrong value type");
assert!(error.to_string().contains("memo") || error.to_string().contains("integer"));
}
#[test]
fn range_diagnostics_name_the_kind_and_complex_refuses_scalars() {
let error = FieldKind::Number(NumericKind::LongInteger)
.validate_value(&FieldValue::Integer(1 << 40))
.expect_err("out of range");
assert!(error.to_string().contains("long integer"), "{error}");
let error = FieldKind::Number(NumericKind::Byte)
.validate_value(&FieldValue::Integer(300))
.expect_err("out of range");
assert!(error.to_string().contains("byte"), "{error}");
let error = FieldKind::Complex
.validate_value(&FieldValue::Integer(1))
.expect_err("scalars do not satisfy complex fields");
assert!(error.to_string().contains("complex"), "{error}");
}
}