use std::{convert::Infallible, sync::Arc, task::Poll};
use headers::{AcceptRanges, ContentLength, ContentType, ETag, HeaderMapExt};
use http::{header::VARY, Response, StatusCode};
use http_api_problem::ApiError;
use hyper::Body;
use if_chain::if_chain;
use iri_string::types::UriReferenceStr;
use manas_access_control::model::KResolvedAccessControl;
use manas_http::{
header::link::{Link, LinkRel, LinkTarget, LinkValue},
representation::{
metadata::{KCompleteContentLength, KContentRange, KDerivedETag, KLastModified},
Representation, RepresentationExt,
},
service::BoxHttpResponseFuture,
};
use manas_repo::service::resource_operator::common::preconditions::KEvaluatedRepValidators;
use manas_space::{resource::slot::SolidResourceSlot, SolidStorageSpace};
use rdf_vocabularies::ns;
use tower::Service;
use typed_record::TypedRecord;
use crate::{
policy::method::MethodPolicyExt,
service::method::{
common::snippet::authorization::attach_authorization_context,
get::base::{error_context::KExistingMutexResourceUri, BaseGetResponse},
},
SgCredentials, SolidStorage,
};
#[derive(Debug, Clone, Default)]
pub struct DefaultBaseGetResponseMarshallerConfig {
pub redirect_if_mutex_resource_exists: bool,
pub dev_mode: bool,
}
#[derive(Debug)]
pub struct DefaultBaseGetResponseMarshaller<Storage> {
pub storage: Arc<Storage>,
pub marshal_config: DefaultBaseGetResponseMarshallerConfig,
}
impl<Storage> Clone for DefaultBaseGetResponseMarshaller<Storage> {
#[inline]
fn clone(&self) -> Self {
Self {
storage: self.storage.clone(),
marshal_config: self.marshal_config.clone(),
}
}
}
impl<Storage> DefaultBaseGetResponseMarshaller<Storage> {
#[inline]
pub fn new(
storage: Arc<Storage>,
marshal_config: DefaultBaseGetResponseMarshallerConfig,
) -> Self {
Self {
storage,
marshal_config,
}
}
}
impl<Storage> Service<Result<BaseGetResponse<Storage>, ApiError>>
for DefaultBaseGetResponseMarshaller<Storage>
where
Storage: SolidStorage,
{
type Response = Response<Body>;
type Error = Infallible;
type Future = BoxHttpResponseFuture<Body>;
#[inline]
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
#[inline]
#[tracing::instrument(skip_all, name = "DefaultBaseGetResponseMarshaller::call")]
fn call(&mut self, req: Result<BaseGetResponse<Storage>, ApiError>) -> Self::Future {
Box::pin(futures::future::ready(Ok(match req {
Ok(resp) => self.marshal_ok(resp),
Err(err) => self.marshal_err(err),
})))
}
}
impl<Storage> DefaultBaseGetResponseMarshaller<Storage>
where
Storage: SolidStorage,
{
#[allow(clippy::vec_init_then_push)]
pub fn marshal_ok(&self, base_response: BaseGetResponse<Storage>) -> Response<Body> {
let rep = base_response.state.representation();
let res_slot: &SolidResourceSlot<Storage::StSpace> = &base_response.state.slot;
let mut builder = Response::builder();
builder = builder.status(if rep.is_complete() {
StatusCode::OK
} else {
StatusCode::PARTIAL_CONTENT
});
let headers = builder.headers_mut().expect("Must be valid");
self.storage
.method_policy()
.set_allow_accept_headers_for_existing(
headers,
res_slot,
rep.metadata().content_type(),
);
headers.typed_insert::<ContentType>(rep.metadata().content_type().clone().into());
if let Some(content_range) = rep.metadata().get_rv::<KContentRange>() {
headers.typed_insert(content_range.clone());
if let Some(bytes_range) = content_range.bytes_range() {
headers.typed_insert(ContentLength(bytes_range.1 - bytes_range.0 + 1));
}
} else {
if let Some(content_length) = rep.metadata().get_rv::<KCompleteContentLength>().cloned()
{
headers.typed_insert(content_length);
}
}
headers.typed_insert(AcceptRanges::bytes());
if let Some(last_modified) = rep.metadata().get_rv::<KLastModified>().copied() {
headers.typed_insert(last_modified);
}
if let Some(etag) = rep.metadata().get_rv::<KDerivedETag>().cloned() {
headers.typed_insert::<ETag>(etag.into());
}
let mut links = Vec::<LinkValue>::new();
links.push(
LinkValue::try_new_basic(ns::ldp::Resource.to_string(), "type").expect("Must be valid"),
);
if res_slot.is_container_slot() {
links.push(
LinkValue::try_new_basic(ns::ldp::BasicContainer.to_string(), "type")
.expect("Must be valid"),
);
}
let space = res_slot.space();
if let Some(slot_rev_link) = res_slot.slot_rev_link() {
links.push(LinkValue::new(
LinkTarget(AsRef::<UriReferenceStr>::as_ref(&*res_slot.id().uri).to_owned()),
LinkRel::new(slot_rev_link.rev_rel_type.clone().into()),
Some(AsRef::<UriReferenceStr>::as_ref(&*slot_rev_link.target).to_owned()),
))
}
links.push(
LinkValue::try_new_basic(
space.description_res_uri().as_str(),
"http://www.w3.org/ns/solid/terms#storageDescription",
)
.expect("Must be valid"),
);
if res_slot.is_root_slot() {
links.push(
LinkValue::try_new_basic(ns::pim::Storage.to_string(), "type")
.expect("Must be valid"),
);
links.push(
LinkValue::try_new_basic(space.owner_id().as_str(), ns::solid::owner.to_string())
.expect("Must be valid"),
);
}
for item in &base_response.aux_links_index {
links.push(item.clone().into())
}
headers.typed_insert(Link { values: links });
headers.insert(
VARY,
"Accept, Authorization, Origin"
.parse()
.expect("Must be valid"),
);
if let Some(resolved_acl) = base_response
.extensions
.get_rv::<KResolvedAccessControl<SgCredentials<Storage>>>()
{
headers.typed_insert(resolved_acl.authorization().to_wac_allow());
}
builder
.body(Body::wrap_stream(
base_response
.state
.into_parts()
.1
.into_basic()
.into_streaming()
.data
.stream,
))
.expect("Must be well formed response")
}
pub fn marshal_err(&self, mut error: ApiError) -> Response<Body> {
if self.marshal_config.dev_mode {
attach_authorization_context::<Storage>(&mut error);
}
let mut response = error.to_http_api_problem().to_hyper_response();
if response.status() == StatusCode::NOT_FOUND {
if_chain! {
if self.marshal_config.redirect_if_mutex_resource_exists;
if let Some(mutex_res_uri) = error.extensions().get_rv::<KExistingMutexResourceUri>();
then {
response = Response::builder()
.status(StatusCode::MOVED_PERMANENTLY)
.header("Location", mutex_res_uri.as_str())
.body(Body::empty())
.expect("Must be valid");
}
else {
self.storage
.method_policy()
.set_allow_accept_headers_for_non_existing(response.headers_mut());
}
};
}
if response.status() == StatusCode::NOT_MODIFIED {
if_chain! {
if let Some(Some(rep_validators)) = error.extensions().get_rv::<KEvaluatedRepValidators>();
if let Some(etag) = rep_validators.get_rv::<KDerivedETag>().cloned();
then {
response.headers_mut().typed_insert::<ETag>(etag.into())
}
};
response.headers_mut().insert(
VARY,
"Accept, Authorization, Origin"
.parse()
.expect("Must be valid"),
);
if let Some(resolved_acl) = error
.extensions()
.get_rv::<KResolvedAccessControl<SgCredentials<Storage>>>()
{
response
.headers_mut()
.typed_insert(resolved_acl.authorization().to_wac_allow());
}
}
response
}
}