1use std::{fmt::Display, str::FromStr};
2
3use derive_builder::Builder;
4use edit_xml::Element;
5use serde::{Deserialize, Serialize};
6use strum::{Display, EnumString};
7
8use crate::{
9 editor::{
10 ChildOfListElement, ComparableElement, ElementConverter, HasElementName, InvalidValueError,
11 PomValue, UpdatableElement,
12 utils::{
13 add_if_present, find_or_create_then_set_text_content, sync_element,
14 typed_from_element_using_builder,
15 },
16 },
17 utils::serde_utils::serde_via_string_types,
18};
19
20#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
21pub struct Repositories {
22 #[serde(rename = "repository")]
23 pub repositories: Vec<Repository>,
24}
25
26#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Builder)]
27#[serde(rename_all = "camelCase")]
28pub struct Repository {
29 #[builder(setter(into, strip_option), default)]
30 pub id: Option<String>,
31 #[builder(setter(into, strip_option), default)]
32 pub name: Option<String>,
33 pub url: String,
34 #[builder(setter(into, strip_option), default)]
35 pub layout: Option<String>,
36 #[builder(setter(into, strip_option), default)]
37 pub update_policy: Option<UpdatePolicy>,
38 #[builder(setter(into, strip_option), default)]
39 pub checksum_policy: Option<ChecksumPolicy>,
40 #[builder(setter(into, strip_option), default)]
41 pub releases: Option<SubRepositoryRules>,
42 #[builder(setter(into, strip_option), default)]
43 pub snapshots: Option<SubRepositoryRules>,
44}
45impl HasElementName for Repository {
46 fn element_name() -> &'static str {
47 "repository"
48 }
49}
50impl ChildOfListElement for Repository {
51 fn parent_element_name() -> &'static str {
52 "repositories"
53 }
54}
55impl ComparableElement for Repository {
56 fn is_same_item(&self, other: &Self) -> bool {
57 if self.name.is_none() {
58 return false;
59 }
60 self.name == other.name
61 }
62}
63impl UpdatableElement for Repository {
64 fn update_element(
65 &self,
66 element: Element,
67 document: &mut edit_xml::Document,
68 ) -> Result<(), crate::editor::XMLEditorError> {
69 sync_element(document, element, "id", self.id.as_deref());
70 sync_element(document, element, "name", self.name.as_deref());
71 find_or_create_then_set_text_content(document, element, "url", self.url.as_str());
72 sync_element(document, element, "layout", self.layout.as_deref());
73 sync_element(document, element, "checksumPolicy", self.checksum_policy);
74 sync_element(document, element, "updatePolicy", self.update_policy);
75 Ok(())
77 }
78}
79impl ElementConverter for Repository {
80 fn from_element(
81 element: edit_xml::Element,
82 document: &edit_xml::Document,
83 ) -> Result<Self, crate::editor::XMLEditorError> {
84 let mut builder = RepositoryBuilder::default();
85 for child in element.child_elements(document) {
86 match child.name(document) {
87 "id" => {
88 builder.id(String::from_element(child, document)?);
89 }
90 "name" => {
91 builder.name(String::from_element(child, document)?);
92 }
93 "url" => {
94 builder.url(String::from_element(child, document)?);
95 }
96 "layout" => {
97 builder.layout(String::from_element(child, document)?);
98 }
99 "updatePolicy" => {
100 builder.update_policy(UpdatePolicy::from_element(child, document)?);
101 }
102 "checksumPolicy" => {
103 builder.checksum_policy(ChecksumPolicy::from_element(child, document)?);
104 }
105 "releases" => {
106 builder.releases(SubRepositoryRules::from_element(child, document)?);
107 }
108 "snapshots" => {
109 builder.snapshots(SubRepositoryRules::from_element(child, document)?);
110 }
111 _ => {}
112 }
113 }
114 let result = builder.build()?;
115 Ok(result)
116 }
117 fn into_children(
120 self,
121 document: &mut edit_xml::Document,
122 ) -> Result<Vec<edit_xml::Element>, crate::editor::XMLEditorError> {
123 let Self {
124 id,
125 name,
126 url,
127 layout,
128 update_policy,
129 checksum_policy,
130 releases,
131 snapshots,
132 } = self;
133 let mut children = vec![];
134 add_if_present!(document, children, id, "id");
135 add_if_present!(document, children, name, "name");
136 children.push(crate::editor::utils::create_basic_text_element(
137 document, "url", url,
138 ));
139 add_if_present!(document, children, layout, "layout");
140 add_if_present!(document, children, update_policy, "updatePolicy");
141 add_if_present!(document, children, checksum_policy, "checksumPolicy");
142 if let Some(releases) = releases {
143 let element = Element::new(document, "releases");
144 let release_children = releases.into_children(document)?;
145 for child in release_children {
146 element.push_child(document, child)?;
147 }
148 children.push(element);
149 }
150 if let Some(snapshots) = snapshots {
151 let element = Element::new(document, "snapshots");
152 let snapshot_children = snapshots.into_children(document)?;
153 for child in snapshot_children {
154 element.push_child(document, child)?;
155 }
156 children.push(element);
157 }
158 Ok(children)
159 }
160}
161
162#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Builder)]
163#[serde(rename_all = "camelCase")]
164pub struct SubRepositoryRules {
165 #[builder(setter(into, strip_option), default)]
166 pub enabled: Option<bool>,
167 #[builder(setter(into, strip_option), default)]
168 pub update_policy: Option<UpdatePolicy>,
169 #[builder(setter(into, strip_option), default)]
170 pub checksum_policy: Option<ChecksumPolicy>,
171}
172impl ElementConverter for SubRepositoryRules {
173 typed_from_element_using_builder!(
174 SubRepositoryRulesBuilder,
175 element,
176 document,
177 "enabled"(bool) => enabled,
178 "updatePolicy"(UpdatePolicy) => update_policy,
179 "checksumPolicy"(ChecksumPolicy) => checksum_policy
180 );
181 fn into_children(
182 self,
183 document: &mut edit_xml::Document,
184 ) -> Result<Vec<edit_xml::Element>, crate::editor::XMLEditorError> {
185 let Self {
186 enabled,
187 update_policy,
188 checksum_policy,
189 } = self;
190 let mut children = vec![];
191 add_if_present!(document, children, enabled, "enabled");
192 add_if_present!(document, children, update_policy, "updatePolicy");
193 add_if_present!(document, children, checksum_policy, "checksumPolicy");
194
195 Ok(children)
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumString)]
200#[strum(serialize_all = "camelCase")]
201pub enum ChecksumPolicy {
202 Ignore,
203 Fail,
204 Warn,
205}
206impl From<ChecksumPolicy> for String {
207 fn from(policy: ChecksumPolicy) -> Self {
208 policy.to_string()
209 }
210}
211
212serde_via_string_types!(ChecksumPolicy);
213impl PomValue for ChecksumPolicy {
214 fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError> {
215 match value {
216 "ignore" => Ok(ChecksumPolicy::Ignore),
217 "fail" => Ok(ChecksumPolicy::Fail),
218 "warn" => Ok(ChecksumPolicy::Warn),
219 _ => Err(InvalidValueError::InvalidValue {
220 expected: "ignore, fail, or warn",
221 found: value.to_owned(),
222 }),
223 }
224 }
225 fn to_string_for_editor(&self) -> String {
226 self.to_string()
227 }
228}
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumString)]
230#[strum(serialize_all = "camelCase")]
231pub enum RepositoryLayout {
232 Default,
233 Legacy,
234}
235serde_via_string_types!(RepositoryLayout);
236impl PomValue for RepositoryLayout {
237 fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError> {
238 match value {
239 "default" => Ok(RepositoryLayout::Default),
240 "legacy" => Ok(RepositoryLayout::Legacy),
241 _ => Err(InvalidValueError::InvalidValue {
242 expected: "default or legacy",
243 found: value.to_owned(),
244 }),
245 }
246 }
247 fn to_string_for_editor(&self) -> String {
248 self.to_string()
249 }
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum UpdatePolicy {
254 Always,
255 Daily,
256 Interval(usize),
257 Never,
258}
259impl From<UpdatePolicy> for String {
260 fn from(policy: UpdatePolicy) -> Self {
261 policy.to_string()
262 }
263}
264
265impl FromStr for UpdatePolicy {
266 type Err = InvalidValueError;
267
268 fn from_str(s: &str) -> Result<Self, Self::Err> {
269 match s {
270 "always" => Ok(UpdatePolicy::Always),
271 "daily" => Ok(UpdatePolicy::Daily),
272 "never" => Ok(UpdatePolicy::Never),
273 other => {
274 if other.starts_with("interval:") {
275 let interval = other.strip_prefix("interval:").ok_or_else(|| {
276 InvalidValueError::InvalidValue {
277 expected: "interval:<number>",
278 found: other.to_owned(),
279 }
280 })?;
281 let interval: usize =
282 interval
283 .parse()
284 .map_err(|_| InvalidValueError::InvalidFormattedValue {
285 error: interval.to_string(),
286 })?;
287 Ok(UpdatePolicy::Interval(interval))
288 } else {
289 Err(InvalidValueError::InvalidValue {
290 expected: "always, daily, never, or interval:<number>",
291 found: other.to_owned(),
292 })
293 }
294 }
295 }
296 }
297}
298impl Display for UpdatePolicy {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 match self {
301 UpdatePolicy::Always => write!(f, "always"),
302 UpdatePolicy::Daily => write!(f, "daily"),
303 UpdatePolicy::Interval(interval) => write!(f, "interval:{}", interval),
304 UpdatePolicy::Never => write!(f, "never"),
305 }
306 }
307}
308
309impl PomValue for UpdatePolicy {
310 fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError> {
311 value.parse()
312 }
313
314 fn to_string_for_editor(&self) -> String {
315 self.to_string()
316 }
317}
318serde_via_string_types!(UpdatePolicy);
319
320#[cfg(test)]
321mod tests {
322 use std::str::FromStr;
323
324 use crate::editor::utils::test_utils;
325
326 use super::*;
327 fn inner_layout_test(layout: RepositoryLayout, expected: &str) {
328 assert_eq!(layout.to_string(), expected);
329 assert_eq!(RepositoryLayout::from_str(expected).unwrap(), layout);
330 }
331 #[test]
332 fn layout() {
333 inner_layout_test(RepositoryLayout::Default, "default");
334 inner_layout_test(RepositoryLayout::Legacy, "legacy");
335 }
336
337 fn inner_update_policy_test(policy: UpdatePolicy, expected: &str) {
338 assert_eq!(policy.to_string(), expected);
339 assert_eq!(UpdatePolicy::from_str(expected).unwrap(), policy);
340 }
341 #[test]
342 fn update_policy() {
343 inner_update_policy_test(UpdatePolicy::Always, "always");
344 inner_update_policy_test(UpdatePolicy::Daily, "daily");
345 inner_update_policy_test(UpdatePolicy::Interval(5), "interval:5");
346 inner_update_policy_test(UpdatePolicy::Never, "never");
347 }
348 fn inner_checksum_policy(policy: ChecksumPolicy, expected: &str) {
349 assert_eq!(policy.to_string(), expected);
350 assert_eq!(ChecksumPolicy::from_str(expected).unwrap(), policy);
351 }
352 #[test]
353 fn checksum_policy() {
354 inner_checksum_policy(ChecksumPolicy::Ignore, "ignore");
355 inner_checksum_policy(ChecksumPolicy::Fail, "fail");
356 inner_checksum_policy(ChecksumPolicy::Warn, "warn");
357 }
358
359 fn test_parse_methods(value: &str, expected: Repository) -> anyhow::Result<()> {
360 let dep_via_edit_xml = test_utils::create_xml_to_element::<Repository>(value)?;
361 let dep_via_serde: Repository = quick_xml::de::from_str(value)?;
362
363 assert_eq!(dep_via_edit_xml, expected);
364 assert_eq!(dep_via_serde, expected);
365 println!("{:#?}", dep_via_edit_xml);
366
367 let dep_serialize_serde = quick_xml::se::to_string(&expected)?;
368 println!("Serialized Over Serde \n {}", dep_serialize_serde);
369 Ok(())
370 }
371
372 #[test]
373 fn basic_repository() -> anyhow::Result<()> {
374 test_parse_methods(
375 r#"
376 <repository>
377 <id>central</id>
378 <name>Maven Central</name>
379 <url>https://repo.maven.apache.org/maven2/</url>
380 </repository>
381 "#,
382 Repository {
383 id: Some("central".to_string()),
384 name: Some("Maven Central".to_string()),
385 url: "https://repo.maven.apache.org/maven2/".to_string(),
386 ..Default::default()
387 },
388 )
389 }
390 #[test]
391 fn just_url() -> anyhow::Result<()> {
392 test_parse_methods(
393 r#"
394 <repository>
395 <url>https://repo.maven.apache.org/maven2/</url>
396 </repository>
397 "#,
398 Repository {
399 url: "https://repo.maven.apache.org/maven2/".to_string(),
400 ..Default::default()
401 },
402 )
403 }
404 #[test]
405 fn with_release_settings() -> anyhow::Result<()> {
406 test_parse_methods(
407 r#"
408 <repository>
409 <url>https://repo.maven.apache.org/maven2/</url>
410 <releases>
411 <enabled>true</enabled>
412 <updatePolicy>daily</updatePolicy>
413 <checksumPolicy>fail</checksumPolicy>
414 </releases>
415 </repository>
416 "#,
417 Repository {
418 url: "https://repo.maven.apache.org/maven2/".to_string(),
419 releases: Some(SubRepositoryRules {
420 enabled: Some(true),
421 update_policy: Some(UpdatePolicy::Daily),
422 checksum_policy: Some(ChecksumPolicy::Fail),
423 }),
424 ..Default::default()
425 },
426 )
427 }
428 #[test]
429 fn with_snapshot_settings() -> anyhow::Result<()> {
430 test_parse_methods(
431 r#"
432 <repository>
433 <url>https://repo.maven.apache.org/maven2/</url>
434 <snapshots>
435 <enabled>true</enabled>
436 <updatePolicy>daily</updatePolicy>
437 <checksumPolicy>fail</checksumPolicy>
438 </snapshots>
439 </repository>
440 "#,
441 Repository {
442 url: "https://repo.maven.apache.org/maven2/".to_string(),
443 snapshots: Some(SubRepositoryRules {
444 enabled: Some(true),
445 update_policy: Some(UpdatePolicy::Daily),
446 checksum_policy: Some(ChecksumPolicy::Fail),
447 }),
448 ..Default::default()
449 },
450 )
451 }
452
453 #[test]
454 fn with_empty_sub_rules() -> anyhow::Result<()> {
455 test_parse_methods(
456 r#"
457 <repository>
458 <url>https://repo.maven.apache.org/maven2/</url>
459 <releases> </releases>
460 <snapshots/>
461 </repository>
462 "#,
463 Repository {
464 url: "https://repo.maven.apache.org/maven2/".to_string(),
465 releases: Some(SubRepositoryRules::default()),
466 snapshots: Some(SubRepositoryRules::default()),
467 ..Default::default()
468 },
469 )
470 }
471
472 #[test]
473 fn update_element_test() -> anyhow::Result<()> {
474 let actual_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
475 <repository>
476 <id>central</id>
477 <url>https://repo.maven.apache.org/maven2/</url>
478 </repository>
479 "#;
480 let mut document = edit_xml::Document::parse_str(actual_xml).unwrap();
481 let Some(raw_element) = document.root_element() else {
482 println!("{}", actual_xml);
483 panic!("No root element found");
484 };
485
486 let repository = Repository {
487 id: Some("central".to_string()),
488 name: Some("Maven Central".to_string()),
489 url: "https://repo.maven.apache.org/maven2/".to_string(),
490 layout: Some("default".to_string()),
491 update_policy: Some(UpdatePolicy::Daily),
492 checksum_policy: Some(ChecksumPolicy::Fail),
493
494 ..Default::default()
495 };
496
497 repository.update_element(raw_element, &mut document)?;
498
499 let new_xml = document.write_str()?;
500 let expected_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
501<repository>
502 <id>central</id>
503 <url>https://repo.maven.apache.org/maven2/</url>
504 <name>Maven Central</name>
505 <layout>default</layout>
506 <checksumPolicy>fail</checksumPolicy>
507 <updatePolicy>daily</updatePolicy>
508</repository>"#;
509 assert_eq!(new_xml, expected_xml);
510 Ok(())
511 }
512}