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]
89#[derive(Default)]
90pub enum Display {
91    #[default]
96    Page,
97
98    Popup,
101
102    Touch,
105
106    Wap,
109
110    Unknown(String),
112}
113
114impl core::fmt::Display for Display {
115    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116        match self {
117            Display::Page => f.write_str("page"),
118            Display::Popup => f.write_str("popup"),
119            Display::Touch => f.write_str("touch"),
120            Display::Wap => f.write_str("wap"),
121            Display::Unknown(s) => f.write_str(s),
122        }
123    }
124}
125
126impl core::str::FromStr for Display {
127    type Err = core::convert::Infallible;
128
129    fn from_str(s: &str) -> Result<Self, Self::Err> {
130        match s {
131            "page" => Ok(Display::Page),
132            "popup" => Ok(Display::Popup),
133            "touch" => Ok(Display::Touch),
134            "wap" => Ok(Display::Wap),
135            s => Ok(Display::Unknown(s.to_owned())),
136        }
137    }
138}
139
140#[derive(
145    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
146)]
147#[non_exhaustive]
148pub enum Prompt {
149    None,
152
153    Login,
156
157    Consent,
160
161    SelectAccount,
168
169    Create,
174
175    Unknown(String),
177}
178
179impl core::fmt::Display for Prompt {
180    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
181        match self {
182            Prompt::None => f.write_str("none"),
183            Prompt::Login => f.write_str("login"),
184            Prompt::Consent => f.write_str("consent"),
185            Prompt::SelectAccount => f.write_str("select_account"),
186            Prompt::Create => f.write_str("create"),
187            Prompt::Unknown(s) => f.write_str(s),
188        }
189    }
190}
191
192impl core::str::FromStr for Prompt {
193    type Err = core::convert::Infallible;
194
195    fn from_str(s: &str) -> Result<Self, Self::Err> {
196        match s {
197            "none" => Ok(Prompt::None),
198            "login" => Ok(Prompt::Login),
199            "consent" => Ok(Prompt::Consent),
200            "select_account" => Ok(Prompt::SelectAccount),
201            "create" => Ok(Prompt::Create),
202            s => Ok(Prompt::Unknown(s.to_owned())),
203        }
204    }
205}
206
207#[skip_serializing_none]
211#[serde_as]
212#[derive(Serialize, Deserialize, Clone)]
213pub struct AuthorizationRequest {
214    pub response_type: ResponseType,
217
218    pub client_id: String,
220
221    pub redirect_uri: Option<Url>,
228
229    pub scope: Scope,
233
234    pub state: Option<String>,
237
238    pub response_mode: Option<ResponseMode>,
245
246    pub nonce: Option<String>,
249
250    pub display: Option<Display>,
253
254    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, Prompt>>")]
259    #[serde(default)]
260    pub prompt: Option<Vec<Prompt>>,
261
262    #[serde(default)]
265    #[serde_as(as = "Option<DisplayFromStr>")]
266    pub max_age: Option<NonZeroU32>,
267
268    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, LanguageTag>>")]
270    #[serde(default)]
271    pub ui_locales: Option<Vec<LanguageTag>>,
272
273    pub id_token_hint: Option<String>,
277
278    pub login_hint: Option<String>,
281
282    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
284    #[serde(default)]
285    pub acr_values: Option<HashSet<String>>,
286
287    pub request: Option<String>,
292
293    pub request_uri: Option<Url>,
299
300    pub registration: Option<String>,
305}
306
307impl AuthorizationRequest {
308    #[must_use]
310    pub fn new(response_type: ResponseType, client_id: String, scope: Scope) -> Self {
311        Self {
312            response_type,
313            client_id,
314            redirect_uri: None,
315            scope,
316            state: None,
317            response_mode: None,
318            nonce: None,
319            display: None,
320            prompt: None,
321            max_age: None,
322            ui_locales: None,
323            id_token_hint: None,
324            login_hint: None,
325            acr_values: None,
326            request: None,
327            request_uri: None,
328            registration: None,
329        }
330    }
331}
332
333impl fmt::Debug for AuthorizationRequest {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        f.debug_struct("AuthorizationRequest")
336            .field("response_type", &self.response_type)
337            .field("redirect_uri", &self.redirect_uri)
338            .field("scope", &self.scope)
339            .field("response_mode", &self.response_mode)
340            .field("display", &self.display)
341            .field("prompt", &self.prompt)
342            .field("max_age", &self.max_age)
343            .field("ui_locales", &self.ui_locales)
344            .field("login_hint", &self.login_hint)
345            .field("acr_values", &self.acr_values)
346            .field("request", &self.request)
347            .field("request_uri", &self.request_uri)
348            .field("registration", &self.registration)
349            .finish_non_exhaustive()
350    }
351}
352
353#[skip_serializing_none]
357#[serde_as]
358#[derive(Serialize, Deserialize, Default, Clone)]
359pub struct AuthorizationResponse {
360    pub code: Option<String>,
362
363    pub access_token: Option<String>,
365
366    pub token_type: Option<OAuthAccessTokenType>,
368
369    pub id_token: Option<String>,
371
372    #[serde_as(as = "Option<DurationSeconds<i64>>")]
374    pub expires_in: Option<Duration>,
375}
376
377impl fmt::Debug for AuthorizationResponse {
378    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379        f.debug_struct("AuthorizationResponse")
380            .field("token_type", &self.token_type)
381            .field("id_token", &self.id_token)
382            .field("expires_in", &self.expires_in)
383            .finish_non_exhaustive()
384    }
385}
386
387#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
391pub struct DeviceAuthorizationRequest {
392    pub scope: Option<Scope>,
394}
395
396pub const DEFAULT_DEVICE_AUTHORIZATION_INTERVAL: Duration = Duration::microseconds(5 * 1000 * 1000);
399
400#[serde_as]
404#[skip_serializing_none]
405#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
406pub struct DeviceAuthorizationResponse {
407    pub device_code: String,
409
410    pub user_code: String,
412
413    pub verification_uri: Url,
418
419    pub verification_uri_complete: Option<Url>,
423
424    #[serde_as(as = "DurationSeconds<i64>")]
426    pub expires_in: Duration,
427
428    #[serde_as(as = "Option<DurationSeconds<i64>>")]
433    pub interval: Option<Duration>,
434}
435
436impl DeviceAuthorizationResponse {
437    #[must_use]
442    pub fn interval(&self) -> Duration {
443        self.interval
444            .unwrap_or(DEFAULT_DEVICE_AUTHORIZATION_INTERVAL)
445    }
446}
447
448impl fmt::Debug for DeviceAuthorizationResponse {
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        f.debug_struct("DeviceAuthorizationResponse")
451            .field("verification_uri", &self.verification_uri)
452            .field("expires_in", &self.expires_in)
453            .field("interval", &self.interval)
454            .finish_non_exhaustive()
455    }
456}
457
458#[skip_serializing_none]
463#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
464pub struct AuthorizationCodeGrant {
465    pub code: String,
468
469    pub redirect_uri: Option<Url>,
474
475    pub code_verifier: Option<String>,
479}
480
481impl fmt::Debug for AuthorizationCodeGrant {
482    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483        f.debug_struct("AuthorizationCodeGrant")
484            .field("redirect_uri", &self.redirect_uri)
485            .finish_non_exhaustive()
486    }
487}
488
489#[skip_serializing_none]
494#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
495pub struct RefreshTokenGrant {
496    pub refresh_token: String,
498
499    pub scope: Option<Scope>,
505}
506
507impl fmt::Debug for RefreshTokenGrant {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        f.debug_struct("RefreshTokenGrant")
510            .field("scope", &self.scope)
511            .finish_non_exhaustive()
512    }
513}
514
515#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
520pub struct ClientCredentialsGrant {
521    pub scope: Option<Scope>,
523}
524
525#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
530pub struct DeviceCodeGrant {
531    pub device_code: String,
533}
534
535impl fmt::Debug for DeviceCodeGrant {
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        f.debug_struct("DeviceCodeGrant").finish_non_exhaustive()
538    }
539}
540
541#[derive(
543    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
544)]
545pub enum GrantType {
546    AuthorizationCode,
548
549    RefreshToken,
551
552    Implicit,
554
555    ClientCredentials,
557
558    Password,
560
561    DeviceCode,
563
564    JwtBearer,
566
567    ClientInitiatedBackchannelAuthentication,
569
570    Unknown(String),
572}
573
574impl core::fmt::Display for GrantType {
575    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
576        match self {
577            GrantType::AuthorizationCode => f.write_str("authorization_code"),
578            GrantType::RefreshToken => f.write_str("refresh_token"),
579            GrantType::Implicit => f.write_str("implicit"),
580            GrantType::ClientCredentials => f.write_str("client_credentials"),
581            GrantType::Password => f.write_str("password"),
582            GrantType::DeviceCode => f.write_str("urn:ietf:params:oauth:grant-type:device_code"),
583            GrantType::JwtBearer => f.write_str("urn:ietf:params:oauth:grant-type:jwt-bearer"),
584            GrantType::ClientInitiatedBackchannelAuthentication => {
585                f.write_str("urn:openid:params:grant-type:ciba")
586            }
587            GrantType::Unknown(s) => f.write_str(s),
588        }
589    }
590}
591
592impl core::str::FromStr for GrantType {
593    type Err = core::convert::Infallible;
594
595    fn from_str(s: &str) -> Result<Self, Self::Err> {
596        match s {
597            "authorization_code" => Ok(GrantType::AuthorizationCode),
598            "refresh_token" => Ok(GrantType::RefreshToken),
599            "implicit" => Ok(GrantType::Implicit),
600            "client_credentials" => Ok(GrantType::ClientCredentials),
601            "password" => Ok(GrantType::Password),
602            "urn:ietf:params:oauth:grant-type:device_code" => Ok(GrantType::DeviceCode),
603            "urn:ietf:params:oauth:grant-type:jwt-bearer" => Ok(GrantType::JwtBearer),
604            "urn:openid:params:grant-type:ciba" => {
605                Ok(GrantType::ClientInitiatedBackchannelAuthentication)
606            }
607            s => Ok(GrantType::Unknown(s.to_owned())),
608        }
609    }
610}
611
612#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
616#[serde(tag = "grant_type", rename_all = "snake_case")]
617#[non_exhaustive]
618pub enum AccessTokenRequest {
619    AuthorizationCode(AuthorizationCodeGrant),
621
622    RefreshToken(RefreshTokenGrant),
624
625    ClientCredentials(ClientCredentialsGrant),
627
628    #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
630    DeviceCode(DeviceCodeGrant),
631
632    #[serde(skip_serializing, other)]
634    Unsupported,
635}
636
637impl AccessTokenRequest {
638    #[must_use]
640    pub fn grant_type(&self) -> &'static str {
641        match self {
642            Self::AuthorizationCode(_) => "authorization_code",
643            Self::RefreshToken(_) => "refresh_token",
644            Self::ClientCredentials(_) => "client_credentials",
645            Self::DeviceCode(_) => "urn:ietf:params:oauth:grant-type:device_code",
646            Self::Unsupported => "unsupported",
647        }
648    }
649}
650
651#[serde_as]
655#[skip_serializing_none]
656#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
657pub struct AccessTokenResponse {
658    pub access_token: String,
660
661    pub refresh_token: Option<String>,
663
664    pub id_token: Option<String>,
667
668    pub token_type: OAuthAccessTokenType,
670
671    #[serde_as(as = "Option<DurationSeconds<i64>>")]
673    pub expires_in: Option<Duration>,
674
675    pub scope: Option<Scope>,
677}
678
679impl AccessTokenResponse {
680    #[must_use]
682    pub fn new(access_token: String) -> AccessTokenResponse {
683        AccessTokenResponse {
684            access_token,
685            refresh_token: None,
686            id_token: None,
687            token_type: OAuthAccessTokenType::Bearer,
688            expires_in: None,
689            scope: None,
690        }
691    }
692
693    #[must_use]
695    pub fn with_refresh_token(mut self, refresh_token: String) -> Self {
696        self.refresh_token = Some(refresh_token);
697        self
698    }
699
700    #[must_use]
702    pub fn with_id_token(mut self, id_token: String) -> Self {
703        self.id_token = Some(id_token);
704        self
705    }
706
707    #[must_use]
709    pub fn with_scope(mut self, scope: Scope) -> Self {
710        self.scope = Some(scope);
711        self
712    }
713
714    #[must_use]
716    pub fn with_expires_in(mut self, expires_in: Duration) -> Self {
717        self.expires_in = Some(expires_in);
718        self
719    }
720}
721
722impl fmt::Debug for AccessTokenResponse {
723    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
724        f.debug_struct("AccessTokenResponse")
725            .field("token_type", &self.token_type)
726            .field("expires_in", &self.expires_in)
727            .field("scope", &self.scope)
728            .finish_non_exhaustive()
729    }
730}
731
732#[skip_serializing_none]
736#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
737pub struct IntrospectionRequest {
738    pub token: String,
740
741    pub token_type_hint: Option<OAuthTokenTypeHint>,
743}
744
745impl fmt::Debug for IntrospectionRequest {
746    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
747        f.debug_struct("IntrospectionRequest")
748            .field("token_type_hint", &self.token_type_hint)
749            .finish_non_exhaustive()
750    }
751}
752
753#[serde_as]
757#[skip_serializing_none]
758#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
759pub struct IntrospectionResponse {
760    pub active: bool,
762
763    pub scope: Option<Scope>,
765
766    pub client_id: Option<String>,
768
769    pub username: Option<String>,
772
773    pub token_type: Option<OAuthTokenTypeHint>,
775
776    #[serde_as(as = "Option<TimestampSeconds>")]
778    pub exp: Option<DateTime<Utc>>,
779
780    #[serde_as(as = "Option<DurationSeconds<i64>>")]
783    pub expires_in: Option<Duration>,
784
785    #[serde_as(as = "Option<TimestampSeconds>")]
787    pub iat: Option<DateTime<Utc>>,
788
789    #[serde_as(as = "Option<TimestampSeconds>")]
791    pub nbf: Option<DateTime<Utc>>,
792
793    pub sub: Option<String>,
795
796    pub aud: Option<String>,
798
799    pub iss: Option<String>,
801
802    pub jti: Option<String>,
804
805    pub device_id: Option<String>,
808}
809
810#[skip_serializing_none]
814#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
815pub struct RevocationRequest {
816    pub token: String,
818
819    pub token_type_hint: Option<OAuthTokenTypeHint>,
821}
822
823impl fmt::Debug for RevocationRequest {
824    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
825        f.debug_struct("RevocationRequest")
826            .field("token_type_hint", &self.token_type_hint)
827            .finish_non_exhaustive()
828    }
829}
830
831#[serde_as]
838#[skip_serializing_none]
839#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
840pub struct PushedAuthorizationResponse {
841    pub request_uri: String,
843
844    #[serde_as(as = "DurationSeconds<i64>")]
846    pub expires_in: Duration,
847}
848
849#[cfg(test)]
850mod tests {
851    use serde_json::json;
852
853    use super::*;
854    use crate::{scope::OPENID, test_utils::assert_serde_json};
855
856    #[test]
857    fn serde_refresh_token_grant() {
858        let expected = json!({
859            "grant_type": "refresh_token",
860            "refresh_token": "abcd",
861            "scope": "openid",
862        });
863
864        let scope: Option<Scope> = Some(vec![OPENID].into_iter().collect());
868
869        let req = AccessTokenRequest::RefreshToken(RefreshTokenGrant {
870            refresh_token: "abcd".into(),
871            scope,
872        });
873
874        assert_serde_json(&req, expected);
875    }
876
877    #[test]
878    fn serde_authorization_code_grant() {
879        let expected = json!({
880            "grant_type": "authorization_code",
881            "code": "abcd",
882            "redirect_uri": "https://example.com/redirect",
883        });
884
885        let req = AccessTokenRequest::AuthorizationCode(AuthorizationCodeGrant {
886            code: "abcd".into(),
887            redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
888            code_verifier: None,
889        });
890
891        assert_serde_json(&req, expected);
892    }
893
894    #[test]
895    fn serialize_grant_type() {
896        assert_eq!(
897            serde_json::to_string(&GrantType::AuthorizationCode).unwrap(),
898            "\"authorization_code\""
899        );
900        assert_eq!(
901            serde_json::to_string(&GrantType::RefreshToken).unwrap(),
902            "\"refresh_token\""
903        );
904        assert_eq!(
905            serde_json::to_string(&GrantType::Implicit).unwrap(),
906            "\"implicit\""
907        );
908        assert_eq!(
909            serde_json::to_string(&GrantType::ClientCredentials).unwrap(),
910            "\"client_credentials\""
911        );
912        assert_eq!(
913            serde_json::to_string(&GrantType::Password).unwrap(),
914            "\"password\""
915        );
916        assert_eq!(
917            serde_json::to_string(&GrantType::DeviceCode).unwrap(),
918            "\"urn:ietf:params:oauth:grant-type:device_code\""
919        );
920        assert_eq!(
921            serde_json::to_string(&GrantType::ClientInitiatedBackchannelAuthentication).unwrap(),
922            "\"urn:openid:params:grant-type:ciba\""
923        );
924    }
925
926    #[test]
927    fn deserialize_grant_type() {
928        assert_eq!(
929            serde_json::from_str::<GrantType>("\"authorization_code\"").unwrap(),
930            GrantType::AuthorizationCode
931        );
932        assert_eq!(
933            serde_json::from_str::<GrantType>("\"refresh_token\"").unwrap(),
934            GrantType::RefreshToken
935        );
936        assert_eq!(
937            serde_json::from_str::<GrantType>("\"implicit\"").unwrap(),
938            GrantType::Implicit
939        );
940        assert_eq!(
941            serde_json::from_str::<GrantType>("\"client_credentials\"").unwrap(),
942            GrantType::ClientCredentials
943        );
944        assert_eq!(
945            serde_json::from_str::<GrantType>("\"password\"").unwrap(),
946            GrantType::Password
947        );
948        assert_eq!(
949            serde_json::from_str::<GrantType>("\"urn:ietf:params:oauth:grant-type:device_code\"")
950                .unwrap(),
951            GrantType::DeviceCode
952        );
953        assert_eq!(
954            serde_json::from_str::<GrantType>("\"urn:openid:params:grant-type:ciba\"").unwrap(),
955            GrantType::ClientInitiatedBackchannelAuthentication
956        );
957    }
958
959    #[test]
960    fn serialize_response_mode() {
961        assert_eq!(
962            serde_json::to_string(&ResponseMode::Query).unwrap(),
963            "\"query\""
964        );
965        assert_eq!(
966            serde_json::to_string(&ResponseMode::Fragment).unwrap(),
967            "\"fragment\""
968        );
969        assert_eq!(
970            serde_json::to_string(&ResponseMode::FormPost).unwrap(),
971            "\"form_post\""
972        );
973    }
974
975    #[test]
976    fn deserialize_response_mode() {
977        assert_eq!(
978            serde_json::from_str::<ResponseMode>("\"query\"").unwrap(),
979            ResponseMode::Query
980        );
981        assert_eq!(
982            serde_json::from_str::<ResponseMode>("\"fragment\"").unwrap(),
983            ResponseMode::Fragment
984        );
985        assert_eq!(
986            serde_json::from_str::<ResponseMode>("\"form_post\"").unwrap(),
987            ResponseMode::FormPost
988        );
989    }
990
991    #[test]
992    fn serialize_display() {
993        assert_eq!(serde_json::to_string(&Display::Page).unwrap(), "\"page\"");
994        assert_eq!(serde_json::to_string(&Display::Popup).unwrap(), "\"popup\"");
995        assert_eq!(serde_json::to_string(&Display::Touch).unwrap(), "\"touch\"");
996        assert_eq!(serde_json::to_string(&Display::Wap).unwrap(), "\"wap\"");
997    }
998
999    #[test]
1000    fn deserialize_display() {
1001        assert_eq!(
1002            serde_json::from_str::<Display>("\"page\"").unwrap(),
1003            Display::Page
1004        );
1005        assert_eq!(
1006            serde_json::from_str::<Display>("\"popup\"").unwrap(),
1007            Display::Popup
1008        );
1009        assert_eq!(
1010            serde_json::from_str::<Display>("\"touch\"").unwrap(),
1011            Display::Touch
1012        );
1013        assert_eq!(
1014            serde_json::from_str::<Display>("\"wap\"").unwrap(),
1015            Display::Wap
1016        );
1017    }
1018
1019    #[test]
1020    fn serialize_prompt() {
1021        assert_eq!(serde_json::to_string(&Prompt::None).unwrap(), "\"none\"");
1022        assert_eq!(serde_json::to_string(&Prompt::Login).unwrap(), "\"login\"");
1023        assert_eq!(
1024            serde_json::to_string(&Prompt::Consent).unwrap(),
1025            "\"consent\""
1026        );
1027        assert_eq!(
1028            serde_json::to_string(&Prompt::SelectAccount).unwrap(),
1029            "\"select_account\""
1030        );
1031        assert_eq!(
1032            serde_json::to_string(&Prompt::Create).unwrap(),
1033            "\"create\""
1034        );
1035    }
1036
1037    #[test]
1038    fn deserialize_prompt() {
1039        assert_eq!(
1040            serde_json::from_str::<Prompt>("\"none\"").unwrap(),
1041            Prompt::None
1042        );
1043        assert_eq!(
1044            serde_json::from_str::<Prompt>("\"login\"").unwrap(),
1045            Prompt::Login
1046        );
1047        assert_eq!(
1048            serde_json::from_str::<Prompt>("\"consent\"").unwrap(),
1049            Prompt::Consent
1050        );
1051        assert_eq!(
1052            serde_json::from_str::<Prompt>("\"select_account\"").unwrap(),
1053            Prompt::SelectAccount
1054        );
1055        assert_eq!(
1056            serde_json::from_str::<Prompt>("\"create\"").unwrap(),
1057            Prompt::Create
1058        );
1059    }
1060}