1use std::{collections::HashSet, fmt, hash::Hash, num::NonZeroU32};
12
13use chrono::{DateTime, Duration, Utc};
14use language_tags::LanguageTag;
15use mas_iana::oauth::{OAuthAccessTokenType, OAuthTokenTypeHint};
16use serde::{Deserialize, Serialize};
17use serde_with::{
18    DeserializeFromStr, DisplayFromStr, DurationSeconds, SerializeDisplay, StringWithSeparator,
19    TimestampSeconds, formats::SpaceSeparator, serde_as, skip_serializing_none,
20};
21use url::Url;
22
23use crate::{response_type::ResponseType, scope::Scope};
24
25#[derive(
32    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
33)]
34#[non_exhaustive]
35pub enum ResponseMode {
36    Query,
39
40    Fragment,
43
44    FormPost,
52
53    Unknown(String),
55}
56
57impl core::fmt::Display for ResponseMode {
58    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59        match self {
60            ResponseMode::Query => f.write_str("query"),
61            ResponseMode::Fragment => f.write_str("fragment"),
62            ResponseMode::FormPost => f.write_str("form_post"),
63            ResponseMode::Unknown(s) => f.write_str(s),
64        }
65    }
66}
67
68impl core::str::FromStr for ResponseMode {
69    type Err = core::convert::Infallible;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        match s {
73            "query" => Ok(ResponseMode::Query),
74            "fragment" => Ok(ResponseMode::Fragment),
75            "form_post" => Ok(ResponseMode::FormPost),
76            s => Ok(ResponseMode::Unknown(s.to_owned())),
77        }
78    }
79}
80
81#[derive(
86    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
87)]
88#[non_exhaustive]
89pub enum Display {
90    Page,
95
96    Popup,
99
100    Touch,
103
104    Wap,
107
108    Unknown(String),
110}
111
112impl core::fmt::Display for Display {
113    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
114        match self {
115            Display::Page => f.write_str("page"),
116            Display::Popup => f.write_str("popup"),
117            Display::Touch => f.write_str("touch"),
118            Display::Wap => f.write_str("wap"),
119            Display::Unknown(s) => f.write_str(s),
120        }
121    }
122}
123
124impl core::str::FromStr for Display {
125    type Err = core::convert::Infallible;
126
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        match s {
129            "page" => Ok(Display::Page),
130            "popup" => Ok(Display::Popup),
131            "touch" => Ok(Display::Touch),
132            "wap" => Ok(Display::Wap),
133            s => Ok(Display::Unknown(s.to_owned())),
134        }
135    }
136}
137
138impl Default for Display {
139    fn default() -> Self {
140        Self::Page
141    }
142}
143
144#[derive(
149    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
150)]
151#[non_exhaustive]
152pub enum Prompt {
153    None,
156
157    Login,
160
161    Consent,
164
165    SelectAccount,
172
173    Create,
178
179    Unknown(String),
181}
182
183impl core::fmt::Display for Prompt {
184    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185        match self {
186            Prompt::None => f.write_str("none"),
187            Prompt::Login => f.write_str("login"),
188            Prompt::Consent => f.write_str("consent"),
189            Prompt::SelectAccount => f.write_str("select_account"),
190            Prompt::Create => f.write_str("create"),
191            Prompt::Unknown(s) => f.write_str(s),
192        }
193    }
194}
195
196impl core::str::FromStr for Prompt {
197    type Err = core::convert::Infallible;
198
199    fn from_str(s: &str) -> Result<Self, Self::Err> {
200        match s {
201            "none" => Ok(Prompt::None),
202            "login" => Ok(Prompt::Login),
203            "consent" => Ok(Prompt::Consent),
204            "select_account" => Ok(Prompt::SelectAccount),
205            "create" => Ok(Prompt::Create),
206            s => Ok(Prompt::Unknown(s.to_owned())),
207        }
208    }
209}
210
211#[skip_serializing_none]
215#[serde_as]
216#[derive(Serialize, Deserialize, Clone)]
217pub struct AuthorizationRequest {
218    pub response_type: ResponseType,
221
222    pub client_id: String,
224
225    pub redirect_uri: Option<Url>,
232
233    pub scope: Scope,
237
238    pub state: Option<String>,
241
242    pub response_mode: Option<ResponseMode>,
249
250    pub nonce: Option<String>,
253
254    pub display: Option<Display>,
257
258    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, Prompt>>")]
263    #[serde(default)]
264    pub prompt: Option<Vec<Prompt>>,
265
266    #[serde(default)]
269    #[serde_as(as = "Option<DisplayFromStr>")]
270    pub max_age: Option<NonZeroU32>,
271
272    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, LanguageTag>>")]
274    #[serde(default)]
275    pub ui_locales: Option<Vec<LanguageTag>>,
276
277    pub id_token_hint: Option<String>,
281
282    pub login_hint: Option<String>,
285
286    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
288    #[serde(default)]
289    pub acr_values: Option<HashSet<String>>,
290
291    pub request: Option<String>,
296
297    pub request_uri: Option<Url>,
303
304    pub registration: Option<String>,
309}
310
311impl AuthorizationRequest {
312    #[must_use]
314    pub fn new(response_type: ResponseType, client_id: String, scope: Scope) -> Self {
315        Self {
316            response_type,
317            client_id,
318            redirect_uri: None,
319            scope,
320            state: None,
321            response_mode: None,
322            nonce: None,
323            display: None,
324            prompt: None,
325            max_age: None,
326            ui_locales: None,
327            id_token_hint: None,
328            login_hint: None,
329            acr_values: None,
330            request: None,
331            request_uri: None,
332            registration: None,
333        }
334    }
335}
336
337impl fmt::Debug for AuthorizationRequest {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        f.debug_struct("AuthorizationRequest")
340            .field("response_type", &self.response_type)
341            .field("redirect_uri", &self.redirect_uri)
342            .field("scope", &self.scope)
343            .field("response_mode", &self.response_mode)
344            .field("display", &self.display)
345            .field("prompt", &self.prompt)
346            .field("max_age", &self.max_age)
347            .field("ui_locales", &self.ui_locales)
348            .field("login_hint", &self.login_hint)
349            .field("acr_values", &self.acr_values)
350            .field("request", &self.request)
351            .field("request_uri", &self.request_uri)
352            .field("registration", &self.registration)
353            .finish_non_exhaustive()
354    }
355}
356
357#[skip_serializing_none]
361#[serde_as]
362#[derive(Serialize, Deserialize, Default, Clone)]
363pub struct AuthorizationResponse {
364    pub code: Option<String>,
366
367    pub access_token: Option<String>,
369
370    pub token_type: Option<OAuthAccessTokenType>,
372
373    pub id_token: Option<String>,
375
376    #[serde_as(as = "Option<DurationSeconds<i64>>")]
378    pub expires_in: Option<Duration>,
379}
380
381impl fmt::Debug for AuthorizationResponse {
382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        f.debug_struct("AuthorizationResponse")
384            .field("token_type", &self.token_type)
385            .field("id_token", &self.id_token)
386            .field("expires_in", &self.expires_in)
387            .finish_non_exhaustive()
388    }
389}
390
391#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
395pub struct DeviceAuthorizationRequest {
396    pub scope: Option<Scope>,
398}
399
400pub const DEFAULT_DEVICE_AUTHORIZATION_INTERVAL: Duration = Duration::microseconds(5 * 1000 * 1000);
403
404#[serde_as]
408#[skip_serializing_none]
409#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
410pub struct DeviceAuthorizationResponse {
411    pub device_code: String,
413
414    pub user_code: String,
416
417    pub verification_uri: Url,
422
423    pub verification_uri_complete: Option<Url>,
427
428    #[serde_as(as = "DurationSeconds<i64>")]
430    pub expires_in: Duration,
431
432    #[serde_as(as = "Option<DurationSeconds<i64>>")]
437    pub interval: Option<Duration>,
438}
439
440impl DeviceAuthorizationResponse {
441    #[must_use]
446    pub fn interval(&self) -> Duration {
447        self.interval
448            .unwrap_or(DEFAULT_DEVICE_AUTHORIZATION_INTERVAL)
449    }
450}
451
452impl fmt::Debug for DeviceAuthorizationResponse {
453    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454        f.debug_struct("DeviceAuthorizationResponse")
455            .field("verification_uri", &self.verification_uri)
456            .field("expires_in", &self.expires_in)
457            .field("interval", &self.interval)
458            .finish_non_exhaustive()
459    }
460}
461
462#[skip_serializing_none]
467#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
468pub struct AuthorizationCodeGrant {
469    pub code: String,
472
473    pub redirect_uri: Option<Url>,
478
479    pub code_verifier: Option<String>,
483}
484
485impl fmt::Debug for AuthorizationCodeGrant {
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        f.debug_struct("AuthorizationCodeGrant")
488            .field("redirect_uri", &self.redirect_uri)
489            .finish_non_exhaustive()
490    }
491}
492
493#[skip_serializing_none]
498#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
499pub struct RefreshTokenGrant {
500    pub refresh_token: String,
502
503    pub scope: Option<Scope>,
509}
510
511impl fmt::Debug for RefreshTokenGrant {
512    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513        f.debug_struct("RefreshTokenGrant")
514            .field("scope", &self.scope)
515            .finish_non_exhaustive()
516    }
517}
518
519#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
524pub struct ClientCredentialsGrant {
525    pub scope: Option<Scope>,
527}
528
529#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
534pub struct DeviceCodeGrant {
535    pub device_code: String,
537}
538
539impl fmt::Debug for DeviceCodeGrant {
540    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541        f.debug_struct("DeviceCodeGrant").finish_non_exhaustive()
542    }
543}
544
545#[derive(
547    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
548)]
549pub enum GrantType {
550    AuthorizationCode,
552
553    RefreshToken,
555
556    Implicit,
558
559    ClientCredentials,
561
562    Password,
564
565    DeviceCode,
567
568    JwtBearer,
570
571    ClientInitiatedBackchannelAuthentication,
573
574    Unknown(String),
576}
577
578impl core::fmt::Display for GrantType {
579    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
580        match self {
581            GrantType::AuthorizationCode => f.write_str("authorization_code"),
582            GrantType::RefreshToken => f.write_str("refresh_token"),
583            GrantType::Implicit => f.write_str("implicit"),
584            GrantType::ClientCredentials => f.write_str("client_credentials"),
585            GrantType::Password => f.write_str("password"),
586            GrantType::DeviceCode => f.write_str("urn:ietf:params:oauth:grant-type:device_code"),
587            GrantType::JwtBearer => f.write_str("urn:ietf:params:oauth:grant-type:jwt-bearer"),
588            GrantType::ClientInitiatedBackchannelAuthentication => {
589                f.write_str("urn:openid:params:grant-type:ciba")
590            }
591            GrantType::Unknown(s) => f.write_str(s),
592        }
593    }
594}
595
596impl core::str::FromStr for GrantType {
597    type Err = core::convert::Infallible;
598
599    fn from_str(s: &str) -> Result<Self, Self::Err> {
600        match s {
601            "authorization_code" => Ok(GrantType::AuthorizationCode),
602            "refresh_token" => Ok(GrantType::RefreshToken),
603            "implicit" => Ok(GrantType::Implicit),
604            "client_credentials" => Ok(GrantType::ClientCredentials),
605            "password" => Ok(GrantType::Password),
606            "urn:ietf:params:oauth:grant-type:device_code" => Ok(GrantType::DeviceCode),
607            "urn:ietf:params:oauth:grant-type:jwt-bearer" => Ok(GrantType::JwtBearer),
608            "urn:openid:params:grant-type:ciba" => {
609                Ok(GrantType::ClientInitiatedBackchannelAuthentication)
610            }
611            s => Ok(GrantType::Unknown(s.to_owned())),
612        }
613    }
614}
615
616#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
620#[serde(tag = "grant_type", rename_all = "snake_case")]
621#[non_exhaustive]
622pub enum AccessTokenRequest {
623    AuthorizationCode(AuthorizationCodeGrant),
625
626    RefreshToken(RefreshTokenGrant),
628
629    ClientCredentials(ClientCredentialsGrant),
631
632    #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
634    DeviceCode(DeviceCodeGrant),
635
636    #[serde(skip_serializing, other)]
638    Unsupported,
639}
640
641impl AccessTokenRequest {
642    #[must_use]
644    pub fn grant_type(&self) -> &'static str {
645        match self {
646            Self::AuthorizationCode(_) => "authorization_code",
647            Self::RefreshToken(_) => "refresh_token",
648            Self::ClientCredentials(_) => "client_credentials",
649            Self::DeviceCode(_) => "urn:ietf:params:oauth:grant-type:device_code",
650            Self::Unsupported => "unsupported",
651        }
652    }
653}
654
655#[serde_as]
659#[skip_serializing_none]
660#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
661pub struct AccessTokenResponse {
662    pub access_token: String,
664
665    pub refresh_token: Option<String>,
667
668    pub id_token: Option<String>,
671
672    pub token_type: OAuthAccessTokenType,
674
675    #[serde_as(as = "Option<DurationSeconds<i64>>")]
677    pub expires_in: Option<Duration>,
678
679    pub scope: Option<Scope>,
681}
682
683impl AccessTokenResponse {
684    #[must_use]
686    pub fn new(access_token: String) -> AccessTokenResponse {
687        AccessTokenResponse {
688            access_token,
689            refresh_token: None,
690            id_token: None,
691            token_type: OAuthAccessTokenType::Bearer,
692            expires_in: None,
693            scope: None,
694        }
695    }
696
697    #[must_use]
699    pub fn with_refresh_token(mut self, refresh_token: String) -> Self {
700        self.refresh_token = Some(refresh_token);
701        self
702    }
703
704    #[must_use]
706    pub fn with_id_token(mut self, id_token: String) -> Self {
707        self.id_token = Some(id_token);
708        self
709    }
710
711    #[must_use]
713    pub fn with_scope(mut self, scope: Scope) -> Self {
714        self.scope = Some(scope);
715        self
716    }
717
718    #[must_use]
720    pub fn with_expires_in(mut self, expires_in: Duration) -> Self {
721        self.expires_in = Some(expires_in);
722        self
723    }
724}
725
726impl fmt::Debug for AccessTokenResponse {
727    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
728        f.debug_struct("AccessTokenResponse")
729            .field("token_type", &self.token_type)
730            .field("expires_in", &self.expires_in)
731            .field("scope", &self.scope)
732            .finish_non_exhaustive()
733    }
734}
735
736#[skip_serializing_none]
740#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
741pub struct IntrospectionRequest {
742    pub token: String,
744
745    pub token_type_hint: Option<OAuthTokenTypeHint>,
747}
748
749impl fmt::Debug for IntrospectionRequest {
750    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
751        f.debug_struct("IntrospectionRequest")
752            .field("token_type_hint", &self.token_type_hint)
753            .finish_non_exhaustive()
754    }
755}
756
757#[serde_as]
761#[skip_serializing_none]
762#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
763pub struct IntrospectionResponse {
764    pub active: bool,
766
767    pub scope: Option<Scope>,
769
770    pub client_id: Option<String>,
772
773    pub username: Option<String>,
776
777    pub token_type: Option<OAuthTokenTypeHint>,
779
780    #[serde_as(as = "Option<TimestampSeconds>")]
782    pub exp: Option<DateTime<Utc>>,
783
784    #[serde_as(as = "Option<DurationSeconds<i64>>")]
787    pub expires_in: Option<Duration>,
788
789    #[serde_as(as = "Option<TimestampSeconds>")]
791    pub iat: Option<DateTime<Utc>>,
792
793    #[serde_as(as = "Option<TimestampSeconds>")]
795    pub nbf: Option<DateTime<Utc>>,
796
797    pub sub: Option<String>,
799
800    pub aud: Option<String>,
802
803    pub iss: Option<String>,
805
806    pub jti: Option<String>,
808
809    pub device_id: Option<String>,
811}
812
813#[skip_serializing_none]
817#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
818pub struct RevocationRequest {
819    pub token: String,
821
822    pub token_type_hint: Option<OAuthTokenTypeHint>,
824}
825
826impl fmt::Debug for RevocationRequest {
827    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
828        f.debug_struct("RevocationRequest")
829            .field("token_type_hint", &self.token_type_hint)
830            .finish_non_exhaustive()
831    }
832}
833
834#[serde_as]
841#[skip_serializing_none]
842#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
843pub struct PushedAuthorizationResponse {
844    pub request_uri: String,
846
847    #[serde_as(as = "DurationSeconds<i64>")]
849    pub expires_in: Duration,
850}
851
852#[cfg(test)]
853mod tests {
854    use serde_json::json;
855
856    use super::*;
857    use crate::{scope::OPENID, test_utils::assert_serde_json};
858
859    #[test]
860    fn serde_refresh_token_grant() {
861        let expected = json!({
862            "grant_type": "refresh_token",
863            "refresh_token": "abcd",
864            "scope": "openid",
865        });
866
867        let scope: Option<Scope> = Some(vec![OPENID].into_iter().collect());
871
872        let req = AccessTokenRequest::RefreshToken(RefreshTokenGrant {
873            refresh_token: "abcd".into(),
874            scope,
875        });
876
877        assert_serde_json(&req, expected);
878    }
879
880    #[test]
881    fn serde_authorization_code_grant() {
882        let expected = json!({
883            "grant_type": "authorization_code",
884            "code": "abcd",
885            "redirect_uri": "https://example.com/redirect",
886        });
887
888        let req = AccessTokenRequest::AuthorizationCode(AuthorizationCodeGrant {
889            code: "abcd".into(),
890            redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
891            code_verifier: None,
892        });
893
894        assert_serde_json(&req, expected);
895    }
896
897    #[test]
898    fn serialize_grant_type() {
899        assert_eq!(
900            serde_json::to_string(&GrantType::AuthorizationCode).unwrap(),
901            "\"authorization_code\""
902        );
903        assert_eq!(
904            serde_json::to_string(&GrantType::RefreshToken).unwrap(),
905            "\"refresh_token\""
906        );
907        assert_eq!(
908            serde_json::to_string(&GrantType::Implicit).unwrap(),
909            "\"implicit\""
910        );
911        assert_eq!(
912            serde_json::to_string(&GrantType::ClientCredentials).unwrap(),
913            "\"client_credentials\""
914        );
915        assert_eq!(
916            serde_json::to_string(&GrantType::Password).unwrap(),
917            "\"password\""
918        );
919        assert_eq!(
920            serde_json::to_string(&GrantType::DeviceCode).unwrap(),
921            "\"urn:ietf:params:oauth:grant-type:device_code\""
922        );
923        assert_eq!(
924            serde_json::to_string(&GrantType::ClientInitiatedBackchannelAuthentication).unwrap(),
925            "\"urn:openid:params:grant-type:ciba\""
926        );
927    }
928
929    #[test]
930    fn deserialize_grant_type() {
931        assert_eq!(
932            serde_json::from_str::<GrantType>("\"authorization_code\"").unwrap(),
933            GrantType::AuthorizationCode
934        );
935        assert_eq!(
936            serde_json::from_str::<GrantType>("\"refresh_token\"").unwrap(),
937            GrantType::RefreshToken
938        );
939        assert_eq!(
940            serde_json::from_str::<GrantType>("\"implicit\"").unwrap(),
941            GrantType::Implicit
942        );
943        assert_eq!(
944            serde_json::from_str::<GrantType>("\"client_credentials\"").unwrap(),
945            GrantType::ClientCredentials
946        );
947        assert_eq!(
948            serde_json::from_str::<GrantType>("\"password\"").unwrap(),
949            GrantType::Password
950        );
951        assert_eq!(
952            serde_json::from_str::<GrantType>("\"urn:ietf:params:oauth:grant-type:device_code\"")
953                .unwrap(),
954            GrantType::DeviceCode
955        );
956        assert_eq!(
957            serde_json::from_str::<GrantType>("\"urn:openid:params:grant-type:ciba\"").unwrap(),
958            GrantType::ClientInitiatedBackchannelAuthentication
959        );
960    }
961
962    #[test]
963    fn serialize_response_mode() {
964        assert_eq!(
965            serde_json::to_string(&ResponseMode::Query).unwrap(),
966            "\"query\""
967        );
968        assert_eq!(
969            serde_json::to_string(&ResponseMode::Fragment).unwrap(),
970            "\"fragment\""
971        );
972        assert_eq!(
973            serde_json::to_string(&ResponseMode::FormPost).unwrap(),
974            "\"form_post\""
975        );
976    }
977
978    #[test]
979    fn deserialize_response_mode() {
980        assert_eq!(
981            serde_json::from_str::<ResponseMode>("\"query\"").unwrap(),
982            ResponseMode::Query
983        );
984        assert_eq!(
985            serde_json::from_str::<ResponseMode>("\"fragment\"").unwrap(),
986            ResponseMode::Fragment
987        );
988        assert_eq!(
989            serde_json::from_str::<ResponseMode>("\"form_post\"").unwrap(),
990            ResponseMode::FormPost
991        );
992    }
993
994    #[test]
995    fn serialize_display() {
996        assert_eq!(serde_json::to_string(&Display::Page).unwrap(), "\"page\"");
997        assert_eq!(serde_json::to_string(&Display::Popup).unwrap(), "\"popup\"");
998        assert_eq!(serde_json::to_string(&Display::Touch).unwrap(), "\"touch\"");
999        assert_eq!(serde_json::to_string(&Display::Wap).unwrap(), "\"wap\"");
1000    }
1001
1002    #[test]
1003    fn deserialize_display() {
1004        assert_eq!(
1005            serde_json::from_str::<Display>("\"page\"").unwrap(),
1006            Display::Page
1007        );
1008        assert_eq!(
1009            serde_json::from_str::<Display>("\"popup\"").unwrap(),
1010            Display::Popup
1011        );
1012        assert_eq!(
1013            serde_json::from_str::<Display>("\"touch\"").unwrap(),
1014            Display::Touch
1015        );
1016        assert_eq!(
1017            serde_json::from_str::<Display>("\"wap\"").unwrap(),
1018            Display::Wap
1019        );
1020    }
1021
1022    #[test]
1023    fn serialize_prompt() {
1024        assert_eq!(serde_json::to_string(&Prompt::None).unwrap(), "\"none\"");
1025        assert_eq!(serde_json::to_string(&Prompt::Login).unwrap(), "\"login\"");
1026        assert_eq!(
1027            serde_json::to_string(&Prompt::Consent).unwrap(),
1028            "\"consent\""
1029        );
1030        assert_eq!(
1031            serde_json::to_string(&Prompt::SelectAccount).unwrap(),
1032            "\"select_account\""
1033        );
1034        assert_eq!(
1035            serde_json::to_string(&Prompt::Create).unwrap(),
1036            "\"create\""
1037        );
1038    }
1039
1040    #[test]
1041    fn deserialize_prompt() {
1042        assert_eq!(
1043            serde_json::from_str::<Prompt>("\"none\"").unwrap(),
1044            Prompt::None
1045        );
1046        assert_eq!(
1047            serde_json::from_str::<Prompt>("\"login\"").unwrap(),
1048            Prompt::Login
1049        );
1050        assert_eq!(
1051            serde_json::from_str::<Prompt>("\"consent\"").unwrap(),
1052            Prompt::Consent
1053        );
1054        assert_eq!(
1055            serde_json::from_str::<Prompt>("\"select_account\"").unwrap(),
1056            Prompt::SelectAccount
1057        );
1058        assert_eq!(
1059            serde_json::from_str::<Prompt>("\"create\"").unwrap(),
1060            Prompt::Create
1061        );
1062    }
1063}