use std::fmt::Display;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::serde_parts::{leaf, Expected};
use crate::HeaderParse;
trait Text {
fn text(&self) -> impl Display + '_;
}
fn serialize<T: Text, S: Serializer>(value: &T, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(&value.text())
}
fn parse<T: HeaderParse, E: serde::de::Error>(text: &str) -> Result<T, E> {
T::parse(text).map_err(E::custom)
}
fn deserialize<'de, T: HeaderParse, D: Deserializer<'de>>(
deserializer: D,
what: &str,
) -> Result<T, D::Error> {
parse(&leaf(
deserializer,
what,
String::TEXT,
String::deserialize,
)?)
}
struct AsText<'a, T>(&'a T);
impl<T: Text> Serialize for AsText<'_, T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serialize(self.0, serializer)
}
}
fn serialize_option<T: Text, S: Serializer>(
value: &Option<T>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value {
Some(v) => serializer.serialize_some(&AsText(v)),
None => serializer.serialize_none(),
}
}
fn deserialize_option<'de, T: HeaderParse, D: Deserializer<'de>>(
deserializer: D,
what: &str,
) -> Result<Option<T>, D::Error> {
leaf(
deserializer,
what,
Option::<String>::TEXT,
Option::<String>::deserialize,
)?
.map(|text| parse(&text))
.transpose()
}
macro_rules! adapter {
($($(#[$doc:meta])* $name:ident => $ty:ident, $text:ident;)*) => {$(
adapter!(@text crate::$ty, $text);
$(#[$doc])*
pub mod $name {
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(value: &crate::$ty, serializer: S) -> Result<S::Ok, S::Error> {
super::serialize(value, serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<crate::$ty, D::Error> {
super::deserialize(deserializer, stringify!($ty))
}
pub mod option {
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(
value: &Option<crate::$ty>,
serializer: S,
) -> Result<S::Ok, S::Error> {
super::super::serialize_option(value, serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<crate::$ty>, D::Error> {
super::super::deserialize_option(deserializer, stringify!($ty))
}
}
}
)*};
(@text $ty:ty, display) => {
impl Text for $ty {
fn text(&self) -> impl Display + '_ {
self
}
}
};
(@text $ty:ty, header_framing) => {
impl Text for $ty {
fn text(&self) -> impl Display + '_ {
self.clone()
.with_framing(crate::DialogFraming::Header)
}
}
};
}
adapter! {
header_addr => SipHeaderAddr, display;
addr_list => SipHeaderAddrList, display;
contact => ContactList, display;
via => SipVia, display;
warning => SipWarning, display;
auth => SipAuthValue, display;
security => SipSecurity, display;
accept => SipAccept, display;
accept_encoding => SipAcceptEncoding, display;
accept_language => SipAcceptLanguage, display;
uri_info => UriInfo, display;
history_info => HistoryInfo, display;
geolocation => SipGeolocation, display;
replaces => SipReplaces, header_framing;
join => SipJoin, header_framing;
reason => SipReason, display;
reason_list => SipReasonList, display;
target_dialog => SipTargetDialog, header_framing;
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use crate::{DialogFraming, HeaderParse, SipTargetDialog, UriHeaderParse};
#[derive(Debug, Serialize, Deserialize)]
struct Holder {
#[serde(with = "super::target_dialog::option")]
target: Option<SipTargetDialog>,
}
#[test]
fn failed_read_reports_the_error_not_the_text() {
let err =
serde_json::from_str::<Holder>(r#"{"target": "secret;local-tag=t"}"#).unwrap_err();
assert!(!err
.to_string()
.contains("secret"));
}
#[test]
fn dialog_id_round_trips_in_header_framing() {
let t = SipTargetDialog::parse("a@example.com;local-tag=l;remote-tag=r").unwrap();
let json = serde_json::to_string(&Holder {
target: Some(t.clone()),
})
.unwrap();
let back: Holder = serde_json::from_str(&json).unwrap();
assert_eq!(back.target, Some(t));
}
#[test]
fn uri_header_framed_value_is_written_in_header_framing() {
let t =
SipTargetDialog::parse_uri_header("a%40example.com%3Blocal-tag%3Dl%3Bremote-tag%3Dr")
.unwrap();
let json = serde_json::to_string(&Holder {
target: Some(t.clone()),
})
.unwrap();
assert_eq!(
json,
r#"{"target":"a@example.com;local-tag=l;remote-tag=r"}"#
);
let back: Holder = serde_json::from_str(&json).unwrap();
assert_eq!(back.target, Some(t.with_framing(DialogFraming::Header)));
}
}