Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #227 #231

Merged
merged 3 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/csl/taxonomy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -810,8 +810,15 @@ impl EntryLike for citationberg::json::Item {
) -> Option<MaybeTyped<PageRanges>> {
match variable {
PageVariable::Page => match self.0.get("page")? {
csl_json::Value::Number(n) => {
Some(MaybeTyped::Typed(PageRanges::from(*n as u64)))
&csl_json::Value::Number(n) => {
// Page ranges use i32 internally, so we check whether the
// number is in range.
Some(match i32::try_from(n) {
Ok(n) => MaybeTyped::Typed(PageRanges::from(n)),
// If the number is not in range, we degrade to a
// string, which disables some CSL features.
Err(_) => MaybeTyped::String(n.to_string()),
})
}
csl_json::Value::String(s) => {
let res = MaybeTyped::<PageRanges>::infallible_from_str(s);
Expand Down
115 changes: 74 additions & 41 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,41 @@ 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)
}
}
};
}

macro_rules! custom_deserialize {
($type_name:ident where $expect:literal $($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::{Visitor};
struct OurVisitor;

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

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str($expect)
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::from_str(value).map_err(|e| E::custom(e.to_string()))
}

$($additional_visitors)*
}

deserializer.deserialize_any(OurVisitor)
}
}
};
Expand Down Expand Up @@ -75,53 +109,29 @@ macro_rules! derive_or_from_str {
}


impl<'de> Deserialize<'de> for $s {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::fmt;
use serde::de::{self, Visitor};
struct OurVisitor;

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

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str($expect)
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::from_str(value).map_err(|e| E::custom(e.to_string()))
}
crate::types::custom_deserialize!(
$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};

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),* })
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Inner {
$(
$(#[serde $serde])*
$i: $t,
)*
}

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

use custom_deserialize;
use derive_or_from_str;
use deserialize_from_str;
use serialize_display;
Expand Down Expand Up @@ -595,4 +605,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)
);
}
}
4 changes: 3 additions & 1 deletion src/types/numeric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ impl<'de> Deserialize<'de> for Numeric {

/// A default serde fallthrough handler for signed integers.
fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
Ok(Numeric::new(v.try_into().map_err(|_| E::custom("value too large"))?))
Ok(Numeric::new(
v.try_into().map_err(|_| E::custom("value out of bounds"))?,
))
}

fn visit_i32<E: Error>(self, v: i32) -> Result<Self::Value, E> {
Expand Down
105 changes: 96 additions & 9 deletions src/types/page.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use std::{cmp::Ordering, fmt::Display, num::NonZeroUsize, str::FromStr};
use std::{
cmp::Ordering,
fmt::Display,
num::{NonZeroUsize, TryFromIntError},
str::FromStr,
};

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

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

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

custom_deserialize!(
PageRanges where "pages, page ranges, ampersands, and commas"
fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> {
Ok(PageRanges::from(v))
}
fn visit_u32<E: serde::de::Error>(self, v: u32) -> Result<Self::Value, E> {
PageRanges::try_from(v).map_err(|_| E::custom("value too large"))
}
fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
PageRanges::try_from(v).map_err(|_| E::custom("value out of bounds"))
}
fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
PageRanges::try_from(v).map_err(|_| E::custom("value too large"))
}
);

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

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

impl TryFrom<u32> for PageRanges {
type Error = TryFromIntError;

fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(Self { ranges: vec![value.try_into()?] })
}
}

impl TryFrom<i64> for PageRanges {
type Error = TryFromIntError;

fn try_from(value: i64) -> Result<Self, Self::Error> {
Ok(Self { ranges: vec![value.try_into()?] })
}
}

impl TryFrom<u64> for PageRanges {
type Error = TryFromIntError;

fn try_from(value: u64) -> Result<Self, Self::Error> {
Ok(Self { ranges: vec![value.try_into()?] })
}
}

impl Display for PageRanges {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.ranges.iter().try_for_each(|r| r.fmt(f))
Expand Down Expand Up @@ -116,6 +161,22 @@ pub enum PageRangesPart {
Range(Numeric, Numeric),
}

custom_deserialize!(
PageRangesPart where "a page, a page range, or a separator"
fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> {
Ok(PageRangesPart::from(v))
}
fn visit_u32<E: serde::de::Error>(self, v: u32) -> Result<Self::Value, E> {
PageRangesPart::try_from(v).map_err(|_| E::custom("value too large"))
}
fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
PageRangesPart::try_from(v).map_err(|_| E::custom("value out of bounds"))
}
fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
PageRangesPart::try_from(v).map_err(|_| E::custom("value too large"))
}
);

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

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

impl TryFrom<u32> for PageRangesPart {
type Error = TryFromIntError;

fn try_from(value: u32) -> Result<Self, Self::Error> {
let value: i32 = value.try_into()?;
Ok(Self::SinglePage(value.into()))
}
}

impl TryFrom<i64> for PageRangesPart {
type Error = TryFromIntError;

fn try_from(value: i64) -> Result<Self, Self::Error> {
let value: i32 = value.try_into()?;
Ok(Self::SinglePage(value.into()))
}
}

impl TryFrom<u64> for PageRangesPart {
type Error = TryFromIntError;

fn try_from(value: u64) -> Result<Self, Self::Error> {
let value: i32 = value.try_into()?;
Ok(Self::SinglePage(value.into()))
PgBiel marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -246,7 +334,6 @@ impl FromStr for PageRangesPart {
}
}

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

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