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