use crate::domain::aggregates::resource_usage::value_objects::{Gpu, Resource};
pub struct ResourceFactory;
impl ResourceFactory {
pub fn create_gpus_from_spec(
spec: &str,
server_name: &str,
device_lookup: impl Fn(u32) -> Option<String>,
) -> Result<Vec<Resource>, ResourceFactoryError> {
let device_numbers = Self::parse_device_numbers(spec)?;
let mut resources = Vec::new();
for device_num in device_numbers {
let model =
device_lookup(device_num).ok_or_else(|| ResourceFactoryError::DeviceNotFound {
server: server_name.to_string(),
device_id: device_num,
})?;
let gpu = Gpu::new(server_name.to_string(), device_num, model);
resources.push(Resource::Gpu(gpu));
}
Ok(resources)
}
fn parse_device_numbers(spec: &str) -> Result<Vec<u32>, ResourceFactoryError> {
let mut numbers = Vec::new();
for part in spec.split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
if part.contains('-') {
let range: Vec<&str> = part.split('-').collect();
if range.len() != 2 {
return Err(ResourceFactoryError::InvalidFormat(part.to_string()));
}
let start: u32 = range[0]
.parse()
.map_err(|_| ResourceFactoryError::InvalidNumber(range[0].to_string()))?;
let end: u32 = range[1]
.parse()
.map_err(|_| ResourceFactoryError::InvalidNumber(range[1].to_string()))?;
if start > end {
return Err(ResourceFactoryError::InvalidRange { start, end });
}
for n in start..=end {
numbers.push(n);
}
} else {
let num: u32 = part
.parse()
.map_err(|_| ResourceFactoryError::InvalidNumber(part.to_string()))?;
numbers.push(num);
}
}
if numbers.is_empty() {
return Err(ResourceFactoryError::EmptySpecification);
}
Ok(numbers)
}
}
#[derive(Debug)]
pub enum ResourceFactoryError {
EmptySpecification,
InvalidNumber(String),
InvalidFormat(String),
InvalidRange {
start: u32,
end: u32,
},
DeviceNotFound {
server: String,
device_id: u32,
},
}
impl std::fmt::Display for ResourceFactoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResourceFactoryError::EmptySpecification => write!(f, "デバイス指定が空です"),
ResourceFactoryError::InvalidNumber(s) => write!(f, "無効な数値: {}", s),
ResourceFactoryError::InvalidFormat(s) => write!(f, "無効なフォーマット: {}", s),
ResourceFactoryError::InvalidRange { start, end } => {
write!(f, "無効な範囲: {}-{}", start, end)
}
ResourceFactoryError::DeviceNotFound { server, device_id } => {
write!(f, "デバイス{}が{}に存在しません", device_id, server)
}
}
}
}
impl std::error::Error for ResourceFactoryError {}
impl crate::domain::errors::DomainError for ResourceFactoryError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_single_device() {
let result = ResourceFactory::parse_device_numbers("0").unwrap();
assert_eq!(result, vec![0]);
}
#[test]
fn test_parse_device_range() {
let result = ResourceFactory::parse_device_numbers("0-2").unwrap();
assert_eq!(result, vec![0, 1, 2]);
}
#[test]
fn test_parse_multiple_devices() {
let result = ResourceFactory::parse_device_numbers("0,2,5").unwrap();
assert_eq!(result, vec![0, 2, 5]);
}
#[test]
fn test_parse_mixed_specification() {
let result = ResourceFactory::parse_device_numbers("0-1,6-7").unwrap();
assert_eq!(result, vec![0, 1, 6, 7]);
}
#[test]
fn test_parse_complex_specification() {
let result = ResourceFactory::parse_device_numbers("0-2,5,7-9").unwrap();
assert_eq!(result, vec![0, 1, 2, 5, 7, 8, 9]);
}
#[test]
fn test_parse_empty_specification() {
let result = ResourceFactory::parse_device_numbers("");
assert!(matches!(
result,
Err(ResourceFactoryError::EmptySpecification)
));
}
#[test]
fn test_parse_invalid_number() {
let result = ResourceFactory::parse_device_numbers("0,abc,2");
assert!(matches!(
result,
Err(ResourceFactoryError::InvalidNumber(_))
));
}
#[test]
fn test_parse_invalid_range() {
let result = ResourceFactory::parse_device_numbers("5-2");
assert!(matches!(
result,
Err(ResourceFactoryError::InvalidRange { start: 5, end: 2 })
));
}
#[test]
fn test_create_gpus_from_spec() {
let resources =
ResourceFactory::create_gpus_from_spec("0-1", "Thalys", |device_id| match device_id {
0 => Some("A100".to_string()),
1 => Some("A100".to_string()),
_ => None,
})
.unwrap();
assert_eq!(resources.len(), 2);
}
#[test]
fn test_create_gpus_device_not_found() {
let result =
ResourceFactory::create_gpus_from_spec("0-2", "Thalys", |device_id| match device_id {
0 | 1 => Some("A100".to_string()),
_ => None,
});
assert!(matches!(
result,
Err(ResourceFactoryError::DeviceNotFound { .. })
));
}
}