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}