Skip to main content

nika_media/tools/
error.rs

1//! Media tool error types (NIKA-290..297)
2//!
3//! Errors specific to media TOOL operations (resize, OCR, SVG render, etc.),
4//! distinct from the media PIPELINE errors (NIKA-251..259) in crate::error.
5
6/// Errors from media tool operations.
7#[derive(Debug, thiserror::Error)]
8pub enum MediaToolError {
9    /// Generic tool error (NIKA-290)
10    #[error("[NIKA-290] nika:{tool}: {reason}")]
11    ToolError { tool: String, reason: String },
12
13    /// Unsupported format (NIKA-291)
14    #[error("[NIKA-291] nika:{tool}: unsupported format '{mime}'")]
15    UnsupportedFormat { tool: String, mime: String },
16
17    /// Missing feature dependency (NIKA-292)
18    #[error("[NIKA-292] nika:{tool}: feature '{feature}' required but not enabled")]
19    DependencyMissing { tool: String, feature: String },
20
21    /// Operation timed out (NIKA-293)
22    #[error("[NIKA-293] nika:{tool}: operation timed out")]
23    Timeout { tool: String },
24
25    /// Invalid arguments (NIKA-294)
26    #[error("[NIKA-294] nika:{tool}: {reason}")]
27    InvalidArgs { tool: String, reason: String },
28
29    /// Pipeline step failed (NIKA-295)
30    #[error("[NIKA-295] nika:pipeline: step {step} failed: {reason}")]
31    PipelineStepFailed { step: usize, reason: String },
32
33    /// Pipeline has no steps (NIKA-296)
34    #[error("[NIKA-296] nika:pipeline: pipeline has no steps")]
35    PipelineEmpty,
36
37    /// Security violation (NIKA-297)
38    #[error("[NIKA-297] nika:{tool}: security violation: {reason}")]
39    SecurityViolation { tool: String, reason: String },
40
41    /// Underlying media pipeline error (NIKA-251..259)
42    #[error(transparent)]
43    Media(#[from] crate::error::MediaError),
44}
45
46/// Convenience Result alias for media tool operations.
47pub type Result<T> = std::result::Result<T, MediaToolError>;
48
49// ─── Error constructors ─────────────────────────────────────────────────────
50
51/// Create a generic media tool error (NIKA-290).
52pub fn tool_error(tool: &str, reason: impl Into<String>) -> MediaToolError {
53    MediaToolError::ToolError {
54        tool: tool.to_string(),
55        reason: reason.into(),
56    }
57}
58
59/// Create an unsupported format error (NIKA-291).
60pub fn unsupported_format(tool: &str, mime: &str) -> MediaToolError {
61    MediaToolError::UnsupportedFormat {
62        tool: tool.to_string(),
63        mime: mime.to_string(),
64    }
65}
66
67/// Create a dependency missing error (NIKA-292).
68#[allow(dead_code)]
69pub fn dependency_missing(tool: &str, feature: &str) -> MediaToolError {
70    MediaToolError::DependencyMissing {
71        tool: tool.to_string(),
72        feature: feature.to_string(),
73    }
74}
75
76/// Create a timeout error (NIKA-293).
77pub fn timeout_error(tool: &str) -> MediaToolError {
78    MediaToolError::Timeout {
79        tool: tool.to_string(),
80    }
81}
82
83/// Create an invalid args error (NIKA-294).
84pub fn invalid_args(tool: &str, reason: impl Into<String>) -> MediaToolError {
85    MediaToolError::InvalidArgs {
86        tool: tool.to_string(),
87        reason: reason.into(),
88    }
89}
90
91/// Create a pipeline step failed error (NIKA-295).
92pub fn pipeline_step_failed(step: usize, reason: impl Into<String>) -> MediaToolError {
93    MediaToolError::PipelineStepFailed {
94        step,
95        reason: reason.into(),
96    }
97}
98
99/// Create a pipeline empty error (NIKA-296).
100pub fn pipeline_empty() -> MediaToolError {
101    MediaToolError::PipelineEmpty
102}
103
104/// Create a security violation error (NIKA-297).
105pub fn security_violation(tool: &str, reason: impl Into<String>) -> MediaToolError {
106    MediaToolError::SecurityViolation {
107        tool: tool.to_string(),
108        reason: reason.into(),
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn tool_error_contains_code() {
118        let err = tool_error("thumbnail", "decode failed");
119        assert!(err.to_string().contains("NIKA-290"));
120        assert!(err.to_string().contains("decode failed"));
121    }
122
123    #[test]
124    fn unsupported_format_contains_mime() {
125        let err = unsupported_format("dimensions", "audio/wav");
126        assert!(err.to_string().contains("NIKA-291"));
127        assert!(err.to_string().contains("audio/wav"));
128    }
129
130    #[test]
131    fn dependency_missing_shows_feature() {
132        let err = dependency_missing("thumbnail", "media-thumbnail");
133        assert!(err.to_string().contains("NIKA-292"));
134        assert!(err.to_string().contains("media-thumbnail"));
135    }
136
137    #[test]
138    fn timeout_error_code() {
139        let err = timeout_error("optimize");
140        assert!(err.to_string().contains("NIKA-293"));
141    }
142
143    #[test]
144    fn invalid_args_code() {
145        let err = invalid_args("thumbnail", "width must be > 0");
146        assert!(err.to_string().contains("NIKA-294"));
147        assert!(err.to_string().contains("width must be > 0"));
148    }
149
150    #[test]
151    fn security_violation_code() {
152        let err = security_violation("svg_render", "SVG contains <script>");
153        assert!(err.to_string().contains("NIKA-297"));
154        assert!(err.to_string().contains("<script>"));
155    }
156
157    #[test]
158    fn pipeline_errors() {
159        let err = pipeline_step_failed(2, "resize failed");
160        assert!(err.to_string().contains("NIKA-295"));
161        assert!(err.to_string().contains("step 2"));
162
163        let err = pipeline_empty();
164        assert!(err.to_string().contains("NIKA-296"));
165    }
166}