diff --git a/bin/inx-chronicle/src/api/error.rs b/bin/inx-chronicle/src/api/error.rs index 19fb885ce..60876758b 100644 --- a/bin/inx-chronicle/src/api/error.rs +++ b/bin/inx-chronicle/src/api/error.rs @@ -106,6 +106,8 @@ pub enum ParseError { #[allow(dead_code)] #[error("Invalid cursor")] BadPagingState, + #[error("Invalid sort order descriptor")] + BadSortDescriptor, #[cfg(feature = "stardust")] #[error(transparent)] BeeBlockStardust(#[from] bee_block_stardust::Error), diff --git a/bin/inx-chronicle/src/api/stardust/history/extractors.rs b/bin/inx-chronicle/src/api/stardust/history/extractors.rs index 7740a5e67..557f160fb 100644 --- a/bin/inx-chronicle/src/api/stardust/history/extractors.rs +++ b/bin/inx-chronicle/src/api/stardust/history/extractors.rs @@ -1,18 +1,54 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::str::FromStr; + use async_trait::async_trait; use axum::extract::{FromRequest, Query}; -use chronicle::types::stardust::block::OutputId; +use chronicle::{db::collections::SortOrder, types::stardust::block::OutputId}; use serde::Deserialize; use crate::api::{error::ParseError, ApiError}; const DEFAULT_PAGE_SIZE: usize = 100; +#[derive(Clone, Debug)] +pub enum Sort { + Ascending, + Descending, +} + +impl Default for Sort { + fn default() -> Self { + Self::Ascending + } +} + +impl FromStr for Sort { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match s { + "asc" => Ok(Self::Ascending), + "desc" => Ok(Self::Descending), + _ => Err(ParseError::BadSortDescriptor), + } + } +} + +impl From for SortOrder { + fn from(sort: Sort) -> Self { + match sort { + Sort::Ascending => Self::Oldest, + Sort::Descending => Self::Newest, + } + } +} + #[derive(Clone)] pub struct HistoryByAddressPagination { pub page_size: usize, + pub sort: Sort, pub start_milestone_index: Option, pub start_output_id: Option, } @@ -22,6 +58,7 @@ pub struct HistoryByAddressPagination { pub struct HistoryByAddressPaginationQuery { #[serde(rename = "pageSize")] pub page_size: Option, + pub sort: Option, #[serde(rename = "startMilestoneIndex")] pub start_milestone_index: Option, pub cursor: Option, @@ -34,6 +71,7 @@ impl FromRequest for HistoryByAddressPagination { async fn from_request(req: &mut axum::extract::RequestParts) -> Result { let Query(HistoryByAddressPaginationQuery { mut page_size, + sort, mut start_milestone_index, cursor, }) = Query::::from_request(req) @@ -52,6 +90,11 @@ impl FromRequest for HistoryByAddressPagination { } Ok(HistoryByAddressPagination { page_size: page_size.unwrap_or(DEFAULT_PAGE_SIZE), + sort: if let Some(sort) = sort { + sort.parse().map_err(ApiError::bad_parse)? + } else { + Sort::default() + }, start_milestone_index, start_output_id, }) @@ -61,6 +104,7 @@ impl FromRequest for HistoryByAddressPagination { #[derive(Clone)] pub struct HistoryByMilestonePagination { pub page_size: usize, + pub sort: Sort, pub start_output_id: Option, } @@ -69,6 +113,7 @@ pub struct HistoryByMilestonePagination { pub struct HistoryByMilestonePaginationQuery { #[serde(rename = "pageSize")] pub page_size: Option, + pub sort: Option, pub cursor: Option, } @@ -77,10 +122,13 @@ impl FromRequest for HistoryByMilestonePagination { type Rejection = ApiError; async fn from_request(req: &mut axum::extract::RequestParts) -> Result { - let Query(HistoryByMilestonePaginationQuery { mut page_size, cursor }) = - Query::::from_request(req) - .await - .map_err(ApiError::QueryError)?; + let Query(HistoryByMilestonePaginationQuery { + mut page_size, + sort, + cursor, + }) = Query::::from_request(req) + .await + .map_err(ApiError::QueryError)?; let mut start_output_id = None; if let Some(cursor) = cursor { let parts = cursor.split('.').collect::>(); @@ -93,6 +141,11 @@ impl FromRequest for HistoryByMilestonePagination { } Ok(HistoryByMilestonePagination { page_size: page_size.unwrap_or(DEFAULT_PAGE_SIZE), + sort: if let Some(sort) = sort { + sort.parse().map_err(ApiError::bad_parse)? + } else { + Sort::default() + }, start_output_id, }) } diff --git a/bin/inx-chronicle/src/api/stardust/history/routes.rs b/bin/inx-chronicle/src/api/stardust/history/routes.rs index 3859a0860..df8ab4050 100644 --- a/bin/inx-chronicle/src/api/stardust/history/routes.rs +++ b/bin/inx-chronicle/src/api/stardust/history/routes.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use axum::{extract::Path, routing::get, Extension, Router}; use chronicle::{ - db::{collections::SortOrder, MongoDb}, + db::MongoDb, types::{stardust::block::Address, tangle::MilestoneIndex}, }; use futures::{StreamExt, TryStreamExt}; @@ -32,6 +32,7 @@ async fn transactions_by_address_history( Path(address): Path, HistoryByAddressPagination { page_size, + sort, start_milestone_index, start_output_id, }: HistoryByAddressPagination, @@ -45,8 +46,7 @@ async fn transactions_by_address_history( page_size + 1, start_milestone_index.map(Into::into), start_output_id, - // TODO: Allow specifying sort in query - SortOrder::Newest, + sort.into(), ) .await?; @@ -83,13 +83,14 @@ async fn transactions_by_milestone_history( Path(milestone_index): Path, HistoryByMilestonePagination { page_size, + sort, start_output_id, }: HistoryByMilestonePagination, ) -> ApiResult { let milestone_index = MilestoneIndex::from_str(&milestone_index).map_err(ApiError::bad_parse)?; let mut record_stream = database - .stream_ledger_updates_at_index_paginated(milestone_index, page_size + 1, start_output_id, SortOrder::Newest) + .stream_ledger_updates_at_index_paginated(milestone_index, page_size + 1, start_output_id, sort.into()) .await?; // Take all of the requested records first