maven_rs/editor/
mod.rs

1//! This module contains the logic for editing XML files without using Serde this is done using the edit-xml crate.
2//!
3//! Why not use Serde?
4//! Using this will keep structure and comments in the XML file.
5//!  This is important for pom files as comments are used to describe the purpose of the element.
6use std::{fmt::Display, path::PathBuf};
7
8use edit_xml::{Document, EditXMLError, Element};
9use thiserror::Error;
10use utils::MissingElementError;
11
12use crate::{
13    pom::{
14        DependencyBuilderError, DeveloperBuilderError, DistributionRepositoryBuilderError,
15        ParentBuilderError, PluginBuilderError, RepositoryBuilderError, ScmBuilderError,
16        SubRepositoryRulesBuilderError,
17    },
18    settings::{MirrorBuilderError, ServerBuilderError},
19};
20pub mod utils;
21
22#[derive(Debug, Error)]
23pub enum XMLEditorError {
24    #[error(transparent)]
25    MissingElement(#[from] MissingElementError),
26    #[error("Unexpected Element Type. Expected {expected}, found {found}")]
27    UnexpectedElementType {
28        expected: &'static str,
29        found: String,
30    },
31    #[error(transparent)]
32    InvalidValue(#[from] InvalidValueError),
33    #[error(transparent)]
34    EditXMLError(#[from] EditXMLError),
35    #[error("Error During Validation of type {pom_type} {error}")]
36    ValidationError {
37        pom_type: &'static str,
38        error: String,
39    },
40}
41macro_rules! builder_err {
42    ($error_type:ident, $pom_type:literal) => {
43        impl From<$error_type> for XMLEditorError {
44            fn from(value: $error_type) -> Self {
45                match value {
46                    $error_type::UninitializedField(missing_field) => {
47                        XMLEditorError::MissingElement(MissingElementError(missing_field))
48                    }
49                    $error_type::ValidationError(other) => XMLEditorError::ValidationError {
50                        pom_type: $pom_type,
51                        error: other,
52                    },
53                }
54            }
55        }
56    };
57    [
58        $(
59            ($error_type:ident, $pom_type:literal)
60        ),*
61    ] => {
62        $(
63            builder_err!($error_type, $pom_type);
64        )*
65    };
66
67}
68
69builder_err![
70    (DependencyBuilderError, "Dependency"),
71    (PluginBuilderError, "Plugin"),
72    (ParentBuilderError, "Parent"),
73    (ServerBuilderError, "Server"),
74    (MirrorBuilderError, "Mirror"),
75    (SubRepositoryRulesBuilderError, "SubRepositoryRules"),
76    (RepositoryBuilderError, "Repository"),
77    (ScmBuilderError, "Scm"),
78    (DeveloperBuilderError, "Developer"),
79    (DistributionRepositoryBuilderError, "DistributionRepository")
80];
81
82/// An element with a specific name.
83pub trait HasElementName {
84    /// The name of the element.
85    fn element_name() -> &'static str;
86}
87
88/// Converting an [Element] back and forth to a specific type.
89pub trait ElementConverter: Sized {
90    /// Converts an [Element] to the specific type.
91    fn from_element(element: Element, document: &Document) -> Result<Self, XMLEditorError>;
92    /// Creates an [Element] from the current type.
93    ///
94    /// Default implementation creates an element with the from [HasElementName::element_name] of the type
95    /// and adds all children returned by [ElementConverter::into_children].
96    fn into_element(self, document: &mut Document) -> Result<Element, XMLEditorError>
97    where
98        Self: HasElementName,
99    {
100        let element = Element::new(document, Self::element_name());
101        let children = self.into_children(document)?;
102        for child in children {
103            element.push_child(document, child)?;
104        }
105        Ok(element)
106    }
107    /// Creates all children of the current type.
108    fn into_children(self, document: &mut Document) -> Result<Vec<Element>, XMLEditorError>;
109}
110pub trait ComparableElement {
111    /// Checks if the current element is the same as the other element.
112    /// Some implementations may only check a subset of fields. Such as [Dependency](crate::pom::Dependency) only checking the group id and artifact id.
113    fn is_same_item(&self, other: &Self) -> bool;
114}
115/// Used Internally for updating a type of element.
116pub trait UpdatableElement: ElementConverter {
117    // Updates the element with the current element.
118    fn update_element(
119        &self,
120        element: Element,
121        document: &mut Document,
122    ) -> Result<(), XMLEditorError>;
123    /// Replaces all children of the element with the children of the current element.
124    fn replace_all_elements(
125        self,
126        element: Element,
127        document: &mut Document,
128    ) -> Result<(), XMLEditorError> {
129        element.clear_children(document);
130        let children = self.into_children(document)?;
131        for child in children {
132            element.push_child(document, child)?;
133        }
134        Ok(())
135    }
136}
137/// Used Internally for updating list type structures such as dependencies and plugins.
138pub trait ChildOfListElement: ElementConverter {
139    fn parent_element_name() -> &'static str;
140}
141#[derive(Debug, Error)]
142pub enum InvalidValueError {
143    InvalidValue {
144        expected: &'static str,
145        found: String,
146    },
147    InvalidFormattedValue {
148        error: String,
149    },
150}
151impl Display for InvalidValueError {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        match self {
154            InvalidValueError::InvalidValue { expected, found } => {
155                write!(f, "Expected {} found {}", expected, found)
156            }
157            InvalidValueError::InvalidFormattedValue { error } => {
158                write!(f, "Invalid Value: {}", error)
159            }
160        }
161    }
162}
163
164pub trait PomValue: Sized {
165    fn from_string_for_editor(value: String) -> Result<Self, InvalidValueError> {
166        Self::from_str_for_editor(&value)
167    }
168
169    fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError>;
170
171    fn to_string_for_editor(&self) -> String;
172
173    fn from_element(element: Element, document: &Document) -> Result<Self, XMLEditorError>
174    where
175        Self: Sized,
176    {
177        let value = element.text_content(document);
178        Self::from_str_for_editor(&value).map_err(|e| e.into())
179    }
180}
181
182impl PomValue for bool {
183    fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError> {
184        match value {
185            "true" => Ok(true),
186            "false" => Ok(false),
187            _ => Err(InvalidValueError::InvalidValue {
188                expected: "true or false",
189                found: value.to_string(),
190            }),
191        }
192    }
193
194    fn to_string_for_editor(&self) -> String {
195        self.to_string()
196    }
197}
198
199impl PomValue for String {
200    fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError> {
201        Ok(value.to_string())
202    }
203    fn from_string_for_editor(value: String) -> Result<Self, InvalidValueError> {
204        Ok(value)
205    }
206    fn to_string_for_editor(&self) -> String {
207        self.clone()
208    }
209}
210macro_rules! pom_value_num {
211    (
212        $(
213            $type:ty
214        ),*
215    ) => {
216        $(
217            impl PomValue for $type {
218                fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError> {
219                    value.parse().map_err(|_| InvalidValueError::InvalidValue {
220                        expected: "A number",
221                        found: value.to_string(),
222                    })
223                }
224
225                fn to_string_for_editor(&self) -> String {
226                    self.to_string()
227                }
228            }
229        )*
230    };
231}
232pom_value_num!(usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64);
233
234impl PomValue for PathBuf {
235    fn from_str_for_editor(value: &str) -> Result<Self, InvalidValueError> {
236        Ok(PathBuf::from(value))
237    }
238
239    fn to_string_for_editor(&self) -> String {
240        // this is probably not the best way to do this.
241        self.to_string_lossy().to_string()
242    }
243}