#[cfg(feature = "std")]
use crate::utils;
use crate::{msa::MessageSourceId, node::BlockNumber};
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_runtime::traits::One;
use sp_std::{prelude::*, vec};
#[cfg(feature = "std")]
use utils::*;
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Default, Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq)]
pub struct MessageResponse {
pub provider_msa_id: MessageSourceId,
pub index: u16,
pub block_number: BlockNumber,
#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none", default))]
pub msa_id: Option<MessageSourceId>,
#[cfg_attr(
feature = "std",
serde(with = "as_hex_option", skip_serializing_if = "Option::is_none", default)
)]
pub payload: Option<Vec<u8>>,
#[cfg_attr(
feature = "std",
serde(with = "as_string_option", skip_serializing_if = "Option::is_none", default)
)]
pub cid: Option<Vec<u8>>,
#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none", default))]
pub payload_length: Option<u32>,
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Default, Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq)]
pub struct BlockPaginationRequest {
pub from_block: BlockNumber,
pub from_index: u32,
pub to_block: BlockNumber,
pub page_size: u32,
}
impl BlockPaginationRequest {
pub const MAX_PAGE_SIZE: u32 = 10000;
pub const MAX_BLOCK_RANGE: u32 = 50000; pub fn validate(&self) -> bool {
self.page_size > 0 &&
self.page_size <= Self::MAX_PAGE_SIZE &&
self.from_block < self.to_block &&
self.to_block - self.from_block <= Self::MAX_BLOCK_RANGE
}
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Default, Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq)]
pub struct BlockPaginationResponse<T> {
pub content: Vec<T>,
pub has_next: bool,
#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none"))]
pub next_block: Option<BlockNumber>,
#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none"))]
pub next_index: Option<u32>,
}
impl<T> BlockPaginationResponse<T> {
pub const fn new() -> BlockPaginationResponse<T> {
BlockPaginationResponse {
content: vec![],
has_next: false,
next_block: None,
next_index: None,
}
}
pub fn check_end_condition_and_set_next_pagination(
&mut self,
block_number: BlockNumber,
current_index: u32,
list_size: u32,
request: &BlockPaginationRequest,
) -> bool {
if self.content.len() as u32 == request.page_size {
let mut next_block = block_number;
let mut next_index = current_index + 1;
if next_index == list_size {
next_block = block_number + BlockNumber::one();
next_index = 0;
}
if next_block < request.to_block {
self.has_next = true;
self.next_block = Some(next_block);
self.next_index = Some(next_index);
}
return true
}
false
}
}
#[cfg(test)]
mod tests {
use crate::{
messages::{BlockPaginationRequest, BlockPaginationResponse, MessageResponse},
node::BlockNumber,
};
struct TestCase<T> {
input: BlockPaginationRequest,
expected: T,
message: String,
}
#[test]
fn as_hex_option_msg_ipfs_serialize_deserialize_test() {
let msg = MessageResponse {
payload: None,
msa_id: None,
provider_msa_id: 1,
index: 1,
block_number: 1,
cid: Some(
"bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq"
.as_bytes()
.to_vec(),
),
payload_length: Some(42),
};
let serialized = serde_json::to_string(&msg).unwrap();
assert_eq!(serialized, "{\"provider_msa_id\":1,\"index\":1,\"block_number\":1,\"cid\":\"bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq\",\"payload_length\":42}");
let deserialized: MessageResponse = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, msg);
}
#[test]
fn as_hex_option_empty_payload_deserialize_as_default_value() {
let expected_msg = MessageResponse {
payload: None,
msa_id: Some(1),
provider_msa_id: 1,
index: 1,
block_number: 1,
cid: None,
payload_length: None,
};
let serialized_msg_without_payload =
"{\"provider_msa_id\":1,\"index\":1,\"block_number\":1,\"msa_id\":1}";
let deserialized_result: MessageResponse =
serde_json::from_str(&serialized_msg_without_payload).unwrap();
assert_eq!(deserialized_result, expected_msg);
}
#[test]
fn block_pagination_request_validation_test() {
let test_cases: Vec<TestCase<bool>> = vec![
TestCase {
input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 12, page_size: 1 },
expected: true,
message: "Should be valid".to_string(),
},
TestCase {
input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 12, page_size: 0 },
expected: false,
message: "Page with size 0 is invalid".to_string(),
},
TestCase {
input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 8, page_size: 1 },
expected: false,
message: "from_block should be less than to_block".to_string(),
},
TestCase {
input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 8, page_size: 10000 + 1 },
expected: false,
message: "page_size should be less than MAX_PAGE_SIZE".to_string(),
},
TestCase {
input: BlockPaginationRequest { from_block: 1, from_index: 0, to_block: 50000 + 2, page_size: 1 },
expected: false,
message: "the difference between from_block and to_block should be less than MAX_BLOCK_RANGE".to_string(),
},
];
for tc in test_cases {
assert_eq!(tc.expected, tc.input.validate(), "{}", tc.message);
}
}
#[test]
fn check_end_condition_does_not_mutate_when_at_the_end() {
let mut resp = BlockPaginationResponse::<u32> {
content: vec![1, 2, 3],
has_next: false,
next_block: None,
next_index: None,
};
let total_data_length: u32 = resp.content.len() as u32;
let request = BlockPaginationRequest {
from_block: 1 as BlockNumber,
from_index: 0,
to_block: 5,
page_size: total_data_length + 10,
};
let current_block = 5;
let current_index = total_data_length - 1;
let list_size = current_index;
let is_full = resp.check_end_condition_and_set_next_pagination(
current_block,
current_index,
list_size,
&request,
);
assert_eq!(false, is_full);
assert_eq!(false, resp.has_next);
assert_eq!(None, resp.next_block);
assert_eq!(None, resp.next_index);
}
#[test]
fn check_end_condition_mutates_when_more_in_list_than_page() {
let mut resp = BlockPaginationResponse::<u32> {
content: vec![1, 2, 3],
has_next: false,
next_block: None,
next_index: None,
};
let total_data_length: u32 = resp.content.len() as u32;
let request = BlockPaginationRequest {
from_block: 1 as BlockNumber,
from_index: 0,
to_block: 5,
page_size: total_data_length,
};
let current_block = 1;
let current_index = total_data_length - 1;
let list_size = total_data_length + 1;
let is_full = resp.check_end_condition_and_set_next_pagination(
current_block,
current_index,
list_size,
&request,
);
assert_eq!(true, is_full);
assert_eq!(true, resp.has_next);
assert_eq!(Some(1), resp.next_block);
assert_eq!(Some(current_index + 1), resp.next_index);
}
#[test]
fn check_end_condition_mutates_when_more_than_page_but_none_left_in_block() {
let mut resp = BlockPaginationResponse::<u32> {
content: vec![1, 2, 3],
has_next: false,
next_block: None,
next_index: None,
};
let total_data_length: u32 = resp.content.len() as u32;
let request = BlockPaginationRequest {
from_block: 1 as BlockNumber,
from_index: 0,
to_block: 5,
page_size: total_data_length,
};
let current_block = 1;
let current_index = total_data_length - 1;
let list_size = total_data_length;
let is_full = resp.check_end_condition_and_set_next_pagination(
current_block,
current_index,
list_size,
&request,
);
assert_eq!(true, is_full);
assert_eq!(true, resp.has_next);
assert_eq!(Some(current_block + 1), resp.next_block);
assert_eq!(Some(0), resp.next_index);
}
}