#[cfg(feature = "uniffi")]
use chrono::NaiveDate;
use chrono::Utc;
use heck::ToKebabCase;
#[cfg(feature = "uniffi")]
use isocountry::CountryCode;
use jsonwebtoken::dangerous::insecure_decode;
use jsonwebtoken::{EncodingKey, Header, decode_header, encode};
pub use routex_models::{
AccountStatus, AccountType, Amount, ConnectionId, CredentialsModel, DialogContext,
DialogOption, Image, InputType, PaymentErrorCode, ProviderErrorCode, SecrecyLevel,
ServiceBlockedCode, UnsupportedProductReason,
};
#[cfg(feature = "uniffi")]
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_with::base64::Base64;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use url::Url;
#[cfg(feature = "uniffi")]
uniffi::setup_scaffolding!();
#[cfg(feature = "uniffi")]
uniffi::custom_type!(ConnectionData, Vec<u8>, {
try_lift: |val| Ok(val.into()),
lower: |obj| obj.into(),
});
#[cfg(feature = "uniffi")]
uniffi::custom_type!(Session, Vec<u8>, {
try_lift: |val| Ok(val.into()),
lower: |obj| obj.into(),
});
#[cfg(feature = "uniffi")]
uniffi::custom_type!(CountryCode, String, {
remote,
try_lift: |val| Ok(Self::for_alpha2(&val)?),
lower: |obj| obj.alpha2().to_string(),
});
#[cfg(feature = "uniffi")]
uniffi::use_remote_type!(routex_models::Decimal);
#[cfg(feature = "uniffi")]
uniffi::custom_type!(NaiveDate, String, {
remote,
try_lift: |val| Ok(val.parse()?),
lower: |obj| obj.to_string(),
});
type DateTime = chrono::DateTime<Utc>;
#[cfg(feature = "uniffi")]
uniffi::custom_type!(DateTime, String, {
remote,
try_lift: |val| Ok(val.parse()?),
lower: |obj| obj.to_string(),
});
pub type Dialog<S> = routex_models::Dialog<ConfirmationContext<S>, InputContext<S>>;
pub type DialogInput<S> = routex_models::DialogInput<ConfirmationContext<S>, InputContext<S>>;
#[must_use]
pub struct Path(Vec<String>);
impl Path {
fn new<I>(segments: I) -> Self
where
I: IntoIterator,
I::Item: ToString,
{
Self(segments.into_iter().map(|s| s.to_string()).collect())
}
#[must_use]
pub fn to_url(&self, base: &Url) -> Url {
let mut url = base.clone();
url.path_segments_mut()
.expect("cannot be base")
.extend(&self.0);
url
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for segment in &self.0 {
f.write_str("/")?;
f.write_str(segment)?;
}
Ok(())
}
}
pub mod headers {
use http::HeaderName;
pub static SESSION_ID: HeaderName = HeaderName::from_static("yaxi-session-id");
pub static TICKET: HeaderName = HeaderName::from_static("yaxi-ticket");
pub static TICKET_ID: HeaderName = HeaderName::from_static("yaxi-ticket-id");
pub static REDIRECT_URI: HeaderName = HeaderName::from_static("yaxi-redirect-uri");
pub static TRACE_ID: HeaderName = HeaderName::from_static("yaxi-trace-id");
}
pub const CURRENT_MEDIA_TYPE: &str = "application/vnd.yaxi.v5";
pub mod keys {
use super::Path;
use clerk_report::PublishedVersionEntry;
use serde::{Deserialize, Serialize};
use serde_with::base64::Base64;
pub fn settlement_path() -> Path {
Path::new(["key-settlement"])
}
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Request {
#[serde_as(as = "Base64")]
pub public_key: [u8; 32],
}
#[serde_with::serde_as]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SettlementBoxMessage {
#[serde_as(as = "Base64")]
pub public_key: [u8; 32],
#[serde_as(as = "Base64")]
pub session_id: [u8; 32],
}
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Response {
#[serde_as(as = "Base64")]
pub attestation_report: Vec<u8>,
pub vcek: String,
#[serde_as(as = "Base64")]
pub chacha_box: Vec<u8>,
pub system_version: PublishedVersionEntry,
}
}
pub mod traces {
use super::Path;
pub fn path(trace_id: &str) -> Path {
Path::new(["traces", trace_id])
}
}
pub mod redirects {
use super::Path;
use serde::{Deserialize, Serialize};
use url::Url;
pub fn path() -> Path {
Path::new(["redirects"])
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Request {
pub handle: String,
pub redirect_uri: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Response {
pub redirect_url: Url,
}
}
pub mod info {
use super::Path;
pub use isocountry::CountryCode;
use routex_models::{ConnectionId, CredentialsModel};
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use serde_with::base64::Base64;
pub fn search_path() -> Path {
Path::new(["search"])
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Request {
pub filters: Vec<SearchFilter>,
pub iban_detection: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
pub enum SearchFilter {
Types(Vec<ConnectionType>),
Countries(Vec<CountryCode>),
Name(String),
Bic(String),
BankCode(String),
Term(String),
#[cfg(feature = "server")]
EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
}
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
#[non_exhaustive]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum ConnectionType {
Production,
Sandboxes,
}
pub fn fetch_path(connection_id: &str) -> Path {
Path::new(["info", connection_id])
}
#[allow(clippy::module_name_repetitions)]
#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
pub struct ConnectionInfo {
pub id: ConnectionId,
pub countries: Vec<CountryCode>,
pub display_name: String,
pub credentials: CredentialsModel,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub advice: Option<String>,
pub logo_id: String,
}
}
macro_rules! context {
($name:ident) => {
#[serde_with::serde_as]
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
impl<S> PartialEq for $name<S> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<S> Eq for $name<S> {}
impl<S> Clone for $name<S> {
fn clone(&self) -> Self {
Self(self.0.clone(), self.1)
}
}
impl<S> fmt::Debug for $name<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.debug_tuple("$name")
.field(&self.0)
.field(&self.1)
.finish()
}
}
impl<S> From<Vec<u8>> for $name<S> {
fn from(value: Vec<u8>) -> Self {
Self(value, PhantomData)
}
}
impl<S> From<$name<S>> for Vec<u8> {
fn from(value: $name<S>) -> Self {
value.0
}
}
impl<S> AsRef<[u8]> for $name<S> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
};
}
context!(InputContext);
context!(ConfirmationContext);
#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
#[serde(transparent)]
pub struct Authenticated<T> {
jwt: String,
_phantom: PhantomData<T>,
}
impl<'de, T> Deserialize<'de> for Authenticated<T>
where
T: for<'t> Deserialize<'t> + Clone,
{
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
impl<T> Authenticated<T>
where
T: Serialize,
{
#[must_use]
pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
let header = Header {
kid: Some(key_id.into()),
..Default::default()
};
let jwt = encode(
&header,
&Claims {
data,
exp: 2_540_808_000,
},
&EncodingKey::from_secret(key),
)
.expect("Encoding should work");
Self {
jwt,
_phantom: PhantomData,
}
}
}
impl<T> FromStr for Authenticated<T>
where
T: for<'de> Deserialize<'de> + Clone,
{
type Err = jsonwebtoken::errors::Error;
fn from_str(jwt: &str) -> Result<Self, Self::Err> {
decode::<T>(jwt)?;
Ok(Self {
jwt: jwt.to_string(),
_phantom: PhantomData,
})
}
}
impl<T> Authenticated<T>
where
T: for<'de> Deserialize<'de> + Clone,
{
#[must_use]
pub fn as_str(&self) -> &str {
&self.jwt
}
#[must_use]
pub fn key_id(&self) -> Option<String> {
decode_header(&self.jwt).unwrap().kid
}
#[cfg(feature = "client")]
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn to_data(&self) -> T {
decode(&self.jwt).unwrap()
}
#[cfg(feature = "server")]
pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
decoder.decode::<T>(&self.jwt).map(|d| d.data)
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Claims<T> {
pub data: T,
pub exp: u64,
}
#[cfg(feature = "server")]
pub trait Decoder {
type Error;
fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
where
T: for<'de> Deserialize<'de> + Clone;
}
#[derive(Serialize, Deserialize, Debug)]
pub enum OBResponse<S: Service> {
#[serde(bound = "S:")]
Result(
Authenticated<OBResult<S::Output>>,
Option<Session>,
Option<ConnectionData>,
),
#[serde(bound = "S:")]
Dialog(Dialog<S>),
#[serde(bound = "S:")]
Redirect(Redirect<S>),
#[serde(bound = "S:")]
RedirectHandle(RedirectHandle<S>),
}
impl<S: Service> Clone for OBResponse<S> {
fn clone(&self) -> Self {
match self {
OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
authenticated.clone(),
session.clone(),
connection_data.clone(),
),
OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
OBResponse::RedirectHandle(redirect_handle) => {
OBResponse::RedirectHandle(redirect_handle.clone())
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
pub struct NonInteractiveResponse<S: Service> {
#[serde(bound = "S:")]
pub result: S::Output,
pub connection_data: Option<ConnectionData>,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct OBResult<T> {
pub data: T,
pub ticket_id: String,
pub timestamp: DateTime,
}
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
impl From<Vec<u8>> for Session {
fn from(value: Vec<u8>) -> Self {
Session(value)
}
}
impl From<Session> for Vec<u8> {
fn from(session: Session) -> Self {
session.0
}
}
impl AsRef<[u8]> for Session {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(transparent)]
pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
impl From<Vec<u8>> for ConnectionData {
fn from(value: Vec<u8>) -> Self {
ConnectionData(value)
}
}
impl From<ConnectionData> for Vec<u8> {
fn from(connection_data: ConnectionData) -> Self {
connection_data.0
}
}
impl AsRef<[u8]> for ConnectionData {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Redirect<S> {
pub url: Url,
#[serde(bound = "S:")]
pub context: ConfirmationContext<S>,
}
impl<S> PartialEq for Redirect<S> {
fn eq(&self, other: &Self) -> bool {
let Self { url, context } = self;
url == &other.url && context == &other.context
}
}
impl<S> Eq for Redirect<S> {}
impl<S> Clone for Redirect<S> {
fn clone(&self) -> Self {
Self {
url: self.url.clone(),
context: self.context.clone(),
}
}
}
impl<S> fmt::Debug for Redirect<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Redirect")
.field("url", &self.url)
.field("context", &self.context)
.finish()
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RedirectHandle<S> {
pub handle: String,
#[serde(bound = "S:")]
pub context: ConfirmationContext<S>,
}
impl<S> PartialEq for RedirectHandle<S> {
fn eq(&self, other: &Self) -> bool {
let Self { handle, context } = self;
handle == &other.handle && context == &other.context
}
}
impl<S> Eq for RedirectHandle<S> {}
impl<S> Clone for RedirectHandle<S> {
fn clone(&self) -> Self {
Self {
handle: self.handle.clone(),
context: self.context.clone(),
}
}
}
impl<S> fmt::Debug for RedirectHandle<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RedirectHandle")
.field("handle", &self.handle)
.field("context", &self.context)
.finish()
}
}
fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
where
T: for<'de> Deserialize<'de> + Clone,
{
insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
pub enum ServiceId {
Accounts,
CollectPayment,
Transactions,
Balances,
}
impl fmt::Display for ServiceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.serialize(f)
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Ticket<S: Service> {
pub service: ServiceId,
pub id: String,
pub data: S::TicketData,
}
impl<S: Service> PartialEq for Ticket<S> {
fn eq(&self, other: &Self) -> bool {
let Self { service, id, data } = self;
service == &other.service && id == &other.id && data == &other.data
}
}
impl<S: Service> Eq for Ticket<S> {}
impl<S: Service> fmt::Debug for Ticket<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ticket")
.field("service", &self.service)
.field("id", &self.id)
.field("data", &self.data)
.finish()
}
}
impl<S: Service> Clone for Ticket<S> {
fn clone(&self) -> Self {
Self {
service: self.service,
id: self.id.clone(),
data: self.data.clone(),
}
}
}
pub trait Service {
const ID: ServiceId;
type TicketData: Serialize
+ for<'de> Deserialize<'de>
+ PartialEq
+ Eq
+ Clone
+ fmt::Debug
+ Send
+ Sync;
type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
fn path() -> Path {
Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
}
fn response_path() -> Path {
Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
}
fn confirmation_path() -> Path {
Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
#[serde(tag = "ticketStatus")]
pub enum TicketStatus<T> {
Unfinished,
Error(ErrorKind),
Success(T),
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
#[serde(tag = "error")]
pub enum ErrorKind {
UnexpectedError,
Canceled,
InvalidCredentials,
ServiceBlocked,
Unauthorized,
ConsentExpired,
AccessExceeded,
PeriodOutOfBounds,
UnsupportedProduct,
PaymentFailed,
UnexpectedValue,
TicketError,
ProviderError,
NotFound,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
pub struct Credentials {
pub connection_id: ConnectionId,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub connection_data: Option<ConnectionData>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseData<S> {
#[serde(bound = "S:")]
pub context: InputContext<S>,
pub response: String,
}
impl<S> PartialEq for ResponseData<S> {
fn eq(&self, other: &Self) -> bool {
let Self { context, response } = self;
context == &other.context && response == &other.response
}
}
impl<S> Eq for ResponseData<S> {}
impl<S> Clone for ResponseData<S> {
fn clone(&self) -> Self {
Self {
context: self.context.clone(),
response: self.response.clone(),
}
}
}
impl<S> fmt::Debug for ResponseData<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResponseData")
.field("context", &self.context)
.field("response", &self.response)
.finish()
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfirmationData<S> {
#[serde(bound = "S:")]
pub context: ConfirmationContext<S>,
}
impl<S> PartialEq for ConfirmationData<S> {
fn eq(&self, other: &Self) -> bool {
let Self { context } = self;
context == &other.context
}
}
impl<S> Eq for ConfirmationData<S> {}
impl<S> Clone for ConfirmationData<S> {
fn clone(&self) -> Self {
Self {
context: self.context.clone(),
}
}
}
impl<S> fmt::Debug for ConfirmationData<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConfirmationData")
.field("context", &self.context)
.finish()
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all_fields = "camelCase")]
#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
pub enum Error {
#[cfg_attr(not(feature = "server"), non_exhaustive)]
UnexpectedError {
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
Canceled {},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
InvalidCredentials {
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
ServiceBlocked {
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
code: Option<ServiceBlockedCode>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
Unauthorized {
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
ConsentExpired {
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
AccessExceeded {
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
PeriodOutOfBounds {
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
UnsupportedProduct {
#[serde(skip_serializing_if = "Option::is_none")]
reason: Option<UnsupportedProductReason>,
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
PaymentFailed {
#[serde(skip_serializing_if = "Option::is_none")]
code: Option<PaymentErrorCode>,
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
UnexpectedValue { error: String },
#[cfg_attr(not(feature = "server"), non_exhaustive)]
TicketError {
error: String,
code: TicketErrorCode,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
ProviderError {
#[serde(skip_serializing_if = "Option::is_none")]
code: Option<ProviderErrorCode>,
#[serde(skip_serializing_if = "Option::is_none")]
user_message: Option<String>,
},
#[cfg_attr(not(feature = "server"), non_exhaustive)]
InterruptError {},
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
pub enum TicketErrorCode {
Missing,
Invalid,
MissingKey,
UnknownKey,
Mismatch,
Expired,
InvalidLifetime,
ExpiredKey,
KeyEnvironmentMismatch,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
pub enum Filter<F> {
#[cfg_attr(not(feature = "server"), non_exhaustive)]
Eq(F, Value),
#[cfg_attr(not(feature = "server"), non_exhaustive)]
NotEq(F, Value),
#[cfg(feature = "server")]
Contains(F, Value),
#[cfg_attr(not(feature = "server"), non_exhaustive)]
And(Box<Self>, Box<Self>),
#[cfg_attr(not(feature = "server"), non_exhaustive)]
Or(Box<Self>, Box<Self>),
#[cfg_attr(not(feature = "server"), non_exhaustive)]
Supports(SupportedService),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum SupportedService {
CollectPayment,
}
impl<F> Filter<F> {
#[must_use]
pub fn and(self, other: Filter<F>) -> Filter<F> {
Filter::And(Box::new(self), Box::new(other))
}
#[must_use]
pub fn or(self, other: Filter<F>) -> Filter<F> {
Filter::Or(Box::new(self), Box::new(other))
}
}
pub struct Field<F, T> {
field: F,
phantom: PhantomData<T>,
}
impl<F, T> Field<F, T>
where
F: Copy,
T: Serialize,
{
pub fn eq(&self, value: T) -> Filter<F> {
Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
}
pub fn not_eq(&self, value: T) -> Filter<F> {
Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
}
}
pub trait GetValue {
type Model;
fn get(&self, model: &Self::Model) -> Value;
}
macro_rules! fields {
{
$model:ty, $enum_name:ident
$(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
$(
server:
$(($server_variant_name:ident, $server_field_name:ident))+
)?
} => {
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
#[non_exhaustive]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum $enum_name {
$($variant_name,)+
$($(
#[cfg(feature = "server")]
$server_variant_name,
)+)?
}
impl $enum_name {
$(
pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
field: $enum_name::$variant_name,
phantom: std::marker::PhantomData,
};
)+
}
impl crate::GetValue for $enum_name {
type Model = $model;
fn get(&self, model: &$model) -> serde_json::Value {
match self {
$($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
$($(
#[cfg(feature = "server")]
$enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
)+)?
}.expect("Serialization should work")
}
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceRequest<S: Service> {
pub credentials: Credentials,
pub session: Option<Session>,
#[serde(default, skip_serializing_if = "is_false")]
pub recurring_consents: bool,
#[serde(flatten, bound = "S:")]
pub data: <S as Service>::RequestData,
}
impl<S: Service> Clone for ServiceRequest<S> {
fn clone(&self) -> Self {
Self {
credentials: self.credentials.clone(),
session: self.session.clone(),
recurring_consents: self.recurring_consents,
data: self.data.clone(),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NonInteractiveRequest<S: Service> {
pub connection_data: ConnectionData,
#[serde(flatten, bound = "S:")]
pub data: <S as Service>::RequestData,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Account {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub iban: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub bic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub bank_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub currency: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub owner_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub product_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub status: Option<AccountStatus>,
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub type_: Option<AccountType>,
}
pub mod accounts {
use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug)]
pub struct Service {}
impl super::Service for Service {
const ID: ServiceId = ServiceId::Accounts;
type TicketData = ();
type RequestData = RequestData;
type Output = Vec<Account>;
}
fields! {
routex_models::Account, AccountField
(IBAN, Iban, iban, Option<String>)
(NUMBER, Number, number, Option<String>)
(BIC, Bic, bic, Option<String>)
(BANK_CODE, BankCode, bank_code, Option<String>)
(CURRENCY, Currency, currency, String)
(NAME, Name, name, Option<String>)
(DISPLAY_NAME, DisplayName, display_name, Option<String>)
(OWNER_NAME, OwnerName, owner_name, Option<String>)
(PRODUCT_NAME, ProductName, product_name, Option<String>)
(STATUS, Status, status, Option<AccountStatus>)
(TYPE, Type, type_, Option<AccountType>)
server:
(Capabilities, capabilities)
}
impl Account {
#[must_use]
pub fn supports(service: SupportedService) -> Filter<AccountField> {
Filter::Supports(service)
}
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RequestData {
pub fields: Vec<AccountField>,
pub filter: Option<Filter<AccountField>>,
}
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum AccountIdentifier {
Iban(String),
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
pub struct AccountReference {
#[serde(flatten)]
pub id: AccountIdentifier,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub currency: Option<String>,
}
pub mod balances {
pub use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::{AccountReference, Path, ServiceId};
pub fn non_interactive_path() -> Path {
Path::new(["balances", "non-interactive"])
}
#[derive(Clone, Debug)]
pub struct Service {}
impl super::Service for Service {
const ID: ServiceId = ServiceId::Balances;
type TicketData = ();
type RequestData = RequestData;
type Output = Balances;
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RequestData {
pub accounts: Vec<AccountReference>,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Balances {
pub balances: Vec<AccountBalances>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub missing_accounts: Vec<AccountReference>,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct AccountBalances {
pub account: AccountReference,
pub balances: Vec<Balance>,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Balance {
pub amount: Decimal,
pub currency: String,
pub balance_type: BalanceType,
}
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum BalanceType {
Booked,
Available,
Expected,
}
}
pub mod transactions {
use chrono::NaiveDate;
use isocountry::CountryCode;
pub use routex_models::{Amount, Fee, TransactionStatus};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{AccountReference, Path, ServiceId};
pub fn non_interactive_path() -> Path {
Path::new(["transactions", "non-interactive"])
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TicketData {
pub account: AccountReference,
pub range: Range,
pub webhook: Option<Url>,
}
#[derive(Clone, Debug)]
pub struct Service {}
impl super::Service for Service {
const ID: ServiceId = ServiceId::Transactions;
type TicketData = TicketData;
type RequestData = RequestData;
type Output = Option<Vec<Transaction>>;
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RequestData {}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
pub enum Range {
Reference(String),
#[serde(untagged)]
Period {
from: NaiveDate,
to: Option<NaiveDate>,
},
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Transaction {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub entry_reference: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub batch: Option<BatchData>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub booking_date: Option<NaiveDate>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub value_date: Option<NaiveDate>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub transaction_date: Option<NaiveDate>,
pub status: TransactionStatus,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub account_servicer_reference: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub payment_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub transaction_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub end_to_end_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub mandate_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub creditor_id: Option<String>,
pub amount: Amount,
#[serde(default, skip_serializing_if = "super::is_false")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub reversal: bool,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub original_amount: Option<Amount>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub exchanges: Vec<ExchangeRate>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub fees: Vec<Fee>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub creditor: Option<Party>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub debtor: Option<Party>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub remittance_information: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub purpose_code: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub bank_transaction_codes: Vec<BankTransactionCode>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub additional_information: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct BatchData {
#[serde(skip_serializing_if = "Option::is_none")]
pub number_of_transactions: Option<u32>,
pub transactions: Vec<BatchTransactionDetails>,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct BatchTransactionDetails {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub account_servicer_reference: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub payment_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub transaction_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub end_to_end_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub mandate_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub creditor_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub amount: Option<Amount>,
#[serde(default, skip_serializing_if = "super::is_false")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub reversal: bool,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub original_amount: Option<Amount>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub exchanges: Vec<ExchangeRate>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub fees: Vec<Fee>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub creditor: Option<Party>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub debtor: Option<Party>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub remittance_information: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub purpose_code: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub bank_transaction_codes: Vec<BankTransactionCode>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub additional_information: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct ExchangeRate {
pub source_currency: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub target_currency: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub unit_currency: Option<String>,
pub exchange_rate: Decimal,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(not(feature = "server"), non_exhaustive)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Party {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub iban: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub bic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default))]
pub ultimate: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[non_exhaustive]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum BankTransactionCode {
Iso {
domain: String,
family: String,
sub_family: String,
},
Swift(String),
Bai(String),
National { code: String, country: CountryCode },
Other {
code: String,
#[serde(skip_serializing_if = "Option::is_none")]
issuer: Option<String>,
},
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(val: &bool) -> bool {
!*val
}
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[non_exhaustive]
pub enum PaymentStatus {
Accepted,
PartiallyAccepted,
CompletedDebtor,
CompletedCreditor,
}
pub mod collect_payment {
pub use routex_models::Amount;
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use serde_with::base64::Base64;
#[cfg(not(feature = "server"))]
pub use super::{
AccountIdentifier as DebtorAccountIdentifier, AccountReference as DebtorAccountReference,
};
use super::{AccountIdentifier, Path, PaymentStatus, ServiceId};
pub fn status_path(ticket_id: &str) -> Path {
Path::new(["collect-payment", "status", ticket_id])
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SuccessStatusData {
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_status: Option<PaymentStatus>,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TicketData {
pub amount: Amount,
pub creditor_account: AccountIdentifier,
pub creditor_name: String,
pub remittance: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instant: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<Vec<Field>>,
}
#[derive(Clone, Debug)]
pub struct Service {}
impl super::Service for Service {
const ID: ServiceId = ServiceId::CollectPayment;
type TicketData = TicketData;
type RequestData = RequestData;
type Output = PaymentInitiation;
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum Field {
DebtorIban,
DebtorName,
}
#[cfg(feature = "server")]
#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum DebtorAccountIdentifier {
#[serde(alias = "Iban")]
Iban(String),
EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
}
#[cfg(feature = "server")]
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[serde(rename_all = "camelCase")]
pub struct DebtorAccountReference {
#[serde(flatten)]
pub id: DebtorAccountIdentifier,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub currency: Option<String>,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RequestData {
pub account: Option<DebtorAccountReference>,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct PaymentInitiation {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<PaymentStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debtor_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debtor_iban: Option<String>,
}
}
#[cfg(test)]
mod tests {
use std::fmt::Debug;
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::{Deserialize, Serialize};
use crate::{
Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
};
#[test]
fn read_response() {
let serialized = serde_json::to_string(
&encode(
&Header::new(Algorithm::HS256),
&Claims {
data: "data",
exp: 2_540_808_000,
},
&EncodingKey::from_secret(b"does_not_matter"),
)
.unwrap(),
)
.unwrap();
let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
}
struct Service;
impl crate::Service for Service {
const ID: ServiceId = ServiceId::Accounts;
type TicketData = ();
type RequestData = ();
type Output = ();
}
#[allow(
dead_code,
unconditional_recursion,
clippy::extra_unused_type_parameters
)]
fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
test_bounds::<InputContext<Service>>();
test_bounds::<ConfirmationContext<Service>>();
test_bounds::<DialogInput<Service>>();
test_bounds::<Dialog<Service>>();
test_bounds::<Redirect<Service>>();
test_bounds::<RedirectHandle<Service>>();
test_bounds::<OBResult<()>>();
test_bounds::<Session>();
test_bounds::<Authenticated<Ticket<Service>>>();
test_bounds::<Ticket<Service>>();
test_bounds::<ResponseData<Service>>();
test_bounds::<ConfirmationData<Service>>();
}
fn test_eq_<T: PartialEq + Debug>(o: &T) {
assert!(o.eq(o));
}
#[test]
fn test_eq() {
let ic = InputContext::<Service>::from(vec![]);
let cc = ConfirmationContext::<Service>::from(vec![]);
test_eq_(&ic);
test_eq_(&cc);
test_eq_(&Redirect {
url: "url:".parse().unwrap(),
context: cc.clone(),
});
test_eq_(&RedirectHandle {
handle: String::new(),
context: cc.clone(),
});
test_eq_(&Ticket::<Service> {
service: ServiceId::Accounts,
id: String::new(),
data: (),
});
test_eq_(&ResponseData {
context: ic,
response: String::new(),
});
test_eq_(&ConfirmationData { context: cc });
}
}