c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

status_tracker.rs (9862B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 use std::fmt;
     15 
     16 use crate::error::{Error, Result};
     17 
     18 #[derive(Debug)]
     19 pub struct LogItem {
     20     pub label: String, // JUBMF label of the item if available, or other descriptive label
     21     pub file: String,  // File where failure occurred
     22     pub function: String, // Function where failure occurred
     23     pub line: String,  // Line number for error
     24     pub description: String, // Description of the failure
     25     err_val: Option<String>, // Actual error code as string
     26     pub validation_status: Option<String>, // C2PA code if available
     27 }
     28 
     29 impl LogItem {
     30     pub fn new(label: &str, description: &str, function: &str, file: &str, line: u32) -> Self {
     31         LogItem {
     32             label: label.to_string(),
     33             file: file.to_string(),
     34             function: function.to_string(),
     35             line: line.to_string(),
     36             description: description.to_string(),
     37             err_val: None,
     38             validation_status: None,
     39         }
     40     }
     41 
     42     // add an error value
     43     pub fn error(self, err: Error) -> Self {
     44         LogItem {
     45             err_val: Some(format!("{err:?}")),
     46             ..self
     47         }
     48     }
     49 
     50     // add an error value
     51     pub fn set_error(self, err: &Error) -> Self {
     52         LogItem {
     53             err_val: Some(format!("{err:?}")),
     54             ..self
     55         }
     56     }
     57 
     58     /// returns a reference to the error string if there is one
     59     pub fn error_str(&self) -> Option<&str> {
     60         self.err_val.as_deref()
     61     }
     62 
     63     // add an error value
     64     pub fn validation_status(self, status: &str) -> Self {
     65         LogItem {
     66             validation_status: Some(status.to_string()),
     67             ..self
     68         }
     69     }
     70 }
     71 
     72 pub trait StatusTracker {
     73     // should we stop on the first error
     74     fn stop_on_error(&self) -> bool;
     75 
     76     // return reference to current set of validation items
     77     fn get_log(&self) -> &Vec<LogItem>;
     78 
     79     // return mutable reference to current set of validation items
     80     fn get_log_mut(&mut self) -> &mut Vec<LogItem>;
     81 
     82     // Log an item.  Returns err if available
     83     // and stop_on_error is true.  Otherwise success OK(())
     84     // log_item - LogItem to be recorded
     85     // err - optional Error value to be returned if stop_on_error is true and item contain an error,
     86     // otherwise if None, Error:LogStop will be returned if stop_on_err is true.
     87     // The actual error is always available in the log_item.  This allows the caller
     88     // to return an error even when the error does not implement Clone.
     89     fn log(&mut self, log_item: LogItem, err: Option<Error>) -> Result<()>;
     90 
     91     // Log an item. No special consideration are given to the contents of the log item.
     92     fn log_silent(&mut self, log_item: LogItem);
     93 }
     94 
     95 impl fmt::Display for dyn StatusTracker {
     96     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     97         write!(f, "{:?}", self.get_log())
     98     }
     99 }
    100 
    101 impl fmt::Debug for dyn StatusTracker {
    102     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    103         write!(f, "{:?}", self.get_log())
    104     }
    105 }
    106 
    107 // Logger that returns success regardless of if LogItem was for an error condition
    108 #[derive(Default, Debug)]
    109 pub struct DetailedStatusTracker {
    110     logged_items: Vec<LogItem>,
    111     stop_on_error: bool,
    112 }
    113 
    114 impl DetailedStatusTracker {
    115     pub const fn new() -> Self {
    116         DetailedStatusTracker {
    117             logged_items: Vec::new(),
    118             stop_on_error: false,
    119         }
    120     }
    121 }
    122 
    123 impl StatusTracker for DetailedStatusTracker {
    124     fn stop_on_error(&self) -> bool {
    125         self.stop_on_error
    126     }
    127 
    128     fn get_log(&self) -> &Vec<LogItem> {
    129         &self.logged_items
    130     }
    131 
    132     fn get_log_mut(&mut self) -> &mut Vec<LogItem> {
    133         &mut self.logged_items
    134     }
    135 
    136     fn log(&mut self, log_item: LogItem, err: Option<Error>) -> Result<()> {
    137         let item_has_err = log_item.err_val.is_some();
    138         self.logged_items.push(log_item);
    139         if self.stop_on_error && item_has_err {
    140             Err(err.unwrap_or(Error::LogStop))
    141         } else {
    142             Ok(())
    143         }
    144     }
    145 
    146     fn log_silent(&mut self, log_item: LogItem) {
    147         self.logged_items.push(log_item);
    148     }
    149 }
    150 
    151 // Logger that will returns error values on LogItems with error
    152 #[derive(Default, Debug)]
    153 pub struct OneShotStatusTracker {
    154     logged_items: Vec<LogItem>,
    155     stop_on_error: bool,
    156 }
    157 
    158 impl OneShotStatusTracker {
    159     pub const fn new() -> Self {
    160         OneShotStatusTracker {
    161             logged_items: Vec::new(),
    162             stop_on_error: true,
    163         }
    164     }
    165 }
    166 
    167 impl StatusTracker for OneShotStatusTracker {
    168     fn stop_on_error(&self) -> bool {
    169         self.stop_on_error
    170     }
    171 
    172     fn get_log(&self) -> &Vec<LogItem> {
    173         &self.logged_items
    174     }
    175 
    176     fn get_log_mut(&mut self) -> &mut Vec<LogItem> {
    177         &mut self.logged_items
    178     }
    179 
    180     fn log(&mut self, log_item: LogItem, err: Option<Error>) -> Result<()> {
    181         let item_has_err = log_item.err_val.is_some();
    182         self.logged_items.push(log_item);
    183         if self.stop_on_error && item_has_err {
    184             Err(err.unwrap_or(Error::LogStop))
    185         } else {
    186             Ok(())
    187         }
    188     }
    189 
    190     fn log_silent(&mut self, log_item: LogItem) {
    191         self.logged_items.push(log_item);
    192     }
    193 }
    194 /// Check to see if report contains a specific C2PA status code
    195 #[allow(dead_code)] // in case we make use of these or export this
    196 pub fn report_has_status(report: &[LogItem], val: &str) -> bool {
    197     report.iter().any(|vi| {
    198         if let Some(vs) = &vi.validation_status {
    199             vs == val
    200         } else {
    201             false
    202         }
    203     })
    204 }
    205 
    206 /// Check to see if report contains a specific error
    207 #[allow(dead_code)] // in case we make use of these or export this
    208 pub fn report_has_err(report: &[LogItem], err: Error) -> bool {
    209     let err_type = format!("{:?}", &err);
    210     report.iter().any(|vi| {
    211         if let Some(e) = &vi.err_val {
    212             e == &err_type
    213         } else {
    214             false
    215         }
    216     })
    217 }
    218 
    219 /// Split Errors off from rest of report
    220 #[allow(dead_code)] // in case we make use of these or export this
    221 pub fn report_split_errors(report: &mut Vec<LogItem>) -> Vec<LogItem> {
    222     let mut output: Vec<LogItem> = Vec::new();
    223 
    224     let mut i = 0;
    225     while i < report.len() {
    226         if report[i].err_val.is_some() {
    227             output.push(report.remove(i));
    228         } else {
    229             i += 1;
    230         }
    231     }
    232     output
    233 }
    234 /// log_item create a log item suitable for StatusTracker
    235 /// label - name of object this LogItem references
    236 /// description - reason for this LogItem
    237 /// function - name of the function generating this LogItem
    238 macro_rules! log_item {
    239     ($label:expr, $description:expr, $function:expr) => {{
    240         use crate::status_tracker::LogItem;
    241         LogItem::new(
    242             &$label.to_string(),
    243             &$description.to_string(),
    244             &$function.to_string(),
    245             file!(),
    246             line!(),
    247         )
    248     }};
    249 }
    250 
    251 pub(crate) use log_item;
    252 
    253 #[cfg(test)]
    254 pub mod tests {
    255     #![allow(clippy::unwrap_used)]
    256 
    257     use super::*;
    258     use crate::validation_status;
    259     //validation_item::log_item!;
    260 
    261     #[test]
    262     fn test_standard_tracker_stopping_for_error() {
    263         let mut tracker = OneShotStatusTracker::new();
    264 
    265         // item without error
    266         let item1 = LogItem::new("test1", "test item 1", "test func", file!(), line!());
    267         assert!(tracker.log(item1, None).is_ok());
    268 
    269         // item with an error
    270         let item2 = LogItem::new("test2", "test item 1", "test func", file!(), line!())
    271             .error(Error::NotFound); // add arbitrary error
    272         assert!(tracker.log(item2, None).is_err());
    273 
    274         // item with error with caller specified error response, testing macro for generation
    275         let item3 = log_item!("test3", "test item 3 from macro", "test func")
    276             .error(Error::UnsupportedType)
    277             .validation_status(validation_status::ALGORITHM_UNSUPPORTED);
    278         assert!(matches!(
    279             tracker.log(item3, Some(Error::NotFound)),
    280             Err(Error::NotFound)
    281         ));
    282     }
    283 
    284     #[test]
    285     fn test_standard_tracker_no_stopping_for_error() {
    286         let mut tracker = DetailedStatusTracker::new();
    287 
    288         // item without error
    289         let item1 = LogItem::new("test1", "test item 1", "test func", file!(), line!());
    290         assert!(tracker.log(item1, None).is_ok());
    291 
    292         // item with an error
    293         let item2 = LogItem::new("test2", "test item 1", "test func", file!(), line!())
    294             .error(Error::NotFound); // add arbitrary error
    295         assert!(tracker.log(item2, None).is_ok());
    296 
    297         // item with error with caller specified error response, testing macro for generation
    298         let item3 =
    299             log_item!("test3", "test item 3 from macro", "test func").error(Error::UnsupportedType);
    300         assert!(tracker.log(item3, Some(Error::NotFound)).is_ok());
    301 
    302         // item with error with caller specified error response, testing macro for generation, test validation_status
    303         let item4 = log_item!("test3", "test item 3 from macro", "test func")
    304             .error(Error::UnsupportedType)
    305             .validation_status(validation_status::ALGORITHM_UNSUPPORTED);
    306         assert!(tracker.log(item4, None).is_ok());
    307 
    308         // there should be two items with error
    309         let errors = report_split_errors(tracker.get_log_mut());
    310         assert_eq!(errors.len(), 3);
    311     }
    312 }