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")]
9pub 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 #[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")]
32pub enum SendAccessTokenInvalidGrantError {
34 #[allow(missing_docs)]
35 SendIdInvalid,
36
37 #[allow(missing_docs)]
38 PasswordHashB64Invalid,
39
40 #[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")]
50pub enum SendAccessTokenApiErrorResponse {
54 InvalidRequest {
57 #[serde(default, skip_serializing_if = "Option::is_none")]
58 #[cfg_attr(feature = "wasm", tsify(optional))]
59 error_description: Option<String>,
61
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 #[cfg_attr(feature = "wasm", tsify(optional))]
64 send_access_error_type: Option<SendAccessTokenInvalidRequestError>,
66 },
67
68 InvalidGrant {
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 #[cfg_attr(feature = "wasm", tsify(optional))]
72 error_description: Option<String>,
74
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 #[cfg_attr(feature = "wasm", tsify(optional))]
77 send_access_error_type: Option<SendAccessTokenInvalidGrantError>,
79 },
80
81 InvalidClient {
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 #[cfg_attr(feature = "wasm", tsify(optional))]
85 error_description: Option<String>,
87 },
88
89 UnauthorizedClient {
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 #[cfg_attr(feature = "wasm", tsify(optional))]
93 error_description: Option<String>,
95 },
96
97 UnsupportedGrantType {
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 #[cfg_attr(feature = "wasm", tsify(optional))]
103 error_description: Option<String>,
105 },
106
107 InvalidScope {
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 #[cfg_attr(feature = "wasm", tsify(optional))]
111 error_description: Option<String>,
113 },
114
115 InvalidTarget {
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 #[cfg_attr(feature = "wasm", tsify(optional))]
120 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 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 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 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 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 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 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 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 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 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 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}