1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code)]
3#![deny(clippy::pedantic)]
4#![allow(
5 clippy::struct_excessive_bools,
6 reason = "structs cannot be changed due to serialization"
7)]
8
9use std::collections::HashMap;
10
11use chrono::{DateTime, Utc};
12
13pub use crate::string_boolean::StringBoolean;
14
15#[cfg(feature = "decode_everything")]
16pub type ExtraValues = HashMap<String, serde_json::Value>;
17
18#[cfg(feature = "decode_everything")]
19pub mod has_extra_data;
20#[cfg(feature = "decode_everything")]
21pub use has_extra_data::*;
22
23macro_rules! enum_with_extra {
27 (
28 $(#[$attrs: meta])*
29 untagged $name: ident, $($(#[$variant_attrs: meta])* $variant: ident($variant_inner_ty: ty),)*
30 ) => {
31 $(#[$attrs])*
32 #[derive(Clone, Debug, PartialEq, serde::Deserialize)]
33 #[serde(untagged)]
34 pub enum $name {
35 $($(#[$variant_attrs])* $variant($variant_inner_ty),)*
36
37 #[cfg(feature = "decode_everything")]
39 SomethingElse(crate::ExtraValues),
40 }
41
42 #[cfg(feature = "decode_everything")]
43 impl crate::HasExtraData for $name {
44 fn has_extra_data(&self) -> bool {
45 match self {
46 Self::SomethingElse(_vals) => true,
47 $(Self::$variant(inner) => inner.has_extra_data(),)*
48 }
49 }
50 }
51 };
52 (
53 $case: expr => $(#[$attrs: meta])* $name: ident,
54 $($(#[$variant_attrs: meta])* $variant: ident,)*
55 ) => {
56 $(#[$attrs])*
57 #[derive(Clone, Debug, PartialEq, serde::Deserialize)]
58 #[serde(rename_all = $case)]
59 pub enum $name {
60 $($(#[$variant_attrs])* $variant,)*
61
62 #[cfg(feature = "decode_everything")]
64 #[serde(untagged)]
65 SomethingElse(String),
66 }
67
68 #[cfg(feature = "decode_everything")]
69 impl crate::HasExtraData for $name {
70 fn has_extra_data(&self) -> bool {
71 match self {
72 Self::SomethingElse(_value) => true,
73 _ => false,
74 }
75 }
76 }
77 };
78}
79
80macro_rules! struct_with_extra {
84 (
85 no_extra $(#[$attrs: meta])*
86 $name: ident, $($(#[$field_attrs: meta])* $field_name: ident: $field_typ: ty,)*
87 ) => {
88 $(#[$attrs])*
89 #[derive(Clone, Debug, PartialEq, serde::Deserialize)]
90 pub struct $name {
91 $($(#[$field_attrs])* pub $field_name: $field_typ,)*
92 }
93
94 #[cfg(feature = "decode_everything")]
95 impl crate::HasExtraData for $name {
96 fn has_extra_data(&self) -> bool {
97 false $(|| self.$field_name.has_extra_data())*
98 }
99 }
100 };
101 (
102 $(#[$attrs: meta])*
103 $name: ident, $($(#[$field_attrs: meta])* $field_name: ident: $field_typ: ty,)*
104 ) => {
105 $(#[$attrs])*
106 #[derive(Clone, Debug, PartialEq, serde::Deserialize)]
107 pub struct $name {
108 $($(#[$field_attrs])* pub $field_name: $field_typ,)*
109
110 #[cfg(feature = "decode_everything")]
113 #[cfg_attr(feature = "decode_everything", serde(flatten))]
114 pub extra: crate::ExtraValues,
115 }
116
117 #[cfg(feature = "decode_everything")]
118 impl crate::HasExtraData for $name {
119 fn has_extra_data(&self) -> bool {
120 let this_has_extra = !self.extra.is_empty();
121 this_has_extra
122 $(|| self.$field_name.has_extra_data())*
123 }
124 }
125 };
126}
127
128#[cfg(test)]
129mod tests;
130
131pub mod counterparty;
132pub mod merchant;
133pub mod metadata;
134pub mod string_boolean;
135
136struct_with_extra! {
137 Webhook,
139 r#type: WebhookType,
141 data: WebhookData,
143}
144
145enum_with_extra! {
146 "snake_case" => WebhookType,
147 #[serde(rename = "transaction.created")]
148 TransactionCreated,
149 #[serde(rename = "transaction.updated")]
150 TransactionUpdated,
151}
152
153struct_with_extra! { WebhookData,
154 id: String,
155 created: DateTime<Utc>,
156 description: String,
157 amount: i64,
159 currency: String,
161 is_load: bool,
162 settled: SettledTimestamp,
163 local_amount: i64,
165 local_currency: String,
167 merchant: Option<merchant::Merchant>,
168 merchant_feedback_uri: String,
169 notes: String,
170 metadata: metadata::WebhookMetadata,
171 category: String,
172 updated: DateTime<Utc>,
173 account_id: String,
174 user_id: String,
175 counterparty: counterparty::CounterpartyOrNone,
176 scheme: Scheme,
177 dedupe_id: String,
178 originator: bool,
179 include_in_spending: bool,
180 can_be_excluded_from_breakdown: bool,
181 can_be_made_subscription: bool,
182 can_split_the_bill: bool,
183 can_add_to_tab: bool,
184 can_match_transactions_in_categorization: bool,
185 amount_is_pending: bool,
186 parent_account_id: String,
187 categories: Option<HashMap<String, i64>>,
188 attachments: (),
190 atm_fees_detailed: (),
191 #[allow(clippy::zero_sized_map_values, reason = "this needs refactor when we establish type anyway")]
192 fees: HashMap<(), ()>,
193 international: (),
194 labels: (),
195}
196
197enum_with_extra! {
198 untagged SettledTimestamp,
199 Settled(DateTime<Utc>),
200 NotYetSettled(String),
202}
203
204enum_with_extra! { "snake_case" => Scheme,
205 Mastercard,
206 PayportFasterPayments,
207 UkRetailPot,
208 MonzoFlex,
209 MonzoToMonzo,
210}