///|
/// Errors returned while constructing or changing a resource calendar.
pub enum ResourceError {
EmptyResourceId
NonPositiveCapacity(Int)
EmptyAvailability
ReservationOutsideAvailability
ReservationNotFound
} derive(Eq, Debug)
///| A bookable thing: a room, a microscope, a CI worker, or a person whose
///|
/// availability has been supplied by the embedding application.
pub struct ResourceCalendar {
id : String
capacity : Int
available : IntervalSet
blocked : IntervalSet
} derive(Debug)
///|
pub fn ResourceCalendar::new(
id : String,
capacity : Int,
available : IntervalSet,
) -> Result[ResourceCalendar, ResourceError] {
if id.length() == 0 {
Err(EmptyResourceId)
} else if capacity <= 0 {
Err(NonPositiveCapacity(capacity))
} else if available.is_empty() {
Err(EmptyAvailability)
} else {
Ok({ id, capacity, available, blocked: IntervalSet::empty() })
}
}
///|
pub fn ResourceCalendar::id(self : ResourceCalendar) -> String {
self.id
}
///|
pub fn ResourceCalendar::capacity(self : ResourceCalendar) -> Int {
self.capacity
}
///|
pub fn ResourceCalendar::availability(self : ResourceCalendar) -> IntervalSet {
self.available
}
///|
pub fn ResourceCalendar::blocked(self : ResourceCalendar) -> IntervalSet {
self.blocked
}
///|
pub fn ResourceCalendar::free_within(
self : ResourceCalendar,
horizon : Interval,
) -> IntervalSet {
self.available.within(horizon).subtract(self.blocked)
}
///|
pub fn ResourceCalendar::is_free(
self : ResourceCalendar,
range : Interval,
) -> Bool {
let free = self.free_within(range)
for candidate in free.ranges() {
if candidate.contains_interval(range) {
return true
}
}
false
}
///|
/// Return a new calendar with the range marked unavailable. The immutable
/// value style lets callers evaluate alternatives without hidden mutation.
pub fn ResourceCalendar::reserve(
self : ResourceCalendar,
range : Interval,
) -> Result[ResourceCalendar, ResourceError] {
if !self.is_free(range) {
return Err(ReservationOutsideAvailability)
}
Ok({
id: self.id,
capacity: self.capacity,
available: self.available,
blocked: self.blocked.add(range),
})
}
///|
/// True only when one normalized blocked range completely covers `range`.
pub fn ResourceCalendar::has_reservation(
self : ResourceCalendar,
range : Interval,
) -> Bool {
for blocked in self.blocked.ranges() {
if blocked.contains_interval(range) {
return true
}
}
false
}
///|
/// Return a successor calendar with a prior reservation released. The range
///| must be covered by an existing reservation so stale clients cannot create
///|
/// availability that was never held.
pub fn ResourceCalendar::release(
self : ResourceCalendar,
range : Interval,
) -> Result[ResourceCalendar, ResourceError] {
if !self.has_reservation(range) {
return Err(ReservationNotFound)
}
Ok({
id: self.id,
capacity: self.capacity,
available: self.available,
blocked: self.blocked.subtract(IntervalSet::from_ranges([range])),
})
}
///|
pub fn ResourceCalendar::with_blocked(
self : ResourceCalendar,
blocked : IntervalSet,
) -> ResourceCalendar {
{
id: self.id,
capacity: self.capacity,
available: self.available,
blocked: self.blocked.union(blocked),
}
}