use super::entity::ResourceUsage;
use super::errors::ResourceUsageError;
use super::value_objects::Resource;
pub struct UsageConflictChecker;
impl Default for UsageConflictChecker {
fn default() -> Self {
Self::new()
}
}
impl UsageConflictChecker {
pub fn new() -> Self {
Self
}
pub fn check_conflicts(
&self,
new_usage: &ResourceUsage,
existing_usages: &[ResourceUsage],
) -> Result<(), ResourceUsageError> {
for existing in existing_usages {
if existing.id() == new_usage.id() {
continue;
}
if !new_usage
.time_period()
.overlaps_with(existing.time_period())
{
continue;
}
for new_resource in new_usage.resources() {
for existing_resource in existing.resources() {
if new_resource.conflicts_with(existing_resource) {
return Err(ResourceUsageError::UsageConflict {
resource: format_resource_item(new_resource),
conflicting_user: existing.owner_email().as_str().to_string(),
});
}
}
}
}
Ok(())
}
}
pub fn format_resource_item(item: &Resource) -> String {
match item {
Resource::Gpu(spec) => {
format!(
"{} / {} / GPU:{}",
spec.server(),
spec.model(),
spec.device_number()
)
}
Resource::Room { name } => name.clone(),
}
}
pub fn format_resources(resources: &[Resource]) -> String {
if resources.is_empty() {
return String::new();
}
resources
.iter()
.map(format_resource_item)
.collect::<Vec<_>>()
.join("\n")
}
pub fn format_time_period(
period: &super::value_objects::TimePeriod,
timezone_str: Option<&str>,
) -> String {
use chrono_tz::Tz;
let tz_result = timezone_str.and_then(|s| s.parse::<Tz>().ok());
match tz_result {
Some(tz) => {
let start = period.start().with_timezone(&tz);
let end = period.end().with_timezone(&tz);
format!(
"{} - {} ({})",
start.format("%Y-%m-%d %H:%M"),
end.format("%Y-%m-%d %H:%M"),
tz.name()
)
}
None => {
use chrono::Local;
let start_local = period.start().with_timezone(&Local);
let end_local = period.end().with_timezone(&Local);
let offset = start_local.offset();
let offset_str = offset.to_string();
format!(
"{} - {} ({})",
start_local.format("%Y-%m-%d %H:%M"),
end_local.format("%Y-%m-%d %H:%M"),
offset_str
)
}
}
}
#[cfg(test)]
mod tests {
use super::super::value_objects::Gpu;
use super::*;
use chrono::TimeZone;
#[test]
fn test_format_time_period_system_default() {
let start = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 10, 0, 0).unwrap();
let end = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
let period = super::super::value_objects::TimePeriod::new(start, end).unwrap();
let result = format_time_period(&period, None);
assert!(result.contains("2024-01-15"));
assert!(result.contains(" - "));
assert!(result.contains("("));
assert!(result.contains(")"));
}
#[test]
fn test_format_time_period_with_jst() {
let start = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 10, 0, 0).unwrap();
let end = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
let period = super::super::value_objects::TimePeriod::new(start, end).unwrap();
let result = format_time_period(&period, Some("Asia/Tokyo"));
assert_eq!(result, "2024-01-15 19:00 - 2024-01-15 21:00 (Asia/Tokyo)");
}
#[test]
fn test_format_time_period_with_est() {
let start = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 10, 0, 0).unwrap();
let end = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
let period = super::super::value_objects::TimePeriod::new(start, end).unwrap();
let result = format_time_period(&period, Some("America/New_York"));
assert_eq!(
result,
"2024-01-15 05:00 - 2024-01-15 07:00 (America/New_York)"
);
}
#[test]
fn test_format_time_period_invalid_timezone() {
let start = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 10, 0, 0).unwrap();
let end = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
let period = super::super::value_objects::TimePeriod::new(start, end).unwrap();
let result = format_time_period(&period, Some("Invalid/Timezone"));
assert!(result.contains("2024-01-15"));
assert!(result.contains(" - "));
assert!(result.contains("("));
assert!(result.contains(")"));
}
#[test]
fn test_format_gpu_resource() {
let gpu = Gpu::new("Thalys".to_string(), 0, "A100 80GB PCIe".to_string());
let resource = Resource::Gpu(gpu);
let formatted = format_resource_item(&resource);
assert_eq!(formatted, "Thalys / A100 80GB PCIe / GPU:0");
}
#[test]
fn test_format_room_resource() {
let resource = Resource::Room {
name: "会議室A".to_string(),
};
let formatted = format_resource_item(&resource);
assert_eq!(formatted, "会議室A");
}
#[test]
fn test_format_multiple_resources() {
let gpu1 = Gpu::new("Thalys".to_string(), 0, "A100".to_string());
let gpu2 = Gpu::new("Thalys".to_string(), 1, "A100".to_string());
let resources = vec![Resource::Gpu(gpu1), Resource::Gpu(gpu2)];
let formatted = format_resources(&resources);
assert_eq!(formatted, "Thalys / A100 / GPU:0\nThalys / A100 / GPU:1");
}
#[test]
fn test_format_empty_resources() {
let resources: Vec<Resource> = vec![];
let formatted = format_resources(&resources);
assert_eq!(formatted, "");
}
}