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}
117
118impl Display for CifWriteError {
119 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
120 match self {
121 Self::MissingBlockId => formatter.write_str(
122 "canonical CIF needs `_entry.id` or an explicit `CifWriteOptions::with_block_id`",
123 ),
124 Self::InvalidBlockId(id) => write!(formatter, "`{id}` is not a valid CIF block id"),
125 Self::MissingModelNumber { model } => {
126 write!(
127 formatter,
128 "model at position {model} has no deposited number"
129 )
130 }
131 Self::ModelIndexOverflow { model } => {
132 write!(
133 formatter,
134 "model position {model} exceeds the supported index range"
135 )
136 }
137 Self::AtomRowCountOverflow => {
138 formatter.write_str("canonical atom-site row count exceeds the platform range")
139 }
140 Self::MissingAtomField { atom, field } => {
141 write!(formatter, "atom {atom} has no required `{field}`")
142 }
143 Self::MissingAtomSiteId { atom } => {
144 write!(formatter, "atom {atom} has no source `_atom_site.id`")
145 }
146 Self::InvalidBondAtom { bond, atom } => {
147 write!(formatter, "bond {bond} refers to absent atom {atom}")
148 }
149 Self::MissingBondField {
150 bond,
151 endpoint,
152 field,
153 } => write!(
154 formatter,
155 "bond {bond} endpoint {endpoint} has no required `{field}`"
156 ),
157 Self::ConnectionIdsNotEnabled => formatter.write_str(
158 "canonical connectivity requires explicit generated connection identifiers",
159 ),
160 Self::MissingConnectionTypeId => formatter.write_str(
161 "canonical connectivity requires an explicit `_struct_conn.conn_type_id`",
162 ),
163 }
164 }
165}
166
167impl std::error::Error for CifWriteError {}
168
169#[derive(Debug)]
171pub enum CifWriteToError {
172 Projection(CifWriteError),
174 Output(io::Error),
176}
177
178impl Display for CifWriteToError {
179 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
180 match self {
181 Self::Projection(error) => Display::fmt(error, formatter),
182 Self::Output(error) => write!(formatter, "output stream failed: {error}"),
183 }
184 }
185}
186
187impl std::error::Error for CifWriteToError {
188 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
189 match self {
190 Self::Projection(error) => Some(error),
191 Self::Output(error) => Some(error),
192 }
193 }
194}
195
196impl From<CifWriteError> for CifWriteToError {
197 fn from(error: CifWriteError) -> Self {
198 Self::Projection(error)
199 }
200}
201
202pub(crate) fn valid_block_id(id: &str) -> bool {
203 !id.is_empty()
204 && id.bytes().all(|byte| {
205 byte.is_ascii_graphic() && !matches!(byte, b'#' | b'\'' | b'"' | b'[' | b']')
206 })
207}