use moq_pattern::{Pattern, Patterns};
use serde::{Deserialize, Deserializer, Serialize};
use serde_with::{TimestampSeconds, serde_as};
#[serde_with::skip_serializing_none]
#[serde_as]
#[derive(Serialize, Deserialize, Default)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct Claims {
#[serde(skip_serializing_if = "String::is_empty")]
root: String,
#[serde(deserialize_with = "present")]
put: Option<Prefixes>,
#[serde(deserialize_with = "present")]
get: Option<Prefixes>,
#[serde(deserialize_with = "present")]
publish: Option<Patterns>,
#[serde(deserialize_with = "present")]
subscribe: Option<Patterns>,
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
exp: Option<std::time::SystemTime>,
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
iat: Option<std::time::SystemTime>,
}
impl From<crate::Claims> for Claims {
fn from(claims: crate::Claims) -> Self {
let grants = Grants::encode(claims.publish, claims.subscribe);
Self {
root: claims.root,
put: grants.put.map(Prefixes::Many),
get: grants.get.map(Prefixes::Many),
publish: grants.publish,
subscribe: grants.subscribe,
exp: claims.expires,
iat: claims.issued,
}
}
}
impl TryFrom<Claims> for crate::Claims {
type Error = String;
fn try_from(wire: Claims) -> Result<Self, Self::Error> {
let grants = Grants {
put: wire.put.map(Prefixes::into_vec),
get: wire.get.map(Prefixes::into_vec),
publish: wire.publish,
subscribe: wire.subscribe,
};
let (publish, subscribe) = grants.decode()?;
Ok(Self {
root: wire.root,
publish,
subscribe,
expires: wire.exp,
issued: wire.iat,
})
}
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Default)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct Scope {
#[serde(skip_serializing_if = "String::is_empty")]
root: String,
#[serde(deserialize_with = "present")]
put: Option<Vec<String>>,
#[serde(deserialize_with = "present")]
get: Option<Vec<String>>,
#[serde(deserialize_with = "present")]
publish: Option<Patterns>,
#[serde(deserialize_with = "present")]
subscribe: Option<Patterns>,
}
impl From<crate::Scope> for Scope {
fn from(scope: crate::Scope) -> Self {
let grants = Grants::encode(scope.publish, scope.subscribe);
Self {
root: scope.root,
put: grants.put,
get: grants.get,
publish: grants.publish,
subscribe: grants.subscribe,
}
}
}
impl TryFrom<Scope> for crate::Scope {
type Error = String;
fn try_from(wire: Scope) -> Result<Self, Self::Error> {
let grants = Grants {
put: wire.put,
get: wire.get,
publish: wire.publish,
subscribe: wire.subscribe,
};
let (publish, subscribe) = grants.decode()?;
Ok(Self {
root: wire.root,
publish,
subscribe,
})
}
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Prefixes {
One(String),
Many(Vec<String>),
}
impl Prefixes {
fn into_vec(self) -> Vec<String> {
match self {
Self::One(prefix) => vec![prefix],
Self::Many(prefixes) => prefixes,
}
}
}
fn present<'de, D: Deserializer<'de>, T: Deserialize<'de>>(deserializer: D) -> Result<Option<T>, D::Error> {
T::deserialize(deserializer).map(Some)
}
struct Grants {
put: Option<Vec<String>>,
get: Option<Vec<String>>,
publish: Option<Patterns>,
subscribe: Option<Patterns>,
}
impl Grants {
fn encode(publish: Patterns, subscribe: Patterns) -> Self {
let prefixes = |patterns: &Patterns| -> Option<Vec<String>> {
patterns
.iter()
.map(|pattern| pattern.as_prefix().map(str::to_string))
.collect()
};
let some = |patterns: Patterns| (!patterns.is_empty()).then_some(patterns);
match (prefixes(&publish), prefixes(&subscribe)) {
(Some(put), Some(get)) => Self {
put: (!put.is_empty()).then_some(put),
get: (!get.is_empty()).then_some(get),
publish: None,
subscribe: None,
},
_ => Self {
put: None,
get: None,
publish: some(publish),
subscribe: some(subscribe),
},
}
}
fn decode(self) -> Result<(Patterns, Patterns), String> {
let legacy = self.put.is_some() || self.get.is_some();
if !legacy {
return Ok((self.publish.unwrap_or_default(), self.subscribe.unwrap_or_default()));
}
if self.publish.is_some() || self.subscribe.is_some() {
return Err("mixes the legacy put/get fields with publish/subscribe".to_string());
}
let subtrees = |prefixes: Option<Vec<String>>| -> Result<Patterns, String> {
prefixes
.unwrap_or_default()
.iter()
.map(|prefix| Pattern::subtree(prefix).map_err(|err| format!("legacy prefix {prefix:?}: {err}")))
.collect()
};
Ok((subtrees(self.put)?, subtrees(self.get)?))
}
}