Skip to content

Commit

Permalink
Fix #227
Browse files Browse the repository at this point in the history
With the new page range type, `Deserialize` only returned `Ok` if the value was a string. This PR adds conversions for i32, u32, and u64 values which YAML can easily yield
for single pages
  • Loading branch information
reknih committed Oct 9, 2024
1 parent a1a27dc commit bcf084c
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 23 deletions.
70 changes: 50 additions & 20 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ macro_rules! deserialize_from_str {
D: serde::Deserializer<'de>,
{
let s = <&'de str>::deserialize(deserializer)?;
FromStr::from_str(s).map_err(de::Error::custom)
FromStr::from_str(s).map_err(serde::de::Error::custom)
}
}
};
Expand Down Expand Up @@ -74,18 +74,38 @@ macro_rules! derive_or_from_str {
)*
}

derive_or_from_str!(@deser_impl $s where $expect,
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where A: serde::de::MapAccess<'de>, {
use serde::{de, Deserialize};

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Inner {
$(
$(#[serde $serde])*
$i: $t,
)*
}

impl<'de> Deserialize<'de> for $s {
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
.map(|inner: Inner| $s { $($i: inner.$i),* })
}
);
};

(@deser_impl $type_name:ident where $expect:literal, $visit_map:item $(, $additional_visitors:item)*) => {
impl<'de> Deserialize<'de> for $type_name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::fmt;
use serde::de::{self, Visitor};
use serde::de::{Visitor};
struct OurVisitor;

impl<'de> Visitor<'de> for OurVisitor {
type Value = $s;
type Value = $type_name;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str($expect)
Expand All @@ -98,22 +118,9 @@ macro_rules! derive_or_from_str {
Self::Value::from_str(value).map_err(|e| E::custom(e.to_string()))
}

fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Inner {
$(
$(#[serde $serde])*
$i: $t,
)*
}

Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
.map(|inner: Inner| $s { $($i: inner.$i),* })
}
$visit_map

$($additional_visitors)*
}

deserializer.deserialize_any(OurVisitor)
Expand Down Expand Up @@ -595,4 +602,27 @@ mod tests {
assert!(Numeric::from_str("second").is_err());
assert!(Numeric::from_str("2nd edition").is_err());
}

#[test]
#[cfg(feature = "biblatex")]
fn test_issue_227() {
let yaml = r#"
AAAnonymous_AventureMortevielle_1987:
type: Book
page-range: 100"#;

let library = crate::io::from_yaml_str(yaml).unwrap();
let entry = library.get("AAAnonymous_AventureMortevielle_1987").unwrap();
assert_eq!(
entry
.page_range
.as_ref()
.unwrap()
.as_typed()
.unwrap()
.first()
.unwrap(),
&Numeric::new(100)
);
}
}
89 changes: 86 additions & 3 deletions src/types/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::{cmp::Ordering, fmt::Display, num::NonZeroUsize, str::FromStr};

use crate::{MaybeTyped, Numeric, NumericError};

use super::{deserialize_from_str, serialize_display};
use serde::{de, Deserialize, Serialize};
use super::{derive_or_from_str, serialize_display};
use serde::{Deserialize, Serialize};
use thiserror::Error;

impl MaybeTyped<PageRanges> {
Expand All @@ -23,6 +23,31 @@ pub struct PageRanges {
pub ranges: Vec<PageRangesPart>,
}

derive_or_from_str!(@deser_impl PageRanges where "pages, page ranges, ampesands, and commas",
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where A: serde::de::MapAccess<'de>, {
use serde::{de, Deserialize};

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Inner {
ranges: Vec<PageRangesPart>,
}

Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
.map(|inner: Inner| PageRanges { ranges: inner.ranges })
}, fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where E: serde::de::Error, {
Ok(PageRanges::from(v))
}, fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where E: serde::de::Error, {
Ok(PageRanges::from(v))
}, fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where E: serde::de::Error, {
Ok(PageRanges::from(v))
}
);

impl PageRanges {
/// Create a new `PageRanges` struct.
pub fn new(ranges: Vec<PageRangesPart>) -> Self {
Expand Down Expand Up @@ -76,6 +101,18 @@ impl PageRanges {
}
}

impl From<i32> for PageRanges {
fn from(value: i32) -> Self {
Self { ranges: vec![value.into()] }
}
}

impl From<u32> for PageRanges {
fn from(value: u32) -> Self {
Self { ranges: vec![value.into()] }
}
}

impl From<u64> for PageRanges {
fn from(value: u64) -> Self {
Self { ranges: vec![value.into()] }
Expand Down Expand Up @@ -116,6 +153,41 @@ pub enum PageRangesPart {
Range(Numeric, Numeric),
}

derive_or_from_str!(@deser_impl PageRangesPart where "a page, a page range, or a separator",
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where A: serde::de::MapAccess<'de>, {
use serde::{de, Deserialize};

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum Inner {
Ampersand,
Comma,
EscapedRange(Numeric, Numeric),
SinglePage(Numeric),
Range(Numeric, Numeric),
}

Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
.map(|inner: Inner| match inner {
Inner::Ampersand => PageRangesPart::Ampersand,
Inner::Comma => PageRangesPart::Comma,
Inner::EscapedRange(s, e) => PageRangesPart::EscapedRange(s, e),
Inner::SinglePage(n) => PageRangesPart::SinglePage(n),
Inner::Range(s, e) => PageRangesPart::Range(s, e),
})
}, fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where E: serde::de::Error, {
Ok(PageRangesPart::from(v))
}, fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where E: serde::de::Error, {
Ok(PageRangesPart::from(v))
}, fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where E: serde::de::Error, {
Ok(PageRangesPart::from(v))
}
);

impl PageRangesPart {
/// The start of a range, if any.
pub fn start(&self) -> Option<&Numeric> {
Expand Down Expand Up @@ -169,6 +241,18 @@ impl PageRangesPart {
}
}

impl From<i32> for PageRangesPart {
fn from(value: i32) -> Self {
Self::SinglePage(value.into())
}
}

impl From<u32> for PageRangesPart {
fn from(value: u32) -> Self {
Self::SinglePage(value.into())
}
}

impl From<u64> for PageRangesPart {
fn from(value: u64) -> Self {
Self::SinglePage((value as u32).into())
Expand Down Expand Up @@ -246,7 +330,6 @@ impl FromStr for PageRangesPart {
}
}

deserialize_from_str!(PageRanges);
serialize_display!(PageRanges);

fn parse_number(s: &str) -> Result<Numeric, NumericError> {
Expand Down

0 comments on commit bcf084c

Please sign in to comment.