use alloc::{
borrow::Cow,
collections::{BTreeMap, BTreeSet},
string::{String, ToString},
vec::Vec,
};
use mediatype::MediaTypeBuf;
use serde::{de::Error, Deserialize, Serialize};
use serde_json::Value;
use super::HeaderValue;
use crate::{jwk::serde_impl::Base64DerCertificate, JsonWebKey, UntypedAdditionalProperties, Uri};
#[derive(Debug)]
#[non_exhaustive]
pub(crate) struct Parameters<T> {
pub(crate) critical_headers: Option<BTreeSet<String>>,
pub(crate) jwk_set_url: Option<HeaderValue<Uri>>,
pub(crate) json_web_key: Option<HeaderValue<JsonWebKey<UntypedAdditionalProperties>>>,
pub(crate) key_id: Option<HeaderValue<String>>,
pub(crate) x509_url: Option<HeaderValue<Uri>>,
pub(crate) x509_certificate_chain: Option<HeaderValue<Vec<Base64DerCertificate>>>,
pub(crate) x509_certificate_sha1_thumbprint: Option<HeaderValue<[u8; 20]>>,
pub(crate) x509_certificate_sha256_thumbprint: Option<HeaderValue<[u8; 32]>>,
pub(crate) typ: Option<HeaderValue<MediaTypeWithMaybeStrippedApplicationTopLevel>>,
pub(crate) content_type: Option<HeaderValue<MediaTypeWithMaybeStrippedApplicationTopLevel>>,
pub(crate) specific: T,
pub(crate) additional: BTreeMap<String, HeaderValue<Value>>,
}
#[derive(Debug)]
pub(crate) struct MediaTypeWithMaybeStrippedApplicationTopLevel(pub(crate) MediaTypeBuf);
impl<'de> Deserialize<'de> for MediaTypeWithMaybeStrippedApplicationTopLevel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw: Cow<'_, str> = Cow::deserialize(deserializer)?;
let corrected = if !raw.contains('/') {
alloc::format!("application/{raw}")
} else {
raw.to_string()
};
let inner = MediaTypeBuf::from_string(corrected).map_err(D::Error::custom)?;
Ok(Self(inner))
}
}
impl Serialize for MediaTypeWithMaybeStrippedApplicationTopLevel {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let correct = self.0.to_string();
if self.0.ty() == mediatype::names::APPLICATION
&& correct.chars().filter(|c| *c == '/').count() == 1
{
let raw = correct.split_once('/').expect("contains one slash").1;
const SHOULD_BE_UPPERCASE: [&str; 2] = ["jwt", "jose"];
Ok(
if SHOULD_BE_UPPERCASE.contains(&raw.to_lowercase().as_str()) {
raw.to_uppercase().serialize(serializer)?
} else {
raw.serialize(serializer)?
},
)
} else {
correct.serialize(serializer)
}
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use mediatype::{
names::{APPLICATION, JWT},
MediaType,
};
use serde::{Deserialize, Serialize};
use super::MediaTypeWithMaybeStrippedApplicationTopLevel;
#[derive(Deserialize, Serialize)]
struct Dummy {
typ: MediaTypeWithMaybeStrippedApplicationTopLevel,
}
#[test]
fn jwt_without_application_roundtrip() {
let payload = r#"{"typ":"JWT"}"#;
let a: Dummy = serde_json::from_str(payload).expect("valid");
assert_eq!(a.typ.0, MediaType::new(APPLICATION, JWT));
let json: String = serde_json::to_string(&a).expect("valid");
assert_eq!(json, payload);
}
}