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