1use serde_json::{Map, Value};
14
15pub const MINIFIED_MARKER: &str = "composer/2.0";
17
18pub const UNSET: &str = "__unset";
21
22#[must_use]
29pub fn expand_versions(versions: Vec<Map<String, Value>>) -> Vec<Map<String, Value>> {
30 let mut acc: Map<String, Value> = Map::new();
31 let mut out = Vec::with_capacity(versions.len());
32 for diff in versions {
33 apply_diff(&mut acc, diff);
34 out.push(acc.clone());
35 }
36 out
37}
38
39#[must_use]
48pub fn minify_versions(versions: &[Map<String, Value>]) -> Vec<Map<String, Value>> {
49 let mut out: Vec<Map<String, Value>> = Vec::with_capacity(versions.len());
50 for (i, cur) in versions.iter().enumerate() {
51 if i == 0 {
52 out.push(cur.clone());
53 continue;
54 }
55 let prev = &versions[i - 1];
56 let mut diff = Map::new();
57 for (k, v) in cur {
59 if prev.get(k) != Some(v) {
60 diff.insert(k.clone(), v.clone());
61 }
62 }
63 for k in prev.keys() {
65 if !cur.contains_key(k) {
66 diff.insert(k.clone(), Value::String(UNSET.to_owned()));
67 }
68 }
69 out.push(diff);
70 }
71 out
72}
73
74fn apply_diff(acc: &mut Map<String, Value>, diff: Map<String, Value>) {
78 for (k, v) in diff {
79 if v.as_str() == Some(UNSET) {
80 acc.remove(&k);
81 } else {
82 acc.insert(k, v);
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use serde_json::json;
91
92 fn obj(v: Value) -> Map<String, Value> {
93 match v {
94 Value::Object(m) => m,
95 _ => panic!("expected a JSON object"),
96 }
97 }
98
99 #[test]
100 fn expand_inherits_and_overrides() {
101 let versions = vec![
102 obj(json!({"name":"a/b","version":"2.0.0","require":{"php":">=8"}})),
103 obj(json!({"version":"1.0.0"})),
104 ];
105 let expanded = expand_versions(versions);
106 assert_eq!(expanded[1]["name"], json!("a/b"));
107 assert_eq!(expanded[1]["require"]["php"], json!(">=8"));
108 assert_eq!(expanded[1]["version"], json!("1.0.0"));
109 }
110
111 #[test]
112 fn minify_is_inverse_of_expand() {
113 let full = vec![
115 obj(json!({"name":"a/b","version":"3.0.0","type":"library","require":{"php":">=8.1"},"dist":{"type":"zip","url":"u3"}})),
116 obj(json!({"name":"a/b","version":"2.0.0","type":"library","require":{"php":">=8.1"},"dist":{"type":"zip","url":"u2"}})),
117 obj(json!({"name":"a/b","version":"1.0.0","type":"library","require":{"php":">=7.4"},"dist":{"type":"zip","url":"u1"}})),
118 ];
119 let mini = minify_versions(&full);
120 assert!(!mini[1].contains_key("name"));
122 assert!(!mini[1].contains_key("type"));
123 assert!(!mini[1].contains_key("require"));
124 assert!(mini[1].contains_key("version"));
125 assert!(mini[2].contains_key("require"));
127 assert_eq!(expand_versions(mini), full);
129 }
130
131 #[test]
132 fn minify_emits_unset_for_dropped_keys() {
133 let full = vec![
134 obj(json!({"name":"a/b","version":"2.0.0","require":{"php":">=8"}})),
135 obj(json!({"name":"a/b","version":"1.0.0"})), ];
137 let mini = minify_versions(&full);
138 assert_eq!(mini[1]["require"], json!("__unset"));
139 assert_eq!(expand_versions(mini), full);
140 }
141
142 #[test]
143 fn unset_in_input_removes_key() {
144 let versions = vec![
145 obj(json!({"name":"a/b","version":"2.0.0","require":{"php":">=8"}})),
146 obj(json!({"version":"1.0.0","require":"__unset"})),
147 ];
148 let expanded = expand_versions(versions);
149 assert!(expanded[1].get("require").is_none());
150 assert_eq!(expanded[1]["name"], json!("a/b"));
151 }
152}