use crate::api_errors::APIErrorBadRequest;
use crate::api_objects::ApiObject;
use crate::pagination::{ApiCountPagination, ApiMinimalItem, ApiPageLinks};
use crate::ServerInfo;
use anyhow::Error;
use df_st_core::fillable::{Fillable, Filler};
use df_st_core::SchemaExample;
use df_st_db::{string_filter, DBObject, MatchBy};
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use rocket::State;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::cmp;
use std::collections::HashMap;
use std::fmt::Debug;
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
#[schemars(example = "Self::example")]
pub struct ApiCountPage<C, D>
where
C: ApiObject + Serialize + SchemaExample,
D: ApiObject + Serialize + SchemaExample,
{
pub max_page_size: u32,
pub total_item_count: u32,
pub page_start: u32,
pub page_size: u32,
pub page_nr: u32,
pub group_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter_value: Option<String>,
pub etag: String,
pub links: ApiPageLinks,
pub data: Vec<ApiMinimalItem<C>>,
#[serde(skip)]
pub given_etag: String,
#[serde(skip)]
pub base_url: String,
#[serde(skip)]
pub server_max_page_size: u32,
#[serde(skip)]
parent_object: D,
}
impl<C, D> SchemaExample for ApiCountPage<C, D>
where
C: SchemaExample + Serialize + ApiObject,
D: SchemaExample + Serialize + ApiObject,
{
fn example() -> Self {
let link = D::get_count_link(&"http://127.0.0.1:20350/api".to_owned());
Self {
max_page_size: 100,
total_item_count: 462,
page_start: 0,
page_size: 9,
page_nr: 0,
group_by: Some("type".to_owned()),
filter_by: Some("type".to_owned()),
filter_value: Some("library".to_owned()),
etag: "b76e1b17f19bf1ab4901c4b21a747fc4898e656b3ac3080983498b07515fb655".to_owned(),
links: ApiPageLinks {
self_: format!("{}?group_by=type&per_page=100&page=1", link),
first: Some(format!("{}?group_by=type&per_page=100&page=0", link)),
prev: Some(format!("{}?group_by=type&per_page=100&page=0", link)),
next: Some(format!("{}?group_by=type&per_page=100&page=2", link)),
last: Some(format!("{}?group_by=type&per_page=100&page=5", link)),
},
data: vec![ApiMinimalItem::<C>::example()],
given_etag: "".to_owned(),
base_url: "".to_owned(),
server_max_page_size: 0,
parent_object: D::example(),
}
}
}
impl<C, D> ApiCountPage<C, D>
where
C: ApiObject + Default + Serialize + SchemaExample,
D: ApiObject + Default + Serialize + SchemaExample,
{
pub fn new(pagination: &ApiCountPagination, server_info: &State<ServerInfo>) -> Self {
let server_info = server_info.inner().clone();
let mut new_object = Self {
base_url: server_info.base_url.clone(),
filter_by: pagination.filter_by.clone(),
filter_value: pagination.filter_value.clone(),
server_max_page_size: server_info.page_max_limit,
max_page_size: server_info.default_max_page_size,
..Default::default()
};
if let Some(per_page) = pagination.per_page {
new_object.max_page_size = cmp::min(per_page, server_info.page_max_limit);
}
new_object.max_page_size = cmp::max(new_object.max_page_size, 1);
if let Some(page) = pagination.page {
new_object.page_start = page * new_object.max_page_size;
}
if let Some(etag) = &pagination.etag {
new_object.given_etag = etag.clone();
}
new_object
}
pub fn wrap(&mut self, list: Vec<C>) -> bool {
self.data = self.warp_data_list(list);
self.page_nr = self.page_start / self.max_page_size;
self.set_links();
self.set_etag()
}
fn set_links(&mut self) {
let mut query_parameters = Vec::new();
let qp_page = if self.page_start != 0 {
format!("page={}", self.page_nr)
} else {
"".to_owned()
};
if self.max_page_size != self.server_max_page_size {
query_parameters.push(format!("per_page={}", self.max_page_size));
}
if let Some(group_by) = &self.group_by {
query_parameters.push(format!("group_by={}", group_by));
}
if let Some(filter_by) = &self.filter_by {
query_parameters.push(format!("filter_by={}", filter_by));
}
if let Some(filter_value) = &self.filter_value {
query_parameters.push(format!("filter_value={}", filter_value));
}
let mut links = ApiPageLinks::default();
let base_api_path = D::get_count_link(&self.base_url);
if qp_page.is_empty() {
links.self_ = format!("{}?{}", base_api_path, query_parameters.join("&"));
} else {
let mut local_qp = query_parameters.clone();
local_qp.push(qp_page);
links.self_ = format!("{}?{}", base_api_path, local_qp.join("&"));
}
if self.page_start >= 1 {
let mut local_qp = query_parameters.clone();
local_qp.push("page=0".to_owned());
links.first = Some(format!("{}?{}", base_api_path, local_qp.join("&")));
}
if self.page_start >= 1 {
let mut local_qp = query_parameters.clone();
local_qp.push(format!("page={}", self.page_nr - 1));
links.prev = Some(format!("{}?{}", base_api_path, local_qp.join("&")));
}
if self.page_start + self.max_page_size < self.total_item_count {
let mut local_qp = query_parameters.clone();
local_qp.push(format!("page={}", self.page_nr + 1));
links.next = Some(format!("{}?{}", base_api_path, local_qp.join("&")));
}
if self.page_start + self.max_page_size < self.total_item_count {
let mut local_qp = query_parameters;
local_qp.push(format!(
"page={}",
(self.total_item_count - 1) / self.max_page_size
));
links.last = Some(format!("{}?{}", base_api_path, local_qp.join("&")));
}
self.links = links;
}
fn set_etag(&mut self) -> bool {
use sha2::{Digest, Sha256};
self.etag = "".to_owned();
let mut hasher = Sha256::new();
let json_data = serde_json::to_string(self).unwrap();
hasher.update(json_data);
self.etag = format!("{:x}", hasher.finalize());
self.etag == self.given_etag
}
fn warp_data_list(&mut self, list: Vec<C>) -> Vec<ApiMinimalItem<C>> {
let mut data = Vec::new();
self.page_size = list.len() as u32;
for item in list {
data.push(ApiMinimalItem::wrap_new(item, &self.base_url));
}
data
}
pub fn get_string_filter(&self) -> HashMap<String, String> {
if self.filter_by.is_some() && self.filter_value.is_some() {
let filter_by = self.filter_by.clone().unwrap_or_default();
let filter_value = self.filter_value.clone().unwrap_or_default();
string_filter![filter_by => filter_value]
} else {
string_filter![]
}
}
pub fn add_int_filter(&self, mut int_filter: HashMap<String, i32>) -> HashMap<String, i32> {
if self.filter_by.is_some() && self.filter_value.is_some() {
let filter_by = self.filter_by.clone().unwrap_or_default();
let filter_value = self.filter_value.clone().unwrap_or_default();
if let Ok(value) = filter_value.parse::<i32>() {
int_filter.insert(filter_by, value);
}
int_filter
} else {
int_filter
}
}
pub fn match_fields<T, CO, DB>(&mut self) -> Result<(), APIErrorBadRequest>
where
T: DBObject<CO, DB>,
CO: Fillable + Filler<CO, DB> + Default + Debug + Clone,
DB: PartialEq<CO> + Debug + Clone,
{
if self.group_by.is_some() {
self.group_by = match T::match_field_by_opt(self.group_by.clone(), MatchBy::GroupBy) {
Some(group_by) => Some(group_by),
None => {
let message = format!(
"Group by field `{}` does not exists. Allowed fields are: {}",
self.group_by.as_ref().unwrap(),
T::match_field_by(MatchBy::OrderBy).join(", "),
);
error!("{}", message);
return Err(APIErrorBadRequest::from(message));
}
}
}
if (self.filter_by.is_some() && self.filter_value.is_none())
|| (self.filter_by.is_none() && self.filter_value.is_some())
{
return Err(APIErrorBadRequest::from(
"Both `filter_by` and `filter_value` \
have to be provided for the filter to work.",
));
}
if self.filter_by.is_some() {
if T::match_field_by_opt(self.filter_by.clone(), MatchBy::IntFilterBy).is_some() {
let filter_value = self.filter_value.clone().unwrap_or_default();
match filter_value.parse::<i32>() {
Ok(_) => {}
Err(err) => match err.to_string().as_ref() {
"invalid digit found in string" => {
let message = format!(
"Filter error value `{}` is not an integer, \
please insert a number.",
filter_value
);
error!("{}", message);
return Err(APIErrorBadRequest::from(message));
}
_ => {
error!("Filter error: {}", err.to_string());
return Err(APIErrorBadRequest::from(Error::from(err)));
}
},
}
}
self.filter_by = match T::match_field_by_opt(self.filter_by.clone(), MatchBy::FilterBy)
{
Some(filter_by) => Some(filter_by),
None => {
let message = format!(
"Filter by field `{}` does not exists. Allowed fields are: {}",
self.filter_by.as_ref().unwrap(),
T::match_field_by(MatchBy::OrderBy).join(", "),
);
error!("{}", message);
return Err(APIErrorBadRequest::from(message));
}
}
}
Ok(())
}
}