Skip to main content

imferno_core/package/
codes.rs

1//! Typed validation-code catalogue for codes emitted by the package module.
2//!
3//! Re-exports per-spec enums from their home modules within imferno-core.
4
5pub use crate::diagnostics::codes::ValidationCode;
6
7pub use crate::assetmap::codes::St2067_2_2020;
8pub use crate::assetmap::volindex_codes::St429_9_2014;
9pub use crate::cpl::codes::St2067_3_2016;
10pub use crate::mxf::codes::St377_1_2011;
11pub use crate::scm::codes::St2067_9_2018;
12
13use crate::diagnostics::{Category, Severity};
14
15// ─────────────────────────────────────────────────────────────────────────────
16// imferno tool-level codes  (not derived from any SMPTE spec)
17// ─────────────────────────────────────────────────────────────────────────────
18
19/// Tool-level observation codes emitted by imferno itself.
20///
21/// These are not normative violations — they reflect structural observations
22/// that the tool surfaces as informational findings.  Code strings use the
23/// `IMFERNO:` namespace prefix to distinguish them from spec codes.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
25pub enum ImfernoCode {
26    /// An asset is present in the AssetMap but has no CPL Virtual Track
27    /// reference and no SCM declaration.  Likely a sidecar essence (e.g.
28    /// Dolby Atmos MXF) delivered without an accompanying SCM document.
29    UnreferencedAsset,
30    /// A file is present in the package directory but not listed as a
31    /// chunk in any AssetMap entry.  The file is completely outside the
32    /// package manifest and will be ignored by any conforming IMF reader.
33    UnlistedEssence,
34    /// IMF package failed to parse (top-level structural failure).
35    ParseError,
36    /// A PKL referenced by the AssetMap could not be parsed.
37    PklParseError,
38    /// An XML asset could not be parsed as CPL, OPL, or SCM.
39    XmlAssetParseError,
40    /// An XML file could not be read from disk.
41    XmlReadError,
42    /// Could not scan the package directory.
43    ReadDirError,
44    /// Could not read a directory entry while scanning for unlisted essences.
45    DirEntryError,
46    /// An asset chunk path attempts to escape the package root directory.
47    PathTraversal,
48}
49
50impl ValidationCode for ImfernoCode {
51    fn code(&self) -> &'static str {
52        match self {
53            Self::UnreferencedAsset => "IMFERNO:Package/UnreferencedAsset",
54            Self::UnlistedEssence => "IMFERNO:Package/UnlistedEssence",
55            Self::ParseError => "IMFERNO:Package/ParseError",
56            Self::PklParseError => "IMFERNO:Package/PklParseError",
57            Self::XmlAssetParseError => "IMFERNO:Package/XmlAssetParseError",
58            Self::XmlReadError => "IMFERNO:Package/XmlReadError",
59            Self::ReadDirError => "IMFERNO:Package/ReadDirError",
60            Self::DirEntryError => "IMFERNO:Package/DirEntryError",
61            Self::PathTraversal => "IMFERNO:Package/PathTraversal",
62        }
63    }
64
65    fn description(&self) -> &'static str {
66        match self {
67            Self::UnreferencedAsset =>
68                "Asset is present in the AssetMap but not referenced by any CPL Virtual Track and has no SCM declaration. Likely a sidecar essence without an SCM.",
69            Self::UnlistedEssence =>
70                "File is present in the package directory but not listed in the AssetMap. The file is invisible to any conforming IMF reader.",
71            Self::ParseError =>
72                "IMF package failed to parse due to a structural error.",
73            Self::PklParseError =>
74                "A Packing List referenced by the AssetMap could not be parsed.",
75            Self::XmlAssetParseError =>
76                "An XML asset could not be parsed as CPL, OPL, or SCM.",
77            Self::XmlReadError =>
78                "An XML file could not be read from disk.",
79            Self::ReadDirError =>
80                "Could not scan the package directory.",
81            Self::DirEntryError =>
82                "Could not read a directory entry while scanning for unlisted essences.",
83            Self::PathTraversal =>
84                "An asset chunk path attempts to escape the package root directory (path traversal).",
85        }
86    }
87
88    fn default_severity(&self) -> Severity {
89        match self {
90            Self::UnreferencedAsset => Severity::Info,
91            Self::UnlistedEssence => Severity::Warning,
92            Self::ParseError => Severity::Critical,
93            Self::PklParseError => Severity::Error,
94            Self::XmlAssetParseError => Severity::Warning,
95            Self::XmlReadError => Severity::Warning,
96            Self::ReadDirError => Severity::Info,
97            Self::DirEntryError => Severity::Info,
98            Self::PathTraversal => Severity::Error,
99        }
100    }
101
102    fn category(&self) -> Category {
103        Category::Structure
104    }
105
106    fn example(&self) -> Option<&'static str> {
107        Some(match self {
108            Self::UnreferencedAsset =>
109                "ASSETMAP lists urn:uuid:abc… as an Asset, but no CPL Virtual Track resource and no SCM Asset entry references it.",
110            Self::UnlistedEssence =>
111                "BONUS_AUDIO.mxf sits next to ASSETMAP.xml on disk but does not appear in any <Asset> entry of ASSETMAP.xml.",
112            Self::ParseError =>
113                "Top-level error parsing ASSETMAP.xml — e.g. missing root <AssetMap> element or truncated stream.",
114            Self::PklParseError =>
115                "PKL_<uuid>.xml is referenced from ASSETMAP but is malformed XML or violates the PKL schema.",
116            Self::XmlAssetParseError =>
117                "An asset listed with a .xml suffix doesn't parse as CPL, OPL, or SCM (likely a misnamed file or a sidecar XML).",
118            Self::XmlReadError =>
119                "ASSETMAP.xml exists in the manifest but couldn't be opened (permission denied, network read failure, etc.).",
120            Self::ReadDirError =>
121                "The package root directory could not be enumerated (permission denied or path missing).",
122            Self::DirEntryError =>
123                "A specific directory entry returned an error during scanning (often a broken symlink).",
124            Self::PathTraversal =>
125                "<Chunk><Path>../../etc/passwd</Path></Chunk> — the asset path resolves outside the package root.",
126        })
127    }
128}
129
130impl ImfernoCode {
131    pub const ALL: &'static [Self] = &[
132        Self::UnreferencedAsset,
133        Self::UnlistedEssence,
134        Self::ParseError,
135        Self::PklParseError,
136        Self::XmlAssetParseError,
137        Self::XmlReadError,
138        Self::ReadDirError,
139        Self::DirEntryError,
140        Self::PathTraversal,
141    ];
142}
143
144impl From<ImfernoCode> for String {
145    fn from(c: ImfernoCode) -> String {
146        c.code().to_string()
147    }
148}