1use std::fmt;
2
3use super::document::Document;
4use super::uinfo::{parse_info_extension, parse_uinfo, Uinfo};
5use crate::error::ParseError;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11#[non_exhaustive]
12pub enum Record {
13 Descriptor,
15 AllGroups,
17 RootGroups,
19 ArtifactAdd(Uinfo),
21 ArtifactRemove(Uinfo),
23 Unknown,
25}
26
27impl fmt::Display for Record {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Record::Descriptor => f.write_str("descriptor"),
31 Record::AllGroups => f.write_str("allGroups"),
32 Record::RootGroups => f.write_str("rootGroups"),
33 Record::ArtifactAdd(u) => write!(f, "add {u}"),
34 Record::ArtifactRemove(u) => write!(f, "remove {u}"),
35 Record::Unknown => f.write_str("unknown"),
36 }
37 }
38}
39
40impl TryFrom<&Document> for Record {
41 type Error = ParseError;
42
43 fn try_from(doc: &Document) -> Result<Self, Self::Error> {
59 if doc.has("DESCRIPTOR") {
60 return Ok(Record::Descriptor);
61 }
62 if doc.has("allGroups") {
63 return Ok(Record::AllGroups);
64 }
65 if doc.has("rootGroups") {
66 return Ok(Record::RootGroups);
67 }
68 if let Some(raw) = doc.find("u") {
69 let mut uinfo = parse_uinfo(raw)?;
70 if uinfo.extension.is_none() {
73 if let Some(info_raw) = doc.find("i") {
74 uinfo.extension = parse_info_extension(info_raw);
75 }
76 }
77 return Ok(Record::ArtifactAdd(uinfo));
78 }
79 if let Some(raw) = doc.find("del") {
80 return Ok(Record::ArtifactRemove(parse_uinfo(raw)?));
81 }
82 Ok(Record::Unknown)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::domain::field::Field;
90 use crate::domain::flags::FieldFlags;
91
92 fn field(name: &str, value: &str) -> Field {
93 Field {
94 flags: FieldFlags::new(0x07),
95 name: name.to_owned(),
96 value: value.to_owned(),
97 }
98 }
99
100 fn doc(fields: Vec<Field>) -> Document {
101 Document::new(fields)
102 }
103
104 #[test]
105 fn classifies_descriptor() {
106 let d = doc(vec![
107 field("DESCRIPTOR", "NexusIndex"),
108 field("IDXINFO", "..."),
109 ]);
110 assert_eq!(Record::try_from(&d).unwrap(), Record::Descriptor);
111 }
112
113 #[test]
114 fn classifies_all_groups() {
115 let d = doc(vec![
116 field("allGroups", "ignored"),
117 field("allGroupsList", "a|b|c"),
118 ]);
119 assert_eq!(Record::try_from(&d).unwrap(), Record::AllGroups);
120 }
121
122 #[test]
123 fn classifies_root_groups() {
124 let d = doc(vec![
125 field("rootGroups", "x"),
126 field("rootGroupsList", "a|b"),
127 ]);
128 assert_eq!(Record::try_from(&d).unwrap(), Record::RootGroups);
129 }
130
131 #[test]
132 fn classifies_add() {
133 let d = doc(vec![field("u", "org.example|lib|1.0|NA|jar")]);
134 let Record::ArtifactAdd(u) = Record::try_from(&d).unwrap() else {
135 panic!("expected Add");
136 };
137 assert_eq!(u.group_id, "org.example");
138 }
139
140 #[test]
141 fn classifies_remove() {
142 let d = doc(vec![field("del", "org.example|lib|1.0|NA|jar")]);
143 let Record::ArtifactRemove(u) = Record::try_from(&d).unwrap() else {
144 panic!("expected Remove");
145 };
146 assert_eq!(u.artifact_id, "lib");
147 }
148
149 #[test]
150 fn unknown_when_no_recognisable_field() {
151 let d = doc(vec![field("foo", "bar")]);
152 assert_eq!(Record::try_from(&d).unwrap(), Record::Unknown);
153 }
154
155 #[test]
156 fn descriptor_beats_u_field_priority() {
157 let d = doc(vec![field("DESCRIPTOR", "x"), field("u", "a|b|c|NA|jar")]);
158 assert_eq!(Record::try_from(&d).unwrap(), Record::Descriptor);
159 }
160
161 #[test]
162 fn all_groups_beats_u_field_priority() {
163 let d = doc(vec![field("allGroups", "x"), field("u", "a|b|c|NA|jar")]);
164 assert_eq!(Record::try_from(&d).unwrap(), Record::AllGroups);
165 }
166
167 #[test]
168 fn malformed_uinfo_on_add_bubbles_up() {
169 let d = doc(vec![field("u", "not-enough-pipes")]);
170 assert!(matches!(
171 Record::try_from(&d),
172 Err(ParseError::MalformedUinfo(_))
173 ));
174 }
175
176 #[test]
177 fn four_segment_uinfo_backfills_extension_from_info() {
178 let d = doc(vec![
179 field("u", "org.example|lib|1.0|NA"),
180 field("i", "jar|1700000000000|123|0|0|0|jar"),
181 ]);
182 let Record::ArtifactAdd(u) = Record::try_from(&d).unwrap() else {
183 panic!("expected ArtifactAdd");
184 };
185 assert_eq!(u.extension.as_deref(), Some("jar"));
186 }
187
188 #[test]
189 fn five_segment_uinfo_ignores_info_extension() {
190 let d = doc(vec![
191 field("u", "org.example|lib|1.0|NA|war"),
192 field("i", "jar|1700000000000|123|0|0|0|jar"),
193 ]);
194 let Record::ArtifactAdd(u) = Record::try_from(&d).unwrap() else {
195 panic!("expected ArtifactAdd");
196 };
197 assert_eq!(u.extension.as_deref(), Some("war"));
198 }
199
200 #[test]
201 fn display_descriptor() {
202 assert_eq!(Record::Descriptor.to_string(), "descriptor");
203 }
204
205 #[test]
206 fn display_unknown() {
207 assert_eq!(Record::Unknown.to_string(), "unknown");
208 }
209
210 #[test]
211 fn display_artifact_add_uses_uinfo_display() {
212 let d = doc(vec![field("u", "org.example|lib|1.0|NA|jar")]);
213 let r = Record::try_from(&d).unwrap();
214 assert_eq!(r.to_string(), "add org.example:lib:1.0:jar");
215 }
216
217 #[test]
218 fn display_artifact_remove_uses_uinfo_display() {
219 let d = doc(vec![field("del", "org.example|lib|1.0|sources|jar")]);
220 let r = Record::try_from(&d).unwrap();
221 assert_eq!(r.to_string(), "remove org.example:lib:1.0:sources:jar");
222 }
223
224 #[test]
225 fn four_segment_uinfo_without_info_stays_none() {
226 let d = doc(vec![field("u", "org.example|lib|1.0|NA")]);
227 let Record::ArtifactAdd(u) = Record::try_from(&d).unwrap() else {
228 panic!("expected ArtifactAdd");
229 };
230 assert_eq!(u.extension, None);
231 }
232}