Skip to main content

qualia_core_db/inference/runtime/artifacts/
budget.rs

1use std::path::{Component, Path};
2
3#[derive(Debug)]
4pub enum ArtifactError {
5    InvalidLabel,
6    InvalidRelativePath,
7    BudgetExceeded {
8        budget_bytes: u64,
9        attempted_bytes: u64,
10    },
11    TargetExists,
12    Io(std::io::Error),
13    Cleanup {
14        path: std::path::PathBuf,
15        source: std::io::Error,
16    },
17    Promotion {
18        staging: std::path::PathBuf,
19        target: std::path::PathBuf,
20        source: std::io::Error,
21    },
22}
23
24impl std::fmt::Display for ArtifactError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            Self::InvalidLabel => write!(
28                f,
29                "artifact run label must be non-empty ASCII [a-zA-Z0-9_-]"
30            ),
31            Self::InvalidRelativePath => {
32                write!(f, "artifact path must be a non-empty relative child path")
33            }
34            Self::BudgetExceeded {
35                budget_bytes,
36                attempted_bytes,
37            } => write!(
38                f,
39                "artifact byte budget exceeded: budget={budget_bytes}, attempted={attempted_bytes}"
40            ),
41            Self::TargetExists => write!(f, "artifact promotion target already exists"),
42            Self::Io(source) => write!(f, "artifact I/O: {source}"),
43            Self::Cleanup { path, source } => {
44                write!(
45                    f,
46                    "artifact cleanup failed for {}: {source}",
47                    path.display()
48                )
49            }
50            Self::Promotion {
51                staging,
52                target,
53                source,
54            } => write!(
55                f,
56                "artifact promotion {} -> {} failed: {source}",
57                staging.display(),
58                target.display()
59            ),
60        }
61    }
62}
63
64impl std::error::Error for ArtifactError {
65    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
66        match self {
67            Self::Io(source) | Self::Cleanup { source, .. } | Self::Promotion { source, .. } => {
68                Some(source)
69            }
70            _ => None,
71        }
72    }
73}
74
75impl From<std::io::Error> for ArtifactError {
76    fn from(value: std::io::Error) -> Self {
77        Self::Io(value)
78    }
79}
80
81pub fn validate_relative_artifact_path(path: &Path) -> Result<(), ArtifactError> {
82    if path.as_os_str().is_empty() || path.is_absolute() {
83        return Err(ArtifactError::InvalidRelativePath);
84    }
85    for component in path.components() {
86        match component {
87            Component::Normal(_) => {}
88            _ => return Err(ArtifactError::InvalidRelativePath),
89        }
90    }
91    Ok(())
92}
93
94pub(super) fn validate_label(label: &str) -> Result<(), ArtifactError> {
95    if label.is_empty()
96        || !label
97            .bytes()
98            .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
99    {
100        return Err(ArtifactError::InvalidLabel);
101    }
102    Ok(())
103}