use failure::Error;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::ServerInfo;
use df_st_core::SchemaExample;
use rocket::request::{FromQuery, Query};
use rocket::State;
use rocket_okapi::{
gen::OpenApiGenerator, request::get_nested_query_parameters, request::OpenApiFromQuery,
};
use std::cmp;
use std::fmt;
pub trait ApiObject {
fn get_type() -> String;
fn get_item_link(&self, base_url: &str) -> String;
fn get_page_link(base_url: &str) -> String;
fn get_count_link(base_url: &str) -> String;
}
#[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>,
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()),
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::default();
new_object.base_url = server_info.base_url.clone();
new_object.order = pagination.order.clone();
new_object.order_by = pagination.order_by.clone();
new_object.server_max_page_size = server_info.page_max_limit;
new_object.max_page_size = server_info.default_max_page_size;
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));
}
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.base_url));
}
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,
}
}
}
#[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(),
),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
#[schemars(example = "Self::example")]
pub struct ApiItem<D>
where
D: ApiObject + Serialize + SchemaExample,
{
#[serde(rename = "_type")]
pub type_: String,
#[serde(rename = "_links")]
pub links: ApiItemLinks,
#[serde(flatten)]
pub data: D,
#[serde(skip)]
pub base_url: String,
}
impl<D> SchemaExample for ApiItem<D>
where
D: SchemaExample + Serialize + ApiObject,
{
fn example() -> Self {
Self {
type_: D::get_type(),
links: ApiItemLinks {
self_: Some(D::example().get_item_link(&"http://127.0.0.1:20350/api".to_owned())),
},
data: D::example(),
base_url: "".to_owned(),
}
}
}
impl<D> ApiItem<D>
where
D: ApiObject + Default + Serialize + SchemaExample,
{
pub fn new(server_info: &State<ServerInfo>) -> Self {
let server_info = server_info.inner().clone();
let mut new_object = Self::default();
new_object.base_url = server_info.base_url;
new_object
}
pub fn wrap(&mut self, item: D) {
let link = item.get_item_link(&self.base_url);
self.data = item;
self.type_ = D::get_type();
self.links = ApiItemLinks { self_: Some(link) };
}
pub fn wrap_new(item: D, base_url: &str) -> ApiItem<D> {
let mut return_object = ApiItem::default();
return_object.base_url = base_url.to_string();
return_object.wrap(item);
return_object
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
#[schemars(example = "Self::example")]
pub struct ApiItemLinks {
#[serde(rename = "self")]
pub self_: Option<String>,
}
impl SchemaExample for ApiItemLinks {
fn example() -> Self {
Self {
self_: Some("http://127.0.0.1:20350/api/examples/5".to_owned()),
}
}
}
#[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>,
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()),
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: &ApiMinimalPagination, server_info: &State<ServerInfo>) -> Self {
let server_info = server_info.inner().clone();
let mut new_object = Self::default();
new_object.base_url = server_info.base_url.clone();
new_object.server_max_page_size = server_info.page_max_limit;
new_object.max_page_size = server_info.default_max_page_size;
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));
}
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
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
#[schemars(example = "Self::example")]
pub struct ApiMinimalItem<D>
where
D: ApiObject + Serialize + SchemaExample,
{
#[serde(rename = "_type")]
pub type_: String,
#[serde(flatten)]
pub data: D,
#[serde(skip)]
pub base_url: String,
}
impl<D> SchemaExample for ApiMinimalItem<D>
where
D: SchemaExample + Serialize + ApiObject,
{
fn example() -> Self {
Self {
type_: D::get_type(),
data: D::example(),
base_url: "".to_owned(),
}
}
}
impl<D> ApiMinimalItem<D>
where
D: ApiObject + Default + Serialize + SchemaExample,
{
pub fn new(server_info: &State<ServerInfo>) -> Self {
let server_info = server_info.inner().clone();
let mut new_object = Self::default();
new_object.base_url = server_info.base_url;
new_object
}
pub fn wrap(&mut self, item: D) {
self.data = item;
self.type_ = D::get_type();
}
pub fn wrap_new(item: D, base_url: &str) -> ApiMinimalItem<D> {
let mut return_object = ApiMinimalItem::default();
return_object.base_url = base_url.to_string();
return_object.wrap(item);
return_object
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
pub struct ApiPagination {
page: Option<u32>,
per_page: Option<u32>,
etag: Option<String>,
order: Option<OrderTypes>,
order_by: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
pub struct ApiMinimalPagination {
page: Option<u32>,
per_page: Option<u32>,
etag: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum OrderTypes {
Asc,
Desc,
}
impl From<df_st_db::OrderTypes> for OrderTypes {
fn from(order: df_st_db::OrderTypes) -> Self {
match order {
df_st_db::OrderTypes::Asc => OrderTypes::Asc,
df_st_db::OrderTypes::Desc => OrderTypes::Desc,
}
}
}
impl From<OrderTypes> for df_st_db::OrderTypes {
fn from(order: OrderTypes) -> Self {
match order {
OrderTypes::Asc => df_st_db::OrderTypes::Asc,
OrderTypes::Desc => df_st_db::OrderTypes::Desc,
}
}
}
impl fmt::Display for OrderTypes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
OrderTypes::Asc => write!(f, "asc"),
OrderTypes::Desc => write!(f, "desc"),
}
}
}
impl<'q> FromQuery<'q> for ApiPagination {
type Error = Error;
fn from_query(query: Query<'q>) -> Result<Self, Self::Error> {
let mut api_page_request = ApiPagination::default();
for key in query {
match key.key.url_decode()?.as_ref() {
"page" => api_page_request.page = Some(key.value.parse::<u32>()?),
"per_page" => api_page_request.per_page = Some(key.value.parse::<u32>()?),
"etag" => api_page_request.etag = Some(key.value.parse::<String>()?),
"order" => {
api_page_request.order = Some(match key.value.url_decode()?.as_ref() {
"desc" => OrderTypes::Desc,
_ => OrderTypes::Asc,
})
}
"order_by" => api_page_request.order_by = Some(key.value.parse::<String>()?),
_ => {}
}
}
Ok(api_page_request)
}
}
impl<'r> OpenApiFromQuery<'r> for ApiPagination {
fn query_multi_parameter(
gen: &mut OpenApiGenerator,
name: String,
required: bool,
) -> rocket_okapi::Result<Vec<okapi::openapi3::Parameter>> {
Ok(get_nested_query_parameters::<ApiPagination>(
gen, name, required,
))
}
}
impl<'q> FromQuery<'q> for ApiMinimalPagination {
type Error = Error;
fn from_query(query: Query<'q>) -> Result<Self, Self::Error> {
let mut api_page_request = ApiMinimalPagination::default();
for key in query {
match key.key.url_decode()?.as_ref() {
"page" => api_page_request.page = Some(key.value.parse::<u32>()?),
"per_page" => api_page_request.per_page = Some(key.value.parse::<u32>()?),
"etag" => api_page_request.etag = Some(key.value.parse::<String>()?),
_ => {}
}
}
Ok(api_page_request)
}
}
impl<'r> OpenApiFromQuery<'r> for ApiMinimalPagination {
fn query_multi_parameter(
gen: &mut OpenApiGenerator,
name: String,
required: bool,
) -> rocket_okapi::Result<Vec<okapi::openapi3::Parameter>> {
Ok(get_nested_query_parameters::<ApiMinimalPagination>(
gen, name, required,
))
}
}