Skip to content

Commit

Permalink
error types instead of strings
Browse files Browse the repository at this point in the history
  • Loading branch information
Fogapod committed Feb 21, 2024
1 parent 99b0528 commit 9d28890
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 62 deletions.
106 changes: 71 additions & 35 deletions src/accent.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,43 @@
#[cfg(feature = "deserialize")]
use crate::deserialize::AccentDef;

use crate::intensity::Intensity;

use std::borrow::Cow;
use std::{borrow::Cow, error::Error, fmt};

/// Replaces patterns in text according to rules
#[derive(Debug)]
#[cfg_attr(
feature = "deserialize",
derive(serde::Deserialize),
serde(try_from = "AccentDef")
)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Accent {
// a set of rules for each intensity level, sorted from lowest to highest
pub(crate) intensities: Vec<Intensity>,
}

impl Accent {
pub fn new(intensities: Vec<Intensity>) -> Result<Self, String> {
if intensities.is_empty() {
return Err("Expected at least a base intensity 0".to_owned());
}

if intensities[0].level != 0 {
return Err("First intensity must have level 0".to_owned());
}

let mut seen = Vec::with_capacity(intensities.len());
seen.push(0);
#[derive(Debug, PartialEq)]
pub enum CreationError {
IntensityZeroMissing,
FirstIntensityNotZero,
UnsortedOrDuplicatedIntensities(u64),
}

for (i, intensity) in intensities[1..].iter().enumerate() {
if intensity.level <= seen[i] {
return Err(format!("Duplicated or out of order intensity level {i}"));
impl fmt::Display for CreationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CreationError::IntensityZeroMissing => {
write!(f, "expected at least a base intensity 0")
}
seen.push(intensity.level);
CreationError::FirstIntensityNotZero => {
write!(f, "first intensity must have level 0")
}
CreationError::UnsortedOrDuplicatedIntensities(level) => write!(
f,
"{}",
format!("duplicated or out of order intensity level {level}")
),
}

// reverse now to not reverse each time to find highest matching intensity
let intensities: Vec<_> = intensities.into_iter().rev().collect();

Ok(Self { intensities })
}
}

impl Error for CreationError {}

impl Accent {
/// Returns all registered intensities in ascending order. Note that there may be gaps
pub fn intensities(&self) -> Vec<u64> {
self.intensities.iter().rev().map(|i| i.level).collect()
Expand All @@ -64,9 +58,42 @@ impl Accent {
}
}

impl TryFrom<Vec<Intensity>> for Accent {
type Error = CreationError;

fn try_from(intensities: Vec<Intensity>) -> Result<Self, Self::Error> {
if intensities.is_empty() {
return Err(CreationError::IntensityZeroMissing);
}

if intensities[0].level != 0 {
return Err(CreationError::FirstIntensityNotZero);
}

let mut seen = Vec::with_capacity(intensities.len());
seen.push(0);

for (i, intensity) in intensities[1..].iter().enumerate() {
if intensity.level <= seen[i] {
return Err(CreationError::UnsortedOrDuplicatedIntensities(
intensity.level,
));
}
seen.push(intensity.level);
}

// reverse now to not reverse each time to find highest matching intensity
let intensities: Vec<_> = intensities.into_iter().rev().collect();

Ok(Self { intensities })
}
}

#[cfg(test)]
mod tests {
use crate::{intensity::Intensity, pass::Pass, tag_impls::Literal, Accent};
use crate::{
accent::CreationError, intensity::Intensity, pass::Pass, tag_impls::Literal, Accent,
};

#[test]
fn e() {
Expand All @@ -81,21 +108,27 @@ mod tests {
)
.unwrap()],
);
let e = Accent::new(vec![base_intensity]).unwrap();
let e = Accent::try_from(vec![base_intensity]).unwrap();

assert_eq!(e.say_it("Hello World!", 0), "Eeeee Eeeee!");
}

#[test]
fn construction_error_empty_intensities() {
assert!(Accent::new(Vec::new()).is_err());
assert_eq!(
Accent::try_from(Vec::new()).err().unwrap(),
CreationError::IntensityZeroMissing
);
}

#[test]
fn construction_error_first_must_be_0() {
let intensities = vec![Intensity::new(12, Vec::new())];

assert!(Accent::new(intensities).is_err());
assert_eq!(
Accent::try_from(intensities).err().unwrap(),
CreationError::FirstIntensityNotZero
);
}

#[test]
Expand All @@ -106,6 +139,9 @@ mod tests {
Intensity::new(1, Vec::new()),
];

assert!(Accent::new(intensities).is_err());
assert_eq!(
Accent::try_from(intensities).err().unwrap(),
CreationError::UnsortedOrDuplicatedIntensities(1)
);
}
}
23 changes: 14 additions & 9 deletions src/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,13 @@ pub(crate) struct AccentDef {
intensities: IntensitiesDef,
}

impl TryFrom<AccentDef> for Accent {
type Error = String;
impl<'de> Deserialize<'de> for Accent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let accent_def = AccentDef::deserialize(deserializer)?;

fn try_from(accent_def: AccentDef) -> Result<Self, Self::Error> {
let mut intensities: Vec<Intensity> =
Vec::with_capacity(accent_def.intensities.0.len() + 1);

Expand All @@ -221,13 +224,15 @@ impl TryFrom<AccentDef> for Accent {
for (i, (level, intensity)) in accent_def.intensities.0.into_iter().enumerate() {
let intensity = match intensity {
IntensityDef::Replace(passes) => Intensity::new(level, passes.0),
IntensityDef::Extend(passes) => intensities[i].extend(level, passes.0)?,
IntensityDef::Extend(passes) => intensities[i]
.extend(level, passes.0)
.map_err(de::Error::custom)?,
};

intensities.push(intensity);
}

Self::new(intensities)
Self::try_from(intensities).map_err(de::Error::custom)
}
}

Expand Down Expand Up @@ -311,7 +316,7 @@ mod tests {
),
];

let accent = Accent::new(manual).unwrap();
let accent = Accent::try_from(manual).unwrap();

assert_eq!(parsed, accent);
assert_eq!(parsed.intensities(), accent.intensities());
Expand Down Expand Up @@ -364,7 +369,7 @@ mod tests {
),
];

let manual = Accent::new(intensities).unwrap();
let manual = Accent::try_from(intensities).unwrap();

assert_eq!(parsed, manual);
}
Expand Down Expand Up @@ -394,7 +399,7 @@ mod tests {

assert_eq!(
zero_sum.code.to_string(),
"Weights must add up to a positive number"
"weights must add up to a positive number"
);
}

Expand Down Expand Up @@ -579,7 +584,7 @@ mod tests {
],
),
];
let manual = Accent::new(intensities).unwrap();
let manual = Accent::try_from(intensities).unwrap();
assert_eq!(manual, parsed);

// TODO: either patch rand::thread_rng somehow or change interface to pass rng directly?
Expand Down
12 changes: 6 additions & 6 deletions src/intensity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Cow;

use crate::pass::Pass;
use crate::pass::{self, Pass};

/// Holds [`Pass`] objects and applies them in order
#[derive(Debug)]
Expand All @@ -16,7 +16,7 @@ impl Intensity {
}

/// Produces new instance by extending inner passes
pub fn extend(&self, level: u64, passes: Vec<Pass>) -> Result<Self, String> {
pub fn extend(&self, level: u64, passes: Vec<Pass>) -> Result<Self, pass::CreationError> {
let mut existing_passes = self.passes.clone();
let mut appended_passes = Vec::new();

Expand Down Expand Up @@ -105,15 +105,15 @@ mod tests {

#[test]
fn passes_appended() {
let old_pass = Pass::new::<&str>("old", Vec::new()).unwrap();
let new_pass = Pass::new::<&str>("new", Vec::new()).unwrap();
let old_pass = Pass::new("old", Vec::<(&str, _)>::new()).unwrap();
let new_pass = Pass::new("new", Vec::<(&str, _)>::new()).unwrap();

let old = Intensity::new(0, vec![old_pass]);

let extended = old.extend(1, vec![new_pass]).unwrap();
let expected = vec![
Pass::new::<&str>("old", Vec::new()).unwrap(),
Pass::new::<&str>("new", Vec::new()).unwrap(),
Pass::new("old", Vec::<(&str, _)>::new()).unwrap(),
Pass::new("new", Vec::<(&str, _)>::new()).unwrap(),
];

assert_eq!(extended.passes, expected);
Expand Down
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,11 @@
//! [`Lower`]: crate::tag_impls::Lower
//! [`Concat`]: crate::tag_impls::Concat
mod accent;
mod intensity;
mod r#match;
mod pass;
mod tag;
pub mod accent;
pub mod intensity;
pub mod r#match;
pub mod pass;
pub mod tag;
pub mod tag_impls;

// pub for bench
Expand Down
35 changes: 30 additions & 5 deletions src/pass.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{borrow::Cow, fmt};
use std::{borrow::Cow, error::Error, fmt};

use regex_automata::{meta::Regex, util::syntax};
use regex_automata::{
meta::{BuildError, Regex},
util::syntax,
};

use crate::{tag::Tag, Match};

Expand All @@ -24,8 +27,30 @@ impl fmt::Debug for Pass {
}
}

#[derive(Debug)]
pub enum CreationError {
BadRegex(BuildError),
}

impl fmt::Display for CreationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
CreationError::BadRegex(err) => format!("regex combination failed: {err}"),
}
)
}
}

impl Error for CreationError {}

impl Pass {
pub fn new<T: AsRef<str>>(name: &str, rules: Vec<(T, Box<dyn Tag>)>) -> Result<Self, String> {
pub fn new<T: AsRef<str>>(
name: &str,
rules: Vec<(T, Box<dyn Tag>)>,
) -> Result<Self, CreationError> {
let (patterns, tags): (Vec<_>, Vec<_>) = rules.into_iter().unzip();

let patterns: Vec<_> = patterns
Expand All @@ -40,7 +65,7 @@ impl Pass {
.case_insensitive(true),
)
.build_many(&patterns)
.map_err(|err| format!("regex combination failed: {err}"))?;
.map_err(|err| CreationError::BadRegex(err))?;

Ok(Self {
name: name.to_owned(),
Expand All @@ -50,7 +75,7 @@ impl Pass {
})
}

pub fn extend(&self, other: Pass) -> Result<Self, String> {
pub fn extend(&self, other: Pass) -> Result<Self, CreationError> {
let mut existing_rules: Vec<_> = self
.regexes
.iter()
Expand Down
18 changes: 16 additions & 2 deletions src/tag_impls.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(feature = "deserialize")]
use crate::deserialize::SortedMap;

use std::{borrow::Cow, fmt::Display};
use std::{borrow::Cow, error::Error, fmt::Display};

use rand::seq::SliceRandom;

Expand Down Expand Up @@ -74,6 +74,20 @@ pub enum AnyError {
ZeroItems,
}

impl Display for AnyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::ZeroItems => "expected at least one element to choose from",
}
)
}
}

impl Error for AnyError {}

/// Selects any of nested items with equal probabilities
#[derive(Clone, Debug)]
pub struct Any(Vec<Box<dyn Tag>>);
Expand Down Expand Up @@ -114,7 +128,7 @@ impl Display for WeightsError {
f,
"{}",
match self {
Self::NonPositiveTotalWeights => "Weights must add up to a positive number",
Self::NonPositiveTotalWeights => "weights must add up to a positive number",
}
)
}
Expand Down

0 comments on commit 9d28890

Please sign in to comment.