rjini/
ops.rs

1use crate::RJini;
2use anyhow::anyhow;
3use anyhow::Result;
4use std::ops::Add;
5use std::panic::Location;
6
7/// Creating a new instance of RJini from a XPATH as string.
8impl From<&str> for RJini {
9    /// It takes a string and returns a RJini object.
10    ///
11    /// For example:
12    /// ```
13    /// use rjini::RJini;
14    /// let j = RJini::from("parent/child");
15    /// assert!(j.xpath.contains("t/c"))
16    /// ```
17    ///
18    /// Arguments:
19    ///
20    /// * `xpath`: The XPath expression to be evaluated.
21    ///
22    /// Returns:
23    ///
24    /// A struct with a XPATH field.
25    fn from(xpath: &str) -> Self {
26        RJini {
27            xpath: xpath.to_string(),
28        }
29    }
30}
31
32/// @todo #1 Harder unit tests.
33/// We have to make our tests as hard as possible.
34impl RJini {
35    /// It adds a node to the body of the XPATH.
36    ///
37    /// For example:
38    /// ```
39    /// use rjini::RJini;
40    /// let j = RJini::from("parent/")
41    ///     .add_node("child").unwrap()
42    ///     .add_node("game").unwrap();
43    /// assert!(j.xpath.contains("child/game/"))
44    /// ```
45    ///
46    /// Arguments:
47    ///
48    /// * `node`: The node to add to the XPATH.
49    ///
50    /// Returns:
51    ///
52    /// A new RJini object with the new body.
53    pub fn add_node(&self, node: &str) -> Result<RJini> {
54        Self::validate(node)?;
55        let b = self.xpath.clone() + node + "/";
56        Ok(RJini { xpath: b })
57    }
58
59    /// It removes a node from the XPATH.
60    ///
61    /// For example:
62    /// ```
63    /// use rjini::RJini;
64    /// let j = RJini::empty()
65    ///     .add_node("parent").unwrap()
66    ///     .add_node("child").unwrap()
67    ///     .add_node("toy").unwrap();
68    /// assert!(j.xpath.contains("child"));
69    /// let j = j.remove_node("child");
70    /// assert!(!j.xpath.contains("child"))
71    /// ```
72    ///
73    /// Arguments:
74    ///
75    /// * `node`: The node to remove.
76    ///
77    /// Returns:
78    ///
79    /// A new RJini object with the XPATH of the old one but with the node removed.
80    pub fn remove_node(&self, node: &str) -> RJini {
81        let b = self.xpath.replace(&node.to_string().add("/"), "");
82        RJini { xpath: b }
83    }
84
85    /// > Replace a node in the xpath with a new node
86    ///
87    /// For example:
88    /// ```
89    /// use rjini::RJini;
90    /// let j = RJini::empty()
91    ///         .add_node("Ruby").unwrap()
92    ///         .add_node("is").unwrap()
93    ///         .add_node("a").unwrap()
94    ///         .add_node("bad").unwrap()
95    ///         .add_node("dog").unwrap()
96    ///         .replace_node("bad", "good").unwrap()
97    ///         .xpath;
98    /// assert_eq!("Ruby/is/a/good/dog/", j);
99    /// ```
100    ///
101    /// Arguments:
102    ///
103    /// * `origin`: The node you want to replace.
104    /// * `new`: The new node name
105    ///
106    /// Returns:
107    ///
108    /// A new RJini object with the xpath replaced.
109    pub fn replace_node(&self, origin: &str, new: &str) -> Result<RJini> {
110        Self::validate(new)?;
111        let x = self.xpath.replace(origin, new);
112        Ok(RJini { xpath: x })
113    }
114
115    /// It splits the xpath string into a vector of strings.
116    ///
117    /// For example:
118    /// ```
119    /// use rjini::RJini;
120    /// let x = RJini::from("parent/child[@key=\"value\"]/next[3]");
121    /// assert_eq!(
122    ///     vec!["parent", "child[@key=\"value\"]", "next[3]"],
123    ///     x.nodes().unwrap()
124    /// )
125    /// ```
126    /// Returns:
127    ///
128    /// A vector of strings.
129    pub fn nodes(&self) -> Result<Vec<&str>> {
130        Ok(regex::Regex::new(r"(//|/)")?.split(&self.xpath).collect())
131    }
132
133    /// `add_property` adds a property to the current XPATH
134    ///
135    /// For example:
136    /// ```
137    /// use rjini::RJini;
138    /// let j = RJini::from("parent/child/")
139    ///     .add_property("p").unwrap();
140    /// assert!(j.xpath.contains("p()"))
141    /// ```
142    /// Arguments:
143    ///
144    /// * `property`: The name of the property to add.
145    ///
146    /// Returns:
147    ///
148    /// A Result<RJini>
149    pub fn add_property(&self, property: &str) -> Result<RJini> {
150        Self::validate(&property)?;
151        Ok(Self::add_node(
152            self,
153            &(String::from(property) + "()").as_str(),
154        )?)
155    }
156
157    /// Removes a property from the current XPATH
158    ///
159    /// For example:
160    /// ```
161    /// use rjini::RJini;
162    /// let j = RJini::from("parent/child/property()");
163    /// assert!(
164    ///     "parent/child/".eq(j.remove_property("property").xpath.as_str())
165    /// );
166    /// ```
167    ///
168    /// Arguments:
169    ///
170    /// * `property`: The property to remove.
171    ///
172    /// Returns:
173    ///
174    /// A new RJini object with the property removed from the xpath.
175    pub fn remove_property(&self, property: &str) -> RJini {
176        let x = self
177            .xpath
178            .clone()
179            .replace((String::from(property) + "()").as_str(), "");
180        RJini { xpath: x }
181    }
182
183    /// It checks if the node contains spaces.
184    ///
185    /// Arguments:
186    ///
187    /// * `node`: The name of the node to validate.
188    ///
189    /// Returns:
190    ///
191    /// Result<()>
192    fn validate(node: &str) -> Result<()> {
193        let location = Location::caller();
194        if node.contains(' ') {
195            return Err(anyhow!(format!(
196                "{location}: The \"{node}\" contain spaces"
197            )));
198        }
199        Ok(())
200    }
201}
202
203#[test]
204fn checks_creates_rjini_from() -> Result<()> {
205    let j = RJini::from("parent/child");
206    assert!(j.xpath.contains("child"));
207    Ok(())
208}
209
210#[test]
211fn checks_adds_node() -> Result<()> {
212    let j = RJini::from("parent/");
213    let j = j.add_node("child")?;
214    let j = j.add_node("toys")?;
215    println!("{}", j.xpath);
216    assert!(j.xpath.contains("child/") && j.xpath.contains("toys/"));
217    Ok(())
218}
219
220#[test]
221fn checks_error_on_add_wrong_node() -> Result<()> {
222    let actual = format!(
223        "{}",
224        RJini::empty()
225            .add_node("so me no de")
226            .unwrap_err()
227            .root_cause()
228    );
229    assert!(actual.contains("The \"so me no de\" contain spaces"));
230    Ok(())
231}
232
233#[test]
234fn checks_removes_node() -> Result<()> {
235    let j = RJini::empty()
236        .add_node("Ruby")?
237        .add_node("is")?
238        .add_node("not")?
239        .add_node("my")?
240        .add_node("dog")?
241        .remove_node("not");
242    assert_eq!("Ruby/is/my/dog/", j.xpath);
243    Ok(())
244}
245
246#[test]
247fn checks_replaces_node() -> Result<()> {
248    let j = RJini::empty()
249        .add_node("Ruby")?
250        .add_node("is")?
251        .add_node("a")?
252        .add_node("bad")?
253        .add_node("dog")?
254        .replace_node("bad", "good")?
255        .xpath;
256    assert_eq!("Ruby/is/a/good/dog/", j);
257    Ok(())
258}
259
260#[test]
261fn checks_error_on_replaces_node() -> Result<()> {
262    let actual = format!(
263        "{}",
264        RJini::empty()
265            .add_node("test")?
266            .replace_node("test", "not test")
267            .unwrap_err()
268            .root_cause()
269    );
270    assert!(actual.contains("The \"not test\" contain spaces"));
271    Ok(())
272}
273
274#[test]
275fn checks_does_nodes() -> Result<()> {
276    let x = RJini::from("parent/child[@key=\"value\"]/next[3]");
277    assert_eq!(
278        vec!["parent", "child[@key=\"value\"]", "next[3]"],
279        x.nodes()?
280    );
281    Ok(())
282}
283
284#[test]
285fn checks_adds_property() -> Result<()> {
286    let x = RJini::from("some/xpath/");
287    assert!(x.add_property("pr")?.xpath.contains("pr()"));
288    Ok(())
289}
290
291#[test]
292fn checks_removes_property() -> Result<()> {
293    let x = RJini::from("some/xpath/");
294    assert!(x.add_property("pr")?.xpath.contains("pr()"));
295    let x = x.remove_property("pr").xpath;
296    assert!(!x.contains("pr"));
297    Ok(())
298}