Skip to main content

bitwarden_auth/send_access/api/
token_api_error_response.rs

1use serde::{Deserialize, Serialize};
2#[cfg(feature = "wasm")]
3use tsify::Tsify;
4
5#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
6#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
7#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
8#[serde(rename_all = "snake_case")]
9/// Invalid request errors - typically due to missing parameters.
10pub enum SendAccessTokenInvalidRequestError {
11    #[allow(missing_docs)]
12    SendIdRequired,
13
14    #[allow(missing_docs)]
15    PasswordHashB64Required,
16
17    #[allow(missing_docs)]
18    EmailRequired,
19
20    #[allow(missing_docs)]
21    EmailAndOtpRequired,
22
23    /// Fallback for unknown variants for forward compatibility
24    #[serde(other)]
25    Unknown,
26}
27
28#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
29#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
30#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
31#[serde(rename_all = "snake_case")]
32/// Invalid grant errors - typically due to invalid credentials.
33pub enum SendAccessTokenInvalidGrantError {
34    #[allow(missing_docs)]
35    SendIdInvalid,
36
37    #[allow(missing_docs)]
38    PasswordHashB64Invalid,
39
40    /// Fallback for unknown variants for forward compatibility
41    #[serde(other)]
42    Unknown,
43}
44
45#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
46#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
47#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
48#[serde(rename_all = "snake_case")]
49#[serde(tag = "error")]
50// ^ "error" becomes the variant discriminator which matches against the rename annotations;
51// "error_description" is the payload for that variant which can be optional.
52/// Represents the possible, expected errors that can occur when requesting a send access token.
53pub enum SendAccessTokenApiErrorResponse {
54    /// Invalid request error, typically due to missing parameters for a specific
55    /// credential flow. Ex. `send_id` is required.
56    InvalidRequest {
57        #[serde(default, skip_serializing_if = "Option::is_none")]
58        #[cfg_attr(feature = "wasm", tsify(optional))]
59        /// The optional error description for invalid request errors.
60        error_description: Option<String>,
61
62        #[serde(default, skip_serializing_if = "Option::is_none")]
63        #[cfg_attr(feature = "wasm", tsify(optional))]
64        /// The optional specific error type for invalid request errors.
65        send_access_error_type: Option<SendAccessTokenInvalidRequestError>,
66    },
67
68    /// Invalid grant error, typically due to invalid credentials.
69    InvalidGrant {
70        #[serde(default, skip_serializing_if = "Option::is_none")]
71        #[cfg_attr(feature = "wasm", tsify(optional))]
72        /// The optional error description for invalid grant errors.
73        error_description: Option<String>,
74
75        #[serde(default, skip_serializing_if = "Option::is_none")]
76        #[cfg_attr(feature = "wasm", tsify(optional))]
77        /// The optional specific error type for invalid grant errors.
78        send_access_error_type: Option<SendAccessTokenInvalidGrantError>,
79    },
80
81    /// Invalid client error, typically due to an invalid client secret or client ID.
82    InvalidClient {
83        #[serde(default, skip_serializing_if = "Option::is_none")]
84        #[cfg_attr(feature = "wasm", tsify(optional))]
85        /// The optional error description for invalid client errors.
86        error_description: Option<String>,
87    },
88
89    /// Unauthorized client error, typically due to an unauthorized client.
90    UnauthorizedClient {
91        #[serde(default, skip_serializing_if = "Option::is_none")]
92        #[cfg_attr(feature = "wasm", tsify(optional))]
93        /// The optional error description for unauthorized client errors.
94        error_description: Option<String>,
95    },
96
97    /// Unsupported grant type error, typically due to an unsupported credential flow.
98    /// Note: during initial feature rollout, this will be used to indicate that the
99    /// feature flag is disabled.
100    UnsupportedGrantType {
101        #[serde(default, skip_serializing_if = "Option::is_none")]
102        #[cfg_attr(feature = "wasm", tsify(optional))]
103        /// The optional error description for unsupported grant type errors.
104        error_description: Option<String>,
105    },
106
107    /// Invalid scope error, typically due to an invalid scope requested.
108    InvalidScope {
109        #[serde(default, skip_serializing_if = "Option::is_none")]
110        #[cfg_attr(feature = "wasm", tsify(optional))]
111        /// The optional error description for invalid scope errors.
112        error_description: Option<String>,
113    },
114
115    /// Invalid target error which is shown if the requested
116    /// resource is invalid, missing, unknown, or malformed.
117    InvalidTarget {
118        #[serde(default, skip_serializing_if = "Option::is_none")]
119        #[cfg_attr(feature = "wasm", tsify(optional))]
120        /// The optional error description for invalid target errors.
121        error_description: Option<String>,
122    },
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    mod send_access_token_invalid_request_error_tests {
130        use serde_json::{Value, from_str, json, to_string, to_value};
131
132        use super::*;
133
134        #[test]
135        fn invalid_request_variants_serde_tests() {
136            // (expected_variant, send_access_error_type)
137            let cases: &[(SendAccessTokenInvalidRequestError, &str)] = &[
138                (
139                    SendAccessTokenInvalidRequestError::SendIdRequired,
140                    "\"send_id_required\"",
141                ),
142                (
143                    SendAccessTokenInvalidRequestError::PasswordHashB64Required,
144                    "\"password_hash_b64_required\"",
145                ),
146                (
147                    SendAccessTokenInvalidRequestError::EmailRequired,
148                    "\"email_required\"",
149                ),
150                (
151                    SendAccessTokenInvalidRequestError::EmailAndOtpRequired,
152                    "\"email_and_otp_required\"",
153                ),
154            ];
155
156            for (expected_variant, send_access_error_type_json) in cases {
157                // Deserialize from send_access_error_type to enum
158                let error_from_send_access_error_type: SendAccessTokenInvalidRequestError =
159                    from_str(send_access_error_type_json).unwrap();
160                assert_eq!(
161                    &error_from_send_access_error_type, expected_variant,
162                    "send_access_error_type should map to the expected variant"
163                );
164
165                // Serializing enum -> JSON string containing send_access_error_type
166                let json_from_variant = to_string(expected_variant).unwrap();
167                assert_eq!(
168                    json_from_variant, *send_access_error_type_json,
169                    "serialization should emit the send_access_error_type_json"
170                );
171
172                // Type-safe check: to_value() → Value::String, then compare the
173                // code; this avoids formatting/quoting concerns from to_string().
174                let value_from_variant = to_value(expected_variant).unwrap();
175                assert_eq!(
176                    value_from_variant,
177                    Value::String(send_access_error_type_json.trim_matches('"').to_string()),
178                    "serialization as value should match json generated from enum"
179                );
180
181                // Round-trip: send_access_error_type -> enum -> send_access_error_type
182                let round_tripped_code = to_string(&error_from_send_access_error_type).unwrap();
183                assert_eq!(
184                    round_tripped_code, *send_access_error_type_json,
185                    "round-trip should preserve the send_access_error_type_json"
186                );
187            }
188        }
189
190        #[test]
191        fn invalid_request_full_payload_with_both_fields_parses() {
192            let payload = json!({
193                "error": "invalid_request",
194                "error_description": "send_id is required.",
195                "send_access_error_type": "send_id_required"
196            })
197            .to_string();
198
199            let parsed: SendAccessTokenApiErrorResponse = from_str(&payload).unwrap();
200            match parsed {
201                SendAccessTokenApiErrorResponse::InvalidRequest {
202                    error_description,
203                    send_access_error_type,
204                } => {
205                    assert_eq!(error_description.as_deref(), Some("send_id is required."));
206                    assert_eq!(
207                        send_access_error_type,
208                        Some(SendAccessTokenInvalidRequestError::SendIdRequired)
209                    );
210                }
211                _ => panic!("expected invalid_request"),
212            }
213        }
214
215        #[test]
216        fn invalid_request_payload_without_description_is_allowed() {
217            let payload = r#"
218            {
219                "error": "invalid_request",
220                "send_access_error_type": "email_required"
221            }"#;
222
223            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
224            match parsed {
225                SendAccessTokenApiErrorResponse::InvalidRequest {
226                    error_description,
227                    send_access_error_type,
228                } => {
229                    assert!(error_description.is_none());
230                    assert_eq!(
231                        send_access_error_type,
232                        Some(SendAccessTokenInvalidRequestError::EmailRequired)
233                    );
234                }
235                _ => panic!("expected invalid_request"),
236            }
237        }
238
239        #[test]
240        fn invalid_request_unknown_code_maps_to_unknown() {
241            let payload = r#"
242            {
243                "error": "invalid_request",
244                "error_description": "something new",
245                "send_access_error_type": "brand_new_code"
246            }"#;
247
248            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
249            match parsed {
250                SendAccessTokenApiErrorResponse::InvalidRequest {
251                    error_description,
252                    send_access_error_type,
253                } => {
254                    assert_eq!(error_description.as_deref(), Some("something new"));
255                    assert_eq!(
256                        send_access_error_type,
257                        Some(SendAccessTokenInvalidRequestError::Unknown)
258                    );
259                }
260                _ => panic!("expected invalid_request"),
261            }
262        }
263
264        #[test]
265        fn invalid_request_minimal_payload_is_allowed() {
266            let payload = r#"{ "error": "invalid_request" }"#;
267            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
268            match parsed {
269                SendAccessTokenApiErrorResponse::InvalidRequest {
270                    error_description,
271                    send_access_error_type,
272                } => {
273                    assert!(error_description.is_none());
274                    assert!(send_access_error_type.is_none());
275                }
276                _ => panic!("expected invalid_request"),
277            }
278        }
279
280        #[test]
281        fn invalid_request_null_fields_become_none() {
282            let payload = r#"
283            {
284                "error": "invalid_request",
285                "error_description": null,
286                "send_access_error_type": null
287            }"#;
288
289            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
290            match parsed {
291                SendAccessTokenApiErrorResponse::InvalidRequest {
292                    error_description,
293                    send_access_error_type,
294                } => {
295                    assert!(error_description.is_none());
296                    assert!(send_access_error_type.is_none());
297                }
298                _ => panic!("expected invalid_request"),
299            }
300        }
301    }
302
303    mod send_access_token_invalid_grant_error_tests {
304        use serde_json::{Value, from_str, json, to_string, to_value};
305
306        use super::*;
307
308        #[test]
309        fn invalid_grant_variants_serde_tests() {
310            // (expected_variant, send_access_error_type)
311            let cases: &[(SendAccessTokenInvalidGrantError, &str)] = &[
312                (
313                    SendAccessTokenInvalidGrantError::SendIdInvalid,
314                    "\"send_id_invalid\"",
315                ),
316                (
317                    SendAccessTokenInvalidGrantError::PasswordHashB64Invalid,
318                    "\"password_hash_b64_invalid\"",
319                ),
320            ];
321
322            for (expected_variant, send_access_error_type_json) in cases {
323                // Deserialize from send_access_error_type to enum
324                let error_from_send_access_error_type: SendAccessTokenInvalidGrantError =
325                    from_str(send_access_error_type_json).unwrap();
326                assert_eq!(
327                    &error_from_send_access_error_type, expected_variant,
328                    "send_access_error_type should map to the expected variant"
329                );
330
331                // Serializing enum -> JSON string containing send_access_error_type
332                let json_from_variant = to_string(expected_variant).unwrap();
333                assert_eq!(
334                    json_from_variant, *send_access_error_type_json,
335                    "serialization should emit the send_access_error_type_json"
336                );
337
338                // Type-safe check: to_value() → Value::String
339                let value_from_variant = to_value(expected_variant).unwrap();
340                assert_eq!(
341                    value_from_variant,
342                    Value::String(send_access_error_type_json.trim_matches('"').to_string()),
343                    "serialization as value should match json generated from enum"
344                );
345
346                // Round-trip: send_access_error_type -> enum -> send_access_error_type
347                let round_tripped_code = to_string(&error_from_send_access_error_type).unwrap();
348                assert_eq!(
349                    round_tripped_code, *send_access_error_type_json,
350                    "round-trip should preserve the send_access_error_type_json"
351                );
352            }
353        }
354
355        #[test]
356        fn invalid_grant_full_payload_with_both_fields_parses() {
357            let payload = json!({
358                "error": "invalid_grant",
359                "error_description": "password_hash_b64 is invalid.",
360                "send_access_error_type": "password_hash_b64_invalid"
361            })
362            .to_string();
363
364            let parsed: SendAccessTokenApiErrorResponse = from_str(&payload).unwrap();
365            match parsed {
366                SendAccessTokenApiErrorResponse::InvalidGrant {
367                    error_description,
368                    send_access_error_type,
369                } => {
370                    assert_eq!(
371                        error_description.as_deref(),
372                        Some("password_hash_b64 is invalid.")
373                    );
374                    assert_eq!(
375                        send_access_error_type,
376                        Some(SendAccessTokenInvalidGrantError::PasswordHashB64Invalid)
377                    );
378                }
379                _ => panic!("expected invalid_grant"),
380            }
381        }
382
383        #[test]
384        fn invalid_grant_payload_without_description_is_allowed() {
385            let payload = r#"
386            {
387                "error": "invalid_grant",
388                "send_access_error_type": "password_hash_b64_invalid"
389            }"#;
390
391            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
392            match parsed {
393                SendAccessTokenApiErrorResponse::InvalidGrant {
394                    error_description,
395                    send_access_error_type,
396                } => {
397                    assert!(error_description.is_none());
398                    assert_eq!(
399                        send_access_error_type,
400                        Some(SendAccessTokenInvalidGrantError::PasswordHashB64Invalid)
401                    );
402                }
403                _ => panic!("expected invalid_grant"),
404            }
405        }
406
407        #[test]
408        fn invalid_grant_unknown_code_maps_to_unknown() {
409            let payload = r#"
410            {
411                "error": "invalid_grant",
412                "error_description": "new server-side reason",
413                "send_access_error_type": "brand_new_grant_code"
414            }"#;
415
416            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
417            match parsed {
418                SendAccessTokenApiErrorResponse::InvalidGrant {
419                    error_description,
420                    send_access_error_type,
421                } => {
422                    assert_eq!(error_description.as_deref(), Some("new server-side reason"));
423                    assert_eq!(
424                        send_access_error_type,
425                        Some(SendAccessTokenInvalidGrantError::Unknown)
426                    );
427                }
428                _ => panic!("expected invalid_grant"),
429            }
430        }
431
432        #[test]
433        fn invalid_grant_minimal_payload_is_allowed() {
434            let payload = r#"{ "error": "invalid_grant" }"#;
435            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
436            match parsed {
437                SendAccessTokenApiErrorResponse::InvalidGrant {
438                    error_description,
439                    send_access_error_type,
440                } => {
441                    assert!(error_description.is_none());
442                    assert!(send_access_error_type.is_none());
443                }
444                _ => panic!("expected invalid_grant"),
445            }
446        }
447
448        #[test]
449        fn invalid_grant_null_fields_become_none() {
450            let payload = r#"
451            {
452                "error": "invalid_grant",
453                "error_description": null,
454                "send_access_error_type": null
455            }"#;
456
457            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
458            match parsed {
459                SendAccessTokenApiErrorResponse::InvalidGrant {
460                    error_description,
461                    send_access_error_type,
462                } => {
463                    assert!(error_description.is_none());
464                    assert!(send_access_error_type.is_none());
465                }
466                _ => panic!("expected invalid_grant"),
467            }
468        }
469    }
470
471    mod send_access_token_invalid_client_error_tests {
472        use serde_json::{from_str, json, to_value};
473
474        use super::*;
475
476        #[test]
477        fn invalid_client_full_payload_with_description_parses() {
478            let payload = json!({
479                "error": "invalid_client",
480                "error_description": "Invalid client credentials."
481            })
482            .to_string();
483
484            let parsed: SendAccessTokenApiErrorResponse = from_str(&payload).unwrap();
485            match parsed {
486                SendAccessTokenApiErrorResponse::InvalidClient { error_description } => {
487                    assert_eq!(
488                        error_description.as_deref(),
489                        Some("Invalid client credentials.")
490                    );
491                }
492                _ => panic!("expected invalid_client"),
493            }
494        }
495
496        #[test]
497        fn invalid_client_without_description_is_allowed() {
498            let payload = r#"{ "error": "invalid_client" }"#;
499
500            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
501            match parsed {
502                SendAccessTokenApiErrorResponse::InvalidClient { error_description } => {
503                    assert!(error_description.is_none());
504                }
505                _ => panic!("expected invalid_client"),
506            }
507        }
508
509        #[test]
510        fn invalid_client_serializes_back() {
511            let value = SendAccessTokenApiErrorResponse::InvalidClient {
512                error_description: Some("Invalid client credentials.".into()),
513            };
514            let j = to_value(value).unwrap();
515            assert_eq!(
516                j,
517                json!({
518                    "error": "invalid_client",
519                    "error_description": "Invalid client credentials."
520                })
521            );
522        }
523
524        #[test]
525        fn invalid_client_minimal_payload_is_allowed() {
526            let payload = r#"{ "error": "invalid_client" }"#;
527            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
528            match parsed {
529                SendAccessTokenApiErrorResponse::InvalidClient { error_description } => {
530                    assert!(error_description.is_none());
531                }
532                _ => panic!("expected invalid_client"),
533            }
534        }
535
536        #[test]
537        fn invalid_client_null_description_becomes_none() {
538            let payload = r#"
539            {
540                "error": "invalid_client",
541                "error_description": null
542            }"#;
543
544            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
545            match parsed {
546                SendAccessTokenApiErrorResponse::InvalidClient { error_description } => {
547                    assert!(error_description.is_none());
548                }
549                _ => panic!("expected invalid_client"),
550            }
551        }
552
553        #[test]
554        fn invalid_client_ignores_send_access_error_type_and_extra_fields() {
555            let payload = r#"
556            {
557                "error": "invalid_client",
558                "send_access_error_type": "should_be_ignored",
559                "extra_field": 123,
560                "error_description": "desc"
561            }"#;
562
563            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
564            match parsed {
565                SendAccessTokenApiErrorResponse::InvalidClient { error_description } => {
566                    assert_eq!(error_description.as_deref(), Some("desc"));
567                }
568                _ => panic!("expected invalid_client"),
569            }
570        }
571    }
572
573    mod send_access_token_unauthorized_client_error_tests {
574        use serde_json::{from_str, json, to_value};
575
576        use super::*;
577
578        #[test]
579        fn unauthorized_client_full_payload_with_description_parses() {
580            let payload = json!({
581                "error": "unauthorized_client",
582                "error_description": "Client not permitted to use this grant."
583            })
584            .to_string();
585
586            let parsed: SendAccessTokenApiErrorResponse = from_str(&payload).unwrap();
587            match parsed {
588                SendAccessTokenApiErrorResponse::UnauthorizedClient { error_description } => {
589                    assert_eq!(
590                        error_description.as_deref(),
591                        Some("Client not permitted to use this grant.")
592                    );
593                }
594                _ => panic!("expected unauthorized_client"),
595            }
596        }
597
598        #[test]
599        fn unauthorized_client_without_description_is_allowed() {
600            let payload = r#"{ "error": "unauthorized_client" }"#;
601
602            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
603            match parsed {
604                SendAccessTokenApiErrorResponse::UnauthorizedClient { error_description } => {
605                    assert!(error_description.is_none());
606                }
607                _ => panic!("expected unauthorized_client"),
608            }
609        }
610
611        #[test]
612        fn unauthorized_client_serializes_back() {
613            let value = SendAccessTokenApiErrorResponse::UnauthorizedClient {
614                error_description: None,
615            };
616            let j = to_value(value).unwrap();
617            assert_eq!(j, json!({ "error": "unauthorized_client" }));
618        }
619
620        #[test]
621        fn unauthorized_client_minimal_payload_is_allowed() {
622            let payload = r#"{ "error": "unauthorized_client" }"#;
623            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
624            match parsed {
625                SendAccessTokenApiErrorResponse::UnauthorizedClient { error_description } => {
626                    assert!(error_description.is_none());
627                }
628                _ => panic!("expected unauthorized_client"),
629            }
630        }
631
632        #[test]
633        fn unauthorized_client_null_description_becomes_none() {
634            let payload = r#"
635            {
636                "error": "unauthorized_client",
637                "error_description": null
638            }"#;
639
640            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
641            match parsed {
642                SendAccessTokenApiErrorResponse::UnauthorizedClient { error_description } => {
643                    assert!(error_description.is_none());
644                }
645                _ => panic!("expected unauthorized_client"),
646            }
647        }
648
649        #[test]
650        fn unauthorized_client_ignores_send_access_error_type_and_extra_fields() {
651            let payload = r#"
652            {
653                "error": "unauthorized_client",
654                "send_access_error_type": "should_be_ignored",
655                "extra_field": true
656            }"#;
657
658            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
659            match parsed {
660                SendAccessTokenApiErrorResponse::UnauthorizedClient { error_description } => {
661                    assert!(error_description.is_none());
662                }
663                _ => panic!("expected unauthorized_client"),
664            }
665        }
666    }
667
668    mod send_access_token_unsupported_grant_type_error_tests {
669        use serde_json::{from_str, json, to_value};
670
671        use super::*;
672
673        #[test]
674        fn unsupported_grant_type_full_payload_with_description_parses() {
675            let payload = json!({
676                "error": "unsupported_grant_type",
677                "error_description": "This grant type is not enabled."
678            })
679            .to_string();
680
681            let parsed: SendAccessTokenApiErrorResponse = from_str(&payload).unwrap();
682            match parsed {
683                SendAccessTokenApiErrorResponse::UnsupportedGrantType { error_description } => {
684                    assert_eq!(
685                        error_description.as_deref(),
686                        Some("This grant type is not enabled.")
687                    );
688                }
689                _ => panic!("expected unsupported_grant_type"),
690            }
691        }
692
693        #[test]
694        fn unsupported_grant_type_without_description_is_allowed() {
695            let payload = r#"{ "error": "unsupported_grant_type" }"#;
696
697            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
698            match parsed {
699                SendAccessTokenApiErrorResponse::UnsupportedGrantType { error_description } => {
700                    assert!(error_description.is_none());
701                }
702                _ => panic!("expected unsupported_grant_type"),
703            }
704        }
705
706        #[test]
707        fn unsupported_grant_type_serializes_back() {
708            let value = SendAccessTokenApiErrorResponse::UnsupportedGrantType {
709                error_description: Some("Disabled by feature flag".into()),
710            };
711            let j = to_value(value).unwrap();
712            assert_eq!(
713                j,
714                json!({
715                    "error": "unsupported_grant_type",
716                    "error_description": "Disabled by feature flag"
717                })
718            );
719        }
720
721        #[test]
722        fn unsupported_grant_type_minimal_payload_is_allowed() {
723            let payload = r#"{ "error": "unsupported_grant_type" }"#;
724            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
725            match parsed {
726                SendAccessTokenApiErrorResponse::UnsupportedGrantType { error_description } => {
727                    assert!(error_description.is_none());
728                }
729                _ => panic!("expected unsupported_grant_type"),
730            }
731        }
732
733        #[test]
734        fn unsupported_grant_type_null_description_becomes_none() {
735            let payload = r#"
736        {
737          "error": "unsupported_grant_type",
738          "error_description": null
739        }"#;
740
741            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
742            match parsed {
743                SendAccessTokenApiErrorResponse::UnsupportedGrantType { error_description } => {
744                    assert!(error_description.is_none());
745                }
746                _ => panic!("expected unsupported_grant_type"),
747            }
748        }
749
750        #[test]
751        fn unsupported_grant_type_ignores_send_access_error_type_and_extra_fields() {
752            let payload = r#"
753            {
754                "error": "unsupported_grant_type",
755                "send_access_error_type": "should_be_ignored",
756                "extra_field": "noise"
757            }"#;
758
759            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
760            match parsed {
761                SendAccessTokenApiErrorResponse::UnsupportedGrantType { error_description } => {
762                    assert!(error_description.is_none());
763                }
764                _ => panic!("expected unsupported_grant_type"),
765            }
766        }
767    }
768
769    mod send_access_token_invalid_scope_error_tests {
770        use serde_json::{from_str, json, to_value};
771
772        use super::*;
773
774        #[test]
775        fn invalid_scope_full_payload_with_description_parses() {
776            let payload = json!({
777                "error": "invalid_scope",
778                "error_description": "Requested scope is not allowed."
779            })
780            .to_string();
781
782            let parsed: SendAccessTokenApiErrorResponse = from_str(&payload).unwrap();
783            match parsed {
784                SendAccessTokenApiErrorResponse::InvalidScope { error_description } => {
785                    assert_eq!(
786                        error_description.as_deref(),
787                        Some("Requested scope is not allowed.")
788                    );
789                }
790                _ => panic!("expected invalid_scope"),
791            }
792        }
793
794        #[test]
795        fn invalid_scope_without_description_is_allowed() {
796            let payload = r#"{ "error": "invalid_scope" }"#;
797
798            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
799            match parsed {
800                SendAccessTokenApiErrorResponse::InvalidScope { error_description } => {
801                    assert!(error_description.is_none());
802                }
803                _ => panic!("expected invalid_scope"),
804            }
805        }
806
807        #[test]
808        fn invalid_scope_serializes_back() {
809            let value = SendAccessTokenApiErrorResponse::InvalidScope {
810                error_description: None,
811            };
812            let j = to_value(value).unwrap();
813            assert_eq!(j, json!({ "error": "invalid_scope" }));
814        }
815
816        #[test]
817        fn invalid_scope_minimal_payload_is_allowed() {
818            let payload = r#"{ "error": "invalid_scope" }"#;
819            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
820            match parsed {
821                SendAccessTokenApiErrorResponse::InvalidScope { error_description } => {
822                    assert!(error_description.is_none());
823                }
824                _ => panic!("expected invalid_scope"),
825            }
826        }
827
828        #[test]
829        fn invalid_scope_null_description_becomes_none() {
830            let payload = r#"
831        {
832          "error": "invalid_scope",
833          "error_description": null
834        }"#;
835
836            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
837            match parsed {
838                SendAccessTokenApiErrorResponse::InvalidScope { error_description } => {
839                    assert!(error_description.is_none());
840                }
841                _ => panic!("expected invalid_scope"),
842            }
843        }
844
845        #[test]
846        fn invalid_scope_ignores_send_access_error_type_and_extra_fields() {
847            let payload = r#"
848            {
849                "error": "invalid_scope",
850                "send_access_error_type": "should_be_ignored",
851                "extra_field": [1,2,3]
852            }"#;
853
854            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
855            match parsed {
856                SendAccessTokenApiErrorResponse::InvalidScope { error_description } => {
857                    assert!(error_description.is_none());
858                }
859                _ => panic!("expected invalid_scope"),
860            }
861        }
862    }
863
864    mod send_access_token_invalid_target_error_tests {
865        use serde_json::{from_str, json, to_value};
866
867        use super::*;
868
869        #[test]
870        fn invalid_target_full_payload_with_description_parses() {
871            let payload = json!({
872                "error": "invalid_target",
873                "error_description": "Unknown or disallowed resource indicator."
874            })
875            .to_string();
876
877            let parsed: SendAccessTokenApiErrorResponse = from_str(&payload).unwrap();
878            match parsed {
879                SendAccessTokenApiErrorResponse::InvalidTarget { error_description } => {
880                    assert_eq!(
881                        error_description.as_deref(),
882                        Some("Unknown or disallowed resource indicator.")
883                    );
884                }
885                _ => panic!("expected invalid_target"),
886            }
887        }
888
889        #[test]
890        fn invalid_target_without_description_is_allowed() {
891            let payload = r#"{ "error": "invalid_target" }"#;
892
893            let parsed: SendAccessTokenApiErrorResponse = serde_json::from_str(payload).unwrap();
894            match parsed {
895                SendAccessTokenApiErrorResponse::InvalidTarget { error_description } => {
896                    assert!(error_description.is_none());
897                }
898                _ => panic!("expected invalid_target"),
899            }
900        }
901
902        #[test]
903        fn invalid_target_serializes_back() {
904            let value = SendAccessTokenApiErrorResponse::InvalidTarget {
905                error_description: Some("Bad resource parameter".into()),
906            };
907            let j = to_value(value).unwrap();
908            assert_eq!(
909                j,
910                json!({
911                    "error": "invalid_target",
912                    "error_description": "Bad resource parameter"
913                })
914            );
915        }
916
917        #[test]
918        fn invalid_target_minimal_payload_is_allowed() {
919            let payload = r#"{ "error": "invalid_target" }"#;
920            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
921            match parsed {
922                SendAccessTokenApiErrorResponse::InvalidTarget { error_description } => {
923                    assert!(error_description.is_none());
924                }
925                _ => panic!("expected invalid_target"),
926            }
927        }
928
929        #[test]
930        fn invalid_target_null_description_becomes_none() {
931            let payload = r#"
932        {
933          "error": "invalid_target",
934          "error_description": null
935        }"#;
936
937            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
938            match parsed {
939                SendAccessTokenApiErrorResponse::InvalidTarget { error_description } => {
940                    assert!(error_description.is_none());
941                }
942                _ => panic!("expected invalid_target"),
943            }
944        }
945
946        #[test]
947        fn invalid_target_ignores_send_access_error_type_and_extra_fields() {
948            let payload = r#"
949            {
950                "error": "invalid_target",
951                "send_access_error_type": "should_be_ignored",
952                "extra_field": {"k":"v"}
953            }"#;
954
955            let parsed: SendAccessTokenApiErrorResponse = from_str(payload).unwrap();
956            match parsed {
957                SendAccessTokenApiErrorResponse::InvalidTarget { error_description } => {
958                    assert!(error_description.is_none());
959                }
960                _ => panic!("expected invalid_target"),
961            }
962        }
963    }
964
965    #[test]
966    fn unknown_top_level_error_rejects() {
967        let payload = r#"{ "error": "totally_new_error" }"#;
968        let err = serde_json::from_str::<SendAccessTokenApiErrorResponse>(payload).unwrap_err();
969        let _ = err;
970    }
971}