#![allow(clippy::module_name_repetitions)]
use mas_storage::Pagination;
use schemars::JsonSchema;
use serde::Serialize;
use ulid::Ulid;
use super::model::Resource;
#[derive(Serialize, JsonSchema)]
struct PaginationLinks {
#[serde(rename = "self")]
self_: String,
first: String,
last: String,
#[serde(skip_serializing_if = "Option::is_none")]
next: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
prev: Option<String>,
}
#[derive(Serialize, JsonSchema)]
struct PaginationMeta {
count: usize,
}
#[derive(Serialize, JsonSchema)]
pub struct PaginatedResponse<T> {
meta: PaginationMeta,
data: Vec<SingleResource<T>>,
links: PaginationLinks,
}
fn url_with_pagination(base: &str, pagination: Pagination) -> String {
let (path, query) = base.split_once('?').unwrap_or((base, ""));
let mut query = query.to_owned();
if let Some(before) = pagination.before {
query += &format!("&page[before]={before}");
}
if let Some(after) = pagination.after {
query += &format!("&page[after]={after}");
}
let count = pagination.count;
match pagination.direction {
mas_storage::pagination::PaginationDirection::Forward => {
query += &format!("&page[first]={count}");
}
mas_storage::pagination::PaginationDirection::Backward => {
query += &format!("&page[last]={count}");
}
}
let query = query.trim_start_matches('&');
format!("{path}?{query}")
}
impl<T: Resource> PaginatedResponse<T> {
pub fn new(
page: mas_storage::Page<T>,
current_pagination: Pagination,
count: usize,
base: &str,
) -> Self {
let links = PaginationLinks {
self_: url_with_pagination(base, current_pagination),
first: url_with_pagination(base, Pagination::first(current_pagination.count)),
last: url_with_pagination(base, Pagination::last(current_pagination.count)),
next: page.has_next_page.then(|| {
url_with_pagination(
base,
current_pagination
.clear_before()
.after(page.edges.last().unwrap().id()),
)
}),
prev: if page.has_previous_page {
Some(url_with_pagination(
base,
current_pagination
.clear_after()
.before(page.edges.first().unwrap().id()),
))
} else {
None
},
};
let data = page.edges.into_iter().map(SingleResource::new).collect();
Self {
meta: PaginationMeta { count },
data,
links,
}
}
}
#[derive(Serialize, JsonSchema)]
struct SingleResource<T> {
#[serde(rename = "type")]
type_: &'static str,
#[schemars(with = "super::schema::Ulid")]
id: Ulid,
attributes: T,
links: SelfLinks,
}
impl<T: Resource> SingleResource<T> {
fn new(resource: T) -> Self {
let self_ = resource.path();
Self {
type_: T::KIND,
id: resource.id(),
attributes: resource,
links: SelfLinks { self_ },
}
}
}
#[derive(Serialize, JsonSchema)]
struct SelfLinks {
#[serde(rename = "self")]
self_: String,
}
#[derive(Serialize, JsonSchema)]
pub struct SingleResponse<T> {
data: SingleResource<T>,
links: SelfLinks,
}
impl<T: Resource> SingleResponse<T> {
pub fn new(resource: T, self_: String) -> Self {
Self {
data: SingleResource::new(resource),
links: SelfLinks { self_ },
}
}
pub fn new_canonical(resource: T) -> Self {
let self_ = resource.path();
Self::new(resource, self_)
}
}
#[derive(Serialize, JsonSchema)]
struct Error {
title: String,
}
impl Error {
fn from_error(error: &(dyn std::error::Error + 'static)) -> Self {
Self {
title: error.to_string(),
}
}
}
#[derive(Serialize, JsonSchema)]
pub struct ErrorResponse {
errors: Vec<Error>,
}
impl ErrorResponse {
pub fn from_error(error: &(dyn std::error::Error + 'static)) -> Self {
let mut errors = Vec::new();
let mut head = Some(error);
while let Some(error) = head {
errors.push(Error::from_error(error));
head = error.source();
}
Self { errors }
}
}