1mod import;
4mod syntax;
5
6use std::collections::BTreeMap;
7use std::error::Error;
8use std::fmt::{Display, Formatter};
9use std::path::PathBuf;
10
11use phasesmith_crystallography::{CellError, SpaceGroup, SymmetryError, UnitCell};
12
13use crate::SpaceGroupLookupError;
14
15pub use import::{parse_cif_text, read_cif_file};
16
17pub const NATIVE_CIF_BACKEND: &str = "phasesmith-native";
19pub const NATIVE_CIF_BACKEND_VERSION: &str = env!("CARGO_PKG_VERSION");
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct CifReadLimits {
25 pub max_bytes: usize,
27 pub max_blocks: usize,
29 pub max_loop_rows: usize,
31 pub max_atom_sites: usize,
33}
34
35impl Default for CifReadLimits {
36 fn default() -> Self {
37 Self {
38 max_bytes: 16 * 1024 * 1024,
39 max_blocks: 100,
40 max_loop_rows: 1_000_000,
41 max_atom_sites: 100_000,
42 }
43 }
44}
45
46impl CifReadLimits {
47 pub fn validate(self) -> Result<(), CifIoError> {
53 if self.max_bytes == 0
54 || self.max_blocks == 0
55 || self.max_loop_rows == 0
56 || self.max_atom_sites == 0
57 {
58 return Err(CifIoError::InvalidLimits);
59 }
60 Ok(())
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum CifDiagnosticSeverity {
67 Warning,
69 Error,
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct CifDiagnostic {
76 pub severity: CifDiagnosticSeverity,
78 pub code: String,
80 pub message: String,
82 pub tag: Option<String>,
84 pub row: Option<usize>,
86}
87
88impl CifDiagnostic {
89 fn warning(code: impl Into<String>, message: impl Into<String>) -> Self {
90 Self {
91 severity: CifDiagnosticSeverity::Warning,
92 code: code.into(),
93 message: message.into(),
94 tag: None,
95 row: None,
96 }
97 }
98
99 fn with_tag(mut self, tag: impl Into<String>) -> Self {
100 self.tag = Some(tag.into());
101 self
102 }
103
104 fn with_row(mut self, row: usize) -> Self {
105 self.row = Some(row);
106 self
107 }
108}
109
110#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct CifStructureSource {
113 pub format: String,
115 pub block_name: String,
117 pub backend: String,
119 pub backend_version: String,
121 pub source_path: Option<PathBuf>,
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127pub enum DisplacementConvention {
128 CifU,
130 CifB,
132}
133
134#[derive(Clone, Debug, PartialEq)]
136pub struct CifAnisotropicDisplacement {
137 pub u_cif_angstrom2: [f64; 6],
139 pub source_convention: DisplacementConvention,
141 pub standard_uncertainty: [Option<f64>; 6],
143}
144
145#[derive(Clone, Debug, PartialEq)]
147pub struct CifAtomSite {
148 pub site_id: String,
150 pub source_label: String,
152 pub type_symbol: String,
154 pub element_symbol: String,
156 pub fractional_xyz: [f64; 3],
158 pub occupancy: f64,
160 pub u_iso_angstrom2: Option<f64>,
162 pub anisotropic_displacement: Option<CifAnisotropicDisplacement>,
164 pub charge: Option<i32>,
166 pub isotope: Option<u32>,
168 pub disorder_group: Option<String>,
170 pub fractional_xyz_standard_uncertainty: [Option<f64>; 3],
172 pub occupancy_standard_uncertainty: Option<f64>,
174 pub u_iso_standard_uncertainty: Option<f64>,
176}
177
178#[derive(Clone, Debug, PartialEq)]
180pub struct CifStructure {
181 pub structure_id: String,
183 pub name: String,
185 pub cell: UnitCell,
187 pub space_group: SpaceGroup,
189 pub sites: Vec<CifAtomSite>,
191 pub source: CifStructureSource,
193 pub cell_standard_uncertainties: [Option<f64>; 6],
195 pub diagnostics: Vec<CifDiagnostic>,
197 pub metadata: BTreeMap<String, String>,
199}
200
201#[derive(Clone, Debug, PartialEq)]
203pub struct CifReadResult {
204 pub structure: CifStructure,
206 pub diagnostics: Vec<CifDiagnostic>,
208 pub selected_block: String,
210 pub available_blocks: Vec<String>,
212}
213
214#[derive(Debug)]
216pub enum CifIoError {
217 InvalidLimits,
219 ByteLimitExceeded {
221 actual: u64,
223 maximum: usize,
225 },
226 Io(std::io::Error),
228 Syntax {
230 message: String,
232 line: Option<usize>,
234 },
235 Limit {
237 message: String,
239 },
240 Import {
242 message: String,
244 },
245 Unsupported {
247 feature: String,
249 message: String,
251 },
252 SpaceGroup(SpaceGroupLookupError),
254 Cell(CellError),
256 Symmetry(SymmetryError),
258}
259
260impl Display for CifIoError {
261 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
262 match self {
263 Self::InvalidLimits => formatter.write_str("all CIF read limits must be positive"),
264 Self::ByteLimitExceeded { actual, maximum } => {
265 write!(
266 formatter,
267 "CIF input exceeds max_bytes: {actual} > {maximum}"
268 )
269 }
270 Self::Io(error) => Display::fmt(error, formatter),
271 Self::Syntax {
272 message,
273 line: Some(line),
274 } => write!(formatter, "invalid CIF syntax at line {line}: {message}"),
275 Self::Syntax {
276 message,
277 line: None,
278 } => write!(formatter, "invalid CIF syntax: {message}"),
279 Self::Limit { message }
280 | Self::Import { message }
281 | Self::Unsupported { message, .. } => formatter.write_str(message),
282 Self::SpaceGroup(error) => Display::fmt(error, formatter),
283 Self::Cell(error) => Display::fmt(error, formatter),
284 Self::Symmetry(error) => Display::fmt(error, formatter),
285 }
286 }
287}
288
289impl Error for CifIoError {
290 fn source(&self) -> Option<&(dyn Error + 'static)> {
291 match self {
292 Self::Io(error) => Some(error),
293 Self::SpaceGroup(error) => Some(error),
294 Self::Cell(error) => Some(error),
295 Self::Symmetry(error) => Some(error),
296 _ => None,
297 }
298 }
299}
300
301fn import_error(message: impl Into<String>) -> CifIoError {
302 CifIoError::Import {
303 message: message.into(),
304 }
305}