use crate::id::{FeedId, StageId};
#[derive(Debug, thiserror::Error)]
pub enum NvError {
#[error("media error: {0}")]
Media(#[from] MediaError),
#[error("stage error: {0}")]
Stage(#[from] StageError),
#[error("temporal error: {0}")]
Temporal(#[from] TemporalError),
#[error("view error: {0}")]
View(#[from] ViewError),
#[error("runtime error: {0}")]
Runtime(#[from] RuntimeError),
#[error("config error: {0}")]
Config(#[from] ConfigError),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum MediaError {
#[error("connection failed to `{redacted_url}`: {detail}", redacted_url = crate::security::redact_url(url))]
ConnectionFailed { url: String, detail: String },
#[error("decode failed: {detail}")]
DecodeFailed { detail: String },
#[error("end of stream")]
Eos,
#[error("source timeout")]
Timeout,
#[error("unsupported: {detail}")]
Unsupported { detail: String },
#[error("insecure RTSP rejected by RequireTls policy (use rtsps:// or set AllowInsecure)")]
InsecureRtspRejected,
#[error(
"custom pipeline rejected: set CustomPipelinePolicy::AllowTrusted on the runtime builder to enable custom pipelines"
)]
CustomPipelineRejected,
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum StageError {
#[error("stage `{stage_id}` processing failed: {detail}")]
ProcessingFailed { stage_id: StageId, detail: String },
#[error("stage `{stage_id}` resource exhausted")]
ResourceExhausted { stage_id: StageId },
#[error("stage `{stage_id}` model/dependency load failed: {detail}")]
ModelLoadFailed { stage_id: StageId, detail: String },
}
#[derive(Debug, thiserror::Error)]
pub enum TemporalError {
#[error("track not found: {0}")]
TrackNotFound(crate::id::TrackId),
#[error("retention limit exceeded: {detail}")]
RetentionLimitExceeded { detail: String },
}
#[derive(Debug, thiserror::Error)]
pub enum ViewError {
#[error("invalid motion report: {detail}")]
InvalidMotionReport { detail: String },
#[error("transform computation failed: {detail}")]
TransformFailed { detail: String },
}
#[derive(Debug, thiserror::Error)]
pub enum RuntimeError {
#[error("feed not found: {feed_id}")]
FeedNotFound { feed_id: FeedId },
#[error("runtime is already running")]
AlreadyRunning,
#[error("feed is already paused")]
AlreadyPaused,
#[error("feed is not paused")]
NotPaused,
#[error("shutdown in progress")]
ShutdownInProgress,
#[error("feed limit exceeded (max: {max})")]
FeedLimitExceeded { max: usize },
#[error("internal registry lock poisoned")]
RegistryPoisoned,
#[error("thread spawn failed: {detail}")]
ThreadSpawnFailed { detail: String },
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("invalid source: {detail}")]
InvalidSource { detail: String },
#[error("invalid policy: {detail}")]
InvalidPolicy { detail: String },
#[error("missing required field: `{field}`")]
MissingRequired { field: &'static str },
#[error("camera mode conflict: {detail}")]
CameraModeConflict { detail: String },
#[error("invalid capacity: {field} must be > 0")]
InvalidCapacity { field: &'static str },
#[error("stage validation failed: {detail}")]
StageValidation { detail: String },
#[error("duplicate batch processor id: {id}")]
DuplicateBatchProcessorId { id: StageId },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nv_error_from_media_error() {
let err: NvError = MediaError::Timeout.into();
assert!(matches!(err, NvError::Media(MediaError::Timeout)));
assert!(err.to_string().contains("timeout"));
}
#[test]
fn nv_error_from_config_error() {
let err: NvError = ConfigError::MissingRequired { field: "source" }.into();
assert!(matches!(
err,
NvError::Config(ConfigError::MissingRequired { .. })
));
assert!(err.to_string().contains("source"));
}
#[test]
fn stage_error_includes_stage_id() {
let err = StageError::ProcessingFailed {
stage_id: StageId("detector"),
detail: "NaN output".into(),
};
let msg = err.to_string();
assert!(msg.contains("detector"));
assert!(msg.contains("NaN output"));
}
#[test]
fn runtime_error_display() {
let err = RuntimeError::FeedLimitExceeded { max: 64 };
assert!(err.to_string().contains("64"));
}
#[test]
fn media_error_is_clone() {
let err = MediaError::ConnectionFailed {
url: "rtsp://cam".into(),
detail: "timeout".into(),
};
let err2 = err.clone();
assert_eq!(err.to_string(), err2.to_string());
}
}