c2pa_text_binding/error.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::fmt;
4
5/// Errors produced by the text soft-binding algorithms.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum Error {
8 /// Input has too little content for the requested algorithm (e.g. not
9 /// enough word boundaries to place a watermark payload).
10 ContentTooShort,
11 /// A fingerprint value could not be produced.
12 GenerationFailed(String),
13 /// A fingerprint comparison failed.
14 MatchFailed(String),
15 /// Reed-Solomon erasure coding/decoding failed.
16 Coding(String),
17 /// The watermark payload was present but its content-binding HMAC did not
18 /// verify against the recomputed content hash (transfer or tamper).
19 TagMismatch,
20 /// The watermark could not be recovered (too many stripped positions).
21 WatermarkUnrecoverable,
22 /// A caller-supplied argument was malformed.
23 InvalidInput(String),
24}
25
26impl Error {
27 /// The registered C2PA validation status code for this error, or `None`
28 /// when the condition carries no status code.
29 ///
30 /// Always `None`. This crate implements *soft* binding — fingerprinting and
31 /// watermarking — whose failures are not hard-binding validation outcomes.
32 /// A soft binding that does not match means the recovery path found no
33 /// candidate, not that a located manifest failed to validate, and the
34 /// specification registers no status code for that.
35 ///
36 /// Every crate in this family exposes this method, so a dispatcher handling
37 /// several embedding methods can ask the same question of any of them.
38 pub fn code(&self) -> Option<&'static str> {
39 None
40 }
41
42 /// Whether this error means the asset carries no provenance at all.
43 ///
44 /// Always `false`: soft binding is a recovery mechanism used *after* the
45 /// hard binding has already failed to locate a manifest, so it is never the
46 /// thing that decides whether an asset is unsigned.
47 pub fn is_no_manifest_located(&self) -> bool {
48 false
49 }
50}
51
52impl fmt::Display for Error {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Self::ContentTooShort => {
56 write!(f, "text content too short for this soft-binding algorithm")
57 }
58 Self::GenerationFailed(s) => write!(f, "fingerprint generation failed: {s}"),
59 Self::MatchFailed(s) => write!(f, "fingerprint match failed: {s}"),
60 Self::Coding(s) => write!(f, "reed-solomon coding failed: {s}"),
61 Self::TagMismatch => write!(
62 f,
63 "watermark content-binding tag did not verify (transferred or modified content)"
64 ),
65 Self::WatermarkUnrecoverable => {
66 write!(
67 f,
68 "watermark could not be recovered from remaining positions"
69 )
70 }
71 Self::InvalidInput(s) => write!(f, "invalid input: {s}"),
72 }
73 }
74}
75
76impl std::error::Error for Error {}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 /// Soft-binding failures are not hard-binding validation outcomes, so no
83 /// variant may claim a status code. Guards against a later edit inventing
84 /// one.
85 #[test]
86 fn no_variant_claims_a_status_code() {
87 for e in [
88 Error::ContentTooShort,
89 Error::GenerationFailed("x".into()),
90 Error::MatchFailed("x".into()),
91 Error::Coding("x".into()),
92 Error::TagMismatch,
93 Error::WatermarkUnrecoverable,
94 Error::InvalidInput("x".into()),
95 ] {
96 assert_eq!(e.code(), None, "{e:?} claimed a status code");
97 assert!(
98 !e.is_no_manifest_located(),
99 "{e:?} must not decide whether an asset is unsigned"
100 );
101 }
102 }
103}