use crate::dataset::{Format, VariableRole};
use crate::metadata::XptVarType;
#[derive(Debug, Clone)]
pub(crate) struct DatasetSchema {
pub domain_code: String,
pub dataset_label: Option<String>,
pub variables: Vec<VariableSpec>,
pub row_len: usize,
}
#[allow(dead_code)]
impl DatasetSchema {
#[must_use]
pub fn new(domain_code: impl Into<String>) -> Self {
Self {
domain_code: domain_code.into(),
dataset_label: None,
variables: Vec::new(),
row_len: 0,
}
}
#[must_use]
pub fn with_label(mut self, label: Option<String>) -> Self {
self.dataset_label = label;
self
}
#[must_use]
pub fn num_variables(&self) -> usize {
self.variables.len()
}
pub fn recalculate_positions(&mut self) {
let mut position = 0;
for var in &mut self.variables {
var.position = position;
position += var.length;
}
self.row_len = position;
}
pub fn numeric_variables(&self) -> impl Iterator<Item = &VariableSpec> {
self.variables.iter().filter(|v| v.xpt_type.is_numeric())
}
pub fn character_variables(&self) -> impl Iterator<Item = &VariableSpec> {
self.variables.iter().filter(|v| v.xpt_type.is_character())
}
}
#[derive(Debug, Clone)]
pub(crate) struct VariableSpec {
pub name: String,
pub xpt_type: XptVarType,
pub length: usize,
pub label: String,
pub format: Option<Format>,
pub informat: Option<Format>,
pub position: usize,
pub role: Option<VariableRole>,
pub source_index: usize,
}
#[allow(dead_code)]
impl VariableSpec {
#[must_use]
pub fn new(name: String, xpt_type: XptVarType, length: usize) -> Self {
Self {
name,
xpt_type,
length,
label: String::new(),
format: None,
informat: None,
position: 0,
role: None,
source_index: 0,
}
}
#[must_use]
pub fn numeric(name: impl Into<String>) -> Self {
Self::new(name.into(), XptVarType::Numeric, 8)
}
#[must_use]
pub fn character(name: impl Into<String>, length: usize) -> Self {
Self::new(name.into(), XptVarType::Character, length)
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = label.into();
self
}
#[must_use]
pub fn with_format(mut self, format: Format) -> Self {
self.format = Some(format);
self
}
#[must_use]
pub fn with_informat(mut self, informat: Format) -> Self {
self.informat = Some(informat);
self
}
#[must_use]
pub fn with_role(mut self, role: VariableRole) -> Self {
self.role = Some(role);
self
}
#[must_use]
pub fn with_source_index(mut self, index: usize) -> Self {
self.source_index = index;
self
}
#[must_use]
pub fn format_name(&self) -> &str {
self.format
.as_ref()
.map(Format::name_without_prefix)
.unwrap_or("")
}
#[must_use]
pub fn format_length(&self) -> u16 {
self.format.as_ref().map(Format::length).unwrap_or(0)
}
#[must_use]
pub fn format_decimals(&self) -> u16 {
self.format.as_ref().map(Format::decimals).unwrap_or(0)
}
#[must_use]
pub fn format_justification(&self) -> i16 {
self.format
.as_ref()
.map(|f| f.justification().as_nfj())
.unwrap_or(0)
}
#[must_use]
pub fn informat_name(&self) -> &str {
self.informat
.as_ref()
.map(Format::name_without_prefix)
.unwrap_or("")
}
#[must_use]
pub fn informat_length(&self) -> u16 {
self.informat.as_ref().map(Format::length).unwrap_or(0)
}
#[must_use]
pub fn informat_decimals(&self) -> u16 {
self.informat.as_ref().map(Format::decimals).unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schema_plan_positions() {
let mut plan = DatasetSchema::new("AE");
plan.variables = vec![
VariableSpec::numeric("AESEQ"),
VariableSpec::character("USUBJID", 20),
VariableSpec::numeric("AESTDY"),
];
plan.recalculate_positions();
assert_eq!(plan.variables[0].position, 0);
assert_eq!(plan.variables[1].position, 8);
assert_eq!(plan.variables[2].position, 28);
assert_eq!(plan.row_len, 36);
}
}