molframe_cif/write/
options.rs1use std::fmt::{Display, Formatter};
4use std::io;
5
6#[derive(Clone, Debug, Default, PartialEq, Eq)]
8pub struct CifWriteOptions {
9 block_id: Option<Box<str>>,
10 generate_connection_ids: bool,
11 extra_categories: Option<Box<str>>,
12}
13
14impl CifWriteOptions {
15 #[must_use]
22 pub const fn new() -> Self {
23 Self {
24 block_id: None,
25 generate_connection_ids: true,
26 extra_categories: None,
27 }
28 }
29
30 #[must_use]
32 pub const fn without_generated_connection_ids(mut self) -> Self {
33 self.generate_connection_ids = false;
34 self
35 }
36
37 #[must_use]
43 pub fn with_extra_categories(mut self, text: impl Into<Box<str>>) -> Self {
44 let text = text.into();
45 self.extra_categories = (!text.is_empty()).then_some(text);
46 self
47 }
48
49 #[must_use]
51 pub fn extra_categories(&self) -> Option<&str> {
52 self.extra_categories.as_deref()
53 }
54
55 #[must_use]
57 pub fn with_block_id(mut self, block_id: impl Into<Box<str>>) -> Self {
58 self.block_id = Some(block_id.into());
59 self
60 }
61
62 #[must_use]
69 pub const fn with_generated_connection_ids(mut self) -> Self {
70 self.generate_connection_ids = true;
71 self
72 }
73
74 #[must_use]
76 pub fn block_id(&self) -> Option<&str> {
77 self.block_id.as_deref()
78 }
79
80 pub(crate) const fn generates_connection_ids(&self) -> bool {
81 self.generate_connection_ids
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Eq)]
87pub enum CifWriteError {
88 MissingBlockId,
90 InvalidBlockId(String),
92 MissingModelNumber {
94 model: u32,
96 },
97 ModelIndexOverflow {
99 model: usize,
101 },
102 AtomRowCountOverflow,
104 MissingAtomField {
106 atom: u32,
108 field: &'static str,
110 },
111 MissingAtomSiteId {
113 atom: u32,
115 },
116 InvalidBondAtom {
118 bond: usize,
120 atom: u32,
122 },
123 MissingBondField {
125 bond: usize,
127 endpoint: u8,
129 field: &'static str,
131 },
132 ConnectionIdsNotEnabled,
134 MissingSecondaryField {
136 residue: u32,
138 field: &'static str,
140 },
141 AmbiguousSecondaryIdentity {
143 residue: u32,
145 },
146 ModelSpecificSecondary,
148}
149
150impl Display for CifWriteError {
151 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
152 match self {
153 Self::MissingBlockId => formatter.write_str(
154 "canonical CIF needs `_entry.id` or an explicit `CifWriteOptions::with_block_id`",
155 ),
156 Self::InvalidBlockId(id) => write!(formatter, "`{id}` is not a valid CIF block id"),
157 Self::MissingModelNumber { model } => {
158 write!(
159 formatter,
160 "model at position {model} has no deposited number"
161 )
162 }
163 Self::ModelIndexOverflow { model } => {
164 write!(
165 formatter,
166 "model position {model} exceeds the supported index range"
167 )
168 }
169 Self::AtomRowCountOverflow => {
170 formatter.write_str("canonical atom-site row count exceeds the platform range")
171 }
172 Self::MissingAtomField { atom, field } => {
173 write!(formatter, "atom {atom} has no required `{field}`")
174 }
175 Self::MissingAtomSiteId { atom } => {
176 write!(formatter, "atom {atom} has no source `_atom_site.id`")
177 }
178 Self::InvalidBondAtom { bond, atom } => {
179 write!(formatter, "bond {bond} refers to absent atom {atom}")
180 }
181 Self::MissingBondField {
182 bond,
183 endpoint,
184 field,
185 } => write!(
186 formatter,
187 "bond {bond} endpoint {endpoint} has no required `{field}`"
188 ),
189 Self::MissingSecondaryField { residue, field } => {
190 write!(
191 formatter,
192 "secondary residue {residue} has no required `{field}`"
193 )
194 }
195 Self::AmbiguousSecondaryIdentity { residue } => write!(
196 formatter,
197 "secondary residue {residue} has an ambiguous label identity"
198 ),
199 Self::ModelSpecificSecondary => formatter.write_str(
200 "canonical secondary categories cannot encode independent model annotations",
201 ),
202 Self::ConnectionIdsNotEnabled => formatter.write_str(
203 "canonical connectivity requires explicit generated connection identifiers",
204 ),
205 }
206 }
207}
208
209impl std::error::Error for CifWriteError {}
210
211#[derive(Debug)]
213pub enum CifWriteToError {
214 Projection(CifWriteError),
216 Output(io::Error),
218}
219
220impl Display for CifWriteToError {
221 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
222 match self {
223 Self::Projection(error) => Display::fmt(error, formatter),
224 Self::Output(error) => write!(formatter, "output stream failed: {error}"),
225 }
226 }
227}
228
229impl std::error::Error for CifWriteToError {
230 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
231 match self {
232 Self::Projection(error) => Some(error),
233 Self::Output(error) => Some(error),
234 }
235 }
236}
237
238impl From<CifWriteError> for CifWriteToError {
239 fn from(error: CifWriteError) -> Self {
240 Self::Projection(error)
241 }
242}
243
244pub(crate) fn valid_block_id(id: &str) -> bool {
245 !id.is_empty()
246 && id.bytes().all(|byte| {
247 byte.is_ascii_graphic() && !matches!(byte, b'#' | b'\'' | b'"' | b'[' | b']')
248 })
249}