Skip to content

Commit

Permalink
Remove map deserialize for page ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
reknih committed Oct 10, 2024
1 parent 67be8d9 commit e2f97a5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 90 deletions.
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
73 changes: 38 additions & 35 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,40 @@ macro_rules! deserialize_from_str {
};
}

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)
}
}
};
}

/// Use the [`FromStr`] implementation of a type for deserialization if it is a
/// string.
macro_rules! derive_or_from_str {
Expand All @@ -74,7 +108,9 @@ macro_rules! derive_or_from_str {
)*
}

derive_or_from_str!(@deser_impl $s where $expect,

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};
Expand All @@ -93,42 +129,9 @@ macro_rules! derive_or_from_str {
}
);
};

(@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::{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()))
}

$visit_map

$($additional_visitors)*
}

deserializer.deserialize_any(OurVisitor)
}
}
};
}

use custom_deserialize;
use derive_or_from_str;
use deserialize_from_str;
use serialize_display;
Expand Down
72 changes: 19 additions & 53 deletions src/types/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{

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

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

Expand All @@ -28,30 +28,18 @@ 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_i32<E>(self, v: i32) -> Result<Self::Value, E>
where E: serde::de::Error, {
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>(self, v: u32) -> Result<Self::Value, E>
where E: serde::de::Error, {
}
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>(self, v: i64) -> Result<Self::Value, E>
where E: serde::de::Error, {
}
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>(self, v: u64) -> Result<Self::Value, E>
where E: serde::de::Error, {
}
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"))
}
);
Expand Down Expand Up @@ -173,40 +161,18 @@ 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_i32<E>(self, v: i32) -> Result<Self::Value, E>
where E: serde::de::Error, {
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>(self, v: u32) -> Result<Self::Value, E>
where E: serde::de::Error, {
}
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>(self, v: i64) -> Result<Self::Value, E>
where E: serde::de::Error, {
}
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>(self, v: u64) -> Result<Self::Value, E>
where E: serde::de::Error, {
}
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"))
}
);
Expand Down

0 comments on commit e2f97a5

Please sign in to comment.