|
@@ -68,6 +68,48 @@ impl TryFrom<u32> for ReceivedPaymentStatus {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// IDs to query transactions
|
|
|
+#[derive(Debug, Clone)]
|
|
|
+pub enum FilterId {
|
|
|
+ /// By transaction ID with the latest revision
|
|
|
+ LatestRevision(TxId),
|
|
|
+ /// A transaction at a given revision
|
|
|
+ RevisionId(RevId),
|
|
|
+}
|
|
|
+
|
|
|
+impl From<TxId> for FilterId {
|
|
|
+ fn from(id: TxId) -> Self {
|
|
|
+ FilterId::LatestRevision(id)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<&TxId> for FilterId {
|
|
|
+ fn from(id: &TxId) -> Self {
|
|
|
+ FilterId::LatestRevision(id.clone())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<RevId> for FilterId {
|
|
|
+ fn from(id: RevId) -> Self {
|
|
|
+ FilterId::RevisionId(id)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<&RevId> for FilterId {
|
|
|
+ fn from(id: &RevId) -> Self {
|
|
|
+ FilterId::RevisionId(id.clone())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl ToString for FilterId {
|
|
|
+ fn to_string(&self) -> String {
|
|
|
+ match self {
|
|
|
+ FilterId::LatestRevision(id) => id.to_string(),
|
|
|
+ FilterId::RevisionId(id) => id.to_string(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#[derive(thiserror::Error, Debug, Serialize)]
|
|
|
/// Storage error
|
|
|
pub enum Error {
|
|
@@ -84,6 +126,11 @@ pub enum Error {
|
|
|
/// The date format is invalid
|
|
|
InvalidDate(String),
|
|
|
|
|
|
+ #[error("Invalid id: {0}")]
|
|
|
+ #[serde(serialize_with = "crate::serialize_error_to_string")]
|
|
|
+ /// Invalid Id
|
|
|
+ InvalidId(#[from] crate::id::Error),
|
|
|
+
|
|
|
#[error("Spend payment: {0}")]
|
|
|
/// The requested payment has been spent already
|
|
|
SpendPayment(String),
|
|
@@ -255,18 +302,63 @@ pub trait Storage {
|
|
|
) -> Result<Vec<(TxId, String, DateTime<Utc>)>, Error>;
|
|
|
*/
|
|
|
|
|
|
- /// Returns a revision with a transaction object by id
|
|
|
- async fn get_transaction(&self, transaction_id: &TxId) -> Result<Transaction, Error>;
|
|
|
+ /// Returns a transaction by id, the transaction is returned as a tuple of base transaction and
|
|
|
+ /// the current revision
|
|
|
+ async fn get_transaction_and_revision(
|
|
|
+ &self,
|
|
|
+ transaction_id: FilterId,
|
|
|
+ ) -> Result<(BaseTx, Revision), Error>;
|
|
|
+
|
|
|
+ /// List all revisions for a given transaction
|
|
|
+ async fn get_revisions(&self, transaction_id: &TxId) -> Result<Vec<RevId>, Error>;
|
|
|
|
|
|
/// Returns a list of a transactions for a given account (and optionally
|
|
|
/// filter by types). The list of transactions is expected to be sorted by
|
|
|
- /// date desc (newest transactions first)
|
|
|
+ /// date desc (newest transactions first),
|
|
|
+ async fn get_transactions_with_latest_revision(
|
|
|
+ &self,
|
|
|
+ account: &AccountId,
|
|
|
+ types: &[Type],
|
|
|
+ tags: &[String],
|
|
|
+ ) -> Result<Vec<(BaseTx, Revision)>, Error>;
|
|
|
+
|
|
|
+ /// Returns a revision with a transaction object by id
|
|
|
+ async fn get_transaction(&self, id: FilterId) -> Result<Transaction, Error> {
|
|
|
+ let (base_tx, revision) = self.get_transaction_and_revision(id).await?;
|
|
|
+ (
|
|
|
+ self.get_revisions(&revision.transaction_id).await?,
|
|
|
+ base_tx,
|
|
|
+ revision,
|
|
|
+ )
|
|
|
+ .try_into()
|
|
|
+ .map_err(|e: crate::transaction::Error| Error::Encoding(e.to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Get list of transactions
|
|
|
async fn get_transactions(
|
|
|
&self,
|
|
|
account: &AccountId,
|
|
|
types: &[Type],
|
|
|
tags: &[String],
|
|
|
- ) -> Result<Vec<Transaction>, Error>;
|
|
|
+ ) -> Result<Vec<Transaction>, Error> {
|
|
|
+ let mut transactions = Vec::new();
|
|
|
+ for (base_tx, revision) in self
|
|
|
+ .get_transactions_with_latest_revision(account, types, tags)
|
|
|
+ .await?
|
|
|
+ .into_iter()
|
|
|
+ {
|
|
|
+ transactions.push(
|
|
|
+ (
|
|
|
+ self.get_revisions(&revision.transaction_id).await?,
|
|
|
+ base_tx,
|
|
|
+ revision,
|
|
|
+ )
|
|
|
+ .try_into()
|
|
|
+ .map_err(|e: crate::transaction::Error| Error::Encoding(e.to_string()))?,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ Ok(transactions)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
@@ -341,7 +433,7 @@ pub mod test {
|
|
|
|
|
|
pub async fn transaction<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let asset: Asset = "USD/2".parse().expect("valid asset");
|
|
|
let deposit = Transaction::new_external_deposit(
|
|
@@ -371,7 +463,7 @@ pub mod test {
|
|
|
.await
|
|
|
.expect("update tx");
|
|
|
storing.rollback().await.expect("rollback");
|
|
|
- assert!(storage.get_transaction(&deposit.id).await.is_err());
|
|
|
+ assert!(storage.get_transaction((&deposit.id).into()).await.is_err());
|
|
|
|
|
|
let mut storing = storage.begin().await.expect("valid tx");
|
|
|
storing
|
|
@@ -391,12 +483,12 @@ pub mod test {
|
|
|
.await
|
|
|
.expect("update tx");
|
|
|
storing.commit().await.expect("commit");
|
|
|
- assert!(storage.get_transaction(&deposit.id).await.is_ok());
|
|
|
+ assert!(storage.get_transaction((&deposit.id).into()).await.is_ok());
|
|
|
}
|
|
|
|
|
|
pub async fn transaction_not_available_until_commit<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let usd: Asset = "USD/2".parse().expect("valid asset");
|
|
|
let deposit = Transaction::new_external_deposit(
|
|
@@ -425,9 +517,9 @@ pub mod test {
|
|
|
)
|
|
|
.await
|
|
|
.expect("update tx");
|
|
|
- assert!(storage.get_transaction(&deposit.id).await.is_err());
|
|
|
+ assert!(storage.get_transaction((&deposit.id).into()).await.is_err());
|
|
|
storing.rollback().await.expect("rollback");
|
|
|
- assert!(storage.get_transaction(&deposit.id).await.is_err());
|
|
|
+ assert!(storage.get_transaction((&deposit.id).into()).await.is_err());
|
|
|
|
|
|
let mut storing = storage.begin().await.expect("valid tx");
|
|
|
storing
|
|
@@ -447,12 +539,12 @@ pub mod test {
|
|
|
.await
|
|
|
.expect("update tx");
|
|
|
storing.commit().await.expect("commit");
|
|
|
- assert!(storage.get_transaction(&deposit.id).await.is_ok());
|
|
|
+ assert!(storage.get_transaction((&deposit.id).into()).await.is_ok());
|
|
|
}
|
|
|
|
|
|
pub async fn does_not_update_spent_payments<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let mut writer = storage.begin().await.expect("writer");
|
|
|
let mut rng = rand::thread_rng();
|
|
@@ -520,7 +612,7 @@ pub mod test {
|
|
|
|
|
|
pub async fn spend_spendable_payments<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let mut writer = storage.begin().await.expect("writer");
|
|
|
let mut rng = rand::thread_rng();
|
|
@@ -571,7 +663,7 @@ pub mod test {
|
|
|
|
|
|
pub async fn does_not_spend_unspendable_payments<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let mut writer = storage.begin().await.expect("writer");
|
|
|
let mut rng = rand::thread_rng();
|
|
@@ -623,7 +715,7 @@ pub mod test {
|
|
|
|
|
|
pub async fn sorted_unspent_payments<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let mut writer = storage.begin().await.expect("writer");
|
|
|
let accounts: Vec<AccountId> = (0..10)
|
|
@@ -687,7 +779,7 @@ pub mod test {
|
|
|
|
|
|
pub async fn relate_account_to_transaction<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let account1: AccountId = "alice1".parse().expect("account");
|
|
|
let account2: AccountId = "alice2".parse().expect("account");
|
|
@@ -769,7 +861,7 @@ pub mod test {
|
|
|
|
|
|
pub async fn not_spendable_new_payments_not_spendable<T>(storage: T)
|
|
|
where
|
|
|
- T: Storage,
|
|
|
+ T: Storage + Send + Sync,
|
|
|
{
|
|
|
let mut writer = storage.begin().await.expect("writer");
|
|
|
let mut rng = rand::thread_rng();
|