use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
use pi_result::{ClassifyErrorKind, ErrorKind, RawResult};
#[derive(Debug, pi_result::thiserror::Error)]
#[non_exhaustive]
pub enum ReadTargetRegionError {
#[error("excluded read target start overflows usize")]
ExcludedStartOverflow,
#[error("included read target end overflows usize")]
IncludedEndOverflow,
#[error("read target region is reversed: start {start} exceeds end {end_exclusive}")]
Reversed {
start: usize,
end_exclusive: usize,
},
#[error(
"read target region [{start}, {end_exclusive}) exceeds initialized length {initialized_len}"
)]
OutOfBounds {
start: usize,
end_exclusive: usize,
initialized_len: usize,
},
}
impl ClassifyErrorKind for ReadTargetRegionError {
fn classify_error_kind(&self) -> ErrorKind {
ErrorKind::InvalidInput
}
}
pub struct ReadTargetRegion {
start: usize,
end_exclusive: Option<usize>,
}
impl ReadTargetRegion {
pub fn new(start: Bound<usize>, end: Bound<usize>) -> RawResult<Self, ReadTargetRegionError> {
let start = match start {
Bound::Unbounded => 0,
Bound::Included(start) => start,
Bound::Excluded(start) => start
.checked_add(1)
.ok_or(ReadTargetRegionError::ExcludedStartOverflow)?,
};
let end_exclusive = match end {
Bound::Unbounded => None,
Bound::Excluded(end_exclusive) => Some(end_exclusive),
Bound::Included(end) => Some(
end.checked_add(1)
.ok_or(ReadTargetRegionError::IncludedEndOverflow)?,
),
};
if let Some(end_exclusive) = end_exclusive {
if start > end_exclusive {
return Err(ReadTargetRegionError::Reversed {
start,
end_exclusive,
});
}
}
Ok(Self {
start,
end_exclusive,
})
}
#[must_use]
pub fn full() -> Self {
Self {
start: 0,
end_exclusive: None,
}
}
#[must_use]
pub fn start(&self) -> usize {
self.start
}
#[must_use]
pub fn end_exclusive(&self) -> Option<usize> {
self.end_exclusive
}
pub fn resolve(
&self,
initialized_len: usize,
) -> RawResult<Range<usize>, ReadTargetRegionError> {
let end_exclusive = self.end_exclusive.unwrap_or(initialized_len);
if self.start > end_exclusive || end_exclusive > initialized_len {
return Err(ReadTargetRegionError::OutOfBounds {
start: self.start,
end_exclusive,
initialized_len,
});
}
Ok(self.start..end_exclusive)
}
}
impl Clone for ReadTargetRegion {
fn clone(&self) -> Self {
Self {
start: self.start,
end_exclusive: self.end_exclusive,
}
}
}
impl fmt::Debug for ReadTargetRegion {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ReadTargetRegion")
.field("start", &self.start)
.field("end_exclusive", &self.end_exclusive)
.finish()
}
}
impl fmt::Display for ReadTargetRegion {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.end_exclusive {
Some(end_exclusive) => {
write!(formatter, "[{}, {})", self.start, end_exclusive)
}
None => write!(formatter, "[{}, end)", self.start),
}
}
}
impl PartialEq for ReadTargetRegion {
fn eq(&self, other: &Self) -> bool {
self.start == other.start && self.end_exclusive == other.end_exclusive
}
}
impl Eq for ReadTargetRegion {}
impl PartialOrd for ReadTargetRegion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ReadTargetRegion {
fn cmp(&self, other: &Self) -> Ordering {
self.start.cmp(&other.start).then_with(|| {
match (self.end_exclusive, other.end_exclusive) {
(Some(left), Some(right)) => left.cmp(&right),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => Ordering::Equal,
}
})
}
}
impl Hash for ReadTargetRegion {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.start.hash(state);
self.end_exclusive.hash(state);
}
}
impl TryFrom<Range<usize>> for ReadTargetRegion {
type Error = ReadTargetRegionError;
fn try_from(range: Range<usize>) -> Result<Self, Self::Error> {
Self::new(Bound::Included(range.start), Bound::Excluded(range.end))
}
}
impl TryFrom<RangeInclusive<usize>> for ReadTargetRegion {
type Error = ReadTargetRegionError;
fn try_from(range: RangeInclusive<usize>) -> Result<Self, Self::Error> {
let (start, end) = range.into_inner();
Self::new(Bound::Included(start), Bound::Included(end))
}
}
impl From<RangeFrom<usize>> for ReadTargetRegion {
fn from(range: RangeFrom<usize>) -> Self {
Self {
start: range.start,
end_exclusive: None,
}
}
}
impl From<RangeTo<usize>> for ReadTargetRegion {
fn from(range: RangeTo<usize>) -> Self {
Self {
start: 0,
end_exclusive: Some(range.end),
}
}
}
impl TryFrom<RangeToInclusive<usize>> for ReadTargetRegion {
type Error = ReadTargetRegionError;
fn try_from(range: RangeToInclusive<usize>) -> Result<Self, Self::Error> {
Self::new(Bound::Unbounded, Bound::Included(range.end))
}
}
impl From<RangeFull> for ReadTargetRegion {
fn from(_: RangeFull) -> Self {
Self::full()
}
}