use crate::api_errors::APIErrorBadRequest;
use crate::api_objects::ApiObject;
use crate::pagination::{ApiItem, ApiPagination, OrderTypes};
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 ApiPage<D>
where
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,
#[serde(skip_serializing_if = "Option::is_none")]
pub order: Option<OrderTypes>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimal_data: Option<bool>,
pub etag: String,
pub links: ApiPageLinks,
pub data: Vec<ApiItem<D>>,
#[serde(skip)]
pub given_etag: String,
#[serde(skip)]
pub base_url: String,
#[serde(skip)]
pub server_max_page_size: u32,
}
impl<D> SchemaExample for ApiPage<D>
where
D: SchemaExample + Serialize + ApiObject,
{
fn example() -> Self {
let link = D::get_page_link(&"http://127.0.0.1:20350/api".to_owned());
Self {
max_page_size: 100,
total_item_count: 2731,
page_start: 300,
page_size: 100,
page_nr: 3,
order: Some(OrderTypes::Asc),
order_by: Some("type".to_owned()),
filter_by: Some("type".to_owned()),
filter_value: Some("library".to_owned()),
minimal_data: Some(false),
etag: "19fb53cca19546fd3eac64e11cc5a24a965dbe426fb932fcfc324569a6cc21a6".to_owned(),
links: ApiPageLinks {
self_: format!("{}?per_page=100&order=asc&order_by=type&page=3", link),
first: Some(format!(
"{}?per_page=100&order=asc&order_by=type&page=0",
link
)),
prev: Some(format!(
"{}?per_page=100&order=asc&order_by=type&page=2",
link
)),
next: Some(format!(
"{}?per_page=100&order=asc&order_by=type&page=4",
link
)),
last: Some(format!(
"{}?per_page=100&order=asc&order_by=type&page=27",
link
)),
},
data: vec![ApiItem::<D>::example()],
given_etag: "".to_owned(),
base_url: "".to_owned(),
server_max_page_size: 0,
}
}
}
impl<D> ApiPage<D>
where
D: ApiObject + Default + Serialize + SchemaExample,
{
pub fn new(pagination: &ApiPagination, server_info: &State<ServerInfo>) -> Self {
let server_info = server_info.inner().clone();
let mut new_object = Self {
base_url: server_info.base_url.clone(),
order: pagination.order.clone(),
order_by: pagination.order_by.clone(),
filter_by: pagination.filter_by.clone(),
filter_value: pagination.filter_value.clone(),
minimal_data: pagination.minimal_data,
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
}
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(order) = &self.order {
query_parameters.push(format!("order={}", order));
}
if let Some(order_by) = &self.order_by {
query_parameters.push(format!("order_by={}", order_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));
}
if let Some(minimal_data) = &self.minimal_data {
query_parameters.push(format!("minimal_data={}", minimal_data));
}
let mut links = ApiPageLinks::default();
let base_api_path = D::get_page_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;
}
pub fn wrap(&mut self, list: Vec<D>) -> 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_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<D>) -> Vec<ApiItem<D>> {
let mut data = Vec::new();
self.page_size = list.len() as u32;
for item in list {
data.push(ApiItem::wrap_new(item, &self));
}
data
}
pub fn get_db_order(&self) -> Option<df_st_db::OrderTypes> {
match &self.order {
Some(x) => Some(match x {
OrderTypes::Asc => df_st_db::OrderTypes::Asc,
OrderTypes::Desc => df_st_db::OrderTypes::Desc,
}),
None => None,
}
}
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, C, DB>(&mut self) -> Result<(), APIErrorBadRequest>
where
T: DBObject<C, DB>,
C: Fillable + Filler<C, DB> + Default + Debug + Clone,
DB: PartialEq<C> + Debug + Clone,
{
if self.order_by.is_some() {
self.order_by = match T::match_field_by_opt(self.order_by.clone(), MatchBy::OrderBy) {
Some(order_by) => Some(order_by),
None => {
let message = format!(
"Order by field `{}` does not exists. Allowed fields are: {}",
self.order_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::FilterBy).join(", "),
);
error!("{}", message);
return Err(APIErrorBadRequest::from(message));
}
}
}
Ok(())
}
pub fn get_nested_items(&self) -> bool {
match self.minimal_data {
Some(true) => false,
Some(false) => true,
None => true,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
#[schemars(example = "Self::example")]
pub struct ApiPageLinks {
#[serde(rename = "self")]
pub self_: String,
pub first: Option<String>,
pub prev: Option<String>,
pub next: Option<String>,
pub last: Option<String>,
}
impl SchemaExample for ApiPageLinks {
fn example() -> Self {
Self {
self_:
"http://127.0.0.1:20350/api/examples?per_page=100&order=asc&order_by=type&page=3"
.to_owned(),
first: Some(
"http:///127.0.0.1:20350/api/examples?per_page=100&order=asc&order_by=type&page=0"
.to_owned(),
),
prev: Some(
"http:///127.0.0.1:20350/api/examples?per_page=100&order=asc&order_by=type&page=2"
.to_owned(),
),
next: Some(
"http:///127.0.0.1:20350/api/examples?per_page=100&order=asc&order_by=type&page=4"
.to_owned(),
),
last: Some(
"http:///127.0.0.1:20350/api/examples?per_page=100&order=asc&order_by=type&page=27"
.to_owned(),
),
}
}
}