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 }