Skip to main content

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}