use serde::{Deserialize, Deserializer};
use serde_json::Value;
use crate::jwt_config::{normalize_status_list_refresh_interval_max, MIN_JWKS_REFRESH_SECS};
pub(super) fn parse_option_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let value: Option<String> = deserialize_or_parse_string_as_json(deserializer)?;
Ok(value.filter(|s| !s.is_empty()))
}
fn to_json(s: &str) -> Option<Value> {
let mut json_string = s.trim().to_string();
if json_string.starts_with('[') && json_string.ends_with(']') {
let json_like = json_string.replace('\'', "\""); json_string = json_like;
}
serde_json::from_str::<Value>(json_string.as_str()).ok()
}
pub(super) fn deserialize_or_parse_string_as_json<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
let value = Value::deserialize(deserializer)?;
if let Value::String(s) = &value
&& let Some(parsed_value) = to_json(s)
&& let Ok(result) = T::deserialize(parsed_value)
{
return Ok(result);
}
T::deserialize(value).map_err(serde::de::Error::custom)
}
pub(super) fn deserialize_jwks_refresh_interval<'de, D>(
deserializer: D,
) -> Result<Option<u64>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: Option<u64> = deserialize_or_parse_string_as_json(deserializer)?;
Ok(value.map(|v| v.max(MIN_JWKS_REFRESH_SECS)))
}
pub(super) fn deserialize_status_list_refresh_interval_max<'de, D>(
deserializer: D,
) -> Result<u64, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: Option<u64> = deserialize_or_parse_string_as_json(deserializer)?;
Ok(normalize_status_list_refresh_interval_max(
value.unwrap_or(0),
))
}
pub(super) fn deserialize_jwks_refresh_min_interval<'de, D>(
deserializer: D,
) -> Result<u64, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: u64 = deserialize_or_parse_string_as_json(deserializer)?;
Ok(value.max(MIN_JWKS_REFRESH_SECS))
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
use test_utils::assert_eq;
#[derive(Debug, Deserialize, PartialEq)]
struct TestStruct {
#[serde(deserialize_with = "deserialize_or_parse_string_as_json", default)]
value: i32,
#[serde(deserialize_with = "deserialize_or_parse_string_as_json", default)]
optional: Option<String>,
#[serde(deserialize_with = "deserialize_or_parse_string_as_json", default)]
vector_int: Vec<i32>,
#[serde(deserialize_with = "deserialize_or_parse_string_as_json", default)]
vector_str: Vec<String>,
}
#[derive(Debug, Deserialize, PartialEq)]
struct ComplexTestStruct {
#[serde(deserialize_with = "deserialize_or_parse_string_as_json", default)]
boolean: bool,
#[serde(deserialize_with = "deserialize_or_parse_string_as_json", default)]
float: f64,
#[serde(deserialize_with = "deserialize_or_parse_string_as_json", default)]
nested: Option<TestStruct>,
}
#[test]
fn test_fallback_deserialize_basic_types() {
let test_cases = vec![
(
r#"{"value": 42, "optional": "test", "vector_int": [1, 2, 3]}"#,
TestStruct {
value: 42,
optional: Some("test".to_string()),
vector_int: vec![1, 2, 3],
vector_str: vec![],
},
),
(
r#"{"value": "42", "optional": null, "vector_int": [4, 5, 6]}"#,
TestStruct {
value: 42,
optional: None,
vector_int: vec![4, 5, 6],
vector_str: vec![],
},
),
(
r#"{"value": 42, "optional": "null", "vector_int": [4, 5, 6]}"#,
TestStruct {
value: 42,
optional: None,
vector_int: vec![4, 5, 6],
vector_str: vec![],
},
),
(
r#"{"value": 42, "optional": null, "vector_int": "[4, 5, 6]"}"#,
TestStruct {
value: 42,
optional: None,
vector_int: vec![4, 5, 6],
vector_str: vec![],
},
),
(
r#"{"value": 42, "vector_int": []}"#,
TestStruct {
value: 42,
optional: None,
vector_int: vec![],
vector_str: vec![],
},
),
(
r#"{"value": 42, "optional": "test", "vector_int": []}"#,
TestStruct {
value: 42,
optional: Some("test".to_string()),
vector_int: vec![],
vector_str: vec![],
},
),
];
for (json, expected) in test_cases {
let result: TestStruct = serde_json::from_str(json).unwrap();
assert_eq!(result, expected);
}
}
#[test]
fn test_fallback_deserialize_string_vectors() {
let test_cases = vec![
(
r#"{"value": 42, "optional": null, "vector_str": ["a", "b", "c"]}"#,
vec!["a".to_string(), "b".to_string(), "c".to_string()],
),
(
r#"{"value": 42, "optional": null, "vector_str": "[\"a\", \"b\", \"c\"]"}"#,
vec!["a".to_string(), "b".to_string(), "c".to_string()],
),
(
r#"{"vector_str": "['sub', 'email']"}"#,
vec!["sub".to_string(), "email".to_string()],
),
(
r#"{"vector_str": "['sub', 'email', 'username']"}"#,
vec![
"sub".to_string(),
"email".to_string(),
"username".to_string(),
],
),
(
r#"{"value": 42, "optional": null, "vector_str": []}"#,
vec![],
),
];
for (json, expected_vec) in test_cases {
let result: TestStruct = serde_json::from_str(json).unwrap();
assert_eq!(result.vector_str, expected_vec);
}
}
#[test]
#[allow(clippy::approx_constant)]
fn test_fallback_deserialize_complex_types() {
let test_cases = vec![
(
r#"{"boolean": "true", "float": 3.14, "nested": null}"#,
ComplexTestStruct {
boolean: true,
float: 3.14,
nested: None,
},
),
(
r#"{"boolean": false, "float": "3.14", "nested": null}"#,
ComplexTestStruct {
boolean: false,
float: 3.14,
nested: None,
},
),
(
r#"{
"boolean": true,
"float": 1.23,
"nested": "{\"value\":42,\"optional\":\"test\",\"vector_int\":[1,2,3]}"
}"#,
ComplexTestStruct {
boolean: true,
float: 1.23,
nested: Some(TestStruct {
value: 42,
optional: Some("test".to_string()),
vector_int: vec![1, 2, 3],
vector_str: vec![],
}),
},
),
];
for (json, expected) in test_cases {
let result: ComplexTestStruct = serde_json::from_str(json).unwrap();
assert_eq!(result, expected);
}
}
#[test]
fn test_fallback_deserialize_error_cases() {
let test_cases = vec![
(
r#"{"value": "not a number", "optional": null, "vector_int": [], "vector_str": []}"#,
"non-numeric string as i32",
),
(
r#"{"value": 42, "optional": null, "vector_int": [], "vector_str": "invalid"}"#,
"invalid string as Vec<String>",
),
(
r#"{"value": 42, "optional": null, "vector_int": [], "vector_str": "[sub, email]"}"#,
"invalid Python-style list",
),
(
r#"{"value": 42, "optional": null, "vector_int": "invalid", "vector_str": []}"#,
"invalid string as Vec<i32>",
),
];
for (json, error_desc) in test_cases {
let result: Result<TestStruct, _> = serde_json::from_str(json);
result.expect_err(&format!("should fail to parse {error_desc}"));
}
}
}