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 connection_type_id: Option<Box<str>>,
12}
13
14impl CifWriteOptions {
15 #[must_use]
17 pub const fn new() -> Self {
18 Self {
19 block_id: None,
20 generate_connection_ids: false,
21 connection_type_id: None,
22 }
23 }
24
25 #[must_use]
27 pub fn with_block_id(mut self, block_id: impl Into<Box<str>>) -> Self {
28 self.block_id = Some(block_id.into());
29 self
30 }
31
32 #[must_use]
37 pub const fn with_generated_connection_ids(mut self) -> Self {
38 self.generate_connection_ids = true;
39 self
40 }
41
42 #[must_use]
47 pub fn with_connection_type_id(mut self, value: impl Into<Box<str>>) -> Self {
48 self.connection_type_id = Some(value.into());
49 self
50 }
51
52 pub(crate) fn block_id(&self) -> Option<&str> {
53 self.block_id.as_deref()
54 }
55
56 pub(crate) const fn generates_connection_ids(&self) -> bool {
57 self.generate_connection_ids
58 }
59
60 pub(crate) fn connection_type_id(&self) -> Option<&str> {
61 self.connection_type_id.as_deref()
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
67pub enum CifWriteError {
68 MissingBlockId,
70 InvalidBlockId(String),
72 MissingModelNumber {
74 model: u32,
76 },
77 ModelIndexOverflow {
79 model: usize,
81 },
82 AtomRowCountOverflow,
84 MissingAtomField {
86 atom: u32,
88 field: &'static str,
90 },
91 MissingAtomSiteId {
93 atom: u32,
95 },
96 InvalidBondAtom {
98 bond: usize,
100 atom: u32,
102 },
103 MissingBondField {
105 bond: usize,
107 endpoint: u8,
109 field: &'static str,
111 },
112 ConnectionIdsNotEnabled,
114 MissingConnectionTypeId,
116 MissingSecondaryField {
118 residue: u32,
120 field: &'static str,
122 },
123 AmbiguousSecondaryIdentity {
125 residue: u32,
127 },
128 ModelSpecificSecondary,
130}
131
132impl Display for CifWriteError {
133 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
134 match self {
135 Self::MissingBlockId => formatter.write_str(
136 "canonical CIF needs `_entry.id` or an explicit `CifWriteOptions::with_block_id`",
137 ),
138 Self::InvalidBlockId(id) => write!(formatter, "`{id}` is not a valid CIF block id"),
139 Self::MissingModelNumber { model } => {
140 write!(
141 formatter,
142 "model at position {model} has no deposited number"
143 )
144 }
145 Self::ModelIndexOverflow { model } => {
146 write!(
147 formatter,
148 "model position {model} exceeds the supported index range"
149 )
150 }
151 Self::AtomRowCountOverflow => {
152 formatter.write_str("canonical atom-site row count exceeds the platform range")
153 }
154 Self::MissingAtomField { atom, field } => {
155 write!(formatter, "atom {atom} has no required `{field}`")
156 }
157 Self::MissingAtomSiteId { atom } => {
158 write!(formatter, "atom {atom} has no source `_atom_site.id`")
159 }
160 Self::InvalidBondAtom { bond, atom } => {
161 write!(formatter, "bond {bond} refers to absent atom {atom}")
162 }
163 Self::MissingBondField {
164 bond,
165 endpoint,
166 field,
167 } => write!(
168 formatter,
169 "bond {bond} endpoint {endpoint} has no required `{field}`"
170 ),
171 Self::MissingSecondaryField { residue, field } => {
172 write!(
173 formatter,
174 "secondary residue {residue} has no required `{field}`"
175 )
176 }
177 Self::AmbiguousSecondaryIdentity { residue } => write!(
178 formatter,
179 "secondary residue {residue} has an ambiguous label identity"
180 ),
181 Self::ModelSpecificSecondary => formatter.write_str(
182 "canonical secondary categories cannot encode independent model annotations",
183 ),
184 Self::ConnectionIdsNotEnabled => formatter.write_str(
185 "canonical connectivity requires explicit generated connection identifiers",
186 ),
187 Self::MissingConnectionTypeId => formatter.write_str(
188 "canonical connectivity requires an explicit `_struct_conn.conn_type_id`",
189 ),
190 }
191 }
192}
193
194impl std::error::Error for CifWriteError {}
195
196#[derive(Debug)]
198pub enum CifWriteToError {
199 Projection(CifWriteError),
201 Output(io::Error),
203}
204
205impl Display for CifWriteToError {
206 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
207 match self {
208 Self::Projection(error) => Display::fmt(error, formatter),
209 Self::Output(error) => write!(formatter, "output stream failed: {error}"),
210 }
211 }
212}
213
214impl std::error::Error for CifWriteToError {
215 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
216 match self {
217 Self::Projection(error) => Some(error),
218 Self::Output(error) => Some(error),
219 }
220 }
221}
222
223impl From<CifWriteError> for CifWriteToError {
224 fn from(error: CifWriteError) -> Self {
225 Self::Projection(error)
226 }
227}
228
229pub(crate) fn valid_block_id(id: &str) -> bool {
230 !id.is_empty()
231 && id.bytes().all(|byte| {
232 byte.is_ascii_graphic() && !matches!(byte, b'#' | b'\'' | b'"' | b'[' | b']')
233 })
234}