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(
116 json!({"name":"a/b","version":"3.0.0","type":"library","require":{"php":">=8.1"},"dist":{"type":"zip","url":"u3"}}),
117 ),
118 obj(
119 json!({"name":"a/b","version":"2.0.0","type":"library","require":{"php":">=8.1"},"dist":{"type":"zip","url":"u2"}}),
120 ),
121 obj(
122 json!({"name":"a/b","version":"1.0.0","type":"library","require":{"php":">=7.4"},"dist":{"type":"zip","url":"u1"}}),
123 ),
124 ];
125 let mini = minify_versions(&full);
126 assert!(!mini[1].contains_key("name"));
128 assert!(!mini[1].contains_key("type"));
129 assert!(!mini[1].contains_key("require"));
130 assert!(mini[1].contains_key("version"));
131 assert!(mini[2].contains_key("require"));
133 assert_eq!(expand_versions(mini), full);
135 }
136
137 #[test]
138 fn minify_emits_unset_for_dropped_keys() {
139 let full = vec![
140 obj(json!({"name":"a/b","version":"2.0.0","require":{"php":">=8"}})),
141 obj(json!({"name":"a/b","version":"1.0.0"})), ];
143 let mini = minify_versions(&full);
144 assert_eq!(mini[1]["require"], json!("__unset"));
145 assert_eq!(expand_versions(mini), full);
146 }
147
148 #[test]
149 fn unset_in_input_removes_key() {
150 let versions = vec![
151 obj(json!({"name":"a/b","version":"2.0.0","require":{"php":">=8"}})),
152 obj(json!({"version":"1.0.0","require":"__unset"})),
153 ];
154 let expanded = expand_versions(versions);
155 assert!(expanded[1].get("require").is_none());
156 assert_eq!(expanded[1]["name"], json!("a/b"));
157 }
158}