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}