compare_manifests.rs (7615B)
1 // Copyright 2024 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 /// Compares two manifest stores and prints out the differences. 15 use std::collections::HashMap; 16 use std::{fs, path::Path}; 17 18 use c2pa::{Error, Reader as ManifestStore, Result}; 19 20 /// Compares all the files in two directories and returns a list of issues 21 pub fn compare_folders<P: AsRef<Path>, Q: AsRef<Path>>(folder1: P, folder2: Q) -> Result<()> { 22 let folder1 = folder1.as_ref(); 23 let folder2 = folder2.as_ref(); 24 25 // handle the case we have files instead of folders 26 if folder1.is_file() && folder2.is_file() { 27 let issues = compare_image_manifests(folder1, folder2)?; 28 if !issues.is_empty() { 29 eprintln!("Failed {:?}", folder1); 30 for issue in issues { 31 eprintln!(" {}", issue); 32 } 33 } else { 34 println!("Passed {:?}", folder1); 35 } 36 return Ok(()); 37 } else if !(folder1.is_dir() && folder2.is_dir()) { 38 eprintln!("must be two folders or two files"); 39 return Err(Error::BadParam( 40 "must be two folders or two files".to_string(), 41 )); 42 } 43 44 for entry in fs::read_dir(folder1)? { 45 let entry = entry?; 46 let path = entry.path(); 47 if path.is_file() { 48 let relative_path = path.strip_prefix(folder1).unwrap(); 49 let other_path = folder2.join(relative_path); 50 //println!("Comparing {:?} to {:?}", path, other_path); 51 let mut issues = Vec::new(); 52 if other_path.exists() { 53 let result = compare_image_manifests(&path, &other_path)?; 54 issues.extend(result); 55 } else { 56 issues.push(format!( 57 "File {} does not exist in {}", 58 relative_path.display(), 59 folder2.display() 60 )); 61 } 62 if !issues.is_empty() { 63 eprintln!("Failed {:?}", relative_path); 64 for issue in issues { 65 eprintln!(" {}", issue); 66 } 67 } else { 68 println!("Passed {:?}", relative_path); 69 } 70 } 71 } 72 Ok(()) 73 } 74 75 /// Compares files with manifest stores and returns a list of issues 76 pub fn compare_image_manifests<P: AsRef<Path>, Q: AsRef<Path>>( 77 m1: P, 78 m2: Q, 79 ) -> Result<Vec<String>> { 80 let manifest_store1 = match m1.as_ref().extension() { 81 Some(ext) if ext == "json" => { 82 ManifestStore::from_json(&fs::read_to_string(m1)?) 83 //serde_json::from_str(&fs::read_to_string(m1)?).map_err(Error::JsonError) 84 } 85 _ => ManifestStore::from_file(m1.as_ref()), 86 }; 87 let manifest_store2 = match m2.as_ref().extension() { 88 Some(ext) if ext == "json" => ManifestStore::from_json(&fs::read_to_string(m2)?), 89 _ => ManifestStore::from_file(m2.as_ref()), 90 }; 91 // let manifest_store1 = ManifestStore::from_file(m1); 92 // let manifest_store2 = ManifestStore::from_file(m2); 93 match (manifest_store1, manifest_store2) { 94 (Ok(manifest_store1), Ok(manifest_store2)) => { 95 compare_manifests(&manifest_store1, &manifest_store2) 96 } 97 (Err(Error::JumbfNotFound), Err(Error::JumbfNotFound)) => Ok(Vec::new()), 98 (_, Err(err)) => Err(err), 99 (Err(err), _) => Err(err), 100 } 101 } 102 103 /// Compares two manifest stores and returns a list of issues. 104 pub fn compare_manifests( 105 manifest_store1: &ManifestStore, 106 manifest_store2: &ManifestStore, 107 ) -> Result<Vec<String>> { 108 // first we need to gather all the manifests in the order they are first seen recursively 109 let mut labels1 = Vec::new(); 110 if let Some(label) = manifest_store1.active_label() { 111 gather_manifests(manifest_store1, label, &mut labels1); 112 } 113 let mut labels2 = Vec::new(); 114 if let Some(label) = manifest_store2.active_label() { 115 gather_manifests(manifest_store2, label, &mut labels2); 116 } 117 // now we have two lists of manifests, we need to match them up 118 let manifest_map: HashMap<_, _> = labels1.into_iter().zip(labels2).collect(); 119 120 // now we can compare the manifests 121 let mut issues = Vec::new(); 122 for (label1, label2) in manifest_map.iter() { 123 // let foo = serde_json::to_string(&manifest_store1.get(label1))?; 124 // let foo = serde_json::from_str(&foo)?; 125 // convert manifests into json values and compare them 126 let value1 = serde_json::to_value(manifest_store1.get_manifest(label1))?; 127 let value2 = serde_json::to_value(manifest_store2.get_manifest(label2))?; 128 compare_json_values( 129 &format!("manifests.{}", label1), 130 &value1, 131 &value2, 132 &mut issues, 133 ); 134 } 135 Ok(issues) 136 } 137 138 // creates list of manifests in the order they are first seen from the active manifest 139 fn gather_manifests( 140 manifest_store: &ManifestStore, 141 manifest_label: &str, 142 labels: &mut Vec<String>, 143 ) { 144 if !labels.contains(&manifest_label.to_string()) { 145 labels.push(manifest_label.to_string()); 146 } 147 if let Some(manifest) = manifest_store.get_manifest(manifest_label) { 148 for ingredient in manifest.ingredients() { 149 if let Some(label) = ingredient.active_manifest() { 150 gather_manifests(manifest_store, label, labels); 151 } 152 } 153 } 154 } 155 156 /// Recursively compare two ManifestStore JSON values 157 fn compare_json_values( 158 path: &str, 159 val1: &serde_json::Value, 160 val2: &serde_json::Value, 161 issues: &mut Vec<String>, 162 ) { 163 match (val1, val2) { 164 (serde_json::Value::Object(map1), serde_json::Value::Object(map2)) => { 165 for (key, val1) in map1 { 166 let val2 = map2.get(key).unwrap_or(&serde_json::Value::Null); 167 compare_json_values(&format!("{}.{}", path, key), val1, val2, issues); 168 } 169 170 for (key, value) in map2 { 171 if map1.get(key).is_none() { 172 issues.push(format!("Added {}.{}: {}", path, key, value)); 173 } 174 } 175 } 176 (serde_json::Value::Array(arr1), serde_json::Value::Array(arr2)) => { 177 for (i, (val1, val2)) in arr1.iter().zip(arr2.iter()).enumerate() { 178 compare_json_values(&format!("{}[{}]", path, i), val1, val2, issues); 179 } 180 } 181 (val1, val2) if val1 != val2 => { 182 if !(path.ends_with(".instance_id") 183 || path.ends_with(".instanceId") 184 || path.ends_with(".time") 185 || path.contains(".hash") 186 || val1.is_string() && val2.is_string() && val1.to_string().contains(":urn:uuid:")) 187 { 188 if val2.is_null() { 189 issues.push(format!("Missing {}: {}", path, val1)); 190 } else if val2.is_null() { 191 issues.push(format!("Added {}: {}", path, val2)); 192 } else { 193 issues.push(format!("Changed {}: {} vs {}", path, val1, val2)); 194 } 195 } 196 } 197 _ => (), 198 } 199 }