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>,
807}
808
809#[skip_serializing_none]
813#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
814pub struct RevocationRequest {
815 pub token: String,
817
818 pub token_type_hint: Option<OAuthTokenTypeHint>,
820}
821
822impl fmt::Debug for RevocationRequest {
823 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
824 f.debug_struct("RevocationRequest")
825 .field("token_type_hint", &self.token_type_hint)
826 .finish_non_exhaustive()
827 }
828}
829
830#[serde_as]
837#[skip_serializing_none]
838#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
839pub struct PushedAuthorizationResponse {
840 pub request_uri: String,
842
843 #[serde_as(as = "DurationSeconds<i64>")]
845 pub expires_in: Duration,
846}
847
848#[cfg(test)]
849mod tests {
850 use serde_json::json;
851
852 use super::*;
853 use crate::{scope::OPENID, test_utils::assert_serde_json};
854
855 #[test]
856 fn serde_refresh_token_grant() {
857 let expected = json!({
858 "grant_type": "refresh_token",
859 "refresh_token": "abcd",
860 "scope": "openid",
861 });
862
863 let scope: Option<Scope> = Some(vec![OPENID].into_iter().collect());
867
868 let req = AccessTokenRequest::RefreshToken(RefreshTokenGrant {
869 refresh_token: "abcd".into(),
870 scope,
871 });
872
873 assert_serde_json(&req, expected);
874 }
875
876 #[test]
877 fn serde_authorization_code_grant() {
878 let expected = json!({
879 "grant_type": "authorization_code",
880 "code": "abcd",
881 "redirect_uri": "https://example.com/redirect",
882 });
883
884 let req = AccessTokenRequest::AuthorizationCode(AuthorizationCodeGrant {
885 code: "abcd".into(),
886 redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
887 code_verifier: None,
888 });
889
890 assert_serde_json(&req, expected);
891 }
892
893 #[test]
894 fn serialize_grant_type() {
895 assert_eq!(
896 serde_json::to_string(&GrantType::AuthorizationCode).unwrap(),
897 "\"authorization_code\""
898 );
899 assert_eq!(
900 serde_json::to_string(&GrantType::RefreshToken).unwrap(),
901 "\"refresh_token\""
902 );
903 assert_eq!(
904 serde_json::to_string(&GrantType::Implicit).unwrap(),
905 "\"implicit\""
906 );
907 assert_eq!(
908 serde_json::to_string(&GrantType::ClientCredentials).unwrap(),
909 "\"client_credentials\""
910 );
911 assert_eq!(
912 serde_json::to_string(&GrantType::Password).unwrap(),
913 "\"password\""
914 );
915 assert_eq!(
916 serde_json::to_string(&GrantType::DeviceCode).unwrap(),
917 "\"urn:ietf:params:oauth:grant-type:device_code\""
918 );
919 assert_eq!(
920 serde_json::to_string(&GrantType::ClientInitiatedBackchannelAuthentication).unwrap(),
921 "\"urn:openid:params:grant-type:ciba\""
922 );
923 }
924
925 #[test]
926 fn deserialize_grant_type() {
927 assert_eq!(
928 serde_json::from_str::<GrantType>("\"authorization_code\"").unwrap(),
929 GrantType::AuthorizationCode
930 );
931 assert_eq!(
932 serde_json::from_str::<GrantType>("\"refresh_token\"").unwrap(),
933 GrantType::RefreshToken
934 );
935 assert_eq!(
936 serde_json::from_str::<GrantType>("\"implicit\"").unwrap(),
937 GrantType::Implicit
938 );
939 assert_eq!(
940 serde_json::from_str::<GrantType>("\"client_credentials\"").unwrap(),
941 GrantType::ClientCredentials
942 );
943 assert_eq!(
944 serde_json::from_str::<GrantType>("\"password\"").unwrap(),
945 GrantType::Password
946 );
947 assert_eq!(
948 serde_json::from_str::<GrantType>("\"urn:ietf:params:oauth:grant-type:device_code\"")
949 .unwrap(),
950 GrantType::DeviceCode
951 );
952 assert_eq!(
953 serde_json::from_str::<GrantType>("\"urn:openid:params:grant-type:ciba\"").unwrap(),
954 GrantType::ClientInitiatedBackchannelAuthentication
955 );
956 }
957
958 #[test]
959 fn serialize_response_mode() {
960 assert_eq!(
961 serde_json::to_string(&ResponseMode::Query).unwrap(),
962 "\"query\""
963 );
964 assert_eq!(
965 serde_json::to_string(&ResponseMode::Fragment).unwrap(),
966 "\"fragment\""
967 );
968 assert_eq!(
969 serde_json::to_string(&ResponseMode::FormPost).unwrap(),
970 "\"form_post\""
971 );
972 }
973
974 #[test]
975 fn deserialize_response_mode() {
976 assert_eq!(
977 serde_json::from_str::<ResponseMode>("\"query\"").unwrap(),
978 ResponseMode::Query
979 );
980 assert_eq!(
981 serde_json::from_str::<ResponseMode>("\"fragment\"").unwrap(),
982 ResponseMode::Fragment
983 );
984 assert_eq!(
985 serde_json::from_str::<ResponseMode>("\"form_post\"").unwrap(),
986 ResponseMode::FormPost
987 );
988 }
989
990 #[test]
991 fn serialize_display() {
992 assert_eq!(serde_json::to_string(&Display::Page).unwrap(), "\"page\"");
993 assert_eq!(serde_json::to_string(&Display::Popup).unwrap(), "\"popup\"");
994 assert_eq!(serde_json::to_string(&Display::Touch).unwrap(), "\"touch\"");
995 assert_eq!(serde_json::to_string(&Display::Wap).unwrap(), "\"wap\"");
996 }
997
998 #[test]
999 fn deserialize_display() {
1000 assert_eq!(
1001 serde_json::from_str::<Display>("\"page\"").unwrap(),
1002 Display::Page
1003 );
1004 assert_eq!(
1005 serde_json::from_str::<Display>("\"popup\"").unwrap(),
1006 Display::Popup
1007 );
1008 assert_eq!(
1009 serde_json::from_str::<Display>("\"touch\"").unwrap(),
1010 Display::Touch
1011 );
1012 assert_eq!(
1013 serde_json::from_str::<Display>("\"wap\"").unwrap(),
1014 Display::Wap
1015 );
1016 }
1017
1018 #[test]
1019 fn serialize_prompt() {
1020 assert_eq!(serde_json::to_string(&Prompt::None).unwrap(), "\"none\"");
1021 assert_eq!(serde_json::to_string(&Prompt::Login).unwrap(), "\"login\"");
1022 assert_eq!(
1023 serde_json::to_string(&Prompt::Consent).unwrap(),
1024 "\"consent\""
1025 );
1026 assert_eq!(
1027 serde_json::to_string(&Prompt::SelectAccount).unwrap(),
1028 "\"select_account\""
1029 );
1030 assert_eq!(
1031 serde_json::to_string(&Prompt::Create).unwrap(),
1032 "\"create\""
1033 );
1034 }
1035
1036 #[test]
1037 fn deserialize_prompt() {
1038 assert_eq!(
1039 serde_json::from_str::<Prompt>("\"none\"").unwrap(),
1040 Prompt::None
1041 );
1042 assert_eq!(
1043 serde_json::from_str::<Prompt>("\"login\"").unwrap(),
1044 Prompt::Login
1045 );
1046 assert_eq!(
1047 serde_json::from_str::<Prompt>("\"consent\"").unwrap(),
1048 Prompt::Consent
1049 );
1050 assert_eq!(
1051 serde_json::from_str::<Prompt>("\"select_account\"").unwrap(),
1052 Prompt::SelectAccount
1053 );
1054 assert_eq!(
1055 serde_json::from_str::<Prompt>("\"create\"").unwrap(),
1056 Prompt::Create
1057 );
1058 }
1059}