diff --git a/lib/modules/gva/resources/schema.gql b/lib/modules/gva/resources/schema.gql index e8a6d119e54087d798a679c3383f574fab533d6a..497a4dffdd980c58d3471a52229cfcab0d3c4e2e 100644 --- a/lib/modules/gva/resources/schema.gql +++ b/lib/modules/gva/resources/schema.gql @@ -14,7 +14,7 @@ type Query { paging: Paging, step: Int = 1, sortOrder: SortOrder = ASC - ): [Block!]! @juniper(ownership: "owned") + ): BlocksPage! @juniper(ownership: "owned") } type Mutation { @@ -64,10 +64,17 @@ type Node { } ################################# -# Block type +# Blocks types ################################# -scalar DateTimeUtc @juniper(with_time_zone: false) +type BlocksPage { + blocks: [Block!]! + currentPageNumber: Int! + intervalFrom: Int!, + intervalTo: Int!, + lastPageNumber: Int!, + totalBlocksCount: Int!, +} type Block { version: Int! @@ -78,3 +85,9 @@ type Block { commonTime: DateTimeUtc! powMin: Int! } + +################################# +# Custom scalars +################################# + +scalar DateTimeUtc @juniper(with_time_zone: false) diff --git a/lib/modules/gva/src/constants.rs b/lib/modules/gva/src/constants.rs index d5e7c7e96887f49b5cf80c045507b4271edd3896..ae9ebb7c77469b98f12c96bf12e580ab0a2026bf 100644 --- a/lib/modules/gva/src/constants.rs +++ b/lib/modules/gva/src/constants.rs @@ -16,3 +16,13 @@ //! Gva Module constants pub const API_VERSION: i32 = 0; + +pub const DEFAULT_PAGE_NUMBER_I32: i32 = 0; +pub const DEFAULT_PAGE_NUMBER: isize = 0; +pub const DEFAULT_PAGE_SIZE: usize = 50; + +pub const MIN_PAGE_SIZE: i32 = 1; +pub const MAX_PAGE_SIZE: i32 = 1_000; + +pub const BLOCK_INTERVAL_MIN_FROM: usize = 0; +pub const BLOCK_INTERVAL_MAX_SIZE: usize = 500_000; diff --git a/lib/modules/gva/src/schema.rs b/lib/modules/gva/src/schema.rs index 319d649f63061d20beb7165aa663622ee8fafd28..1680be6b4753fb1ef74e489620048935622b618a 100644 --- a/lib/modules/gva/src/schema.rs +++ b/lib/modules/gva/src/schema.rs @@ -20,6 +20,7 @@ pub mod inputs; mod queries; use self::entities::block::Block; +use self::entities::blocks_page::BlocksPage; use self::entities::node::{Node, Summary}; use crate::context::QueryContext; #[cfg(not(test))] @@ -81,12 +82,12 @@ impl QueryFields for Query { fn field_blocks( &self, executor: &Executor<'_, QueryContext>, - trail: &QueryTrail<'_, Block, Walked>, + trail: &QueryTrail<'_, BlocksPage, Walked>, block_interval_opt: Option<BlockInterval>, paging_opt: Option<Paging>, mut step: i32, sort_order: SortOrder, - ) -> FieldResult<Vec<Block>> { + ) -> FieldResult<BlocksPage> { if step <= 0 { step = 1; } diff --git a/lib/modules/gva/src/schema/entities.rs b/lib/modules/gva/src/schema/entities.rs index a6ec3bb52a566a59449d367e9c2cb8fbb1a3a835..c277e3739ea0f7c6b41a4e374c316e3be04d665b 100644 --- a/lib/modules/gva/src/schema/entities.rs +++ b/lib/modules/gva/src/schema/entities.rs @@ -16,4 +16,5 @@ // ! Module define GraphQl schema entities pub mod block; +pub mod blocks_page; pub mod node; diff --git a/lib/modules/gva/src/schema/entities/blocks_page.rs b/lib/modules/gva/src/schema/entities/blocks_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..5232d7a0aa6ce5a115955b5f3ca999eb832f30f6 --- /dev/null +++ b/lib/modules/gva/src/schema/entities/blocks_page.rs @@ -0,0 +1,67 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +// ! Module define graphql BlocksPage type + +use crate::context::QueryContext; +use crate::schema::entities::block::Block; +use juniper::{Executor, FieldResult}; +use juniper_from_schema::{QueryTrail, Walked}; + +pub struct BlocksPage { + pub(crate) blocks: Vec<Block>, + pub(crate) current_page_number: i32, + pub(crate) interval_from: i32, + pub(crate) interval_to: i32, + pub(crate) last_page_number: i32, + pub(crate) total_blocks_count: i32, +} + +impl super::super::BlocksPageFields for BlocksPage { + #[inline] + fn field_blocks( + &self, + _executor: &Executor<'_, QueryContext>, + _trail: &QueryTrail<'_, Block, Walked>, + ) -> FieldResult<&Vec<Block>> { + Ok(&self.blocks) + } + #[inline] + fn field_current_page_number( + &self, + _executor: &Executor<'_, QueryContext>, + ) -> FieldResult<&i32> { + Ok(&self.current_page_number) + } + #[inline] + fn field_interval_from(&self, _executor: &Executor<'_, QueryContext>) -> FieldResult<&i32> { + Ok(&self.interval_from) + } + #[inline] + fn field_interval_to(&self, _executor: &Executor<'_, QueryContext>) -> FieldResult<&i32> { + Ok(&self.interval_to) + } + #[inline] + fn field_last_page_number(&self, _executor: &Executor<'_, QueryContext>) -> FieldResult<&i32> { + Ok(&self.last_page_number) + } + #[inline] + fn field_total_blocks_count( + &self, + _executor: &Executor<'_, QueryContext>, + ) -> FieldResult<&i32> { + Ok(&self.total_blocks_count) + } +} diff --git a/lib/modules/gva/src/schema/inputs/block_interval.rs b/lib/modules/gva/src/schema/inputs/block_interval.rs index 4c283935d929222e30b57997342971ba730b363c..ba70c065653818e61b69f6f8d2aff8c0b0474437 100644 --- a/lib/modules/gva/src/schema/inputs/block_interval.rs +++ b/lib/modules/gva/src/schema/inputs/block_interval.rs @@ -17,113 +17,121 @@ pub use crate::schema::BlockInterval; -use durs_bc_db_reader::{BcDbRoTrait, DbError}; +use crate::constants::*; +use dubp_common_doc::BlockNumber; +use std::cmp::{max, min}; use std::ops::RangeInclusive; -const DEFAULT_START: usize = 0; -const END_WHEN_EMPTY_BLOCKCHAIN: usize = 0; +pub struct FilledBlockInterval { + from: usize, + to: usize, +} -impl BlockInterval { - fn get_default_end<DB: BcDbRoTrait>(db: &DB) -> Result<usize, DbError> { - if let Some(current_blockstamp) = db.get_current_blockstamp()? { - Ok(current_blockstamp.id.0 as usize) - } else { - Ok(END_WHEN_EMPTY_BLOCKCHAIN) - } - } - pub(crate) fn get_range<DB: BcDbRoTrait>( - db: &DB, +impl FilledBlockInterval { + pub(crate) fn new( block_interval_opt: Option<BlockInterval>, - ) -> Result<RangeInclusive<usize>, DbError> { + current_block_number_opt: Option<BlockNumber>, + ) -> Self { + let current_block_number = current_block_number_opt.unwrap_or(BlockNumber(0)).0 as usize; + if let Some(block_interval) = block_interval_opt { - let start = if let Some(from) = block_interval.from { - if from.is_negative() { - 0 - } else { - from as usize - } - } else { - DEFAULT_START - }; - let mut end = if let Some(to) = block_interval.to { - if to.is_negative() { - 0 + if let Some(from) = block_interval.from { + if let Some(to) = block_interval.to { + Self::new_with_from_and_to(current_block_number, from, to) } else { - to as usize + Self::new_with_from(current_block_number, from) } + } else if let Some(to) = block_interval.to { + Self::new_with_to(current_block_number, to) } else { - Self::get_default_end(db)? - }; - if start > end { - end = start; + Self::new_with_nothing(current_block_number) } - Ok(start..=end) } else { - Ok(DEFAULT_START..=Self::get_default_end(db)?) + Self::new_with_nothing(current_block_number) } } + fn new_with_from(current_block_number: usize, from: i32) -> Self { + let mut from = max(from, 0) as usize; + let to = min(current_block_number, from + BLOCK_INTERVAL_MAX_SIZE); + + from = min(from, to); + + FilledBlockInterval { from, to } + } + fn new_with_to(current_block_number: usize, to: i32) -> Self { + let mut to = max(0, to) as usize; + to = min(current_block_number, to); + let mut from = if to >= BLOCK_INTERVAL_MAX_SIZE { + to - BLOCK_INTERVAL_MAX_SIZE + } else { + BLOCK_INTERVAL_MIN_FROM + }; + + from = min(from, to); + + FilledBlockInterval { from, to } + } + fn new_with_from_and_to(current_block_number: usize, from: i32, to: i32) -> Self { + let mut from = max(from, 0) as usize; + let mut to = max(0, to) as usize; + to = min(current_block_number, to); + from = min(from, to); + + FilledBlockInterval { from, to } + } + fn new_with_nothing(current_block_number: usize) -> Self { + let filled_to = current_block_number; + let filled_from = if current_block_number >= BLOCK_INTERVAL_MAX_SIZE { + current_block_number - BLOCK_INTERVAL_MAX_SIZE + } else { + 0 + }; + FilledBlockInterval { + from: filled_from, + to: filled_to, + } + } + #[inline] + pub(crate) fn get_range(&self) -> RangeInclusive<usize> { + self.from..=self.to + } } #[cfg(test)] mod tests { use super::*; - use crate::db::BcDbRo; - use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp}; + use dubp_common_doc::BlockNumber; #[test] - fn test_block_interval_get_range_with_short_bc() -> Result<(), DbError> { - let mut mock_db = BcDbRo::new(); - mock_db - .expect_get_current_blockstamp() - .times(1) - .returning(|| { - Ok(Some(Blockstamp { - id: BlockNumber(42), - hash: BlockHash(dup_crypto::hashs::Hash::default()), - })) - }); + fn test_block_interval_get_range_with_short_bc() { assert_eq! { 0..=42, - BlockInterval::get_range(&mock_db, None)? + FilledBlockInterval::new(None, Some(BlockNumber(42))).get_range() } - Ok(()) } #[test] - fn test_block_interval_get_range_with_long_bc() -> Result<(), DbError> { - let mut mock_db = BcDbRo::new(); - mock_db - .expect_get_current_blockstamp() - .times(2) - .returning(|| { - Ok(Some(Blockstamp { - id: BlockNumber(750), - hash: BlockHash(dup_crypto::hashs::Hash::default()), - })) - }); - + fn test_block_interval_get_range_with_long_bc() { assert_eq! { 0..=750, - BlockInterval::get_range(&mock_db, None)? + FilledBlockInterval::new(None, Some(BlockNumber(750))).get_range() } assert_eq! { 500..=750, - BlockInterval::get_range(&mock_db, Some(BlockInterval { + FilledBlockInterval::new(Some(BlockInterval { from: Some(500), to: None, - }))? + }), Some(BlockNumber(750))).get_range() } assert_eq! { 500..=700, - BlockInterval::get_range(&mock_db, Some(BlockInterval { + FilledBlockInterval::new(Some(BlockInterval { from: Some(500), to: Some(700), - }))? + }), Some(BlockNumber(750))).get_range() } - - Ok(()) } } diff --git a/lib/modules/gva/src/schema/inputs/paging.rs b/lib/modules/gva/src/schema/inputs/paging.rs index b9853226f0553f614a32de43a0203f067e449c17..a756b7e707fdb16788f81f477c51812a48b5813d 100644 --- a/lib/modules/gva/src/schema/inputs/paging.rs +++ b/lib/modules/gva/src/schema/inputs/paging.rs @@ -17,15 +17,9 @@ pub use crate::schema::Paging; +use crate::constants::*; use std::ops::Range; -const DEFAULT_PAGE_NUMBER_I32: i32 = 0; -const DEFAULT_PAGE_NUMBER: isize = 0; -const DEFAULT_PAGE_SIZE: usize = 50; - -const MIN_PAGE_SIZE: i32 = 1; -const MAX_PAGE_SIZE: i32 = 500; - #[derive(Debug, PartialEq)] pub struct FilledPaging { pub page_number: isize, @@ -65,7 +59,7 @@ impl From<Option<Paging>> for FilledPaging { } impl FilledPaging { - pub(crate) fn get_page_range(&self, count_elems: usize, step: usize) -> Range<usize> { + pub(crate) fn get_page_range(&self, count_elems: usize, step: usize) -> (Range<usize>, usize) { let page_extended_size = self.page_size * step; let mut count_pages = count_elems / page_extended_size; if count_elems % page_extended_size > 0 { @@ -77,10 +71,13 @@ impl FilledPaging { self.page_number as usize }; - Range { - start: std::cmp::min(count_elems, page_number * page_extended_size), - end: std::cmp::min(count_elems, (page_number + 1) * page_extended_size), - } + ( + Range { + start: std::cmp::min(count_elems, page_number * page_extended_size), + end: std::cmp::min(count_elems, (page_number + 1) * page_extended_size), + }, + count_pages, + ) } } @@ -148,7 +145,7 @@ mod tests { #[test] fn test_get_page_range() { assert_eq!( - Range { start: 10, end: 20 }, + (Range { start: 10, end: 20 }, 500), FilledPaging { page_number: 1, page_size: 10, @@ -156,10 +153,13 @@ mod tests { .get_page_range(5_000, 1), ); assert_eq!( - Range { - start: 4_980, - end: 4_990 - }, + ( + Range { + start: 4_980, + end: 4_990 + }, + 500 + ), FilledPaging { page_number: -2, page_size: 10, @@ -167,7 +167,7 @@ mod tests { .get_page_range(5_000, 1), ); assert_eq!( - Range { start: 10, end: 15 }, + (Range { start: 10, end: 15 }, 2), FilledPaging { page_number: 1, page_size: 10, @@ -175,7 +175,7 @@ mod tests { .get_page_range(15, 1), ); assert_eq!( - Range { start: 15, end: 15 }, + (Range { start: 15, end: 15 }, 2), FilledPaging { page_number: 2, page_size: 10, @@ -183,7 +183,7 @@ mod tests { .get_page_range(15, 1), ); assert_eq!( - Range { start: 20, end: 40 }, + (Range { start: 20, end: 40 }, 250), FilledPaging { page_number: 1, page_size: 10, @@ -191,10 +191,13 @@ mod tests { .get_page_range(5_000, 2), ); assert_eq!( - Range { - start: 4_980, - end: 5_000 - }, + ( + Range { + start: 4_980, + end: 5_000 + }, + 250 + ), FilledPaging { page_number: -1, page_size: 10, @@ -202,7 +205,7 @@ mod tests { .get_page_range(5_000, 2), ); assert_eq!( - Range { start: 0, end: 400 }, + (Range { start: 0, end: 400 }, 1), FilledPaging { page_number: -1, page_size: 500, @@ -210,10 +213,13 @@ mod tests { .get_page_range(400, 2), ); assert_eq!( - Range { - start: 0, - end: 1_000 - }, + ( + Range { + start: 0, + end: 1_000 + }, + 1 + ), FilledPaging { page_number: -3, page_size: 400, @@ -221,10 +227,13 @@ mod tests { .get_page_range(1_000, 5), ); assert_eq!( - Range { - start: 2_000, - end: 3_000 - }, + ( + Range { + start: 2_000, + end: 3_000 + }, + 2 + ), FilledPaging { page_number: -1, page_size: 400, @@ -232,7 +241,7 @@ mod tests { .get_page_range(3_000, 5), ); assert_eq!( - Range { start: 40, end: 80 }, + (Range { start: 40, end: 80 }, 3), FilledPaging { page_number: -2, page_size: 40, diff --git a/lib/modules/gva/src/schema/queries/blocks.rs b/lib/modules/gva/src/schema/queries/blocks.rs index 8787b8fa1f9e971199619daafa8842030409f798..6737e8502f1fdbcc71fbd7394bd0a98575ea0c43 100644 --- a/lib/modules/gva/src/schema/queries/blocks.rs +++ b/lib/modules/gva/src/schema/queries/blocks.rs @@ -16,24 +16,32 @@ // ! Module execute GraphQl schema blocks query use crate::schema::entities::block::Block; -use crate::schema::inputs::block_interval::BlockInterval; +use crate::schema::entities::blocks_page::BlocksPage; +use crate::schema::inputs::block_interval::{BlockInterval, FilledBlockInterval}; use crate::schema::inputs::paging::{FilledPaging, Paging}; use crate::schema::inputs::sort_order::SortOrder; use dubp_common_doc::BlockNumber; -use durs_bc_db_reader::blocks::DbBlock; use durs_bc_db_reader::{BcDbRoTrait, DbError}; use juniper_from_schema::{QueryTrail, Walked}; pub(crate) fn execute<DB: BcDbRoTrait>( db: &DB, - _trail: &QueryTrail<'_, Block, Walked>, + _trail: &QueryTrail<'_, BlocksPage, Walked>, paging_opt: Option<Paging>, block_interval_opt: Option<BlockInterval>, step: usize, sort_order: SortOrder, -) -> Result<Vec<Block>, DbError> { +) -> Result<BlocksPage, DbError> { + // Get current block number opt + let current_block_number_opt = if let Some(current_blockstamp) = db.get_current_blockstamp()? { + Some(current_blockstamp.id) + } else { + None + }; + // Get interval - let interval = BlockInterval::get_range(db, block_interval_opt)?; + let interval = + FilledBlockInterval::new(block_interval_opt, current_block_number_opt).get_range(); // Get blocks numbers that respect filters let blocks_numbers: Vec<BlockNumber> = @@ -44,7 +52,7 @@ pub(crate) fn execute<DB: BcDbRoTrait>( .into_iter() .filter(|n| interval.contains(&(n.0 as usize))) .collect(); - let count = blocks_numbers.len(); + let total_blocks_count = blocks_numbers.len(); // Apply sort if let SortOrder::Desc = sort_order { @@ -53,7 +61,7 @@ pub(crate) fn execute<DB: BcDbRoTrait>( // Apply paging and step let paging = FilledPaging::from(paging_opt); - let page_range = paging.get_page_range(count, step); + let (page_range, count_pages) = paging.get_page_range(total_blocks_count, step); let blocks_numbers_len = blocks_numbers.len(); let blocks_numbers: Vec<BlockNumber> = page_range .step_by(step) @@ -67,9 +75,20 @@ pub(crate) fn execute<DB: BcDbRoTrait>( .collect(); // Get blocks - let blocks: Vec<DbBlock> = db.get_db_blocks_in_local_blockchain(blocks_numbers)?; + let blocks: Vec<Block> = db + .get_db_blocks_in_local_blockchain(blocks_numbers)? + .into_iter() + .map(Into::into) + .collect(); - Ok(blocks.into_iter().map(Into::into).collect()) + Ok(BlocksPage { + blocks, + current_page_number: paging.page_number as i32, + interval_from: *interval.start() as i32, + interval_to: *interval.end() as i32, + last_page_number: (count_pages - 1) as i32, + total_blocks_count: (interval.end() - interval.start() + 1) as i32, + }) } #[cfg(test)] @@ -242,14 +261,24 @@ mod tests { tests::test_gql_query( schema, - "{ blocks(interval: { from: 2 }) { commonTime, currency, hash, issuer, number, version } }", + "{ blocks(interval: { from: 2 }) { + blocks { commonTime, currency, hash, issuer, number, version }, + currentPageNumber, intervalFrom, intervalTo, lastPageNumber, totalBlocksCount + } }", json!({ "data": { - "blocks": [ - block_2_json(), - block_3_json(), - block_4_json(), - ] + "blocks": { + "blocks": [ + block_2_json(), + block_3_json(), + block_4_json(), + ], + "currentPageNumber": 0, + "intervalFrom": 2, + "intervalTo": 4, + "lastPageNumber": 0, + "totalBlocksCount": 3, + } } }), ); @@ -290,25 +319,35 @@ mod tests { tests::test_gql_query( schema, - "{ blocks(step: 2) { commonTime, currency, hash, issuer, number, version } }", + "{ blocks(step: 2) { + blocks { commonTime, currency, hash, issuer, number, version }, + currentPageNumber, intervalFrom, intervalTo, lastPageNumber, totalBlocksCount + } }", json!({ "data": { - "blocks": [{ - "commonTime": 1_488_987_127.0, - "currency": "test_currency", - "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "issuer": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "number": 0, - "version": 10 - }, - { - "commonTime": 1_488_987_129.0, - "currency": "test_currency", - "hash": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", - "issuer": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", - "number": 2, - "version": 10 - }] + "blocks": { + "blocks": [{ + "commonTime": 1_488_987_127.0, + "currency": "test_currency", + "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "issuer": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "number": 0, + "version": 10 + }, + { + "commonTime": 1_488_987_129.0, + "currency": "test_currency", + "hash": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + "issuer": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + "number": 2, + "version": 10 + }], + "currentPageNumber": 0, + "intervalFrom": 0, + "intervalTo": 2, + "lastPageNumber": 0, + "totalBlocksCount": 3 + } } }), ); @@ -354,14 +393,24 @@ mod tests { tests::test_gql_query( global_context, - "{ blocks(sortOrder: DESC) { commonTime, currency, hash, issuer, number, version } }", + "{ blocks(sortOrder: DESC) { + blocks { commonTime, currency, hash, issuer, number, version }, + currentPageNumber, intervalFrom, intervalTo, lastPageNumber, totalBlocksCount + } }", json!({ "data": { - "blocks": [ - block_2_json(), - block_1_json(), - block_0_json() - ] + "blocks": { + "blocks": [ + block_2_json(), + block_1_json(), + block_0_json() + ], + "currentPageNumber": 0, + "intervalFrom": 0, + "intervalTo": 2, + "lastPageNumber": 0, + "totalBlocksCount": 3 + } } }), ); @@ -407,13 +456,24 @@ mod tests { tests::test_gql_query( schema, - "{ blocks { commonTime, currency, hash, issuer, number, version } }", + "{ blocks { + blocks { commonTime, currency, hash, issuer, number, version }, + currentPageNumber, intervalFrom, intervalTo, lastPageNumber, totalBlocksCount + } }", json!({ "data": { - "blocks": [ - block_0_json(), - block_1_json(), - block_2_json()] + "blocks": { + "blocks": [ + block_0_json(), + block_1_json(), + block_2_json() + ], + "currentPageNumber": 0, + "intervalFrom": 0, + "intervalTo": 2, + "lastPageNumber": 0, + "totalBlocksCount": 3 + } } }), );