use super::BoxError;
use super::read::{
ExactPathReadOperation, ResolvedOperators, exact_path_absent_error, read_exact_path,
};
use super::{Location, LocationRoleError, OperatorResolver};
use crate::PackArchiveBytes;
use crate::pack_archive::{ReadLimitError, ReadLimits, ReadResource};
use crate::redacted_error::RedactedError;
#[derive(Clone, Debug)]
pub struct PackArchiveReadRequest {
source: Location,
limits: ReadLimits,
}
impl PackArchiveReadRequest {
pub fn new(source: Location, limits: ReadLimits) -> Result<Self, PackArchiveReadRequestError> {
if let Err(role_error) = source.require_object() {
return Err(PackArchiveReadRequestError::InvalidSourceRole {
location: source,
source: role_error,
});
}
Ok(Self { source, limits })
}
pub fn source(&self) -> &Location {
&self.source
}
pub const fn limits(&self) -> ReadLimits {
self.limits
}
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum PackArchiveReadRequestError {
#[error("Pack Archive source {location} is not an exact object: {source}")]
InvalidSourceRole {
location: Location,
#[source]
source: LocationRoleError,
},
}
#[allow(clippy::result_large_err)]
pub async fn read_pack_archive<R: OperatorResolver + ?Sized>(
resolver: &R,
request: &PackArchiveReadRequest,
) -> Result<PackArchiveBytes, PackArchiveReadError> {
let error = |cause| PackArchiveReadError {
source_location: request.source().clone(),
cause: RedactedError::new(cause),
};
let mut operators = ResolvedOperators::new(resolver);
let resolved = operators
.resolve(request.source().binding())
.map_err(|source| error(PackArchiveReadErrorCause::ResolveOperator(Box::new(source))))?;
if !resolved.read {
return Err(error(PackArchiveReadErrorCause::ReadUnsupported));
}
let ceiling = request.limits().archive_bytes();
let operation = PackArchiveExactPathOperation { request };
let bytes = read_exact_path(
&resolved.operator,
request.source().dispatch_path(),
ceiling,
ceiling,
&operation,
)
.await?
.ok_or_else(|| {
operation.error(PackArchiveReadErrorCause::ObjectAbsent(
exact_path_absent_error(),
))
})?;
Ok(PackArchiveBytes::from_vec(bytes))
}
#[derive(Debug, thiserror::Error)]
#[error(
"Pack Archive Read failed for binding {binding} at exact-object operation path {operation_path:?}: {cause}",
binding = .source_location.binding(),
operation_path = .source_location.operation_path(),
)]
pub struct PackArchiveReadError {
source_location: Location,
#[source]
cause: RedactedError<PackArchiveReadErrorCause>,
}
impl PackArchiveReadError {
pub fn source_location(&self) -> &Location {
&self.source_location
}
pub fn cause(&self) -> &PackArchiveReadErrorCause {
self.cause.inner()
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PackArchiveReadErrorCause {
#[error("operator resolution failed")]
ResolveOperator(#[source] BoxError),
#[error("read capability is unsupported")]
ReadUnsupported,
#[error("the exact object is absent")]
ObjectAbsent(#[source] ::opendal::Error),
#[error("the exact object read failed")]
Read(#[source] ::opendal::Error),
#[error("the archive byte limit failed")]
Limit(#[source] ReadLimitError),
}
struct PackArchiveExactPathOperation<'a> {
request: &'a PackArchiveReadRequest,
}
impl PackArchiveExactPathOperation<'_> {
fn error(&self, cause: PackArchiveReadErrorCause) -> PackArchiveReadError {
PackArchiveReadError {
source_location: self.request.source().clone(),
cause: RedactedError::new(cause),
}
}
}
impl ExactPathReadOperation for PackArchiveExactPathOperation<'_> {
type Error = PackArchiveReadError;
fn read(&self, source: ::opendal::Error) -> PackArchiveReadError {
self.error(PackArchiveReadErrorCause::Read(source))
}
fn limit_exceeded(&self, ceiling: u64, _: u64) -> PackArchiveReadError {
self.error(PackArchiveReadErrorCause::Limit(ReadLimitError::exceeded(
ReadResource::ArchiveBytes,
ceiling,
)))
}
fn accounting_overflow(&self) -> PackArchiveReadError {
self.error(PackArchiveReadErrorCause::Limit(
ReadLimitError::AccountingOverflow {
resource: ReadResource::ArchiveBytes,
},
))
}
}