Skip to main content

imferno_core/cpl/
codes.rs

1//! Typed validation-code catalogue for SMPTE ST 2067-3 (Composition Playlist).
2
3use crate::diagnostics::codes::ValidationCode;
4use crate::diagnostics::{Category, Severity};
5
6// ─── Spec-agnostic reason enum ────────────────────────────────────────────────
7
8/// Reason codes for ST 2067-3 validators, independent of spec edition year.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum St2067_3Code {
11    /// ST 2067-3 §5.5.1.2: ContentKind uses an unrecognized value under the SMPTE scope.
12    ContentKindUnknown,
13    /// ST 2067-3 §6.4.2: SourceEncoding present but EssenceDescriptorList absent.
14    SourceEncodingNoEssenceDescriptorList,
15    /// ST 2067-3 §6.4.2: SourceEncoding does not match any EssenceDescriptor Id.
16    SourceEncodingUnresolved,
17    /// ST 2067-3 §6.4.2: EssenceDescriptorList present but contains no descriptors.
18    EssenceDescriptorListEmpty,
19    /// ST 2067-3 §6.11: ContentVersionList present but empty.
20    ContentVersionListEmpty,
21    /// ST 2067-3 §6.11: ContentVersion/Id is empty (shall be a URI).
22    ContentVersionIdInvalid,
23    /// ST 2067-3 §6.11: ContentVersion/LabelText is absent.
24    ContentVersionLabelTextMissing,
25    /// ST 2067-3 §6.12: Locale language tag does not conform to RFC 5646.
26    LocaleLanguageTagInvalid,
27    /// ST 2067-3 §7.3: TrackId is not unique within a segment.
28    TrackIdNotUnique,
29    /// ST 2067-3 §7.4: Marker offset exceeds resource effective duration.
30    MarkerOffsetOutOfRange,
31    /// ST 2067-3 §7.4: Marker label is not a recognized SMPTE standard value.
32    MarkerLabelUnknown,
33    /// ST 2067-3 §7.2.2: All virtual tracks in a segment must span the same edit units.
34    SegmentDuration,
35    /// ST 2067-3 §6.1.9: Two ContentVersion elements share the same Id value.
36    ContentVersionIdDuplicate,
37    /// ST 2067-3 §7.3: Sequence duration is not an integer number of Composition Edit Units.
38    SegmentDurationIntegerEditUnits,
39}
40
41// ─── Per-edition enums ────────────────────────────────────────────────────────
42
43macro_rules! define_st2067_3_enum {
44    ($name:ident, $prefix:literal) => {
45        #[allow(non_camel_case_types)]
46        #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
47        pub enum $name {
48            ContentKindUnknown,
49            SourceEncodingNoEssenceDescriptorList,
50            SourceEncodingUnresolved,
51            EssenceDescriptorListEmpty,
52            ContentVersionListEmpty,
53            ContentVersionIdInvalid,
54            ContentVersionLabelTextMissing,
55            LocaleLanguageTagInvalid,
56            TrackIdNotUnique,
57            MarkerOffsetOutOfRange,
58            MarkerLabelUnknown,
59            SegmentDuration,
60            ContentVersionIdDuplicate,
61            SegmentDurationIntegerEditUnits,
62        }
63
64        impl $name {
65            pub fn for_code(r: St2067_3Code) -> &'static str {
66                match r {
67                    St2067_3Code::ContentKindUnknown => {
68                        concat!($prefix, ":5.5.1.2/ContentKindUnknown")
69                    }
70                    St2067_3Code::SourceEncodingNoEssenceDescriptorList => {
71                        concat!($prefix, ":6.4.2/SourceEncodingNoEssenceDescriptorList")
72                    }
73                    St2067_3Code::SourceEncodingUnresolved => {
74                        concat!($prefix, ":6.4.2/SourceEncodingUnresolved")
75                    }
76                    St2067_3Code::EssenceDescriptorListEmpty => {
77                        concat!($prefix, ":6.4.2/EssenceDescriptorListEmpty")
78                    }
79                    St2067_3Code::ContentVersionListEmpty => {
80                        concat!($prefix, ":6.11/ContentVersionListEmpty")
81                    }
82                    St2067_3Code::ContentVersionIdInvalid => {
83                        concat!($prefix, ":6.11/ContentVersionIdInvalid")
84                    }
85                    St2067_3Code::ContentVersionLabelTextMissing => {
86                        concat!($prefix, ":6.11/ContentVersionLabelTextMissing")
87                    }
88                    St2067_3Code::LocaleLanguageTagInvalid => {
89                        concat!($prefix, ":6.12/LocaleLanguageTagInvalid")
90                    }
91                    St2067_3Code::TrackIdNotUnique => concat!($prefix, ":7.3/TrackIdNotUnique"),
92                    St2067_3Code::MarkerOffsetOutOfRange => {
93                        concat!($prefix, ":7.4/MarkerOffsetOutOfRange")
94                    }
95                    St2067_3Code::MarkerLabelUnknown => concat!($prefix, ":7.4/MarkerLabelUnknown"),
96                    St2067_3Code::SegmentDuration => concat!($prefix, ":7.2.2/SegmentDuration"),
97                    St2067_3Code::ContentVersionIdDuplicate => {
98                        concat!($prefix, ":6.1.9/ContentVersionIdDuplicate")
99                    }
100                    St2067_3Code::SegmentDurationIntegerEditUnits => {
101                        concat!($prefix, ":7.3/SegmentDurationIntegerEditUnits")
102                    }
103                }
104            }
105
106            pub const ALL: &'static [Self] = &[
107                Self::ContentKindUnknown,
108                Self::SourceEncodingNoEssenceDescriptorList,
109                Self::SourceEncodingUnresolved,
110                Self::EssenceDescriptorListEmpty,
111                Self::ContentVersionListEmpty,
112                Self::ContentVersionIdInvalid,
113                Self::ContentVersionLabelTextMissing,
114                Self::LocaleLanguageTagInvalid,
115                Self::TrackIdNotUnique,
116                Self::MarkerOffsetOutOfRange,
117                Self::MarkerLabelUnknown,
118                Self::SegmentDuration,
119                Self::ContentVersionIdDuplicate,
120                Self::SegmentDurationIntegerEditUnits,
121            ];
122        }
123
124        impl ValidationCode for $name {
125            fn code(&self) -> &'static str {
126                match self {
127                    Self::ContentKindUnknown => concat!($prefix, ":5.5.1.2/ContentKindUnknown"),
128                    Self::SourceEncodingNoEssenceDescriptorList => {
129                        concat!($prefix, ":6.4.2/SourceEncodingNoEssenceDescriptorList")
130                    }
131                    Self::SourceEncodingUnresolved => {
132                        concat!($prefix, ":6.4.2/SourceEncodingUnresolved")
133                    }
134                    Self::EssenceDescriptorListEmpty => {
135                        concat!($prefix, ":6.4.2/EssenceDescriptorListEmpty")
136                    }
137                    Self::ContentVersionListEmpty => {
138                        concat!($prefix, ":6.11/ContentVersionListEmpty")
139                    }
140                    Self::ContentVersionIdInvalid => {
141                        concat!($prefix, ":6.11/ContentVersionIdInvalid")
142                    }
143                    Self::ContentVersionLabelTextMissing => {
144                        concat!($prefix, ":6.11/ContentVersionLabelTextMissing")
145                    }
146                    Self::LocaleLanguageTagInvalid => {
147                        concat!($prefix, ":6.12/LocaleLanguageTagInvalid")
148                    }
149                    Self::TrackIdNotUnique => concat!($prefix, ":7.3/TrackIdNotUnique"),
150                    Self::MarkerOffsetOutOfRange => concat!($prefix, ":7.4/MarkerOffsetOutOfRange"),
151                    Self::MarkerLabelUnknown => concat!($prefix, ":7.4/MarkerLabelUnknown"),
152                    Self::SegmentDuration => concat!($prefix, ":7.2.2/SegmentDuration"),
153                    Self::ContentVersionIdDuplicate => {
154                        concat!($prefix, ":6.1.9/ContentVersionIdDuplicate")
155                    }
156                    Self::SegmentDurationIntegerEditUnits => {
157                        concat!($prefix, ":7.3/SegmentDurationIntegerEditUnits")
158                    }
159                }
160            }
161            fn description(&self) -> &'static str {
162                match self {
163                    Self::ContentKindUnknown => {
164                        "ContentKind uses an unrecognized value under the SMPTE scope."
165                    }
166                    Self::SourceEncodingNoEssenceDescriptorList => {
167                        "SourceEncoding present but EssenceDescriptorList absent."
168                    }
169                    Self::SourceEncodingUnresolved => {
170                        "SourceEncoding does not match any EssenceDescriptor Id."
171                    }
172                    Self::EssenceDescriptorListEmpty => {
173                        "EssenceDescriptorList present but contains no descriptors."
174                    }
175                    Self::ContentVersionListEmpty => "ContentVersionList present but empty.",
176                    Self::ContentVersionIdInvalid => "ContentVersion/Id is empty (shall be a URI).",
177                    Self::ContentVersionLabelTextMissing => "ContentVersion/LabelText is absent.",
178                    Self::LocaleLanguageTagInvalid => {
179                        "Locale language tag does not conform to RFC 5646."
180                    }
181                    Self::TrackIdNotUnique => "TrackId is not unique within a segment.",
182                    Self::MarkerOffsetOutOfRange => {
183                        "Marker offset exceeds resource effective duration."
184                    }
185                    Self::MarkerLabelUnknown => {
186                        "Marker label is not a recognized SMPTE standard value."
187                    }
188                    Self::SegmentDuration => {
189                        "All virtual tracks in a segment must span the same number of edit units."
190                    }
191                    Self::ContentVersionIdDuplicate => {
192                        "No two ContentVersion elements shall have identical Id values."
193                    }
194                    Self::SegmentDurationIntegerEditUnits => {
195                        "Sequence duration shall be an integer number of Composition Edit Units."
196                    }
197                }
198            }
199            fn default_severity(&self) -> Severity {
200                match self {
201                    Self::ContentKindUnknown => Severity::Warning,
202                    Self::SourceEncodingNoEssenceDescriptorList => Severity::Error,
203                    Self::SourceEncodingUnresolved => Severity::Error,
204                    Self::EssenceDescriptorListEmpty => Severity::Error,
205                    Self::ContentVersionListEmpty => Severity::Error,
206                    Self::ContentVersionIdInvalid => Severity::Error,
207                    Self::ContentVersionLabelTextMissing => Severity::Warning,
208                    Self::LocaleLanguageTagInvalid => Severity::Warning,
209                    Self::TrackIdNotUnique => Severity::Error,
210                    Self::MarkerOffsetOutOfRange => Severity::Error,
211                    Self::MarkerLabelUnknown => Severity::Warning,
212                    Self::SegmentDuration => Severity::Error,
213                    Self::ContentVersionIdDuplicate => Severity::Error,
214                    Self::SegmentDurationIntegerEditUnits => Severity::Error,
215                }
216            }
217            fn category(&self) -> Category {
218                match self {
219                    Self::ContentKindUnknown => Category::Metadata,
220                    Self::SourceEncodingNoEssenceDescriptorList => Category::Reference,
221                    Self::SourceEncodingUnresolved => Category::Reference,
222                    Self::EssenceDescriptorListEmpty => Category::Structure,
223                    Self::ContentVersionListEmpty => Category::Structure,
224                    Self::ContentVersionIdInvalid => Category::Metadata,
225                    Self::ContentVersionLabelTextMissing => Category::Metadata,
226                    Self::LocaleLanguageTagInvalid => Category::Metadata,
227                    Self::TrackIdNotUnique => Category::Structure,
228                    Self::MarkerOffsetOutOfRange => Category::Timing,
229                    Self::MarkerLabelUnknown => Category::Metadata,
230                    Self::SegmentDuration => Category::Timing,
231                    Self::ContentVersionIdDuplicate => Category::Structure,
232                    Self::SegmentDurationIntegerEditUnits => Category::Timing,
233                }
234            }
235        }
236
237        impl From<$name> for String {
238            fn from(c: $name) -> String {
239                c.code().to_string()
240            }
241        }
242    };
243}
244
245define_st2067_3_enum!(St2067_3_2013, "ST2067-3:2013");
246define_st2067_3_enum!(St2067_3_2016, "ST2067-3:2016");
247define_st2067_3_enum!(St2067_3_2020, "ST2067-3:2020");