use crate::{
BeaErr, Csv, FromStrError, IoError, JsonParseError, JsonParseErrorKind, KeyMissing, SerdeJson,
};
pub fn to_csv<T: serde::Serialize + Clone, P: AsRef<std::path::Path>>(
item: &mut [T],
path: P,
) -> Result<(), BeaErr> {
match csv::Writer::from_path(path.as_ref()) {
Ok(mut wtr) => {
for i in item {
match wtr.serialize(i) {
Ok(_) => {}
Err(source) => {
let path = std::path::PathBuf::from(path.as_ref());
return Err(Csv::new(path, source, line!(), file!().to_string()).into());
}
}
}
match wtr.flush() {
Ok(_) => {}
Err(source) => {
let path = std::path::PathBuf::from(path.as_ref());
return Err(IoError::new(path, source, line!(), file!().to_string()).into());
}
}
Ok(())
}
Err(source) => Err(Csv::new(
std::path::PathBuf::from(path.as_ref()),
source,
line!(),
file!().to_string(),
)
.into()),
}
}
pub fn from_csv<T: serde::de::DeserializeOwned + Clone, P: AsRef<std::path::Path>>(
path: P,
) -> Result<Vec<T>, IoError> {
let mut records = Vec::new();
match std::fs::File::open(&path) {
Ok(file) => {
let mut rdr = csv::Reader::from_reader(file);
let mut dropped = 0;
for result in rdr.deserialize() {
match result {
Ok(record) => records.push(record),
Err(e) => {
tracing::trace!("Dropping: {}", e.to_string());
dropped += 1;
}
}
}
tracing::trace!("{} records dropped.", dropped);
Ok(records)
}
Err(source) => {
let path = std::path::PathBuf::from(path.as_ref());
Err(IoError::new(path, source, line!(), file!().to_string()))
}
}
}
#[tracing::instrument(skip_all)]
pub fn write_json<T: ?Sized + serde::Serialize, P: AsRef<std::path::Path>>(
contents: &T,
path: P,
) -> Result<(), BeaErr> {
let path = path.as_ref();
let file = std::fs::File::create(path)
.map_err(|e| IoError::new(path.to_owned(), e, line!(), file!().to_string()))?;
let writer = std::io::BufWriter::new(file);
serde_json::to_writer_pretty(writer, contents)
.map_err(|e| SerdeJson::new(e, line!(), file!().into()))?;
Ok(())
}
#[tracing::instrument]
pub fn json_str(json: &serde_json::Value) -> Result<String, JsonParseError> {
match json {
serde_json::Value::String(s) => {
tracing::trace!("String detected: {s}");
if s.starts_with('"') {
match serde_json::from_str(s) {
Ok(v) => Ok(v),
Err(e) => {
let error = FromStrError::new(s.to_string(), e);
Err(error.into())
}
}
} else {
Ok(s.to_string())
}
}
_ => {
tracing::warn!("Unexpected value.");
let error = JsonParseErrorKind::NotString;
Err(error.into())
}
}
}
#[tracing::instrument]
pub fn json_bool(json: &serde_json::Value) -> Result<bool, JsonParseError> {
match json {
serde_json::Value::Number(n) => {
tracing::trace!("Number detected: {n}");
if n.as_u64() == Some(1) {
Ok(true)
} else {
Ok(false)
}
}
serde_json::Value::String(s) => {
tracing::trace!("String detected: {s}");
if s == "1" { Ok(true) } else { Ok(false) }
}
_ => {
tracing::warn!("Unexpected value.");
let error = JsonParseErrorKind::NotBool;
Err(error.into())
}
}
}
#[tracing::instrument]
pub fn json_float(json: &serde_json::Value) -> Result<f64, JsonParseError> {
match json {
serde_json::Value::Number(n) => {
tracing::trace!("Number detected: {n}");
if let Some(num) = n.as_f64() {
Ok(num)
} else {
tracing::warn!("Number failed to parse as float.");
let error = NotFloat::new(
format!("Number {n} failed to parse as float"),
line!(),
file!().to_string(),
);
Err(error.into())
}
}
serde_json::Value::String(s) => {
tracing::trace!("String detected: {s}");
let s = s.replace(",", "");
match s.parse::<f64>() {
Ok(num) => Ok(num),
Err(source) => {
let error = ParseFloat::new(s, source, line!(), file!().to_string());
Err(error.into())
}
}
}
_ => {
tracing::warn!("Unexpected value.");
let error = NotFloat::new(
format!("Value {json:#?} is not a Number or String variant of serde_json::Value"),
line!(),
file!().to_string(),
);
Err(error.into())
}
}
}
#[tracing::instrument]
pub fn json_int(json: &serde_json::Value) -> Result<i64, JsonParseError> {
match json {
serde_json::Value::Number(n) => {
tracing::trace!("Number detected: {n}");
if let Some(num) = n.as_i64() {
Ok(num)
} else {
tracing::warn!("Number failed to parse as integer.");
let error = NotInteger::new(
format!("Number {n} failed to parse as float"),
line!(),
file!().to_string(),
);
Err(error.into())
}
}
serde_json::Value::String(s) => {
tracing::trace!("String detected: {s}");
match s.parse::<i64>() {
Ok(num) => Ok(num),
Err(source) => {
let error = ParseInteger::new(s.into(), source, line!(), file!().to_string());
Err(error.into())
}
}
}
_ => {
tracing::warn!("Unexpected value.");
let error = NotInteger::new(
format!("Value {json:#?} is not a Number or String variant of serde_json::Value"),
line!(),
file!().to_string(),
);
Err(error.into())
}
}
}
#[tracing::instrument(skip(m))]
pub fn map_to_bool(
key: &str,
m: &serde_json::Map<String, serde_json::Value>,
) -> Result<bool, JsonParseError> {
tracing::trace!("Mapping value to bool.");
if let Some(value) = m.get(key) {
tracing::trace!("Boolean candidate found.");
let flag = json_bool(value)?;
Ok(flag)
} else {
tracing::warn!("Key missing: {key}");
let error = KeyMissing::new(key.to_string(), line!(), file!().to_string());
Err(error.into())
}
}
#[tracing::instrument(skip(m))]
pub fn map_to_float(
key: &str,
m: &serde_json::Map<String, serde_json::Value>,
) -> Result<f64, JsonParseError> {
tracing::trace!("Mapping value to float.");
if let Some(value) = m.get(key) {
tracing::trace!("Float candidate found.");
let float = json_float(value)?;
Ok(float)
} else {
tracing::debug!("Key missing: {key}");
let error = KeyMissing::new(key.to_string(), line!(), file!().to_string());
Err(error.into())
}
}
#[tracing::instrument(skip(m))]
pub fn map_to_int(
key: &str,
m: &serde_json::Map<String, serde_json::Value>,
) -> Result<i64, JsonParseError> {
tracing::trace!("Mapping value to integer.");
if let Some(value) = m.get(key) {
tracing::trace!("Integer candidate found.");
let flag = json_int(value)?;
Ok(flag)
} else {
tracing::warn!("Key missing: {key}");
let error = KeyMissing::new(key.to_string(), line!(), file!().to_string());
Err(error.into())
}
}
#[tracing::instrument(skip(m))]
pub fn map_to_string(
key: &str,
m: &serde_json::Map<String, serde_json::Value>,
) -> Result<String, JsonParseError> {
if let Some(value) = m.get(key) {
json_str(value)
} else {
let error = KeyMissing::new(key.to_string(), line!(), file!().to_string());
Err(error.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display, derive_new::new)]
#[display("could not parse {input} to integer at line {line} in file {file}")]
pub struct NotInteger {
input: String,
line: u32,
file: String,
}
impl std::error::Error for NotInteger {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display, derive_new::new)]
#[display("could not parse {input} to float at line {line} in file {file}")]
pub struct NotFloat {
input: String,
line: u32,
file: String,
}
impl std::error::Error for NotFloat {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display, derive_new::new)]
#[display("could not parse {input} to integer at line {line} in file {file}")]
pub struct ParseInteger {
input: String,
source: std::num::ParseIntError,
line: u32,
file: String,
}
impl std::error::Error for ParseInteger {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display, derive_new::new)]
#[display("could not parse {input} to float at line {line} in file {file}")]
pub struct ParseFloat {
input: String,
source: std::num::ParseFloatError,
line: u32,
file: String,
}
impl std::error::Error for ParseFloat {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
#[derive(
Debug,
derive_getters::Getters,
derive_setters::Setters,
derive_more::Display,
derive_more::Error,
derive_new::new,
)]
#[display("{target} failed to parse to a valid url at line {line} in {file}")]
#[setters(prefix = "with_", borrow_self)]
pub struct UrlParseError {
pub target: String,
pub source: url::ParseError,
line: u32,
file: String,
}