c2pa_vtt/error.rs
1use std::fmt;
2
3/// Errors returned when embedding, extracting, or hard-binding a manifest.
4#[derive(Debug)]
5pub enum Error {
6 /// The input does not begin with the `WEBVTT` signature line.
7 NotVtt,
8 /// No manifest `NOTE` block was found in the file.
9 NotFound,
10 /// More than one manifest `NOTE` block was found. Per the C2PA structured
11 /// text embedding rules there shall be at most one; the file is rejected.
12 MultipleManifests,
13 /// A manifest `NOTE` block was found but the reference between the
14 /// delimiters is empty.
15 EmptyReference,
16 /// The manifest reference could not be parsed (e.g. a malformed
17 /// `data:application/c2pa;base64,...` URI).
18 MalformedReference(String),
19 /// The hard-binding exclusion range extends beyond the end of the asset.
20 ExclusionOutOfRange,
21}
22
23impl Error {
24 /// The registered C2PA validation status code for this error, or `None`
25 /// when the condition carries no status code.
26 ///
27 /// WebVTT is carried by the structured-text method, for which the
28 /// specification defines no format-specific codes — unlike HTML or
29 /// unstructured text. Only the data-hash condition maps to a code.
30 ///
31 /// Every crate in this family exposes this method, so a dispatcher handling
32 /// several embedding methods can ask the same question of any of them.
33 pub fn code(&self) -> Option<&'static str> {
34 Some(match self {
35 Self::ExclusionOutOfRange => "assertion.dataHash.malformed",
36 Self::NotVtt
37 | Self::NotFound
38 | Self::MultipleManifests
39 | Self::EmptyReference
40 | Self::MalformedReference(_) => return None,
41 })
42 }
43
44 /// Whether this error means the asset carries no provenance at all, as
45 /// opposed to provenance that was found and rejected.
46 ///
47 /// [`Error::MultipleManifests`] counts: the structured-text rules require
48 /// an asset with more than one manifest block to be treated as if no
49 /// manifests were located.
50 pub fn is_no_manifest_located(&self) -> bool {
51 matches!(self, Self::NotFound | Self::MultipleManifests)
52 }
53}
54
55impl fmt::Display for Error {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::NotVtt => write!(f, "file does not start with WEBVTT header"),
59 Self::NotFound => write!(f, "no manifest NOTE block found"),
60 Self::MultipleManifests => write!(f, "multiple manifest NOTE blocks found"),
61 Self::EmptyReference => write!(f, "empty manifest reference"),
62 Self::MalformedReference(s) => write!(f, "malformed manifest reference: {s}"),
63 Self::ExclusionOutOfRange => {
64 write!(
65 f,
66 "hard-binding exclusion range extends beyond end of asset"
67 )
68 }
69 }
70 }
71}
72
73impl std::error::Error for Error {}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn all() -> Vec<Error> {
80 vec![
81 Error::NotVtt,
82 Error::NotFound,
83 Error::MultipleManifests,
84 Error::EmptyReference,
85 Error::MalformedReference("x".into()),
86 Error::ExclusionOutOfRange,
87 ]
88 }
89
90 /// Guards against inventing a code. WebVTT rides the structured-text
91 /// method, which has no format-specific codes.
92 #[test]
93 fn every_code_is_a_registered_identifier() {
94 for e in all() {
95 if let Some(code) = e.code() {
96 assert_eq!(
97 code, "assertion.dataHash.malformed",
98 "{e:?} invented a code"
99 );
100 }
101 }
102 }
103
104 #[test]
105 fn no_vtt_specific_code_is_emitted() {
106 for e in all() {
107 if let Some(code) = e.code() {
108 assert!(!code.starts_with("manifest."), "{e:?} emits {code}");
109 }
110 }
111 }
112
113 #[test]
114 fn locating_outcomes_carry_no_code() {
115 for e in [Error::NotFound, Error::MultipleManifests] {
116 assert_eq!(e.code(), None, "{e:?} must not report a status code");
117 assert!(
118 e.is_no_manifest_located(),
119 "{e:?} must classify as unsigned"
120 );
121 }
122 // Not a VTT file at all is a different thing from an unsigned one.
123 assert!(!Error::NotVtt.is_no_manifest_located());
124 }
125}