use crate::schema::DatasetSchema;
use super::issues::Issue;
pub mod constraints {
pub const MAX_DATASET_NAME_BYTES: usize = 8;
pub const MAX_VARIABLE_NAME_BYTES: usize = 8;
pub const MAX_LABEL_BYTES: usize = 40;
pub const NUMERIC_LENGTH: usize = 8;
pub const MIN_CHARACTER_LENGTH: usize = 1;
pub const MAX_CHARACTER_LENGTH: usize = 200;
}
#[must_use]
pub(crate) fn validate_v5_schema(plan: &DatasetSchema) -> Vec<Issue> {
let mut issues = Vec::new();
if plan.domain_code.len() > constraints::MAX_DATASET_NAME_BYTES {
issues.push(Issue::DatasetNameTooLong {
dataset: plan.domain_code.clone(),
max: constraints::MAX_DATASET_NAME_BYTES,
actual: plan.domain_code.len(),
});
}
if let Some(ref label) = plan.dataset_label {
if label.len() > constraints::MAX_LABEL_BYTES {
issues.push(Issue::DatasetLabelTooLong {
dataset: plan.domain_code.clone(),
max: constraints::MAX_LABEL_BYTES,
actual: label.len(),
});
}
} else {
issues.push(Issue::MissingDatasetLabel {
dataset: plan.domain_code.clone(),
});
}
for var in &plan.variables {
if var.name.len() > constraints::MAX_VARIABLE_NAME_BYTES {
issues.push(Issue::VariableNameTooLong {
variable: var.name.clone(),
max: constraints::MAX_VARIABLE_NAME_BYTES,
actual: var.name.len(),
});
}
if var.label.is_empty() {
issues.push(Issue::MissingVariableLabel {
variable: var.name.clone(),
});
} else if var.label.len() > constraints::MAX_LABEL_BYTES {
issues.push(Issue::VariableLabelTooLong {
variable: var.name.clone(),
max: constraints::MAX_LABEL_BYTES,
actual: var.label.len(),
});
}
if var.xpt_type.is_numeric() && var.length != constraints::NUMERIC_LENGTH {
issues.push(Issue::NumericWrongLength {
variable: var.name.clone(),
expected: constraints::NUMERIC_LENGTH,
actual: var.length,
});
}
if var.xpt_type.is_character() && var.length < constraints::MIN_CHARACTER_LENGTH {
issues.push(Issue::CharacterLengthTooShort {
variable: var.name.clone(),
min: constraints::MIN_CHARACTER_LENGTH,
actual: var.length,
});
}
if var.xpt_type.is_character() && var.length > constraints::MAX_CHARACTER_LENGTH {
issues.push(Issue::CharacterLengthTooLong {
variable: var.name.clone(),
max: constraints::MAX_CHARACTER_LENGTH,
actual: var.length,
});
}
}
let expected_row_len: usize = plan.variables.iter().map(|v| v.length).sum();
if plan.row_len != expected_row_len {
issues.push(Issue::RowLenInconsistent {
recorded: plan.row_len,
computed: expected_row_len,
});
}
issues
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::plan::VariableSpec;
#[test]
fn test_valid_schema_with_labels() {
let mut plan = DatasetSchema::new("AE").with_label(Some("Adverse Events".into()));
plan.variables = vec![
VariableSpec::numeric("AESEQ").with_label("Sequence Number"),
VariableSpec::character("USUBJID", 20).with_label("Unique Subject Identifier"),
];
plan.recalculate_positions();
let issues = validate_v5_schema(&plan);
assert!(issues.is_empty());
}
#[test]
fn test_missing_labels_warnings() {
let mut plan = DatasetSchema::new("AE");
plan.variables = vec![
VariableSpec::numeric("AESEQ"),
VariableSpec::character("USUBJID", 20),
];
plan.recalculate_positions();
let issues = validate_v5_schema(&plan);
assert!(!issues.is_empty());
for issue in &issues {
assert!(
issue.is_warning(),
"Expected warning, got error: {:?}",
issue
);
}
assert!(
issues
.iter()
.any(|i| matches!(i, Issue::MissingDatasetLabel { .. }))
);
assert!(
issues.iter().any(
|i| matches!(i, Issue::MissingVariableLabel { variable } if variable == "AESEQ")
)
);
assert!(issues.iter().any(
|i| matches!(i, Issue::MissingVariableLabel { variable } if variable == "USUBJID")
));
}
#[test]
fn test_name_too_long() {
let mut plan = DatasetSchema::new("TOOLONGNAME");
plan.variables = vec![VariableSpec::numeric("AESEQ")];
plan.recalculate_positions();
let issues = validate_v5_schema(&plan);
assert!(!issues.is_empty());
assert!(
issues
.iter()
.any(|i| matches!(i, Issue::DatasetNameTooLong { .. }))
);
}
#[test]
fn test_numeric_wrong_length() {
let mut plan = DatasetSchema::new("AE");
plan.variables = vec![VariableSpec::new(
"AESEQ".into(),
crate::metadata::XptVarType::Numeric,
4, )];
plan.recalculate_positions();
let issues = validate_v5_schema(&plan);
assert!(!issues.is_empty());
assert!(
issues
.iter()
.any(|i| matches!(i, Issue::NumericWrongLength { .. }))
);
}
}