destination/
utils.rs

1//! The `utils` module contains utility functions accessed by multiple data types, where declaring
2//! a stand-alone function eliminates code duplication in different methods.
3use crate::{AddressError, AddressErrorKind, Bincode, Csv, Io};
4use indicatif::{ProgressBar, ProgressStyle};
5use serde::de::{Deserialize, DeserializeOwned, Deserializer};
6use serde::Serialize;
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::time::Duration;
10use tracing::info;
11use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
12
13/// Function for deserailizing ArcGIS data that may contain either empty (Null) fields, or fields
14/// with string value "\<Null\>", either of which should translate to `None`.
15pub fn deserialize_arcgis_data<'de, D: Deserializer<'de>>(
16    de: D,
17) -> Result<Option<String>, D::Error> {
18    let intermediate = Deserialize::deserialize(de)?;
19
20    match intermediate {
21        None => Ok(None),
22        Some("<Null>") => Ok(None),
23        Some(other_value) => Ok(Some(other_value.to_string())),
24    }
25}
26
27/// Generic function to serialize data types into a CSV file.  Called by methods to avoid code
28/// duplication.
29pub fn to_csv<T: Serialize + Clone>(item: &mut [T], path: PathBuf) -> Result<(), AddressErrorKind> {
30    match csv::Writer::from_path(&path) {
31        Ok(mut wtr) => {
32            for i in item {
33                wtr.serialize(i)
34                    .map_err(|source| Csv::new(path.clone(), source, line!(), file!().into()))?;
35            }
36            wtr.flush()
37                .map_err(|source| Io::new(path.clone(), source, line!(), file!().into()))?;
38            Ok(())
39        }
40        Err(source) => Err(Csv::new(path, source, line!(), file!().to_string()).into()),
41    }
42}
43
44/// Generic function to deserialize data types from a CSV file.  Called by methods to avoid code
45/// duplication.
46pub fn from_csv<T: DeserializeOwned + Clone, P: AsRef<std::path::Path>>(
47    path: P,
48) -> Result<Vec<T>, Io> {
49    let mut records = Vec::new();
50    match std::fs::File::open(&path) {
51        Ok(file) => {
52            let mut rdr = csv::Reader::from_reader(file);
53
54            let mut dropped = 0;
55            for result in rdr.deserialize() {
56                match result {
57                    Ok(record) => records.push(record),
58                    Err(e) => {
59                        tracing::trace!("Dropping: {}", e.to_string());
60                        dropped += 1;
61                    }
62                }
63            }
64            tracing::info!("{} records dropped.", dropped);
65
66            Ok(records)
67        }
68        Err(source) => Err(Io::new(
69            path.as_ref().into(),
70            source,
71            line!(),
72            file!().into(),
73        )),
74    }
75}
76
77/// The `save` method serializes the contents of self into binary and writes to a file at
78/// location `path`.  Errors bubble up from serialization in [`bincode`] or file system access during write.
79pub fn to_bin<T: Serialize, P: AsRef<Path>>(data: &T, path: P) -> Result<(), AddressError> {
80    info!("Serializing to binary.");
81    let encode =
82        bincode::serialize(data).map_err(|source| Bincode::new(source, line!(), file!().into()))?;
83    info!("Writing to file.");
84    std::fs::write(&path, encode)
85        .map_err(|source| Io::new(path.as_ref().into(), source, line!(), file!().into()))?;
86    Ok(())
87}
88
89/// The `from_bin` function loads the contents of a file at location `path` into a `Vec<u8>`.
90/// May error reading the file, for example if the location is invalid, or when deserializing
91/// the binary if the format is invalid.
92pub fn from_bin<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Io> {
93    info!("Loading from binary.");
94    let bar = ProgressBar::new_spinner();
95    bar.enable_steady_tick(Duration::from_millis(120));
96    bar.set_style(
97        ProgressStyle::with_template("{spinner:.blue} {msg}")
98            .unwrap()
99            .tick_strings(&[
100                "▹▹▹▹▹",
101                "▸▹▹▹▹",
102                "▹▸▹▹▹",
103                "▹▹▸▹▹",
104                "▹▹▹▸▹",
105                "▹▹▹▹▸",
106                "▪▪▪▪▪",
107            ]),
108    );
109    bar.set_message("Loading...");
110    match fs::read(path.as_ref()) {
111        Ok(vec) => {
112            bar.finish_with_message("Loaded!");
113            Ok(vec)
114        }
115        Err(source) => Err(Io::new(
116            path.as_ref().into(),
117            source,
118            line!(),
119            file!().into(),
120        )),
121    }
122}
123
124/// The `IntoCsv` trait indicates the type can be read from and to a csv file.
125pub trait IntoCsv<T> {
126    /// The `from_csv` method attempts to deserialize the data from a `csv` file located at `path`.
127    fn from_csv<P: AsRef<Path>>(path: P) -> Result<T, Io>;
128    /// The `to_csv` method attempts to serialize the data to a `csv` file at location `path`.
129    fn to_csv<P: AsRef<Path>>(&mut self, path: P) -> Result<(), AddressErrorKind>;
130}
131
132/// The `IntoBin` trait indicates the type can be read from and to a binary file.
133pub trait IntoBin<T> {
134    /// The `load` method attempts to deserialize the data from a binary file located at `path`.
135    fn load<P: AsRef<Path>>(path: P) -> Result<T, AddressError>;
136    /// The `save` method attempts to serialize the data to a binary file at location `path`.
137    fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), AddressError>;
138}
139
140/// The `trace_init` function initializing the tracing subscriber.
141pub fn trace_init() {
142    if tracing_subscriber::registry()
143        .with(
144            tracing_subscriber::EnvFilter::try_from_default_env()
145                .unwrap_or_else(|_| "address=info".into()),
146        )
147        .with(tracing_subscriber::fmt::layer())
148        .try_init()
149        .is_ok()
150    {};
151    info!("Subscriber initialized.");
152}