gsm_core/messaging_card/
spec.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::messaging_card::ir::MessageCardIr;
5use crate::messaging_card::types::{MessageCard, OauthCard, OauthPrompt, OauthProvider};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum RenderIntent {
10    Card,
11    Auth,
12}
13
14#[derive(Debug, Clone, PartialEq)]
15pub enum RenderSpec {
16    Card(Box<MessageCardIr>),
17    Auth(AuthRenderSpec),
18}
19
20impl RenderSpec {
21    pub fn intent(&self) -> RenderIntent {
22        match self {
23            RenderSpec::Card(_) => RenderIntent::Card,
24            RenderSpec::Auth(_) => RenderIntent::Auth,
25        }
26    }
27
28    pub fn as_card(&self) -> Option<&MessageCardIr> {
29        match self {
30            RenderSpec::Card(ir) => Some(ir.as_ref()),
31            _ => None,
32        }
33    }
34
35    pub fn as_auth(&self) -> Option<&AuthRenderSpec> {
36        match self {
37            RenderSpec::Auth(spec) => Some(spec),
38            _ => None,
39        }
40    }
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct AuthRenderSpec {
45    pub provider: OauthProvider,
46    pub scopes: Vec<String>,
47    pub resource: Option<String>,
48    pub prompt: Option<OauthPrompt>,
49    pub metadata: Option<Value>,
50    pub start_url: Option<String>,
51    pub connection_name: Option<String>,
52    pub fallback_button: FallbackButton,
53}
54
55impl AuthRenderSpec {
56    pub fn from_card(card: &MessageCard, oauth: &OauthCard) -> Self {
57        let fallback_title = card
58            .title
59            .clone()
60            .unwrap_or_else(|| format!("Sign in with {}", oauth.provider.display_name()));
61        let fallback_button = FallbackButton {
62            title: fallback_title,
63            url: oauth.start_url.clone(),
64        };
65        Self {
66            provider: oauth.provider.clone(),
67            scopes: oauth.scopes.clone(),
68            resource: oauth.resource.clone(),
69            prompt: oauth.prompt.clone(),
70            metadata: oauth.metadata.clone(),
71            start_url: oauth.start_url.clone(),
72            connection_name: oauth.connection_name.clone(),
73            fallback_button,
74        }
75    }
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct FallbackButton {
80    pub title: String,
81    pub url: Option<String>,
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::messaging_card::types::{MessageCardKind, OauthPrompt};
88
89    #[test]
90    fn auth_spec_defaults_title() {
91        let oauth = OauthCard {
92            provider: OauthProvider::Microsoft,
93            scopes: vec!["User.Read".into()],
94            resource: None,
95            prompt: Some(OauthPrompt::Consent),
96            start_url: Some("https://auth/start".into()),
97            connection_name: Some("graph".into()),
98            metadata: None,
99        };
100        let card = MessageCard {
101            kind: MessageCardKind::Oauth,
102            title: None,
103            text: None,
104            footer: None,
105            images: Vec::new(),
106            actions: Vec::new(),
107            allow_markdown: true,
108            #[cfg(feature = "adaptive-cards")]
109            adaptive: None,
110            oauth: Some(oauth.clone()),
111        };
112
113        let spec = AuthRenderSpec::from_card(&card, card.oauth.as_ref().unwrap());
114        assert_eq!(spec.provider, OauthProvider::Microsoft);
115        assert_eq!(spec.scopes, vec!["User.Read".to_string()]);
116        assert_eq!(
117            spec.fallback_button.title,
118            "Sign in with Microsoft".to_string()
119        );
120        assert_eq!(
121            spec.fallback_button.url,
122            Some("https://auth/start".to_string())
123        );
124        assert_eq!(spec.connection_name.as_deref(), Some("graph"));
125        assert_eq!(spec.prompt, Some(OauthPrompt::Consent));
126    }
127}