#[cfg(any(
feature = "bridge",
feature = "clob",
feature = "data",
feature = "gamma",
))]
use {serde::de::DeserializeOwned, serde_json::Value};
#[cfg(any(feature = "clob", feature = "gamma"))]
pub struct StringFromAny;
#[cfg(any(feature = "clob", feature = "gamma"))]
impl<'de> serde_with::DeserializeAs<'de, String> for StringFromAny {
fn deserialize_as<D>(deserializer: D) -> std::result::Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::fmt;
use serde::de::{self, Visitor};
struct StringOrNumberVisitor;
impl Visitor<'_> for StringOrNumberVisitor {
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or integer")
}
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
Ok(v.to_owned())
}
fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
Ok(v)
}
fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
Ok(v.to_string())
}
fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
Ok(v.to_string())
}
}
deserializer.deserialize_any(StringOrNumberVisitor)
}
}
#[cfg(any(feature = "clob", feature = "gamma"))]
impl serde_with::SerializeAs<String> for StringFromAny {
fn serialize_as<S>(source: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(source)
}
}
#[cfg(all(
feature = "tracing",
any(
feature = "bridge",
feature = "clob",
feature = "data",
feature = "gamma"
)
))]
pub fn deserialize_with_warnings<T: DeserializeOwned>(value: Value) -> crate::Result<T> {
use std::any::type_name;
tracing::trace!(
type_name = %type_name::<T>(),
json = %value,
"deserializing JSON"
);
let original = value.clone();
let mut unknown_paths: Vec<String> = Vec::new();
let result: T = serde_ignored::deserialize(value, |path| {
unknown_paths.push(path.to_string());
})
.inspect_err(|_| {
let json_str = original.to_string();
let jd = &mut serde_json::Deserializer::from_str(&json_str);
let path_result: Result<T, _> = serde_path_to_error::deserialize(jd);
if let Err(path_err) = path_result {
let path = path_err.path().to_string();
let inner_error = path_err.inner();
let value_at_path = lookup_value(&original, &path);
let value_display = format_value(value_at_path);
tracing::error!(
type_name = %type_name::<T>(),
path = %path,
value = %value_display,
error = %inner_error,
"deserialization failed"
);
}
})?;
if !unknown_paths.is_empty() {
let type_name = type_name::<T>();
for path in unknown_paths {
let field_value = lookup_value(&original, &path);
let value_display = format_value(field_value);
tracing::warn!(
type_name = %type_name,
field = %path,
value = %value_display,
"unknown field in API response"
);
}
}
Ok(result)
}
#[cfg(all(
not(feature = "tracing"),
any(
feature = "bridge",
feature = "clob",
feature = "data",
feature = "gamma"
)
))]
pub fn deserialize_with_warnings<T: DeserializeOwned>(value: Value) -> crate::Result<T> {
Ok(serde_json::from_value(value)?)
}
#[cfg(feature = "tracing")]
fn lookup_value<'value>(value: &'value Value, path: &str) -> Option<&'value Value> {
if path.is_empty() {
return Some(value);
}
let mut current = value;
let segments = parse_path_segments(path);
for segment in segments {
if segment.is_empty() || segment == "?" {
continue;
}
match current {
Value::Object(map) => {
current = map.get(&segment)?;
}
Value::Array(arr) => {
let index: usize = segment.parse().ok()?;
current = arr.get(index)?;
}
_ => return None,
}
}
Some(current)
}
#[cfg(feature = "tracing")]
fn parse_path_segments(path: &str) -> Vec<String> {
let mut segments = Vec::new();
let mut current = String::new();
let mut chars = path.chars().peekable();
while let Some(ch) = chars.next() {
match ch {
'.' => {
if !current.is_empty() {
segments.push(std::mem::take(&mut current));
}
}
'[' => {
if !current.is_empty() {
segments.push(std::mem::take(&mut current));
}
for inner in chars.by_ref() {
if inner == ']' {
break;
}
current.push(inner);
}
if !current.is_empty() {
segments.push(std::mem::take(&mut current));
}
}
']' => {
}
_ => {
current.push(ch);
}
}
}
if !current.is_empty() {
segments.push(current);
}
segments
}
#[cfg(feature = "tracing")]
fn format_value(value: Option<&Value>) -> String {
match value {
Some(v) => v.to_string(),
None => "<unable to retrieve>".to_owned(),
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "tracing")]
use serde_json::Value;
#[cfg(feature = "tracing")]
use super::{format_value, lookup_value};
#[cfg(any(
feature = "bridge",
feature = "clob",
feature = "data",
feature = "gamma"
))]
mod deserialize_with_warnings_tests {
use serde::Deserialize;
use super::super::deserialize_with_warnings;
#[derive(Debug, Deserialize, PartialEq)]
struct TestStruct {
known_field: String,
#[serde(default)]
optional_field: Option<i32>,
}
#[test]
fn deserialize_known_fields_only() {
let json = serde_json::json!({
"known_field": "value",
"optional_field": 42
});
let result: TestStruct =
deserialize_with_warnings(json).expect("deserialization failed");
assert_eq!(result.known_field, "value");
assert_eq!(result.optional_field, Some(42));
}
#[test]
fn deserialize_with_unknown_fields() {
let json = serde_json::json!({
"known_field": "value",
"unknown_field": "extra",
"another_unknown": 123
});
let result: TestStruct =
deserialize_with_warnings(json).expect("deserialization failed");
assert_eq!(result.known_field, "value");
assert_eq!(result.optional_field, None);
}
#[test]
fn deserialize_missing_required_field_fails() {
let json = serde_json::json!({
"optional_field": 42
});
let result: crate::Result<TestStruct> = deserialize_with_warnings(json);
result.unwrap_err();
}
#[test]
fn deserialize_array() {
let json = serde_json::json!([1, 2, 3]);
let result: Vec<i32> = deserialize_with_warnings(json).expect("deserialization failed");
assert_eq!(result, vec![1, 2, 3]);
}
#[derive(Debug, Deserialize, PartialEq)]
struct NestedStruct {
outer: String,
inner: InnerStruct,
}
#[derive(Debug, Deserialize, PartialEq)]
struct InnerStruct {
value: i32,
}
#[test]
fn deserialize_nested_unknown_fields() {
let json = serde_json::json!({
"outer": "test",
"inner": {
"value": 42,
"nested_unknown": "surprise"
}
});
let result: NestedStruct =
deserialize_with_warnings(json).expect("deserialization failed");
assert_eq!(result.outer, "test");
assert_eq!(result.inner.value, 42);
}
#[cfg(feature = "tracing")]
#[test]
fn warning_is_emitted_for_unknown_fields() {
use std::sync::{Arc, Mutex};
use tracing_subscriber::layer::SubscriberExt as _;
let warnings: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let warnings_clone = Arc::clone(&warnings);
let layer = tracing_subscriber::fmt::layer()
.with_writer(move || {
struct CaptureWriter(Arc<Mutex<Vec<String>>>);
impl std::io::Write for CaptureWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if let Ok(s) = std::str::from_utf8(buf) {
self.0.lock().expect("lock").push(s.to_owned());
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
CaptureWriter(Arc::clone(&warnings_clone))
})
.with_ansi(false);
let subscriber = tracing_subscriber::registry().with(layer);
tracing::subscriber::with_default(subscriber, || {
let json = serde_json::json!({
"known_field": "value",
"secret_new_field": "surprise!",
"another_unknown": 42
});
let result: TestStruct =
deserialize_with_warnings(json).expect("deserialization should succeed");
assert_eq!(result.known_field, "value");
});
let captured = warnings.lock().expect("lock");
let all_output = captured.join("");
assert!(
all_output.contains("unknown field"),
"Expected 'unknown field' in output, got: {all_output}"
);
assert!(
all_output.contains("secret_new_field"),
"Expected 'secret_new_field' in output, got: {all_output}"
);
}
}
#[cfg(any(feature = "clob", feature = "gamma"))]
mod string_from_any_tests {
use serde::Deserialize;
use super::super::StringFromAny;
#[derive(Debug, Deserialize, PartialEq, serde::Serialize)]
struct StringFromAnyStruct {
#[serde(with = "serde_with::As::<StringFromAny>")]
id: String,
}
#[derive(Debug, Deserialize, PartialEq, serde::Serialize)]
struct OptionalStringFromAny {
#[serde(with = "serde_with::As::<Option<StringFromAny>>")]
id: Option<String>,
}
#[test]
fn string_from_any_deserialize_string() {
let json = serde_json::json!({ "id": "hello" });
let result: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, "hello");
}
#[test]
fn string_from_any_deserialize_positive_integer() {
let json = serde_json::json!({ "id": 12345 });
let result: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, "12345");
}
#[test]
fn string_from_any_deserialize_negative_integer() {
let json = serde_json::json!({ "id": -42 });
let result: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, "-42");
}
#[test]
fn string_from_any_deserialize_zero() {
let json = serde_json::json!({ "id": 0 });
let result: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, "0");
}
#[test]
fn string_from_any_deserialize_large_u64() {
let json = serde_json::json!({ "id": u64::MAX });
let result: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, u64::MAX.to_string());
}
#[test]
fn string_from_any_deserialize_large_negative_i64() {
let json = serde_json::json!({ "id": i64::MIN });
let result: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, i64::MIN.to_string());
}
#[test]
fn string_from_any_serialize_back_to_string() {
let obj = StringFromAnyStruct {
id: "12345".to_owned(),
};
let json = serde_json::to_value(&obj).expect("serialization failed");
assert_eq!(json, serde_json::json!({ "id": "12345" }));
}
#[test]
fn string_from_any_roundtrip_from_string() {
let json = serde_json::json!({ "id": "hello" });
let obj: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
let back = serde_json::to_value(&obj).expect("serialization failed");
assert_eq!(back, serde_json::json!({ "id": "hello" }));
}
#[test]
fn string_from_any_roundtrip_from_integer() {
let json = serde_json::json!({ "id": 42 });
let obj: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
let back = serde_json::to_value(&obj).expect("serialization failed");
assert_eq!(back, serde_json::json!({ "id": "42" }));
}
#[test]
fn string_from_any_option_some_string() {
let json = serde_json::json!({ "id": "hello" });
let result: OptionalStringFromAny =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, Some("hello".to_owned()));
}
#[test]
fn string_from_any_option_some_integer() {
let json = serde_json::json!({ "id": 123 });
let result: OptionalStringFromAny =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, Some("123".to_owned()));
}
#[test]
fn string_from_any_option_none() {
let json = serde_json::json!({ "id": null });
let result: OptionalStringFromAny =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, None);
}
#[test]
fn string_from_any_option_serialize_some() {
let obj = OptionalStringFromAny {
id: Some("test".to_owned()),
};
let json = serde_json::to_value(&obj).expect("serialization failed");
assert_eq!(json, serde_json::json!({ "id": "test" }));
}
#[test]
fn string_from_any_option_serialize_none() {
let obj = OptionalStringFromAny { id: None };
let json = serde_json::to_value(&obj).expect("serialization failed");
assert_eq!(json, serde_json::json!({ "id": null }));
}
#[test]
fn string_from_any_empty_string() {
let json = serde_json::json!({ "id": "" });
let result: StringFromAnyStruct =
serde_json::from_value(json).expect("deserialization failed");
assert_eq!(result.id, "");
}
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_simple_path() {
let json = serde_json::json!({
"foo": "bar"
});
let result = lookup_value(&json, "foo");
assert_eq!(result, Some(&Value::String("bar".to_owned())));
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_nested_path() {
let json = serde_json::json!({
"outer": {
"inner": "value"
}
});
let result = lookup_value(&json, "outer.inner");
assert_eq!(result, Some(&Value::String("value".to_owned())));
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_array_index() {
let json = serde_json::json!({
"items": ["a", "b", "c"]
});
let result = lookup_value(&json, "items.1");
assert_eq!(result, Some(&Value::String("b".to_owned())));
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_empty_path_returns_root() {
let json = serde_json::json!({"foo": "bar"});
let result = lookup_value(&json, "");
assert_eq!(result, Some(&json));
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_consecutive_dots_handled() {
let json = serde_json::json!({"foo": {"bar": "value"}});
let result = lookup_value(&json, "foo..bar");
assert_eq!(result, Some(&Value::String("value".to_owned())));
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_leading_dot_handled() {
let json = serde_json::json!({"foo": "bar"});
let result = lookup_value(&json, ".foo");
assert_eq!(result, Some(&Value::String("bar".to_owned())));
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_invalid_array_index_returns_none() {
let json = serde_json::json!({"items": [1, 2, 3]});
let result = lookup_value(&json, "items.abc");
assert_eq!(result, None);
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_array_out_of_bounds_returns_none() {
let json = serde_json::json!({"items": [1, 2, 3]});
let result = lookup_value(&json, "items.100");
assert_eq!(result, None);
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_through_primitive_returns_none() {
let json = serde_json::json!({"foo": "bar"});
let result = lookup_value(&json, "foo.baz");
assert_eq!(result, None);
}
#[cfg(feature = "tracing")]
#[test]
fn format_shows_full_string() {
let long_string = "a".repeat(300);
let value = Value::String(long_string.clone());
let formatted = format_value(Some(&value));
assert_eq!(formatted, format!("\"{long_string}\""));
}
#[cfg(feature = "tracing")]
#[test]
fn format_array_shows_full_json() {
let value = serde_json::json!([1, 2, 3, 4, 5]);
let formatted = format_value(Some(&value));
assert_eq!(formatted, "[1,2,3,4,5]");
}
#[cfg(feature = "tracing")]
#[test]
fn format_object_shows_full_json() {
let value = serde_json::json!({"a": 1, "b": 2});
let formatted = format_value(Some(&value));
assert!(formatted.contains("\"a\":1"));
assert!(formatted.contains("\"b\":2"));
}
#[cfg(feature = "tracing")]
#[test]
fn format_none_shows_placeholder() {
let formatted = format_value(None);
assert_eq!(formatted, "<unable to retrieve>");
}
#[cfg(feature = "tracing")]
#[test]
fn lookup_option_marker_skipped() {
let json = serde_json::json!({"outer": {"inner": "value"}});
let result = lookup_value(&json, "?.outer.?.inner");
assert_eq!(result, Some(&Value::String("value".to_owned())));
}
}