-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): add
ledger/updates/by-milestone
endpoint (#326)
- Loading branch information
Showing
9 changed files
with
221 additions
and
68 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,96 @@ | ||
// 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 regex::Regex; | ||
use serde::Deserialize; | ||
|
||
use crate::api::{error::ParseError, ApiError}; | ||
|
||
const HISTORY_PAGING_REGEX: &str = r"^([0-9]+)\.(0x(?:[0-9a-fA-F]{2})+)\.([0-9]+)$"; | ||
const DEFAULT_PAGE_SIZE: usize = 100; | ||
|
||
#[derive(Clone)] | ||
pub struct HistoryPagination { | ||
pub struct HistoryByAddressPagination { | ||
pub page_size: usize, | ||
pub start_milestone_index: Option<u32>, | ||
pub start_output_id: Option<OutputId>, | ||
} | ||
|
||
#[derive(Clone, Deserialize, Default)] | ||
#[serde(default)] | ||
pub struct HistoryPaginationQuery { | ||
pub struct HistoryByAddressPaginationQuery { | ||
pub page_size: Option<usize>, | ||
pub start_milestone_index: Option<u32>, | ||
pub cursor: Option<String>, | ||
} | ||
|
||
#[async_trait] | ||
impl<B: Send> FromRequest<B> for HistoryPagination { | ||
impl<B: Send> FromRequest<B> for HistoryByAddressPagination { | ||
type Rejection = ApiError; | ||
|
||
async fn from_request(req: &mut axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> { | ||
let Query(HistoryPaginationQuery { | ||
let Query(HistoryByAddressPaginationQuery { | ||
mut page_size, | ||
mut start_milestone_index, | ||
cursor, | ||
}) = Query::<HistoryPaginationQuery>::from_request(req) | ||
}) = Query::<HistoryByAddressPaginationQuery>::from_request(req) | ||
.await | ||
.map_err(ApiError::QueryError)?; | ||
let mut start_output_id = None; | ||
if let Some(cursor) = cursor { | ||
// Unwrap: Infallable as long as the regex is valid | ||
let regex = Regex::new(HISTORY_PAGING_REGEX).unwrap(); | ||
let captures = regex.captures(&cursor).ok_or(ParseError::BadPagingState)?; | ||
start_milestone_index.replace(captures.get(1).unwrap().as_str().parse().map_err(ApiError::bad_parse)?); | ||
start_output_id | ||
.replace(OutputId::from_str(captures.get(2).unwrap().as_str()).map_err(ApiError::bad_parse)?); | ||
page_size.replace(captures.get(3).unwrap().as_str().parse().map_err(ApiError::bad_parse)?); | ||
let parts = cursor.split('.').collect::<Vec<_>>(); | ||
if parts.len() != 3 { | ||
return Err(ApiError::bad_parse(ParseError::BadPagingState)); | ||
} else { | ||
start_milestone_index.replace(parts[0].parse().map_err(ApiError::bad_parse)?); | ||
start_output_id.replace(parts[1].parse().map_err(ApiError::bad_parse)?); | ||
page_size.replace(parts[2].parse().map_err(ApiError::bad_parse)?); | ||
} | ||
} | ||
Ok(HistoryPagination { | ||
page_size: page_size.unwrap_or(100), | ||
Ok(HistoryByAddressPagination { | ||
page_size: page_size.unwrap_or(DEFAULT_PAGE_SIZE), | ||
start_milestone_index, | ||
start_output_id, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct HistoryByMilestonePagination { | ||
pub page_size: usize, | ||
pub start_output_id: Option<OutputId>, | ||
} | ||
|
||
#[derive(Clone, Deserialize, Default)] | ||
#[serde(default)] | ||
pub struct HistoryByMilestonePaginationQuery { | ||
pub page_size: Option<usize>, | ||
pub cursor: Option<String>, | ||
} | ||
|
||
#[async_trait] | ||
impl<B: Send> FromRequest<B> for HistoryByMilestonePagination { | ||
type Rejection = ApiError; | ||
|
||
async fn from_request(req: &mut axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> { | ||
let Query(HistoryByMilestonePaginationQuery { mut page_size, cursor }) = | ||
Query::<HistoryByMilestonePaginationQuery>::from_request(req) | ||
.await | ||
.map_err(ApiError::QueryError)?; | ||
let mut start_output_id = None; | ||
if let Some(cursor) = cursor { | ||
let parts = cursor.split('.').collect::<Vec<_>>(); | ||
if parts.len() != 2 { | ||
return Err(ApiError::bad_parse(ParseError::BadPagingState)); | ||
} else { | ||
start_output_id.replace(parts[0].parse().map_err(ApiError::bad_parse)?); | ||
page_size.replace(parts[1].parse().map_err(ApiError::bad_parse)?); | ||
} | ||
} | ||
Ok(HistoryByMilestonePagination { | ||
page_size: page_size.unwrap_or(DEFAULT_PAGE_SIZE), | ||
start_output_id, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.