seamless_auth/
delivery.rs1use std::future::Future;
2use std::pin::Pin;
3
4use serde_json::Value;
5
6pub type DeliveryError = Box<dyn std::error::Error + Send + Sync>;
8
9pub(crate) type DeliveryFuture = Pin<Box<dyn Future<Output = Result<(), DeliveryError>> + Send>>;
10
11#[derive(Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub struct Delivery {
15 pub kind: String,
17 pub to: String,
19 pub token: Option<String>,
21 pub magic_link_url: Option<String>,
22 pub sign_in_url: Option<String>,
23}
24
25impl std::fmt::Debug for Delivery {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 let redacted = |v: &Option<String>| v.as_ref().map(|_| "[redacted]");
30 f.debug_struct("Delivery")
31 .field("kind", &self.kind)
32 .field("to", &self.to)
33 .field("token", &redacted(&self.token))
34 .field("magic_link_url", &redacted(&self.magic_link_url))
35 .field("sign_in_url", &self.sign_in_url)
36 .finish()
37 }
38}
39
40pub(crate) fn parse_delivery(raw: &Value) -> Result<Delivery, String> {
41 let obj = raw.as_object().ok_or("delivery is not an object")?;
42 let text = |name: &str| {
43 obj.get(name)
44 .and_then(Value::as_str)
45 .filter(|s| !s.is_empty())
46 .map(str::to_string)
47 };
48
49 let delivery = Delivery {
50 kind: text("kind").unwrap_or_default(),
51 to: text("to").unwrap_or_default(),
52 token: match obj.get("token") {
54 Some(Value::String(s)) => Some(s.clone()),
55 Some(Value::Number(n)) => Some(n.to_string()),
56 _ => None,
57 },
58 magic_link_url: text("magicLinkUrl"),
59 sign_in_url: text("signInUrl"),
60 };
61 if delivery.kind.is_empty() || delivery.to.is_empty() {
62 return Err("delivery has no kind or recipient".into());
63 }
64 Ok(delivery)
65}