diff --git a/src/csl/taxonomy.rs b/src/csl/taxonomy.rs index 778e152..de98feb 100644 --- a/src/csl/taxonomy.rs +++ b/src/csl/taxonomy.rs @@ -810,8 +810,15 @@ impl EntryLike for citationberg::json::Item { ) -> Option> { 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::::infallible_from_str(s); diff --git a/src/types/mod.rs b/src/types/mod.rs index e643a8e..437ced6 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -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(deserializer: D) -> Result + 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(self, value: &str) -> Result + 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 { @@ -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(self, map: A) -> Result where A: serde::de::MapAccess<'de>, { use serde::{de, Deserialize}; @@ -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(deserializer: D) -> Result - 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(self, value: &str) -> Result - 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; diff --git a/src/types/page.rs b/src/types/page.rs index 684585b..af792eb 100644 --- a/src/types/page.rs +++ b/src/types/page.rs @@ -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; @@ -28,30 +28,18 @@ pub struct PageRanges { pub ranges: Vec, } -derive_or_from_str!(@deser_impl PageRanges where "pages, page ranges, ampesands, and commas", - fn visit_map(self, map: A) -> Result - where A: serde::de::MapAccess<'de>, { - use serde::{de, Deserialize}; - - #[derive(Deserialize)] - #[serde(rename_all = "kebab-case")] - struct Inner { - ranges: Vec, - } - - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) - .map(|inner: Inner| PageRanges { ranges: inner.ranges }) - }, fn visit_i32(self, v: i32) -> Result - where E: serde::de::Error, { +custom_deserialize!( + PageRanges where "pages, page ranges, ampersands, and commas" + fn visit_i32(self, v: i32) -> Result { Ok(PageRanges::from(v)) - }, fn visit_u32(self, v: u32) -> Result - where E: serde::de::Error, { + } + fn visit_u32(self, v: u32) -> Result { PageRanges::try_from(v).map_err(|_| E::custom("value too large")) - }, fn visit_i64(self, v: i64) -> Result - where E: serde::de::Error, { + } + fn visit_i64(self, v: i64) -> Result { PageRanges::try_from(v).map_err(|_| E::custom("value out of bounds")) - }, fn visit_u64(self, v: u64) -> Result - where E: serde::de::Error, { + } + fn visit_u64(self, v: u64) -> Result { PageRanges::try_from(v).map_err(|_| E::custom("value too large")) } ); @@ -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(self, map: A) -> Result - 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(self, v: i32) -> Result - where E: serde::de::Error, { +custom_deserialize!( + PageRangesPart where "a page, a page range, or a separator" + fn visit_i32(self, v: i32) -> Result { Ok(PageRangesPart::from(v)) - }, fn visit_u32(self, v: u32) -> Result - where E: serde::de::Error, { + } + fn visit_u32(self, v: u32) -> Result { PageRangesPart::try_from(v).map_err(|_| E::custom("value too large")) - }, fn visit_i64(self, v: i64) -> Result - where E: serde::de::Error, { + } + fn visit_i64(self, v: i64) -> Result { PageRangesPart::try_from(v).map_err(|_| E::custom("value out of bounds")) - }, fn visit_u64(self, v: u64) -> Result - where E: serde::de::Error, { + } + fn visit_u64(self, v: u64) -> Result { PageRangesPart::try_from(v).map_err(|_| E::custom("value too large")) } );