use std::{error::Error, fmt, str::FromStr};
use chrono::{DateTime, NaiveDate};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use serde_json::Number;
use crate::types::{ApiDateTime, Date, InvalidTemporalValue};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FiscalYear {
Integer(u32),
String(FiscalYearString),
}
impl FiscalYear {
pub const fn as_integer(&self) -> Option<u32> {
match self {
Self::Integer(value) => Some(*value),
Self::String(_) => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
Self::Integer(_) => None,
Self::String(value) => Some(value.as_str()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FiscalYearString(String);
impl FiscalYearString {
pub fn new(value: impl Into<String>) -> Result<Self, InvalidFiscalYear> {
let value = value.into();
if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(InvalidFiscalYear);
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for FiscalYearString {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for FiscalYearString {
type Err = InvalidFiscalYear;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidFiscalYear;
impl fmt::Display for InvalidFiscalYear {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("fiscal year must contain only ASCII digits")
}
}
impl Error for InvalidFiscalYear {}
impl Serialize for FiscalYearString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for FiscalYearString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::new(String::deserialize(deserializer)?).map_err(de::Error::custom)
}
}
impl Serialize for FiscalYear {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Integer(value) => serializer.serialize_u32(*value),
Self::String(value) => value.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for FiscalYear {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum WireFiscalYear {
Integer(u32),
String(FiscalYearString),
}
Ok(match WireFiscalYear::deserialize(deserializer)? {
WireFiscalYear::Integer(value) => Self::Integer(value),
WireFiscalYear::String(value) => Self::String(value),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidNumericString;
impl fmt::Display for InvalidNumericString {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("value must be a JSON numeric string")
}
}
impl Error for InvalidNumericString {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NumericString(String);
impl NumericString {
pub fn new(value: impl Into<String>) -> Result<Self, InvalidNumericString> {
let value = value.into();
if value.trim() != value || serde_json::from_str::<Number>(&value).is_err() {
return Err(InvalidNumericString);
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl fmt::Display for NumericString {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for NumericString {
type Err = InvalidNumericString;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
impl Serialize for NumericString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for NumericString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::new(String::deserialize(deserializer)?).map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum NumberOrNumericString {
Number(Number),
String(NumericString),
}
impl NumberOrNumericString {
pub const fn as_number(&self) -> Option<&Number> {
match self {
Self::Number(value) => Some(value),
Self::String(_) => None,
}
}
pub fn as_numeric_string(&self) -> Option<&str> {
match self {
Self::Number(_) => None,
Self::String(value) => Some(value.as_str()),
}
}
}
impl Serialize for NumberOrNumericString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Number(value) => value.serialize(serializer),
Self::String(value) => value.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for NumberOrNumericString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum WireNumber {
Number(Number),
String(NumericString),
}
Ok(match WireNumber::deserialize(deserializer)? {
WireNumber::Number(value) => Self::Number(value),
WireNumber::String(value) => Self::String(value),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PercentString(String);
impl PercentString {
pub fn new(value: impl Into<String>) -> Result<Self, InvalidNumericString> {
let value = value.into();
let Some(number) = value.strip_suffix('%') else {
return Err(InvalidNumericString);
};
NumericString::new(number)?;
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for PercentString {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for PercentString {
type Err = InvalidNumericString;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
impl Serialize for PercentString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for PercentString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::new(String::deserialize(deserializer)?).map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum PercentageValue {
Number(Number),
NumericString(NumericString),
PercentString(PercentString),
}
impl PercentageValue {
pub const fn as_number(&self) -> Option<&Number> {
match self {
Self::Number(value) => Some(value),
Self::NumericString(_) | Self::PercentString(_) => None,
}
}
pub fn as_numeric_string(&self) -> Option<&str> {
match self {
Self::NumericString(value) => Some(value.as_str()),
Self::Number(_) | Self::PercentString(_) => None,
}
}
pub fn as_percent_string(&self) -> Option<&str> {
match self {
Self::PercentString(value) => Some(value.as_str()),
Self::Number(_) | Self::NumericString(_) => None,
}
}
}
impl Serialize for PercentageValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Number(value) => value.serialize(serializer),
Self::NumericString(value) => value.serialize(serializer),
Self::PercentString(value) => value.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for PercentageValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum WirePercentage {
Number(Number),
String(String),
}
match WirePercentage::deserialize(deserializer)? {
WirePercentage::Number(value) => Ok(Self::Number(value)),
WirePercentage::String(value) if value.ends_with('%') => PercentString::new(value)
.map(Self::PercentString)
.map_err(de::Error::custom),
WirePercentage::String(value) => NumericString::new(value)
.map(Self::NumericString)
.map_err(de::Error::custom),
}
}
}
macro_rules! string_bool {
($name:ident, $true_wire:literal, $false_wire:literal, $description:literal) => {
#[doc = $description]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum $name {
True,
False,
}
impl $name {
pub const fn as_bool(self) -> bool {
matches!(self, Self::True)
}
pub const fn as_str(self) -> &'static str {
match self {
Self::True => $true_wire,
Self::False => $false_wire,
}
}
}
impl fmt::Display for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match String::deserialize(deserializer)?.as_str() {
$true_wire => Ok(Self::True),
$false_wire => Ok(Self::False),
other => Err(de::Error::unknown_variant(
other,
&[$true_wire, $false_wire],
)),
}
}
}
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct WireBool(pub bool);
string_bool!(YnFlag, "Y", "N", "A provider `Y`/`N` string flag.");
string_bool!(YesNoFlag, "Yes", "No", "A provider `Yes`/`No` string flag.");
string_bool!(
TrueFalseFlag,
"true",
"false",
"A provider lowercase `true`/`false` string flag."
);
string_bool!(
TitleCaseBoolFlag,
"True",
"False",
"A provider title-case `True`/`False` string flag."
);
pub type DynamicJson = serde_json::Value;
pub type DynamicObject = serde_json::Map<String, DynamicJson>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IsoTimestamp(String);
impl IsoTimestamp {
pub fn parse(value: &str) -> Result<Self, InvalidTemporalValue> {
DateTime::parse_from_rfc3339(value)
.map(|_| Self(value.to_owned()))
.map_err(|_| InvalidTemporalValue::new("RFC 3339 timestamp"))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for IsoTimestamp {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for IsoTimestamp {
type Err = InvalidTemporalValue;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::parse(value)
}
}
impl Serialize for IsoTimestamp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for IsoTimestamp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::parse(&String::deserialize(deserializer)?).map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DateOrDateTime {
Date(Date),
DateTime(ApiDateTime),
}
impl DateOrDateTime {
pub const fn as_date(self) -> Option<Date> {
match self {
Self::Date(value) => Some(value),
Self::DateTime(_) => None,
}
}
pub const fn as_datetime(self) -> Option<ApiDateTime> {
match self {
Self::Date(_) => None,
Self::DateTime(value) => Some(value),
}
}
}
impl fmt::Display for DateOrDateTime {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Date(value) => value.fmt(formatter),
Self::DateTime(value) => value.fmt(formatter),
}
}
}
impl FromStr for DateOrDateTime {
type Err = InvalidTemporalValue;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.len() == 10 {
Date::parse(value).map(Self::Date)
} else if value.len() == 19 {
ApiDateTime::parse(value).map(Self::DateTime)
} else {
Err(InvalidTemporalValue::new(
"YYYY-MM-DD date or YYYY-MM-DD HH:MM:SS datetime",
))
}
}
}
impl Serialize for DateOrDateTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for DateOrDateTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DateOrYear {
Date(Date),
Year(u16),
}
impl DateOrYear {
pub const fn as_date(self) -> Option<Date> {
match self {
Self::Date(value) => Some(value),
Self::Year(_) => None,
}
}
pub const fn as_year(self) -> Option<u16> {
match self {
Self::Date(_) => None,
Self::Year(value) => Some(value),
}
}
}
impl fmt::Display for DateOrYear {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Date(value) => value.fmt(formatter),
Self::Year(value) => write!(formatter, "{value:04}"),
}
}
}
impl FromStr for DateOrYear {
type Err = InvalidTemporalValue;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.len() == 10 {
Date::parse(value).map(Self::Date)
} else if value.len() == 4 && value.bytes().all(|byte| byte.is_ascii_digit()) {
value
.parse()
.map(Self::Year)
.map_err(|_| InvalidTemporalValue::new("YYYY-MM-DD date or YYYY year"))
} else {
Err(InvalidTemporalValue::new("YYYY-MM-DD date or YYYY year"))
}
}
}
impl Serialize for DateOrYear {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for DateOrYear {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UsDate(NaiveDate);
impl UsDate {
pub fn parse(value: &str) -> Result<Self, InvalidTemporalValue> {
const EXPECTED: &str = "MM-DD-YYYY date";
let exact_shape = value.len() == 10
&& value.is_ascii()
&& value.bytes().enumerate().all(|(index, byte)| match index {
2 | 5 => byte == b'-',
_ => byte.is_ascii_digit(),
});
if !exact_shape {
return Err(InvalidTemporalValue::new(EXPECTED));
}
NaiveDate::parse_from_str(value, "%m-%d-%Y")
.map(Self)
.map_err(|_| InvalidTemporalValue::new(EXPECTED))
}
pub const fn into_inner(self) -> NaiveDate {
self.0
}
}
impl fmt::Display for UsDate {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.0.format("%m-%d-%Y"))
}
}
impl FromStr for UsDate {
type Err = InvalidTemporalValue;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::parse(value)
}
}
impl Serialize for UsDate {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for UsDate {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::parse(&String::deserialize(deserializer)?).map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct OpaqueDateText(pub String);
macro_rules! optional_temporal_module {
($module:ident, $type:ty) => {
#[doc(hidden)]
pub mod $module {
use super::*;
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<$type>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<String>::deserialize(deserializer)?;
value
.filter(|value| !value.is_empty())
.map(|value| value.parse().map_err(de::Error::custom))
.transpose()
}
pub fn serialize<S>(value: &Option<$type>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(value) => serializer.collect_str(value),
None => serializer.serialize_none(),
}
}
}
};
}
optional_temporal_module!(empty_or_null_date, Date);
optional_temporal_module!(empty_or_null_date_or_datetime, DateOrDateTime);
#[doc(hidden)]
pub mod empty_date {
use super::*;
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Date>, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
if value.is_empty() {
Ok(None)
} else {
value.parse().map(Some).map_err(de::Error::custom)
}
}
pub fn serialize<S>(value: &Option<Date>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(value) => serializer.collect_str(value),
None => serializer.serialize_str(""),
}
}
}
pub(crate) mod empty_or_null {
use super::*;
use crate::types::StringValueError;
pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr<Err = StringValueError>,
{
match Option::<String>::deserialize(deserializer)? {
None => Ok(None),
Some(value) if value.is_empty() => Ok(None),
Some(value) => value.parse().map(Some).map_err(de::Error::custom),
}
}
}
pub(crate) mod empty_or_null_object {
use super::*;
use std::marker::PhantomData;
const EXPECTED: &str = "a JSON object, an empty string or null";
pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
deserializer.deserialize_any(ObjectVisitor(PhantomData))
}
struct ObjectVisitor<T>(PhantomData<T>);
impl<T> ObjectVisitor<T> {
fn reject<E: de::Error>(self) -> Result<Option<T>, E> {
Err(E::custom(format_args!("expected {EXPECTED}")))
}
}
impl<'de, T: Deserialize<'de>> de::Visitor<'de> for ObjectVisitor<T> {
type Value = Option<T>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(EXPECTED)
}
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_some<D: Deserializer<'de>>(
self,
deserializer: D,
) -> Result<Self::Value, D::Error> {
deserializer.deserialize_any(self)
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
if value.is_empty() {
Ok(None)
} else {
self.reject()
}
}
fn visit_bool<E: de::Error>(self, _: bool) -> Result<Self::Value, E> {
self.reject()
}
fn visit_i64<E: de::Error>(self, _: i64) -> Result<Self::Value, E> {
self.reject()
}
fn visit_u64<E: de::Error>(self, _: u64) -> Result<Self::Value, E> {
self.reject()
}
fn visit_f64<E: de::Error>(self, _: f64) -> Result<Self::Value, E> {
self.reject()
}
fn visit_seq<A: de::SeqAccess<'de>>(self, _: A) -> Result<Self::Value, A::Error> {
self.reject()
}
fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
T::deserialize(de::value::MapAccessDeserializer::new(map)).map(Some)
}
}
}
pub(crate) mod null_text {
use super::*;
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Option<NumericString>, D::Error>
where
D: Deserializer<'de>,
{
match Option::<String>::deserialize(deserializer)? {
None => Ok(None),
Some(value) if value == "NULL" => Ok(None),
Some(value) => NumericString::new(value)
.map(Some)
.map_err(de::Error::custom),
}
}
}
pub(crate) mod integral_f64 {
use super::*;
pub(crate) fn serialize<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if value.is_finite() && value.fract() == 0.0 {
if *value >= 0.0 && *value < u64::MAX as f64 {
return serializer.serialize_u64(*value as u64);
}
if *value < 0.0 && *value >= i64::MIN as f64 {
return serializer.serialize_i64(*value as i64);
}
}
serializer.serialize_f64(*value)
}
pub(crate) fn serialize_option<S>(value: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(value) => serialize(value, serializer),
None => serializer.serialize_none(),
}
}
}
pub(crate) mod count {
use super::*;
const EXPECTED: &str = "a count must be a non-negative integral JSON number below 2^64";
fn from_number<E: de::Error>(number: &Number) -> Result<u64, E> {
if let Some(value) = number.as_u64() {
return Ok(value);
}
match number.as_f64() {
Some(value)
if value.is_finite()
&& value.fract() == 0.0
&& value >= 0.0
&& value < u64::MAX as f64 =>
{
Ok(value as u64)
}
_ => Err(E::custom(EXPECTED)),
}
}
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
from_number(&Number::deserialize(deserializer)?)
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
Option::<Number>::deserialize(deserializer)?
.map(|number| from_number(&number))
.transpose()
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use crate::types::{Count, CountryCode, Ticker, Volume};
#[test]
fn empty_or_null_maps_empty_and_null_to_none_and_parses_the_rest() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Row {
#[serde(deserialize_with = "super::empty_or_null::deserialize")]
removed_ticker: Option<Ticker>,
#[serde(deserialize_with = "super::empty_or_null::deserialize")]
country_code: Option<CountryCode>,
}
let decode = |text: &str| serde_json::from_str::<Row>(text);
let from_value =
|text: &str| serde_json::from_value::<Row>(serde_json::from_str(text).unwrap());
for text in [
r#"{"removed_ticker":"","country_code":null}"#,
r#"{"removed_ticker":null,"country_code":""}"#,
] {
let row = decode(text).unwrap();
assert_eq!(row.removed_ticker, None);
assert_eq!(row.country_code, None);
assert_eq!(from_value(text).unwrap(), row);
}
let row = decode(r#"{"removed_ticker":"BRK.B","country_code":"US"}"#).unwrap();
assert_eq!(row.removed_ticker, Some(Ticker::new("BRK.B").unwrap()));
assert_eq!(row.country_code, Some(CountryCode::new("US").unwrap()));
assert_eq!(
serde_json::to_string(&row).unwrap(),
r#"{"removed_ticker":"BRK.B","country_code":"US"}"#
);
assert_eq!(
serde_json::to_string(&Row {
removed_ticker: None,
country_code: None,
})
.unwrap(),
r#"{"removed_ticker":null,"country_code":null}"#
);
for (text, secret) in [
(
r#"{"removed_ticker":"SECRET,X","country_code":"US"}"#,
"SECRET",
),
(r#"{"removed_ticker":" ","country_code":"US"}"#, " "),
(
r#"{"removed_ticker":"A","country_code":"SECRET\u0007"}"#,
"SECRET",
),
] {
let error = decode(text).unwrap_err().to_string();
assert!(from_value(text).is_err(), "from_value {text}");
assert!(
!error.contains(secret),
"the error must not echo the value: {error}"
);
}
assert!(decode(r#"{"removed_ticker":"A","country_code":3}"#).is_err());
let missing = decode(r#"{"removed_ticker":"A"}"#).unwrap_err().to_string();
assert!(
missing.contains("missing field `country_code`"),
"{missing}"
);
}
#[test]
fn empty_or_null_object_maps_empty_and_null_to_none_and_decodes_objects() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Range {
min: i64,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Row {
#[serde(deserialize_with = "super::empty_or_null_object::deserialize")]
range: Option<Range>,
}
let decode = |text: &str| serde_json::from_str::<Row>(text);
let from_value =
|text: &str| serde_json::from_value::<Row>(serde_json::from_str(text).unwrap());
for text in [r#"{"range":""}"#, r#"{"range":null}"#] {
assert_eq!(decode(text).unwrap(), Row { range: None });
assert_eq!(from_value(text).unwrap(), Row { range: None });
}
let row = decode(r#"{"range":{"min":-10}}"#).unwrap();
assert_eq!(
row,
Row {
range: Some(Range { min: -10 })
}
);
assert_eq!(from_value(r#"{"range":{"min":-10}}"#).unwrap(), row);
assert_eq!(
serde_json::to_string(&row).unwrap(),
r#"{"range":{"min":-10}}"#
);
assert_eq!(
serde_json::to_string(&Row { range: None }).unwrap(),
r#"{"range":null}"#
);
for (text, secret) in [
(r#"{"range":"SECRET"}"#, "SECRET"),
(r#"{"range":" "}"#, "\" \""),
(r#"{"range":31337}"#, "31337"),
(r#"{"range":["SECRET"]}"#, "SECRET"),
(r#"{"range":true}"#, "true"),
] {
let error = decode(text).unwrap_err().to_string();
assert!(from_value(text).is_err(), "from_value {text}");
assert!(
!error.contains(secret),
"the error must not echo the value: {error}"
);
}
assert!(decode(r#"{"range":{}}"#).is_err());
let missing = decode("{}").unwrap_err().to_string();
assert!(missing.contains("missing field `range`"), "{missing}");
}
#[test]
fn null_text_maps_the_null_text_and_null_to_none_and_parses_the_rest() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Row {
#[serde(deserialize_with = "super::null_text::deserialize")]
org_type: Option<super::NumericString>,
}
let decode = |text: &str| serde_json::from_str::<Row>(text);
let from_value =
|text: &str| serde_json::from_value::<Row>(serde_json::from_str(text).unwrap());
for text in [r#"{"org_type":"NULL"}"#, r#"{"org_type":null}"#] {
assert_eq!(decode(text).unwrap(), Row { org_type: None });
assert_eq!(from_value(text).unwrap(), Row { org_type: None });
}
let row = decode(r#"{"org_type":"30"}"#).unwrap();
assert_eq!(
row.org_type.as_ref().map(|value| value.as_str()),
Some("30")
);
assert_eq!(serde_json::to_string(&row).unwrap(), r#"{"org_type":"30"}"#);
assert_eq!(
serde_json::to_string(&Row { org_type: None }).unwrap(),
r#"{"org_type":null}"#
);
for (text, secret) in [
(r#"{"org_type":"SECRET"}"#, "SECRET"),
(r#"{"org_type":"null"}"#, "null"),
(r#"{"org_type":" NULL"}"#, "NULL"),
(r#"{"org_type":""}"#, "\"\""),
] {
let error = decode(text).unwrap_err().to_string();
assert!(from_value(text).is_err(), "from_value {text}");
assert!(
!error.contains(secret),
"the error must not echo the value: {error}"
);
}
assert!(decode(r#"{"org_type":30}"#).is_err());
let missing = decode("{}").unwrap_err().to_string();
assert!(missing.contains("missing field `org_type`"), "{missing}");
}
#[test]
fn count_accepts_integers_and_integral_floats_on_both_decode_paths() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Row {
#[serde(deserialize_with = "super::count::deserialize")]
buy: Count,
}
let from_text = |text: &str| {
serde_json::from_str::<Row>(&format!(r#"{{"buy":{text}}}"#)).map(|row| row.buy)
};
let from_value = |text: &str| {
let value: serde_json::Value =
serde_json::from_str(&format!(r#"{{"buy":{text}}}"#)).unwrap();
serde_json::from_value::<Row>(value).map(|row| row.buy)
};
for (text, expected) in [
("3", 3),
("3.0", 3),
("0", 0),
("0.0", 0),
("-0.0", 0),
("1e2", 100),
("18446744073709551615", u64::MAX),
("18446744073709549568.0", 18_446_744_073_709_549_568),
] {
assert_eq!(from_text(text).unwrap(), expected, "from_str {text}");
assert_eq!(from_value(text).unwrap(), expected, "from_value {text}");
}
for text in [
"2.9",
"-1",
"-1.0",
"18446744073709551616",
"18446744073709551616.0",
"1e20",
"null",
r#""3""#,
"true",
] {
let error = from_text(text).unwrap_err();
assert!(from_value(text).is_err(), "from_value {text}");
if text.len() > 2 && text.starts_with(['1', '2', '-']) {
assert!(
!error.to_string().contains(text.trim_start_matches('-')),
"the error must not echo the value: {error}"
);
}
}
assert_eq!(
serde_json::to_string(&Row { buy: 3 }).unwrap(),
r#"{"buy":3}"#
);
}
#[test]
fn count_option_accepts_null_and_integral_floats() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Row {
#[serde(default, deserialize_with = "super::count::deserialize_option")]
buy: Option<Count>,
}
let decode = |text: &str| serde_json::from_str::<Row>(text).map(|row| row.buy);
assert_eq!(decode(r#"{"buy":3.0}"#).unwrap(), Some(3));
assert_eq!(decode(r#"{"buy":7}"#).unwrap(), Some(7));
assert_eq!(decode(r#"{"buy":null}"#).unwrap(), None);
assert_eq!(decode("{}").unwrap(), None);
assert!(decode(r#"{"buy":2.9}"#).is_err());
assert!(decode(r#"{"buy":-1}"#).is_err());
assert_eq!(
serde_json::to_string(&Row { buy: Some(3) }).unwrap(),
r#"{"buy":3}"#
);
}
#[test]
fn integral_f64_accepts_any_json_number_and_re_encodes_integral_values_as_integers() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Row {
#[serde(serialize_with = "super::integral_f64::serialize")]
volume: Volume,
}
let encoded = |value: f64| serde_json::to_string(&Row { volume: value }).unwrap();
let documented: Row = serde_json::from_str(r#"{"volume":28718014}"#).unwrap();
let observed: Row = serde_json::from_str(r#"{"volume":20201922.82733}"#).unwrap();
assert_eq!(documented.volume, 28_718_014.0);
assert_eq!(observed.volume, 20_201_922.827_33);
assert_eq!(
serde_json::to_string(&documented).unwrap(),
r#"{"volume":28718014}"#
);
assert_eq!(
serde_json::to_string(&observed).unwrap(),
r#"{"volume":20201922.82733}"#
);
assert_eq!(encoded(0.0), r#"{"volume":0}"#);
assert_eq!(encoded(-0.0), r#"{"volume":0}"#);
assert_eq!(encoded(4_294_967_296.0), r#"{"volume":4294967296}"#);
assert_eq!(encoded(-1.0), r#"{"volume":-1}"#);
assert_eq!(encoded(1.5), r#"{"volume":1.5}"#);
assert_eq!(
encoded(9_007_199_254_740_992.0),
r#"{"volume":9007199254740992}"#,
"2^53 is integral and still emits an integer"
);
assert_eq!(
encoded(18_446_744_073_709_549_568.0),
r#"{"volume":18446744073709549568}"#,
"the largest f64 below 2^64 casts to u64 exactly"
);
assert_eq!(
encoded(i64::MIN as f64),
r#"{"volume":-9223372036854775808}"#,
"-2^63 is the last negative integral value that fits an i64"
);
assert_eq!(encoded(f64::NAN), r#"{"volume":null}"#);
assert_eq!(encoded(f64::INFINITY), r#"{"volume":null}"#);
assert_eq!(encoded(f64::NEG_INFINITY), r#"{"volume":null}"#);
assert_eq!(
serde_json::to_value(Row {
volume: 32_030_003_200.0
})
.unwrap()["volume"]
.as_u64(),
Some(32_030_003_200)
);
let beyond_u64 = serde_json::to_value(Row {
volume: u64::MAX as f64,
})
.unwrap();
assert!(beyond_u64["volume"].is_f64());
assert_eq!(beyond_u64["volume"].as_f64(), Some(u64::MAX as f64));
assert!(serde_json::from_str::<Row>(r#"{"volume":"1"}"#).is_err());
assert!(serde_json::from_str::<Row>(r#"{"volume":null}"#).is_err());
}
#[test]
fn integral_f64_option_writes_null_for_none_and_integers_for_integral_values() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Row {
#[serde(serialize_with = "super::integral_f64::serialize_option")]
market_cap: Option<f64>,
}
let encoded =
|value: Option<f64>| serde_json::to_string(&Row { market_cap: value }).unwrap();
assert_eq!(encoded(None), r#"{"market_cap":null}"#);
assert_eq!(encoded(Some(1e9)), r#"{"market_cap":1000000000}"#);
assert_eq!(encoded(Some(1.5)), r#"{"market_cap":1.5}"#);
assert_eq!(encoded(Some(f64::NAN)), r#"{"market_cap":null}"#);
let exponent: Row = serde_json::from_str(r#"{"market_cap":3.1e12}"#).unwrap();
assert_eq!(exponent.market_cap, Some(3.1e12));
let null: Row = serde_json::from_str(r#"{"market_cap":null}"#).unwrap();
assert_eq!(null.market_cap, None);
}
}