use std::fmt::Display;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, de};
pub fn empty_string_as_none<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: Display,
{
let raw = Option::<String>::deserialize(deserializer)?;
match raw.as_deref().map(str::trim) {
None | Some("") => Ok(None),
Some(value) => T::from_str(value).map(Some).map_err(de::Error::custom),
}
}
#[cfg(feature = "trading")]
#[derive(Debug, Clone)]
pub(crate) enum OneOrMany<T> {
Many(Vec<T>),
One(T),
}
#[cfg(feature = "trading")]
impl<'de, T: Deserialize<'de>> Deserialize<'de> for OneOrMany<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error as _;
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::Array(_) => Vec::<T>::deserialize(value)
.map(Self::Many)
.map_err(D::Error::custom),
other => T::deserialize(other)
.map(Self::One)
.map_err(D::Error::custom),
}
}
}
#[cfg(feature = "trading")]
impl<T> OneOrMany<T> {
pub(crate) fn into_vec(self) -> Vec<T> {
match self {
Self::Many(values) => values,
Self::One(value) => vec![value],
}
}
}
pub mod int {
use std::fmt;
use serde::de::{self, Unexpected, Visitor};
use serde::{Deserializer, Serializer};
struct IntVisitor;
impl Visitor<'_> for IntVisitor {
type Value = i64;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an integer as a number or string")
}
fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
Ok(value)
}
fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
i64::try_from(value).map_err(|_| E::invalid_value(Unexpected::Unsigned(value), &self))
}
fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
if value.fract() == 0.0 {
#[allow(clippy::cast_possible_truncation)]
return Ok(value as i64);
}
Err(E::invalid_value(Unexpected::Float(value), &self))
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
value
.trim()
.parse()
.map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<i64, D::Error> {
deserializer.deserialize_any(IntVisitor)
}
pub fn serialize<S: Serializer>(value: &i64, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_i64(*value)
}
pub mod option {
use super::{Deserializer, IntVisitor, Serializer, Visitor, de, fmt};
struct OptionVisitor;
impl<'de> Visitor<'de> for OptionVisitor {
type Value = Option<i64>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an integer as a number or string, or null")
}
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_some<D: Deserializer<'de>>(
self,
deserializer: D,
) -> Result<Self::Value, D::Error> {
struct MaybeEmpty;
impl Visitor<'_> for MaybeEmpty {
type Value = Option<i64>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an integer as a number or string")
}
fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
IntVisitor.visit_i64(value).map(Some)
}
fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
IntVisitor.visit_u64(value).map(Some)
}
fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
IntVisitor.visit_f64(value).map(Some)
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
if value.trim().is_empty() {
return Ok(None);
}
IntVisitor.visit_str(value).map(Some)
}
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
}
deserializer.deserialize_any(MaybeEmpty)
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<i64>, D::Error> {
deserializer.deserialize_option(OptionVisitor)
}
pub fn serialize<S: Serializer>(
value: &Option<i64>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value {
Some(int) => serializer.serialize_i64(*int),
None => serializer.serialize_none(),
}
}
}
}
pub fn null_as_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
}
pub fn comma_separated<S, T>(values: &Option<Vec<T>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: Display,
{
match values {
Some(values) => comma_joined(values, serializer),
None => serializer.serialize_none(),
}
}
pub fn comma_separated_required<S, T>(values: &[T], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: Display,
{
comma_joined(values, serializer)
}
fn comma_joined<S, T>(values: &[T], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: Display,
{
let joined = values
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(",");
serializer.serialize_str(&joined)
}
pub fn string_or_list<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
where
D: Deserializer<'de>,
{
struct StringOrList;
impl<'de> serde::de::Visitor<'de> for StringOrList {
type Value = Option<Vec<String>>;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a string, a list of strings, or null")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
Ok(Some(vec![value.to_owned()]))
}
fn visit_string<E: de::Error>(self, value: String) -> Result<Self::Value, E> {
Ok(Some(vec![value]))
}
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_unit<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_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut values = Vec::new();
while let Some(value) = seq.next_element::<String>()? {
values.push(value);
}
Ok(Some(values))
}
}
deserializer.deserialize_option(StringOrList)
}
#[cfg(all(test, feature = "trading"))]
mod tests {
use serde::Deserialize;
use uuid::Uuid;
use crate::trading::{AssetClass, OrderSide};
#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Leg {
#[serde(default, deserialize_with = "super::empty_string_as_none")]
asset_id: Option<Uuid>,
#[serde(default, deserialize_with = "super::empty_string_as_none")]
symbol: Option<String>,
#[serde(default, deserialize_with = "super::empty_string_as_none")]
asset_class: Option<AssetClass>,
#[serde(default, deserialize_with = "super::empty_string_as_none")]
side: Option<OrderSide>,
}
#[test]
fn empty_strings_from_multi_leg_orders_become_none() {
let leg: Leg =
serde_json::from_str(r#"{"asset_id":"","symbol":"","asset_class":"","side":""}"#)
.unwrap();
assert_eq!(
leg,
Leg {
asset_id: None,
symbol: None,
asset_class: None,
side: None,
}
);
}
#[test]
fn populated_values_still_parse() {
let leg: Leg = serde_json::from_str(
r#"{
"asset_id":"b0b6dd9d-8b9b-48a9-ba46-b9d54906e415",
"symbol":"AAPL",
"asset_class":"us_equity",
"side":"buy"
}"#,
)
.unwrap();
assert_eq!(leg.symbol.as_deref(), Some("AAPL"));
assert_eq!(leg.asset_class, Some(AssetClass::UsEquity));
assert_eq!(leg.side, Some(OrderSide::Buy));
assert!(leg.asset_id.is_some());
}
#[test]
fn null_and_missing_are_also_none() {
let leg: Leg = serde_json::from_str(r#"{"asset_id":null}"#).unwrap();
assert_eq!(leg.asset_id, None);
assert_eq!(leg.symbol, None);
}
#[test]
fn whitespace_only_counts_as_empty() {
let leg: Leg = serde_json::from_str(r#"{"symbol":" "}"#).unwrap();
assert_eq!(leg.symbol, None);
}
#[test]
fn a_malformed_uuid_is_still_an_error() {
let err = serde_json::from_str::<Leg>(r#"{"asset_id":"not-a-uuid"}"#).unwrap_err();
assert!(err.to_string().contains("invalid character"), "{err}");
}
#[test]
fn an_unknown_enum_value_survives_rather_than_failing() {
let leg: Leg = serde_json::from_str(r#"{"side":"short_exempt"}"#).unwrap();
assert_eq!(
leg.side,
Some(OrderSide::Unknown("short_exempt".to_owned()))
);
}
}