1#[cfg(feature = "uniffi")]
2use chrono::NaiveDate;
3use chrono::Utc;
4use heck::ToKebabCase;
5#[cfg(feature = "uniffi")]
6use isocountry::CountryCode;
7use jsonwebtoken::dangerous::insecure_decode;
8use jsonwebtoken::{EncodingKey, Header, decode_header, encode};
9pub use routex_models::{
10 AccountStatus, AccountType, Amount, ConnectionId, CredentialsModel, DialogContext,
11 DialogOption, Image, InputType, PaymentErrorCode, ProviderErrorCode, SecrecyLevel,
12 ServiceBlockedCode, UnsupportedProductReason,
13};
14#[cfg(feature = "uniffi")]
15use rust_decimal::Decimal;
16use serde::{Deserialize, Serialize, Serializer};
17use serde_json::Value;
18use serde_with::base64::Base64;
19use std::any::Any;
20use std::fmt;
21use std::marker::PhantomData;
22use std::net::IpAddr;
23use std::str::FromStr;
24use url::Url;
25use uuid::Uuid;
26
27#[cfg(feature = "uniffi")]
28uniffi::setup_scaffolding!();
29
30#[cfg(feature = "uniffi")]
31uniffi::custom_type!(ConnectionData, Vec<u8>, {
32 try_lift: |val| Ok(val.into()),
33 lower: |obj| obj.into(),
34});
35
36#[cfg(feature = "uniffi")]
37uniffi::custom_type!(Session, Vec<u8>, {
38 try_lift: |val| Ok(val.into()),
39 lower: |obj| obj.into(),
40});
41
42#[cfg(feature = "uniffi")]
43uniffi::use_remote_type!(routex_models::CountryCode);
44
45#[cfg(feature = "uniffi")]
46uniffi::use_remote_type!(routex_models::Decimal);
47
48#[cfg(feature = "uniffi")]
49uniffi::custom_type!(NaiveDate, String, {
50 remote,
51 try_lift: |val| Ok(val.parse()?),
52 lower: |obj| obj.to_string(),
53});
54
55type DateTime = chrono::DateTime<Utc>;
56
57#[cfg(feature = "uniffi")]
58uniffi::custom_type!(DateTime, String, {
59 remote,
60 try_lift: |val| Ok(val.parse()?),
61 lower: |obj| obj.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
62});
63
64pub type Dialog<S> = routex_models::Dialog<ConfirmationContext<S>, InputContext<S>>;
65pub type DialogInput<S> = routex_models::DialogInput<ConfirmationContext<S>, InputContext<S>>;
66
67#[must_use]
74pub struct Path(Vec<String>);
75
76impl Path {
77 fn new<I>(segments: I) -> Self
78 where
79 I: IntoIterator,
80 I::Item: ToString,
81 {
82 Self(segments.into_iter().map(|s| s.to_string()).collect())
83 }
84
85 #[must_use]
86 pub fn to_url(&self, base: &Url) -> Url {
92 let mut url = base.clone();
93 url.path_segments_mut()
94 .expect("cannot be base")
95 .extend(&self.0);
96 url
97 }
98}
99
100impl fmt::Display for Path {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 for segment in &self.0 {
103 f.write_str("/")?;
104 f.write_str(segment)?;
105 }
106
107 Ok(())
108 }
109}
110
111pub mod headers {
112 use http::HeaderName;
113
114 pub static SESSION_ID: HeaderName = HeaderName::from_static("yaxi-session-id");
115
116 pub static TICKET: HeaderName = HeaderName::from_static("yaxi-ticket");
117
118 pub static TICKET_ID: HeaderName = HeaderName::from_static("yaxi-ticket-id");
119
120 pub static REDIRECT_URI: HeaderName = HeaderName::from_static("yaxi-redirect-uri");
121
122 pub static TRACE_ID: HeaderName = HeaderName::from_static("yaxi-trace-id");
123}
124
125#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
135#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
136pub enum UserInSession {
137 #[serde(rename = "connection")]
139 OnThisConnection,
140 #[serde(untagged)]
142 At(IpAddr),
143}
144
145#[cfg(feature = "uniffi")]
146uniffi::custom_type!(IpAddr, String, {
147 remote,
148 try_lift: |val| Ok(val.parse()?),
149 lower: |obj| obj.to_string(),
150});
151
152pub const CURRENT_MEDIA_TYPE: &str = "application/vnd.yaxi.v6";
153
154pub mod keys {
155 use super::Path;
156 use clerk_report::PublishedVersionEntry;
157 use serde::{Deserialize, Serialize};
158 use serde_with::base64::Base64;
159
160 pub fn settlement_path() -> Path {
166 Path::new(["key-settlement"])
167 }
168
169 #[serde_with::serde_as]
170 #[derive(Serialize, Deserialize, Clone, Debug)]
171 #[serde(rename_all = "camelCase")]
172 pub struct Request {
173 #[serde_as(as = "Base64")]
174 pub public_key: Vec<u8>,
175 pub version: Option<u8>,
176 }
177
178 #[serde_with::serde_as]
180 #[derive(Serialize, Deserialize)]
181 #[serde(rename_all = "camelCase")]
182 pub struct SettlementBoxMessage {
183 #[serde_as(as = "Base64")]
184 pub public_key: Vec<u8>,
185 #[serde_as(as = "Base64")]
186 pub session_id: [u8; 32],
187 }
188
189 #[serde_with::serde_as]
191 #[derive(Serialize, Deserialize, Clone, Debug)]
192 #[serde(rename_all = "camelCase")]
193 pub struct Response {
194 #[serde_as(as = "Base64")]
196 pub attestation_report: Vec<u8>,
197 pub vcek: String,
199 #[serde_as(as = "Base64")]
201 pub chacha_box: Vec<u8>,
202 pub system_version: PublishedVersionEntry,
205 #[serde(default = "default_vcpus")]
207 pub vcpus: usize,
208 }
209
210 fn default_vcpus() -> usize {
211 2
212 }
213}
214
215pub mod traces {
216 use super::Path;
217
218 pub fn path(trace_id: &str) -> Path {
223 Path::new(["traces", trace_id])
224 }
225}
226
227pub mod redirects {
228 use super::Path;
229 use serde::{Deserialize, Serialize};
230 use url::Url;
231
232 pub fn path() -> Path {
238 Path::new(["redirects"])
239 }
240
241 #[derive(Serialize, Deserialize, Clone, Debug)]
242 #[serde(rename_all = "camelCase")]
243 pub struct Request {
244 pub handle: String,
245 pub redirect_uri: String,
246 }
247
248 #[derive(Serialize, Deserialize, Clone, Debug)]
249 #[serde(rename_all = "camelCase")]
250 pub struct Response {
251 #[serde(serialize_with = "super::serialize_url")]
252 pub redirect_url: Url,
253 }
254}
255
256pub mod info {
257 use super::Path;
258 pub use isocountry::CountryCode;
259 use routex_models::{ConnectionId, CredentialsModel};
260 use serde::{Deserialize, Serialize};
261 #[cfg(feature = "server")]
262 use serde_with::base64::Base64;
263
264 pub fn search_path() -> Path {
270 Path::new(["search"])
271 }
272
273 #[derive(Serialize, Deserialize, Clone, Debug)]
274 #[serde(rename_all = "camelCase")]
275 #[non_exhaustive]
276 pub struct Request {
277 pub filters: Vec<SearchFilter>,
278 #[serde(default)]
279 pub iban_detection: bool,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub limit: Option<usize>,
282 #[serde(default)]
283 #[serde(skip_serializing_if = "Vec::is_empty")]
284 pub details: Vec<Details>,
285 }
286
287 impl Request {
288 pub fn new(filters: impl IntoIterator<Item = SearchFilter>) -> Self {
289 Self {
290 filters: filters.into_iter().collect(),
291 iban_detection: false,
292 limit: None,
293 details: Vec::new(),
294 }
295 }
296 }
297
298 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
300 #[serde(rename_all = "camelCase")]
301 #[non_exhaustive]
302 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
303 pub enum Details {
304 Bics,
305 BankCodes,
306 }
307
308 #[serde_with::serde_as]
312 #[derive(Serialize, Deserialize, Clone, Debug)]
313 #[serde(rename_all = "camelCase")]
314 #[serde(rename_all_fields = "camelCase")]
315 pub enum SearchFilter {
316 Types(Vec<ConnectionType>),
318 Countries(Vec<CountryCode>),
320 Name(String),
322 Bic(String),
324 BankCode(String),
326 Term(String),
328 #[cfg(feature = "server")]
329 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
330 }
331
332 #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
334 #[non_exhaustive]
335 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
336 pub enum ConnectionType {
337 Production,
339 Sandboxes,
341 }
342
343 pub fn fetch_path(connection_id: &str) -> Path {
348 Path::new(["info", connection_id])
349 }
350
351 #[allow(clippy::module_name_repetitions)]
353 #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
354 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
355 #[serde(rename_all = "camelCase")]
356 #[cfg_attr(not(feature = "server"), non_exhaustive)]
357 pub struct ConnectionInfo {
358 pub id: ConnectionId,
360
361 pub countries: Vec<CountryCode>,
363
364 pub display_name: String,
366
367 pub credentials: CredentialsModel,
369
370 #[serde(skip_serializing_if = "Option::is_none")]
372 #[cfg_attr(feature = "uniffi", uniffi(default))]
373 pub user_id: Option<String>,
374
375 #[serde(skip_serializing_if = "Option::is_none")]
377 #[cfg_attr(feature = "uniffi", uniffi(default))]
378 pub password: Option<String>,
379
380 #[serde(skip_serializing_if = "Option::is_none")]
382 #[cfg_attr(feature = "uniffi", uniffi(default))]
383 pub advice: Option<String>,
384
385 pub logo_id: String,
387
388 #[allow(clippy::doc_markdown)]
389 #[serde(skip_serializing_if = "Option::is_none")]
393 #[cfg_attr(feature = "uniffi", uniffi(default))]
394 pub bics: Option<Vec<String>>,
395
396 #[serde(skip_serializing_if = "Option::is_none")]
400 #[cfg_attr(feature = "uniffi", uniffi(default))]
401 pub bank_codes: Option<Vec<String>>,
402
403 #[serde(default, skip_serializing_if = "Vec::is_empty")]
405 #[cfg_attr(feature = "uniffi", uniffi(default))]
406 pub labels: Vec<String>,
407 }
408}
409
410macro_rules! context {
411 ($name:ident) => {
412 #[serde_with::serde_as]
413 #[derive(Serialize, Deserialize)]
414 #[serde(transparent)]
415 pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
416
417 impl<S> PartialEq for $name<S> {
418 fn eq(&self, other: &Self) -> bool {
419 self.0 == other.0
420 }
421 }
422
423 impl<S> Eq for $name<S> {}
424
425 impl<S> Clone for $name<S> {
426 fn clone(&self) -> Self {
427 Self(self.0.clone(), self.1)
428 }
429 }
430
431 impl<S> fmt::Debug for $name<S> {
432 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
433 f.debug_tuple("$name")
434 .field(&self.0)
435 .field(&self.1)
436 .finish()
437 }
438 }
439
440 impl<S> From<Vec<u8>> for $name<S> {
441 fn from(value: Vec<u8>) -> Self {
442 Self(value, PhantomData)
443 }
444 }
445
446 impl<S> From<$name<S>> for Vec<u8> {
447 fn from(value: $name<S>) -> Self {
448 value.0
449 }
450 }
451
452 impl<S> AsRef<[u8]> for $name<S> {
453 fn as_ref(&self) -> &[u8] {
454 &self.0
455 }
456 }
457 };
458}
459
460context!(InputContext);
461
462context!(ConfirmationContext);
463
464#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
490#[serde(transparent)]
491pub struct Authenticated<T> {
492 jwt: String,
493 _phantom: PhantomData<T>,
494}
495
496impl<'de, T> Deserialize<'de> for Authenticated<T>
497where
498 T: for<'t> Deserialize<'t> + Clone,
499{
500 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
501 where
502 D: serde::Deserializer<'de>,
503 {
504 String::deserialize(deserializer)?
505 .parse()
506 .map_err(serde::de::Error::custom)
507 }
508}
509
510impl<T> Authenticated<T>
511where
512 T: Serialize,
513{
514 #[must_use]
515 pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
521 let header = Header {
522 kid: Some(key_id.into()),
523 ..Default::default()
524 };
525
526 let jwt = encode(
527 &header,
528 &Claims {
529 data,
530 exp: 2_540_808_000,
532 },
533 &EncodingKey::from_secret(key),
534 )
535 .expect("Encoding should work");
536
537 Self {
538 jwt,
539 _phantom: PhantomData,
540 }
541 }
542}
543
544impl<T> FromStr for Authenticated<T>
545where
546 T: for<'de> Deserialize<'de> + Clone,
547{
548 type Err = jsonwebtoken::errors::Error;
549
550 fn from_str(jwt: &str) -> Result<Self, Self::Err> {
551 decode::<T>(jwt)?;
553 Ok(Self {
554 jwt: jwt.to_string(),
555 _phantom: PhantomData,
556 })
557 }
558}
559
560impl<T> Authenticated<T>
561where
562 T: for<'de> Deserialize<'de> + Clone,
563{
564 #[must_use]
565 pub fn as_str(&self) -> &str {
566 &self.jwt
567 }
568
569 #[must_use]
570 pub fn key_id(&self) -> Option<String> {
576 decode_header(&self.jwt).unwrap().kid
577 }
578
579 #[cfg(feature = "client")]
580 #[must_use]
581 #[allow(clippy::missing_panics_doc)]
582 pub fn to_data(&self) -> T {
583 decode(&self.jwt).unwrap()
584 }
585
586 #[cfg(feature = "server")]
587 pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
593 decoder.decode::<T>(&self.jwt).map(|d| d.data)
594 }
595}
596
597#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
598#[serde(rename_all = "camelCase")]
599pub struct Claims<T> {
600 pub data: T,
601 pub exp: u64,
602}
603
604#[cfg(feature = "server")]
605pub trait Decoder {
606 type Error;
607
608 fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
614 where
615 T: for<'de> Deserialize<'de> + Clone;
616}
617
618#[derive(Serialize, Deserialize, Debug)]
623pub enum OBResponse<S: Service> {
624 #[serde(bound = "S:")]
625 Result(
626 Authenticated<OBResult<S::Output>>,
627 Option<Session>,
628 Option<ConnectionData>,
629 ),
630 #[serde(bound = "S:")]
631 Dialog(Dialog<S>),
632 #[serde(bound = "S:")]
633 Redirect(Redirect<S>),
634 #[serde(bound = "S:")]
635 RedirectHandle(RedirectHandle<S>),
636}
637
638impl<S: Service> Clone for OBResponse<S> {
639 fn clone(&self) -> Self {
640 match self {
641 OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
642 authenticated.clone(),
643 session.clone(),
644 connection_data.clone(),
645 ),
646 OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
647 OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
648 OBResponse::RedirectHandle(redirect_handle) => {
649 OBResponse::RedirectHandle(redirect_handle.clone())
650 }
651 }
652 }
653}
654
655#[derive(Serialize, Deserialize, Debug)]
656#[cfg_attr(not(feature = "server"), non_exhaustive)]
657#[serde(rename_all = "camelCase")]
658pub struct NonInteractiveResponse<S: Service> {
659 #[serde(bound = "S:")]
660 pub result: S::Output,
661 #[serde(skip_serializing_if = "Option::is_none")]
662 pub session: Option<Session>,
663 #[serde(skip_serializing_if = "Option::is_none")]
664 pub connection_data: Option<ConnectionData>,
665}
666
667#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
672#[serde(rename_all = "camelCase")]
673pub struct OBResult<T> {
674 pub data: T,
675 pub ticket_id: Uuid,
676 pub timestamp: DateTime,
677}
678
679#[serde_with::serde_as]
680#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
681pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
682
683impl From<Vec<u8>> for Session {
684 fn from(value: Vec<u8>) -> Self {
685 Session(value)
686 }
687}
688
689impl From<Session> for Vec<u8> {
690 fn from(session: Session) -> Self {
691 session.0
692 }
693}
694
695impl AsRef<[u8]> for Session {
696 fn as_ref(&self) -> &[u8] {
697 &self.0
698 }
699}
700
701#[serde_with::serde_as]
702#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
703#[serde(transparent)]
704pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
705
706impl From<Vec<u8>> for ConnectionData {
707 fn from(value: Vec<u8>) -> Self {
708 ConnectionData(value)
709 }
710}
711
712impl From<ConnectionData> for Vec<u8> {
713 fn from(connection_data: ConnectionData) -> Self {
714 connection_data.0
715 }
716}
717
718impl AsRef<[u8]> for ConnectionData {
719 fn as_ref(&self) -> &[u8] {
720 &self.0
721 }
722}
723
724#[derive(Serialize, Deserialize)]
732#[serde(rename_all = "camelCase")]
733pub struct Redirect<S> {
734 #[serde(serialize_with = "serialize_url")]
735 pub url: Url,
736 #[serde(bound = "S:")]
737 pub context: ConfirmationContext<S>,
738}
739
740impl<S> PartialEq for Redirect<S> {
741 fn eq(&self, other: &Self) -> bool {
742 let Self { url, context } = self;
743 url == &other.url && context == &other.context
744 }
745}
746
747impl<S> Eq for Redirect<S> {}
748
749impl<S> Clone for Redirect<S> {
750 fn clone(&self) -> Self {
751 Self {
752 url: self.url.clone(),
753 context: self.context.clone(),
754 }
755 }
756}
757
758impl<S> fmt::Debug for Redirect<S> {
759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760 f.debug_struct("Redirect")
761 .field("url", &self.url)
762 .field("context", &self.context)
763 .finish()
764 }
765}
766
767fn serialize_url<S: Serializer>(value: &Url, serializer: S) -> Result<S::Ok, S::Error> {
770 let string_repr = value.to_string().replace('+', "%20");
771 serializer.serialize_str(&string_repr)
772}
773
774#[derive(Serialize, Deserialize)]
778#[serde(rename_all = "camelCase")]
779pub struct RedirectHandle<S> {
780 pub handle: String,
781 #[serde(bound = "S:")]
782 pub context: ConfirmationContext<S>,
783}
784
785impl<S> PartialEq for RedirectHandle<S> {
786 fn eq(&self, other: &Self) -> bool {
787 let Self { handle, context } = self;
788 handle == &other.handle && context == &other.context
789 }
790}
791
792impl<S> Eq for RedirectHandle<S> {}
793
794impl<S> Clone for RedirectHandle<S> {
795 fn clone(&self) -> Self {
796 Self {
797 handle: self.handle.clone(),
798 context: self.context.clone(),
799 }
800 }
801}
802
803impl<S> fmt::Debug for RedirectHandle<S> {
804 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805 f.debug_struct("RedirectHandle")
806 .field("handle", &self.handle)
807 .field("context", &self.context)
808 .finish()
809 }
810}
811
812fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
814where
815 T: for<'de> Deserialize<'de> + Clone,
816{
817 insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
818}
819
820macro_rules! enum_with_display {
821 {
822 $(#[$meta:meta])*
823 pub enum $name:ident {
824 $($variant:ident,)+
825 }
826 } => {
827 $(#[$meta])*
828 pub enum $name {
829 $($variant,)+
830 }
831
832 impl fmt::Display for $name {
833 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834 f.write_str(match self {
835 $(Self::$variant => stringify!($variant),)+
836 })
837 }
838 }
839 }
840}
841
842enum_with_display! {
843 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
844 pub enum ServiceId {
845 Accounts,
846 Balances,
847 Transactions,
848 StandingOrders,
849 CollectPayment,
850 Transfer,
851 }
852}
853
854#[derive(Serialize, Deserialize)]
914#[serde(rename_all = "camelCase")]
915#[serde(try_from = "DeserializedTicket<S::TicketData>")]
916pub struct Ticket<S: Service> {
917 pub service: ServiceId,
918 pub id: Uuid,
919 pub data: S::TicketData,
920}
921
922impl<S: Service> PartialEq for Ticket<S> {
923 fn eq(&self, other: &Self) -> bool {
924 let Self { service, id, data } = self;
925 service == &other.service && id == &other.id && data == &other.data
926 }
927}
928
929impl<S: Service> Eq for Ticket<S> {}
930
931impl<S: Service> fmt::Debug for Ticket<S> {
932 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
933 f.debug_struct("Ticket")
934 .field("service", &self.service)
935 .field("id", &self.id)
936 .field("data", &self.data)
937 .finish()
938 }
939}
940
941impl<S: Service> Clone for Ticket<S> {
942 fn clone(&self) -> Self {
943 Self {
944 service: self.service,
945 id: self.id,
946 data: self.data.clone(),
947 }
948 }
949}
950
951#[derive(Deserialize, Debug)]
952struct DeserializedTicket<T> {
953 service: ServiceId,
954 id: Uuid,
955 data: Option<T>,
956}
957
958impl<S: Service> TryFrom<DeserializedTicket<S::TicketData>> for Ticket<S> {
959 type Error = &'static str;
960
961 fn try_from(
962 DeserializedTicket { service, id, data }: DeserializedTicket<S::TicketData>,
963 ) -> Result<Self, Self::Error> {
964 let data = (&() as &dyn Any)
965 .downcast_ref::<S::TicketData>()
966 .cloned()
967 .or(data)
968 .ok_or("missing field `data`")?;
969
970 Ok(Self { service, id, data })
971 }
972}
973
974pub trait Service {
975 const ID: ServiceId;
976
977 type TicketData: Serialize
978 + for<'de> Deserialize<'de>
979 + PartialEq
980 + Eq
981 + Clone
982 + fmt::Debug
983 + Send
984 + Sync
985 + 'static;
986
987 type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
988
989 type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
990
991 fn path() -> Path {
997 Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
998 }
999
1000 fn response_path() -> Path {
1001 Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
1002 }
1003
1004 fn confirmation_path() -> Path {
1005 Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
1006 }
1007}
1008
1009pub trait NonInteractiveService: Service {
1010 fn non_interactive_path() -> Path {
1011 Path::new([&Self::ID.to_string().to_kebab_case(), "non-interactive"])
1012 }
1013}
1014
1015#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1016#[serde(tag = "ticketStatus")]
1017pub enum TicketStatus<T> {
1018 Unfinished,
1019 Error(ErrorKind),
1020 Success(T),
1021}
1022
1023enum_with_display! {
1024 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1025 #[serde(tag = "error")]
1026 pub enum ErrorKind {
1027 UnexpectedError,
1028 Canceled,
1029 InvalidCredentials,
1030 ServiceBlocked,
1031 Unauthorized,
1032 AccessExceeded,
1033 PeriodOutOfBounds,
1034 UnsupportedProduct,
1035 PaymentFailed,
1036 UnexpectedValue,
1037 TicketError,
1038 ProviderError,
1039 NotFound,
1040 }
1041}
1042
1043#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1044#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1045#[serde(rename_all = "camelCase")]
1046pub struct Credentials {
1047 pub connection_id: ConnectionId,
1048 #[serde(skip_serializing_if = "Option::is_none")]
1049 #[cfg_attr(feature = "uniffi", uniffi(default))]
1050 pub user_id: Option<String>,
1051 #[serde(skip_serializing_if = "Option::is_none")]
1052 #[cfg_attr(feature = "uniffi", uniffi(default))]
1053 pub password: Option<String>,
1054 #[serde(skip_serializing_if = "Option::is_none")]
1055 #[cfg_attr(feature = "uniffi", uniffi(default))]
1056 pub connection_data: Option<ConnectionData>,
1057}
1058
1059#[derive(Serialize, Deserialize)]
1060#[serde(rename_all = "camelCase")]
1061pub struct ResponseData<S> {
1062 #[serde(bound = "S:")]
1063 pub context: InputContext<S>,
1064 pub response: String,
1065}
1066
1067impl<S> PartialEq for ResponseData<S> {
1068 fn eq(&self, other: &Self) -> bool {
1069 let Self { context, response } = self;
1070 context == &other.context && response == &other.response
1071 }
1072}
1073
1074impl<S> Eq for ResponseData<S> {}
1075
1076impl<S> Clone for ResponseData<S> {
1077 fn clone(&self) -> Self {
1078 Self {
1079 context: self.context.clone(),
1080 response: self.response.clone(),
1081 }
1082 }
1083}
1084
1085impl<S> fmt::Debug for ResponseData<S> {
1086 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1087 f.debug_struct("ResponseData")
1088 .field("context", &self.context)
1089 .field("response", &self.response)
1090 .finish()
1091 }
1092}
1093
1094#[derive(Serialize, Deserialize)]
1095#[serde(rename_all = "camelCase")]
1096pub struct ConfirmationData<S> {
1097 #[serde(bound = "S:")]
1098 pub context: ConfirmationContext<S>,
1099}
1100
1101impl<S> PartialEq for ConfirmationData<S> {
1102 fn eq(&self, other: &Self) -> bool {
1103 let Self { context } = self;
1104 context == &other.context
1105 }
1106}
1107
1108impl<S> Eq for ConfirmationData<S> {}
1109
1110impl<S> Clone for ConfirmationData<S> {
1111 fn clone(&self) -> Self {
1112 Self {
1113 context: self.context.clone(),
1114 }
1115 }
1116}
1117
1118impl<S> fmt::Debug for ConfirmationData<S> {
1119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1120 f.debug_struct("ConfirmationData")
1121 .field("context", &self.context)
1122 .finish()
1123 }
1124}
1125
1126#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1127#[serde(rename_all_fields = "camelCase")]
1128#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1129pub enum Error {
1130 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1131 UnexpectedError {
1132 #[serde(skip_serializing_if = "Option::is_none")]
1133 user_message: Option<String>,
1134 },
1135 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1136 Canceled {},
1137 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1138 InvalidCredentials {
1139 #[serde(skip_serializing_if = "Option::is_none")]
1140 user_message: Option<String>,
1141 },
1142 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1143 ServiceBlocked {
1144 #[serde(skip_serializing_if = "Option::is_none")]
1145 user_message: Option<String>,
1146 #[serde(skip_serializing_if = "Option::is_none")]
1147 code: Option<ServiceBlockedCode>,
1148 },
1149 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1150 Unauthorized {
1151 #[serde(skip_serializing_if = "Option::is_none")]
1152 user_message: Option<String>,
1153 },
1154 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1161 AccessExceeded {
1162 #[serde(skip_serializing_if = "Option::is_none")]
1163 user_message: Option<String>,
1164 },
1165 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1166 PeriodOutOfBounds {
1167 #[serde(skip_serializing_if = "Option::is_none")]
1168 user_message: Option<String>,
1169 },
1170 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1171 UnsupportedProduct {
1172 #[serde(skip_serializing_if = "Option::is_none")]
1173 reason: Option<UnsupportedProductReason>,
1174 #[serde(skip_serializing_if = "Option::is_none")]
1175 user_message: Option<String>,
1176 },
1177 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1178 PaymentFailed {
1179 #[serde(skip_serializing_if = "Option::is_none")]
1180 code: Option<PaymentErrorCode>,
1181 #[serde(skip_serializing_if = "Option::is_none")]
1182 user_message: Option<String>,
1183 },
1184 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1185 UnexpectedValue { error: String },
1186 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1187 TicketError {
1188 error: String,
1189 code: TicketErrorCode,
1190 },
1191 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1192 ProviderError {
1193 #[serde(skip_serializing_if = "Option::is_none")]
1194 code: Option<ProviderErrorCode>,
1195 #[serde(skip_serializing_if = "Option::is_none")]
1196 user_message: Option<String>,
1197 },
1198 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1199 InterruptError {},
1200}
1201
1202#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1203#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1204#[cfg_attr(not(feature = "server"), non_exhaustive)]
1205pub enum TicketErrorCode {
1206 Missing,
1208 Invalid,
1210 MissingKey,
1212 UnknownKey,
1214 Mismatch,
1216 Expired,
1218 InvalidLifetime,
1220 ExpiredKey,
1222 KeyEnvironmentMismatch,
1224}
1225
1226#[derive(Serialize, Deserialize, Clone, Debug)]
1227#[cfg_attr(not(feature = "server"), non_exhaustive)]
1228pub enum Filter<F> {
1229 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1230 Eq(F, Value),
1231 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1232 NotEq(F, Value),
1233 #[cfg(feature = "server")]
1234 Contains(F, Value),
1235 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1236 And(Box<Self>, Box<Self>),
1237 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1238 Or(Box<Self>, Box<Self>),
1239 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1240 Supports(SupportedService),
1241}
1242
1243#[derive(Serialize, Deserialize, Clone, Debug)]
1244#[cfg_attr(not(feature = "server"), non_exhaustive)]
1245#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1246pub enum SupportedService {
1247 CollectPayment,
1248}
1249
1250impl<F> Filter<F> {
1251 #[must_use]
1252 pub fn and(self, other: Filter<F>) -> Filter<F> {
1254 Filter::And(Box::new(self), Box::new(other))
1255 }
1256
1257 #[must_use]
1258 pub fn or(self, other: Filter<F>) -> Filter<F> {
1260 Filter::Or(Box::new(self), Box::new(other))
1261 }
1262}
1263
1264pub struct Field<F, T> {
1265 field: F,
1266 phantom: PhantomData<T>,
1267}
1268
1269impl<F, T> Field<F, T>
1270where
1271 F: Copy,
1272 T: Serialize,
1273{
1274 pub fn eq(&self, value: T) -> Filter<F> {
1280 Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1281 }
1282
1283 pub fn not_eq(&self, value: T) -> Filter<F> {
1289 Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1290 }
1291}
1292
1293pub trait GetValue {
1294 type Model;
1295
1296 fn get(&self, model: &Self::Model) -> Value;
1297}
1298
1299macro_rules! fields {
1300 {
1301 $model:ty, $enum_name:ident
1302 $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1303 $(
1304 server:
1305 $(($server_variant_name:ident, $server_field_name:ident))+
1306 )?
1307 } => {
1308 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1309 #[non_exhaustive]
1310 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1311 pub enum $enum_name {
1312 $($variant_name,)+
1313 $($(
1314 #[cfg(feature = "server")]
1315 $server_variant_name,
1316 )+)?
1317 }
1318
1319 impl $enum_name {
1320 $(
1321 pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1322 field: $enum_name::$variant_name,
1323 phantom: std::marker::PhantomData,
1324 };
1325 )+
1326 }
1327
1328 impl crate::GetValue for $enum_name {
1329 type Model = $model;
1330
1331 fn get(&self, model: &$model) -> serde_json::Value {
1332 match self {
1333 $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1334 $($(
1335 #[cfg(feature = "server")]
1336 $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1337 )+)?
1338 }.expect("Serialization should work")
1339 }
1340 }
1341 }
1342}
1343
1344#[derive(Serialize, Deserialize)]
1345#[serde(rename_all = "camelCase")]
1346pub struct ServiceRequest<S: Service> {
1347 pub credentials: Credentials,
1348 #[serde(skip_serializing_if = "Option::is_none")]
1349 pub session: Option<Session>,
1350 #[serde(default, skip_serializing_if = "is_false")]
1351 pub recurring_consents: bool,
1352 #[serde(flatten, bound = "S:")]
1353 pub data: <S as Service>::RequestData,
1354}
1355
1356impl<S: Service> Clone for ServiceRequest<S> {
1357 fn clone(&self) -> Self {
1358 Self {
1359 credentials: self.credentials.clone(),
1360 session: self.session.clone(),
1361 recurring_consents: self.recurring_consents,
1362 data: self.data.clone(),
1363 }
1364 }
1365}
1366
1367#[derive(Serialize, Deserialize)]
1368#[serde(rename_all = "camelCase")]
1369pub struct NonInteractiveRequest<S: Service> {
1370 pub connection_data: ConnectionData,
1371 #[serde(skip_serializing_if = "Option::is_none")]
1372 pub session: Option<Session>,
1373 #[serde(default, skip_serializing_if = "Option::is_none")]
1374 pub user_in_session: Option<UserInSession>,
1375 #[serde(flatten, bound = "S:")]
1376 pub data: <S as Service>::RequestData,
1377}
1378
1379#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1380#[serde(rename_all = "camelCase")]
1381#[non_exhaustive]
1382#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1383pub struct Account {
1384 #[serde(skip_serializing_if = "Option::is_none")]
1386 #[cfg_attr(feature = "uniffi", uniffi(default))]
1387 pub iban: Option<String>,
1388
1389 #[serde(skip_serializing_if = "Option::is_none")]
1391 #[cfg_attr(feature = "uniffi", uniffi(default))]
1392 pub number: Option<String>,
1393
1394 #[serde(skip_serializing_if = "Option::is_none")]
1396 #[cfg_attr(feature = "uniffi", uniffi(default))]
1397 pub masked_pan: Option<String>,
1398
1399 #[serde(skip_serializing_if = "Option::is_none")]
1401 #[cfg_attr(feature = "uniffi", uniffi(default))]
1402 pub bic: Option<String>,
1403
1404 #[serde(skip_serializing_if = "Option::is_none")]
1406 #[cfg_attr(feature = "uniffi", uniffi(default))]
1407 pub bank_code: Option<String>,
1408
1409 #[serde(skip_serializing_if = "Option::is_none")]
1411 #[cfg_attr(feature = "uniffi", uniffi(default))]
1412 pub currency: Option<String>,
1413
1414 #[serde(skip_serializing_if = "Option::is_none")]
1416 #[cfg_attr(feature = "uniffi", uniffi(default))]
1417 pub name: Option<String>,
1418
1419 #[serde(skip_serializing_if = "Option::is_none")]
1421 #[cfg_attr(feature = "uniffi", uniffi(default))]
1422 pub display_name: Option<String>,
1423
1424 #[serde(skip_serializing_if = "Option::is_none")]
1426 #[cfg_attr(feature = "uniffi", uniffi(default))]
1427 pub owner_name: Option<String>,
1428
1429 #[serde(skip_serializing_if = "Option::is_none")]
1431 #[cfg_attr(feature = "uniffi", uniffi(default))]
1432 pub product_name: Option<String>,
1433
1434 #[serde(skip_serializing_if = "Option::is_none")]
1436 #[cfg_attr(feature = "uniffi", uniffi(default))]
1437 pub status: Option<AccountStatus>,
1438
1439 #[serde(rename = "type")]
1441 #[serde(skip_serializing_if = "Option::is_none")]
1442 #[cfg_attr(feature = "uniffi", uniffi(default))]
1443 pub type_: Option<AccountType>,
1444}
1445
1446pub mod accounts {
1447 use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1448 use serde::{Deserialize, Serialize};
1449
1450 #[derive(Clone, Debug)]
1451 pub struct Service {}
1452
1453 impl super::Service for Service {
1454 const ID: ServiceId = ServiceId::Accounts;
1455
1456 type TicketData = ();
1457
1458 type RequestData = RequestData;
1459
1460 type Output = Vec<Account>;
1461 }
1462
1463 impl super::NonInteractiveService for Service {}
1464
1465 fields! {
1466 routex_models::Account, AccountField
1467 (IBAN, Iban, iban, Option<String>)
1468 (NUMBER, Number, number, Option<String>)
1469 (MASKED_PAN, MaskedPan, masked_pan, Option<String>)
1470 (BIC, Bic, bic, Option<String>)
1471 (BANK_CODE, BankCode, bank_code, Option<String>)
1472 (CURRENCY, Currency, currency, String)
1473 (NAME, Name, name, Option<String>)
1474 (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1475 (OWNER_NAME, OwnerName, owner_name, Option<String>)
1476 (PRODUCT_NAME, ProductName, product_name, Option<String>)
1477 (STATUS, Status, status, Option<AccountStatus>)
1478 (TYPE, Type, type_, Option<AccountType>)
1479 server:
1480 (Capabilities, capabilities)
1481 }
1482
1483 impl Account {
1484 #[must_use]
1485 pub fn supports(service: SupportedService) -> Filter<AccountField> {
1487 Filter::Supports(service)
1488 }
1489 }
1490
1491 #[derive(Serialize, Deserialize, Clone)]
1492 #[serde(rename_all = "camelCase")]
1493 pub struct RequestData {
1494 pub fields: Vec<AccountField>,
1495 #[serde(skip_serializing_if = "Option::is_none")]
1496 pub filter: Option<Filter<AccountField>>,
1497 }
1498}
1499
1500#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1501#[serde(rename_all = "camelCase")]
1502#[serde(rename_all_fields = "camelCase")]
1503#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1504pub enum AccountIdentifier {
1505 Iban(String),
1507
1508 Number(String),
1510
1511 MaskedPan(String),
1513}
1514
1515#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1516#[serde(rename_all = "camelCase")]
1517#[serde(rename_all_fields = "camelCase")]
1518#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1519pub enum CreditorAccountIdentifier {
1520 Iban(String),
1522}
1523
1524#[serde_with::serde_as]
1525#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1526#[serde(rename_all = "camelCase")]
1527#[serde(rename_all_fields = "camelCase")]
1528#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1529pub enum DebtorAccountIdentifier {
1530 #[serde(alias = "Iban")]
1532 Iban(String),
1533 #[cfg(feature = "server")]
1534 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
1535}
1536
1537#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1538#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1539#[serde(rename_all = "camelCase")]
1540pub struct AccountReference {
1541 #[serde(flatten)]
1542 pub id: AccountIdentifier,
1543 #[serde(skip_serializing_if = "Option::is_none")]
1544 #[cfg_attr(feature = "uniffi", uniffi(default))]
1545 pub currency: Option<String>,
1546}
1547
1548#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1549#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1550#[serde(rename_all = "camelCase")]
1551pub struct DebtorAccountReference {
1552 #[serde(flatten)]
1553 pub id: DebtorAccountIdentifier,
1554 #[serde(skip_serializing_if = "Option::is_none")]
1555 #[cfg_attr(feature = "uniffi", uniffi(default))]
1556 pub currency: Option<String>,
1557}
1558
1559pub mod balances {
1560 pub use rust_decimal::Decimal;
1561 use serde::{Deserialize, Serialize};
1562
1563 use crate::{AccountReference, DateTime, ServiceId};
1564
1565 #[derive(Clone, Debug)]
1566 pub struct Service {}
1567
1568 impl super::Service for Service {
1569 const ID: ServiceId = ServiceId::Balances;
1570
1571 type TicketData = ();
1572
1573 type RequestData = RequestData;
1574
1575 type Output = Balances;
1576 }
1577
1578 impl super::NonInteractiveService for Service {}
1579
1580 #[derive(Serialize, Deserialize, Clone)]
1581 #[serde(rename_all = "camelCase")]
1582 pub struct RequestData {
1583 pub accounts: Vec<AccountReference>,
1584 }
1585
1586 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1587 #[serde(rename_all = "camelCase")]
1588 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1589 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1590 pub struct Balances {
1591 pub balances: Vec<AccountBalances>,
1593
1594 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1596 #[cfg_attr(feature = "uniffi", uniffi(default))]
1597 pub missing_accounts: Vec<AccountReference>,
1598 }
1599
1600 #[allow(clippy::module_name_repetitions)]
1601 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1602 #[serde(rename_all = "camelCase")]
1603 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1604 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1605 pub struct AccountBalances {
1606 pub account: AccountReference,
1607
1608 pub balances: Vec<Balance>,
1610 }
1611
1612 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1613 #[serde(rename_all = "camelCase")]
1614 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1615 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1616 pub struct Balance {
1617 pub amount: Decimal,
1618 pub currency: String,
1619 pub balance_type: BalanceType,
1620 #[serde(default, skip_serializing_if = "Option::is_none")]
1621 #[cfg_attr(feature = "uniffi", uniffi(default))]
1622 pub credit_limit_included: Option<bool>,
1623 #[serde(default, skip_serializing_if = "Option::is_none")]
1624 #[cfg_attr(feature = "uniffi", uniffi(default))]
1625 pub date_time: Option<DateTime>,
1626 }
1627
1628 #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1629 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1630 pub enum BalanceType {
1631 Booked,
1633
1634 Available,
1641
1642 Expected,
1644 }
1645}
1646
1647pub mod transactions {
1648 use chrono::NaiveDate;
1649 use isocountry::CountryCode;
1650 pub use routex_models::{Amount, Fee, TransactionStatus};
1651 use rust_decimal::Decimal;
1652 use serde::{Deserialize, Serialize};
1653 use url::Url;
1654
1655 use crate::{AccountReference, ServiceId};
1656
1657 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1658 #[serde(rename_all = "camelCase")]
1659 pub struct TicketData {
1660 pub account: AccountReference,
1661 pub range: Range,
1662 pub webhook: Option<Url>,
1663 }
1664
1665 #[derive(Clone, Debug)]
1666 pub struct Service {}
1667
1668 impl super::Service for Service {
1669 const ID: ServiceId = ServiceId::Transactions;
1670
1671 type TicketData = TicketData;
1672
1673 type RequestData = RequestData;
1674
1675 type Output = Option<Vec<Transaction>>;
1676 }
1677
1678 impl super::NonInteractiveService for Service {}
1679
1680 #[derive(Serialize, Deserialize, Clone)]
1681 #[serde(rename_all = "camelCase")]
1682 pub struct RequestData {}
1683
1684 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1685 #[serde(rename_all = "camelCase")]
1686 #[serde(rename_all_fields = "camelCase")]
1687 pub enum Range {
1688 Reference(String),
1689 #[serde(untagged)]
1690 Period {
1691 from: NaiveDate,
1693 to: Option<NaiveDate>,
1695 },
1696 }
1697
1698 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1699 #[serde(rename_all = "camelCase")]
1700 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1701 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1702 pub struct Transaction {
1703 #[serde(skip_serializing_if = "Option::is_none")]
1705 #[cfg_attr(feature = "uniffi", uniffi(default))]
1706 pub entry_reference: Option<String>,
1707
1708 #[serde(skip_serializing_if = "Option::is_none")]
1709 #[cfg_attr(feature = "uniffi", uniffi(default))]
1710 pub batch: Option<BatchData>,
1711
1712 #[serde(skip_serializing_if = "Option::is_none")]
1714 #[cfg_attr(feature = "uniffi", uniffi(default))]
1715 pub booking_date: Option<NaiveDate>,
1716
1717 #[serde(skip_serializing_if = "Option::is_none")]
1719 #[cfg_attr(feature = "uniffi", uniffi(default))]
1720 pub value_date: Option<NaiveDate>,
1721
1722 #[serde(skip_serializing_if = "Option::is_none")]
1724 #[cfg_attr(feature = "uniffi", uniffi(default))]
1725 pub transaction_date: Option<NaiveDate>,
1726
1727 pub status: TransactionStatus,
1729
1730 #[serde(skip_serializing_if = "Option::is_none")]
1732 #[cfg_attr(feature = "uniffi", uniffi(default))]
1733 pub account_servicer_reference: Option<String>,
1734
1735 #[serde(skip_serializing_if = "Option::is_none")]
1737 #[cfg_attr(feature = "uniffi", uniffi(default))]
1738 pub payment_id: Option<String>,
1739
1740 #[serde(skip_serializing_if = "Option::is_none")]
1742 #[cfg_attr(feature = "uniffi", uniffi(default))]
1743 pub transaction_id: Option<String>,
1744
1745 #[serde(skip_serializing_if = "Option::is_none")]
1747 #[cfg_attr(feature = "uniffi", uniffi(default))]
1748 pub end_to_end_id: Option<String>,
1749
1750 #[serde(skip_serializing_if = "Option::is_none")]
1752 #[cfg_attr(feature = "uniffi", uniffi(default))]
1753 pub mandate_id: Option<String>,
1754
1755 #[serde(skip_serializing_if = "Option::is_none")]
1757 #[cfg_attr(feature = "uniffi", uniffi(default))]
1758 pub creditor_id: Option<String>,
1759
1760 pub amount: Amount,
1762
1763 #[serde(default, skip_serializing_if = "super::is_false")]
1765 #[cfg_attr(feature = "uniffi", uniffi(default))]
1766 pub reversal: bool,
1767
1768 #[serde(skip_serializing_if = "Option::is_none")]
1770 #[cfg_attr(feature = "uniffi", uniffi(default))]
1771 pub original_amount: Option<Amount>,
1772
1773 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1775 #[cfg_attr(feature = "uniffi", uniffi(default))]
1776 pub exchanges: Vec<ExchangeRate>,
1777
1778 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1780 #[cfg_attr(feature = "uniffi", uniffi(default))]
1781 pub fees: Vec<Fee>,
1782
1783 #[serde(skip_serializing_if = "Option::is_none")]
1785 #[cfg_attr(feature = "uniffi", uniffi(default))]
1786 pub creditor: Option<Party>,
1787
1788 #[serde(skip_serializing_if = "Option::is_none")]
1790 #[cfg_attr(feature = "uniffi", uniffi(default))]
1791 pub debtor: Option<Party>,
1792
1793 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1795 #[cfg_attr(feature = "uniffi", uniffi(default))]
1796 pub remittance_information: Vec<String>,
1797
1798 #[serde(skip_serializing_if = "Option::is_none")]
1800 #[cfg_attr(feature = "uniffi", uniffi(default))]
1801 pub purpose_code: Option<String>,
1802
1803 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1805 #[cfg_attr(feature = "uniffi", uniffi(default))]
1806 pub bank_transaction_codes: Vec<BankTransactionCode>,
1807
1808 #[serde(skip_serializing_if = "Option::is_none")]
1810 #[cfg_attr(feature = "uniffi", uniffi(default))]
1811 pub additional_information: Option<String>,
1812 }
1813
1814 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1815 #[serde(rename_all = "camelCase")]
1816 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1817 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1818 pub struct BatchData {
1819 #[serde(skip_serializing_if = "Option::is_none")]
1821 #[cfg_attr(feature = "uniffi", uniffi(default))]
1822 pub number_of_transactions: Option<u32>,
1823
1824 pub transactions: Vec<BatchTransactionDetails>,
1829 }
1830
1831 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1832 #[serde(rename_all = "camelCase")]
1833 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1834 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1835 pub struct BatchTransactionDetails {
1836 #[serde(skip_serializing_if = "Option::is_none")]
1838 #[cfg_attr(feature = "uniffi", uniffi(default))]
1839 pub account_servicer_reference: Option<String>,
1840
1841 #[serde(skip_serializing_if = "Option::is_none")]
1843 #[cfg_attr(feature = "uniffi", uniffi(default))]
1844 pub payment_id: Option<String>,
1845
1846 #[serde(skip_serializing_if = "Option::is_none")]
1848 #[cfg_attr(feature = "uniffi", uniffi(default))]
1849 pub transaction_id: Option<String>,
1850
1851 #[serde(skip_serializing_if = "Option::is_none")]
1853 #[cfg_attr(feature = "uniffi", uniffi(default))]
1854 pub end_to_end_id: Option<String>,
1855
1856 #[serde(skip_serializing_if = "Option::is_none")]
1858 #[cfg_attr(feature = "uniffi", uniffi(default))]
1859 pub mandate_id: Option<String>,
1860
1861 #[serde(skip_serializing_if = "Option::is_none")]
1863 #[cfg_attr(feature = "uniffi", uniffi(default))]
1864 pub creditor_id: Option<String>,
1865
1866 #[serde(skip_serializing_if = "Option::is_none")]
1868 #[cfg_attr(feature = "uniffi", uniffi(default))]
1869 pub amount: Option<Amount>,
1870
1871 #[serde(default, skip_serializing_if = "super::is_false")]
1873 #[cfg_attr(feature = "uniffi", uniffi(default))]
1874 pub reversal: bool,
1875
1876 #[serde(skip_serializing_if = "Option::is_none")]
1878 #[cfg_attr(feature = "uniffi", uniffi(default))]
1879 pub original_amount: Option<Amount>,
1880
1881 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1883 #[cfg_attr(feature = "uniffi", uniffi(default))]
1884 pub exchanges: Vec<ExchangeRate>,
1885
1886 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1888 #[cfg_attr(feature = "uniffi", uniffi(default))]
1889 pub fees: Vec<Fee>,
1890
1891 #[serde(skip_serializing_if = "Option::is_none")]
1893 #[cfg_attr(feature = "uniffi", uniffi(default))]
1894 pub creditor: Option<Party>,
1895
1896 #[serde(skip_serializing_if = "Option::is_none")]
1898 #[cfg_attr(feature = "uniffi", uniffi(default))]
1899 pub debtor: Option<Party>,
1900
1901 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1903 #[cfg_attr(feature = "uniffi", uniffi(default))]
1904 pub remittance_information: Vec<String>,
1905
1906 #[serde(skip_serializing_if = "Option::is_none")]
1908 #[cfg_attr(feature = "uniffi", uniffi(default))]
1909 pub purpose_code: Option<String>,
1910
1911 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1913 #[cfg_attr(feature = "uniffi", uniffi(default))]
1914 pub bank_transaction_codes: Vec<BankTransactionCode>,
1915
1916 #[serde(skip_serializing_if = "Option::is_none")]
1918 #[cfg_attr(feature = "uniffi", uniffi(default))]
1919 pub additional_information: Option<String>,
1920 }
1921
1922 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1923 #[serde(rename_all = "camelCase")]
1924 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1925 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1926 pub struct ExchangeRate {
1927 pub source_currency: String,
1929 #[serde(skip_serializing_if = "Option::is_none")]
1932 #[cfg_attr(feature = "uniffi", uniffi(default))]
1933 pub target_currency: Option<String>,
1934 #[serde(skip_serializing_if = "Option::is_none")]
1936 #[cfg_attr(feature = "uniffi", uniffi(default))]
1937 pub unit_currency: Option<String>,
1938 pub exchange_rate: Decimal,
1939 }
1940
1941 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1942 #[serde(rename_all = "camelCase")]
1943 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1944 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1945 pub struct Party {
1946 #[serde(skip_serializing_if = "Option::is_none")]
1948 #[cfg_attr(feature = "uniffi", uniffi(default))]
1949 pub name: Option<String>,
1950
1951 #[serde(skip_serializing_if = "Option::is_none")]
1953 #[cfg_attr(feature = "uniffi", uniffi(default))]
1954 pub iban: Option<String>,
1955
1956 #[serde(skip_serializing_if = "Option::is_none")]
1958 #[cfg_attr(feature = "uniffi", uniffi(default))]
1959 pub bic: Option<String>,
1960
1961 #[serde(skip_serializing_if = "Option::is_none")]
1963 #[cfg_attr(feature = "uniffi", uniffi(default))]
1964 pub ultimate: Option<String>,
1965 }
1966
1967 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1968 #[serde(rename_all = "camelCase")]
1969 #[serde(rename_all_fields = "camelCase")]
1970 #[non_exhaustive]
1971 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1972 pub enum BankTransactionCode {
1973 Iso {
1975 domain: String,
1977
1978 family: String,
1980
1981 sub_family: String,
1983 },
1984 Swift(String),
1986 Bai(String),
1988 National { code: String, country: CountryCode },
1990 Other {
1992 code: String,
1993 #[serde(skip_serializing_if = "Option::is_none")]
1994 issuer: Option<String>,
1995 },
1996 }
1997}
1998
1999pub mod standing_orders {
2000 use chrono::NaiveDate;
2001 pub use routex_models::{Amount, ExecutionRule, Frequency, Month};
2002 use serde::{Deserialize, Serialize};
2003
2004 pub use crate::transactions::Party;
2005 use crate::{AccountReference, ServiceId};
2006
2007 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2008 #[serde(rename_all = "camelCase")]
2009 pub struct TicketData {
2010 pub account: AccountReference,
2011 }
2012
2013 #[derive(Clone, Debug)]
2014 pub struct Service {}
2015
2016 impl super::Service for Service {
2017 const ID: ServiceId = ServiceId::StandingOrders;
2018
2019 type TicketData = TicketData;
2020
2021 type RequestData = RequestData;
2022
2023 type Output = Vec<StandingOrder>;
2024 }
2025
2026 #[derive(Serialize, Deserialize, Clone)]
2027 #[serde(rename_all = "camelCase")]
2028 pub struct RequestData {}
2029
2030 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
2031 #[serde(rename_all = "camelCase")]
2032 #[cfg_attr(not(feature = "server"), non_exhaustive)]
2033 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2034 pub struct StandingOrder {
2035 pub start_date: NaiveDate,
2037
2038 #[serde(skip_serializing_if = "Option::is_none")]
2040 #[cfg_attr(feature = "uniffi", uniffi(default))]
2041 pub end_date: Option<NaiveDate>,
2042
2043 #[serde(skip_serializing_if = "Option::is_none")]
2045 #[cfg_attr(feature = "uniffi", uniffi(default))]
2046 pub execution_rule: Option<ExecutionRule>,
2047
2048 #[serde(skip_serializing_if = "Option::is_none")]
2050 #[cfg_attr(feature = "uniffi", uniffi(default))]
2051 pub frequency: Option<Frequency>,
2052
2053 #[serde(skip_serializing_if = "Option::is_none")]
2055 #[cfg_attr(feature = "uniffi", uniffi(default))]
2056 pub day: Option<u8>,
2057
2058 #[serde(skip_serializing_if = "Option::is_none")]
2061 #[cfg_attr(feature = "uniffi", uniffi(default))]
2062 pub skimming_limit: Option<Amount>,
2063
2064 #[serde(skip_serializing_if = "Option::is_none")]
2066 #[cfg_attr(feature = "uniffi", uniffi(default))]
2067 pub end_to_end_id: Option<String>,
2068
2069 pub amount: Amount,
2071
2072 #[serde(skip_serializing_if = "Option::is_none")]
2074 #[cfg_attr(feature = "uniffi", uniffi(default))]
2075 pub creditor: Option<Party>,
2076
2077 #[serde(skip_serializing_if = "Option::is_none")]
2079 #[cfg_attr(feature = "uniffi", uniffi(default))]
2080 pub debtor: Option<Party>,
2081
2082 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2084 #[cfg_attr(feature = "uniffi", uniffi(default))]
2085 pub remittance_information: Vec<String>,
2086
2087 #[serde(skip_serializing_if = "Option::is_none")]
2089 #[cfg_attr(feature = "uniffi", uniffi(default))]
2090 pub purpose_code: Option<String>,
2091
2092 #[serde(skip_serializing_if = "Option::is_none")]
2094 #[cfg_attr(feature = "uniffi", uniffi(default))]
2095 pub additional_information: Option<String>,
2096 }
2097}
2098
2099#[allow(clippy::trivially_copy_pass_by_ref)]
2100fn is_false(val: &bool) -> bool {
2101 !*val
2102}
2103
2104#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
2105#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2106#[non_exhaustive]
2107pub enum PaymentStatus {
2108 Accepted,
2112
2113 PartiallyAccepted,
2115
2116 CompletedDebtor,
2118
2119 CompletedCreditor,
2121}
2122
2123pub mod collect_payment {
2124 pub use routex_models::Amount;
2125 use serde::{Deserialize, Serialize};
2126 use serde_with::base64::Base64;
2127
2128 use super::{
2129 CreditorAccountIdentifier, DebtorAccountReference, Path, PaymentStatus, ServiceId,
2130 };
2131
2132 pub fn status_path(ticket_id: &str) -> Path {
2137 Path::new(["collect-payment", "status", ticket_id])
2138 }
2139
2140 #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
2141 #[serde(rename_all = "camelCase")]
2142 #[non_exhaustive]
2143 pub struct SuccessStatusData {
2144 #[serde(skip_serializing_if = "Option::is_none")]
2145 pub payment_status: Option<PaymentStatus>,
2146 }
2147
2148 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2149 #[serde(rename_all = "camelCase")]
2150 pub struct TicketData {
2151 pub amount: Amount,
2152 pub creditor_account: CreditorAccountIdentifier,
2153 pub creditor_name: String,
2154 pub remittance: String,
2155 #[serde(skip_serializing_if = "Option::is_none")]
2156 pub instant: Option<bool>,
2157 #[serde(skip_serializing_if = "Option::is_none")]
2158 pub fields: Option<Vec<Field>>,
2159 }
2160
2161 #[derive(Clone, Debug)]
2162 pub struct Service {}
2163
2164 impl super::Service for Service {
2165 const ID: ServiceId = ServiceId::CollectPayment;
2166
2167 type TicketData = TicketData;
2168
2169 type RequestData = RequestData;
2170
2171 type Output = PaymentInitiation;
2172 }
2173
2174 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
2175 #[serde(rename_all = "camelCase")]
2176 #[non_exhaustive]
2177 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2178 pub enum Field {
2179 DebtorIban,
2180 DebtorName,
2181 EncryptedDebtorIban,
2182 EncryptedDebtorName,
2183 }
2184
2185 #[derive(Serialize, Deserialize, Clone)]
2186 #[serde(rename_all = "camelCase")]
2187 pub struct RequestData {
2188 #[serde(skip_serializing_if = "Option::is_none")]
2189 pub account: Option<DebtorAccountReference>,
2190 }
2191
2192 #[serde_with::serde_as]
2193 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2194 #[serde(rename_all = "camelCase")]
2195 #[non_exhaustive]
2196 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2197 pub struct PaymentInitiation {
2198 #[serde(skip_serializing_if = "Option::is_none")]
2199 #[cfg_attr(feature = "uniffi", uniffi(default))]
2200 pub status: Option<PaymentStatus>,
2201 #[serde(skip_serializing_if = "Option::is_none")]
2202 #[cfg_attr(feature = "uniffi", uniffi(default))]
2203 pub debtor_name: Option<String>,
2204 #[serde(skip_serializing_if = "Option::is_none")]
2205 #[cfg_attr(feature = "uniffi", uniffi(default))]
2206 pub debtor_iban: Option<String>,
2207 #[serde(skip_serializing_if = "Option::is_none")]
2208 #[cfg_attr(feature = "uniffi", uniffi(default))]
2209 #[serde_as(as = "Option<Base64>")]
2210 pub encrypted_debtor_name: Option<Vec<u8>>,
2211 #[serde(skip_serializing_if = "Option::is_none")]
2212 #[cfg_attr(feature = "uniffi", uniffi(default))]
2213 #[serde_as(as = "Option<Base64>")]
2214 pub encrypted_debtor_iban: Option<Vec<u8>>,
2215 }
2216}
2217
2218pub mod transfer {
2219 pub use routex_models::{ChargeBearer, CreditorAddress, ISODateTimeOrDate, PaymentProduct};
2220 use serde::{Deserialize, Serialize};
2221
2222 use super::{
2223 Amount, CreditorAccountIdentifier, DebtorAccountReference, PaymentStatus, ServiceId,
2224 };
2225
2226 #[derive(Clone, Debug)]
2227 pub struct Service {}
2228
2229 impl super::Service for Service {
2230 const ID: ServiceId = ServiceId::Transfer;
2231
2232 type TicketData = ();
2233
2234 type RequestData = RequestData;
2235
2236 type Output = Transfer;
2237 }
2238
2239 #[derive(Serialize, Deserialize, Clone)]
2240 #[serde(rename_all = "camelCase")]
2241 pub struct RequestData {
2242 pub product: PaymentProduct,
2243 #[serde(skip_serializing_if = "Option::is_none")]
2244 pub debtor_account: Option<DebtorAccountReference>,
2245 #[serde(skip_serializing_if = "Option::is_none")]
2246 pub debtor_name: Option<String>,
2247 #[serde(skip_serializing_if = "Option::is_none")]
2248 pub requested_execution_date: Option<ISODateTimeOrDate>,
2249 pub details: Vec<TransferDetails>,
2250 }
2251
2252 #[derive(Serialize, Deserialize, Clone)]
2253 #[serde(rename_all = "camelCase")]
2254 #[non_exhaustive]
2255 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2256 pub struct TransferDetails {
2257 #[serde(skip_serializing_if = "Option::is_none")]
2258 #[cfg_attr(feature = "uniffi", uniffi(default))]
2259 pub end_to_end_identification: Option<String>,
2260 pub amount: Amount,
2261 pub creditor_account: CreditorAccountIdentifier,
2262 #[serde(skip_serializing_if = "Option::is_none")]
2263 #[cfg_attr(feature = "uniffi", uniffi(default))]
2264 pub creditor_agent_bic: Option<String>,
2265 pub creditor_name: String,
2266 #[serde(skip_serializing_if = "Option::is_none")]
2267 #[cfg_attr(feature = "uniffi", uniffi(default))]
2268 pub creditor_address: Option<CreditorAddress>,
2269 #[serde(skip_serializing_if = "Option::is_none")]
2270 #[cfg_attr(feature = "uniffi", uniffi(default))]
2271 pub remittance: Option<String>,
2272 #[serde(skip_serializing_if = "Option::is_none")]
2273 #[cfg_attr(feature = "uniffi", uniffi(default))]
2274 pub charge_bearer: Option<ChargeBearer>,
2275 }
2276
2277 impl TransferDetails {
2278 pub fn new(
2279 amount: Amount,
2280 creditor_account: CreditorAccountIdentifier,
2281 creditor_name: impl Into<String>,
2282 ) -> Self {
2283 Self {
2284 end_to_end_identification: None,
2285 amount,
2286 creditor_account,
2287 creditor_agent_bic: None,
2288 creditor_name: creditor_name.into(),
2289 creditor_address: None,
2290 remittance: None,
2291 charge_bearer: None,
2292 }
2293 }
2294 }
2295
2296 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2297 #[serde(rename_all = "camelCase")]
2298 #[non_exhaustive]
2299 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2300 pub struct Transfer {
2301 #[serde(skip_serializing_if = "Option::is_none")]
2302 #[cfg_attr(feature = "uniffi", uniffi(default))]
2303 pub status: Option<PaymentStatus>,
2304 }
2305}
2306
2307#[cfg(test)]
2308mod tests {
2309 use std::fmt::Debug;
2310
2311 use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
2312 use serde::{Deserialize, Serialize};
2313 use serde_json::json;
2314 use uuid::Uuid;
2315
2316 use crate::{
2317 Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
2318 InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
2319 };
2320
2321 #[test]
2322 fn read_response() {
2323 let serialized = serde_json::to_string(
2324 &encode(
2325 &Header::new(Algorithm::HS256),
2326 &Claims {
2327 data: "data",
2328 exp: 2_540_808_000,
2329 },
2330 &EncodingKey::from_secret(b"does_not_matter"),
2331 )
2332 .unwrap(),
2333 )
2334 .unwrap();
2335
2336 let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
2337
2338 assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
2339 }
2340
2341 struct Service;
2342
2343 impl crate::Service for Service {
2344 const ID: ServiceId = ServiceId::Accounts;
2345
2346 type TicketData = ();
2347
2348 type RequestData = ();
2349
2350 type Output = ();
2351 }
2352
2353 #[allow(
2354 dead_code,
2355 unconditional_recursion,
2356 clippy::extra_unused_type_parameters
2357 )]
2358 fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
2359 test_bounds::<InputContext<Service>>();
2360 test_bounds::<ConfirmationContext<Service>>();
2361 test_bounds::<DialogInput<Service>>();
2362 test_bounds::<Dialog<Service>>();
2363 test_bounds::<Redirect<Service>>();
2364 test_bounds::<RedirectHandle<Service>>();
2365 test_bounds::<OBResult<()>>();
2366 test_bounds::<Session>();
2367
2368 test_bounds::<Authenticated<Ticket<Service>>>();
2369 test_bounds::<Ticket<Service>>();
2370 test_bounds::<ResponseData<Service>>();
2371 test_bounds::<ConfirmationData<Service>>();
2372 }
2373
2374 fn test_eq_<T: PartialEq + Debug>(o: &T) {
2375 assert!(o.eq(o));
2376 }
2377
2378 #[test]
2379 fn test_eq() {
2380 let ic = InputContext::<Service>::from(vec![]);
2381 let cc = ConfirmationContext::<Service>::from(vec![]);
2382
2383 test_eq_(&ic);
2384 test_eq_(&cc);
2385 test_eq_(&Redirect {
2386 url: "url:".parse().unwrap(),
2387 context: cc.clone(),
2388 });
2389 test_eq_(&RedirectHandle {
2390 handle: String::new(),
2391 context: cc.clone(),
2392 });
2393 test_eq_(&Ticket::<Service> {
2394 service: ServiceId::Accounts,
2395 id: Uuid::new_v4(),
2396 data: (),
2397 });
2398 test_eq_(&ResponseData {
2399 context: ic,
2400 response: String::new(),
2401 });
2402 test_eq_(&ConfirmationData { context: cc });
2403 }
2404
2405 #[test]
2406 fn no_ticket_data() {
2407 assert!(
2408 serde_json::from_value::<Ticket<Service>>(json!({
2409 "id": Uuid::new_v4(),
2410 "service": ServiceId::Accounts,
2411 }))
2412 .is_ok()
2413 );
2414 }
2415
2416 #[test]
2417 fn missing_ticket_data() {
2418 let err = serde_json::from_value::<Ticket<super::collect_payment::Service>>(json!({
2419 "id": Uuid::new_v4(),
2420 "service": ServiceId::CollectPayment,
2421 }))
2422 .unwrap_err();
2423
2424 assert_eq!(err.to_string(), "missing field `data`");
2425 }
2426
2427 #[test]
2428 fn user_in_session_wire() {
2429 use super::UserInSession;
2430
2431 let at = UserInSession::At("203.0.113.7".parse().unwrap());
2432 assert_eq!(
2433 serde_json::to_value(UserInSession::OnThisConnection).unwrap(),
2434 json!("connection")
2435 );
2436 assert_eq!(serde_json::to_value(&at).unwrap(), json!("203.0.113.7"));
2437
2438 assert_eq!(
2439 serde_json::from_value::<UserInSession>(json!("connection")).unwrap(),
2440 UserInSession::OnThisConnection,
2441 );
2442 assert_eq!(
2443 serde_json::from_value::<UserInSession>(json!("203.0.113.7")).unwrap(),
2444 at
2445 );
2446
2447 assert!(serde_json::from_value::<UserInSession>(json!("nonsense")).is_err());
2448 }
2449}