honeycomb_core/cmap/builder/
structure.rs

1use std::fs::File;
2use std::io::Read;
3
4use thiserror::Error;
5use vtkio::Vtk;
6
7use crate::attributes::{AttrStorageManager, AttributeBind};
8use crate::cmap::{CMap2, CMap3};
9use crate::geometry::CoordsFloat;
10
11use super::io::CMapFile;
12
13/// # Builder-level error enum
14///
15/// This enum is used to describe all non-panic errors that can occur when using the builder
16/// structure.
17#[derive(Error, Debug, PartialEq, Eq)]
18pub enum BuilderError {
19    // custom format variants
20    /// A value could not be parsed.
21    #[error("error parsing a value in one of the cmap file section - {0}")]
22    BadValue(&'static str),
23    /// The meta section of the file is incorrect.
24    #[error("error parsing the cmap file meta section - {0}")]
25    BadMetaData(&'static str),
26    /// The file contains a duplicated section.
27    #[error("duplicated section in cmap file - {0}")]
28    DuplicatedSection(String),
29    /// The file contains contradicting data.
30    #[error("inconsistent data - {0}")]
31    InconsistentData(&'static str),
32    /// A required section is missing from the file.
33    #[error("required section missing in cmap file - {0}")]
34    MissingSection(&'static str),
35    /// The file contains an unrecognized section header.
36    #[error("unknown header in cmap file - {0}")]
37    UnknownHeader(String),
38
39    // vtk-related variants
40    /// Specified VTK file contains inconsistent data.
41    #[error("invalid/corrupted data in the vtk file - {0}")]
42    BadVtkData(&'static str),
43    /// Specified VTK file contains unsupported data.
44    #[error("unsupported data in the vtk file - {0}")]
45    UnsupportedVtkData(&'static str),
46}
47
48/// # Combinatorial map builder structure
49///
50/// ## Example
51///
52/// ```rust
53/// # use honeycomb_core::cmap::BuilderError;
54/// # fn main() -> Result<(), BuilderError> {
55/// use honeycomb_core::cmap::{CMap2, CMap3, CMapBuilder};
56///
57/// let builder_2d = CMapBuilder::<2>::from_n_darts(10);
58/// let map_2d: CMap2<f64> = builder_2d.build()?;
59/// assert_eq!(map_2d.n_darts(), 11); // 10 + null dart = 11
60///
61/// let builder_3d = CMapBuilder::<3>::from_n_darts(10);
62/// let map_3d: CMap3<f64> = builder_3d.build()?;
63/// assert_eq!(map_3d.n_darts(), 11); // 10 + null dart = 11
64/// # Ok(())
65/// # }
66/// ```
67pub struct CMapBuilder<const D: usize> {
68    builder_kind: BuilderType,
69    attributes: AttrStorageManager,
70}
71
72enum BuilderType {
73    CMap(CMapFile),
74    FreeDarts(usize),
75    Vtk(Vtk),
76}
77
78#[doc(hidden)]
79pub trait Builder<T: CoordsFloat> {
80    type MapType;
81    fn build(self) -> Result<Self::MapType, BuilderError>;
82}
83
84impl<T: CoordsFloat> Builder<T> for CMapBuilder<2> {
85    type MapType = CMap2<T>;
86
87    fn build(self) -> Result<Self::MapType, BuilderError> {
88        match self.builder_kind {
89            BuilderType::CMap(cfile) => super::io::build_2d_from_cmap_file(cfile, self.attributes),
90            BuilderType::FreeDarts(n_darts) => Ok(CMap2::new_with_undefined_attributes(
91                n_darts,
92                self.attributes,
93            )),
94            BuilderType::Vtk(vfile) => super::io::build_2d_from_vtk(vfile, self.attributes),
95        }
96    }
97}
98
99impl<T: CoordsFloat> Builder<T> for CMapBuilder<3> {
100    type MapType = CMap3<T>;
101
102    fn build(self) -> Result<Self::MapType, BuilderError> {
103        match self.builder_kind {
104            BuilderType::CMap(cfile) => super::io::build_3d_from_cmap_file(cfile, self.attributes),
105            BuilderType::FreeDarts(n_darts) => Ok(CMap3::new_with_undefined_attributes(
106                n_darts,
107                self.attributes,
108            )),
109            BuilderType::Vtk(_vfile) => unimplemented!(),
110        }
111    }
112}
113/// # Regular methods
114impl<const D: usize> CMapBuilder<D> {
115    /// Create a builder structure for a map with a set number of darts and the attribute set of
116    /// another builder.
117    #[must_use = "unused builder object"]
118    pub fn from_n_darts_and_attributes(n_darts: usize, other: Self) -> Self {
119        Self {
120            builder_kind: BuilderType::FreeDarts(n_darts),
121            attributes: other.attributes,
122        }
123    }
124
125    /// Create a builder structure for a map with a set number of darts.
126    #[must_use = "unused builder object"]
127    pub fn from_n_darts(n_darts: usize) -> Self {
128        Self {
129            builder_kind: BuilderType::FreeDarts(n_darts),
130            attributes: AttrStorageManager::default(),
131        }
132    }
133
134    /// Create a builder structure from a `cmap` file.
135    ///
136    /// # Panics
137    ///
138    /// This function may panic if the file cannot be loaded, or basic section parsing fails.
139    #[must_use = "unused builder object"]
140    pub fn from_cmap_file(file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
141        let mut f = File::open(file_path).expect("E: could not open specified file");
142        let mut buf = String::new();
143        f.read_to_string(&mut buf)
144            .expect("E: could not read content from file");
145        let cmap_file = CMapFile::try_from(buf).unwrap();
146
147        Self {
148            builder_kind: BuilderType::CMap(cmap_file),
149            attributes: AttrStorageManager::default(),
150        }
151    }
152
153    /// Create a builder structure from a VTK file.
154    ///
155    /// # Panics
156    ///
157    /// This function may panic if the file cannot be loaded.
158    #[must_use = "unused builder object"]
159    pub fn from_vtk_file(file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
160        let vtk_file =
161            Vtk::import(file_path).unwrap_or_else(|e| panic!("E: failed to load file: {e:?}"));
162
163        Self {
164            builder_kind: BuilderType::Vtk(vtk_file),
165            attributes: AttrStorageManager::default(),
166        }
167    }
168
169    /// Add the attribute `A` to the attributes the created map will contain.
170    ///
171    /// # Usage
172    ///
173    /// Each attribute must be uniquely typed, i.e. a single type or struct cannot be added twice
174    /// to the builder / map. This includes type aliases as these are not distinct from the
175    /// compiler's perspective.
176    ///
177    /// If you have multiple attributes that are represented using the same data type, you may want
178    /// to look into the **Newtype** pattern
179    /// [here](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)
180    /// and [here](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)
181    #[must_use = "unused builder object"]
182    pub fn add_attribute<A: AttributeBind + 'static>(mut self) -> Self {
183        self.attributes.add_storage::<A>(1);
184        self
185    }
186
187    #[allow(clippy::missing_errors_doc)]
188    /// Consumes the builder and produce a combinatorial map object.
189    ///
190    /// # Return / Errors
191    ///
192    /// This method return a `Result` taking the following values:
193    /// - `Ok(map: _)` if generation was successful,
194    /// - `Err(BuilderError)` otherwise. See [`BuilderError`] for possible failures.
195    ///
196    /// Depending on the dimension `D` associated with this structure, the map will either be a
197    /// `CMap2` or `CMap3`. If `D` isn't 2 or 3, this method will not be available as it uses a
198    /// trait not implemented for other values of `D`. This is necessary to handle the multiple
199    /// return types as Rust is slightly lacking in terms of comptime capabilities.
200    ///
201    /// # Panics
202    ///
203    /// This method may panic if type casting goes wrong during parameters parsing.
204    #[allow(private_interfaces, private_bounds)]
205    pub fn build<T: CoordsFloat>(self) -> Result<<Self as Builder<T>>::MapType, BuilderError>
206    where
207        Self: Builder<T>,
208    {
209        Builder::build(self)
210    }
211}