1use super::ModelError;
7use crate::info::elements::Element;
8
9pub const MISSING_SEQ_ID: i32 = i32::MIN;
11
12fn require_len(table: &str, field: &str, actual: usize, expected: usize) -> Result<(), ModelError> {
13 if actual != expected {
14 return Err(ModelError::new(format!(
15 "{table}.{field} has length {actual}, expected {expected}"
16 )));
17 }
18 Ok(())
19}
20
21#[derive(Clone, Debug, PartialEq)]
23pub enum ResidueGroup {
24 Polymer,
26 NonPolymer,
28}
29
30#[derive(Clone, Debug)]
35pub struct AtomsTable {
36 pub atom_name: Vec<String>,
38 pub auth_atom_name: Vec<String>,
40 pub element: Vec<Element>,
42 pub alt_loc: Vec<Option<char>>,
44 pub formal_charge: Vec<Option<i8>>,
46}
47
48impl AtomsTable {
49 pub fn validate(&self) -> Result<(), ModelError> {
51 let n = self.atom_name.len();
52 require_len("atoms", "auth_atom_name", self.auth_atom_name.len(), n)?;
53 require_len("atoms", "element", self.element.len(), n)?;
54 require_len("atoms", "alt_loc", self.alt_loc.len(), n)?;
55 require_len("atoms", "formal_charge", self.formal_charge.len(), n)
56 }
57 pub fn len(&self) -> usize {
59 self.atom_name.len()
60 }
61
62 pub fn is_empty(&self) -> bool {
64 self.atom_name.is_empty()
65 }
66}
67
68#[derive(Clone, Debug)]
72pub struct ResiduesTable {
73 pub comp_id: Vec<String>,
75 pub auth_comp_id: Vec<String>,
77 pub label_seq_id: Vec<i32>,
80 pub auth_seq_id: Vec<i32>,
83 pub ins_code: Vec<Option<char>>,
85 pub group: Vec<ResidueGroup>,
87}
88
89impl ResiduesTable {
90 pub fn validate(&self) -> Result<(), ModelError> {
92 let n = self.comp_id.len();
93 require_len("residues", "auth_comp_id", self.auth_comp_id.len(), n)?;
94 require_len("residues", "label_seq_id", self.label_seq_id.len(), n)?;
95 require_len("residues", "auth_seq_id", self.auth_seq_id.len(), n)?;
96 require_len("residues", "ins_code", self.ins_code.len(), n)?;
97 require_len("residues", "group", self.group.len(), n)
98 }
99 pub fn len(&self) -> usize {
101 self.comp_id.len()
102 }
103
104 pub fn is_empty(&self) -> bool {
106 self.comp_id.is_empty()
107 }
108}
109
110#[derive(Clone, Debug)]
114pub struct ChainsTable {
115 pub label_asym_id: Vec<String>,
117 pub auth_asym_id: Vec<String>,
119 pub entity_id: Vec<String>,
121}
122
123impl ChainsTable {
124 pub fn validate(&self) -> Result<(), ModelError> {
126 let n = self.label_asym_id.len();
127 require_len("chains", "auth_asym_id", self.auth_asym_id.len(), n)?;
128 require_len("chains", "entity_id", self.entity_id.len(), n)
129 }
130 pub fn len(&self) -> usize {
132 self.label_asym_id.len()
133 }
134
135 pub fn is_empty(&self) -> bool {
137 self.label_asym_id.is_empty()
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_atoms_table_len() {
147 let table = AtomsTable {
148 atom_name: vec!["N".into(), "CA".into(), "C".into()],
149 auth_atom_name: vec!["N".into(), "CA".into(), "C".into()],
150 element: vec![Element::N, Element::C, Element::C],
151 alt_loc: vec![None, None, None],
152 formal_charge: vec![None, None, None],
153 };
154 assert_eq!(table.len(), 3);
155 assert!(!table.is_empty());
156
157 let empty = AtomsTable {
158 atom_name: vec![],
159 auth_atom_name: vec![],
160 element: vec![],
161 alt_loc: vec![],
162 formal_charge: vec![],
163 };
164 assert_eq!(empty.len(), 0);
165 assert!(empty.is_empty());
166 }
167
168 #[test]
169 fn test_residue_group_variants() {
170 let polymer = ResidueGroup::Polymer;
171 let non_polymer = ResidueGroup::NonPolymer;
172
173 assert_eq!(polymer, ResidueGroup::Polymer);
174 assert_eq!(non_polymer, ResidueGroup::NonPolymer);
175 assert_ne!(polymer, non_polymer);
176
177 let cloned = polymer.clone();
179 assert_eq!(cloned, ResidueGroup::Polymer);
180 assert!(!format!("{:?}", non_polymer).is_empty());
181 }
182
183 #[test]
184 fn test_atoms_table_validation_rejects_mismatched_columns() {
185 let table = AtomsTable {
186 atom_name: vec!["CA".into()],
187 auth_atom_name: vec![],
188 element: vec![Element::C],
189 alt_loc: vec![None],
190 formal_charge: vec![None],
191 };
192 assert_eq!(
193 table.validate().unwrap_err().to_string(),
194 "atoms.auth_atom_name has length 0, expected 1"
195 );
196 }
197}