vivacity_core/
content_hash.rs1use crate::error::{Error, Result};
6use crate::phpjson::php_json_encode;
7use md5::{Digest, Md5};
8use serde_json::{Map, Value};
9
10const RELEVANT_KEYS: [&str; 11] = [
13 "name",
14 "version",
15 "require",
16 "require-dev",
17 "conflict",
18 "replace",
19 "provide",
20 "minimum-stability",
21 "prefer-stable",
22 "repositories",
23 "extra",
24];
25
26pub fn content_hash(composer_json_text: &str) -> Result<String> {
27 let content: Value =
28 serde_json::from_str(composer_json_text).map_err(|source| Error::Json {
29 context: "composer.json".to_owned(),
30 source,
31 })?;
32
33 let mut relevant = Map::new();
34 if let Value::Object(obj) = &content {
35 for key in RELEVANT_KEYS {
37 if let Some(v) = obj.get(key).filter(|v| !v.is_null()) {
38 relevant.insert(key.to_owned(), v.clone());
39 }
40 }
41 if let Some(platform) = obj
42 .get("config")
43 .and_then(|c| c.get("platform"))
44 .filter(|v| !v.is_null())
45 {
46 let mut config = Map::new();
47 config.insert("platform".to_owned(), platform.clone());
48 relevant.insert("config".to_owned(), Value::Object(config));
49 }
50 }
51
52 let mut entries: Vec<(String, Value)> = relevant.into_iter().collect();
56 entries.sort_by(|(a, _), (b, _)| a.cmp(b));
57 let sorted: Map<String, Value> = entries.into_iter().collect();
58
59 let encoded = php_json_encode(&Value::Object(sorted))?;
60 let mut hasher = Md5::new();
61 hasher.update(encoded.as_bytes());
62 Ok(format!("{:x}", hasher.finalize()))
63}
64
65pub fn md5_hex(data: &[u8]) -> String {
67 let mut h = Md5::new();
68 h.update(data);
69 h.finalize().iter().map(|b| format!("{b:02x}")).collect()
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn minimal_manifest_is_stable() {
78 let h = content_hash(r#"{"require":{"php":">=8.1"}}"#).expect("hash");
81 assert_eq!(h.len(), 32);
82 let h2 = content_hash(r#"{"require":{"php":">=8.1"},"description":"x"}"#).expect("hash");
84 assert_eq!(h, h2);
85 let h3 = content_hash(r#"{"require":{"php":">=8.2"}}"#).expect("hash");
87 assert_ne!(h, h3);
88 }
89
90 #[test]
91 fn config_platform_is_renested() {
92 let a =
93 content_hash(r#"{"require":{},"config":{"platform":{"php":"8.2.0"}}}"#).expect("hash");
94 let b = content_hash(r#"{"require":{},"config":{"sort-packages":true}}"#).expect("hash");
95 let c = content_hash(r#"{"require":{}}"#).expect("hash");
96 assert_ne!(a, c); assert_eq!(b, c); }
99}