1use std::collections::HashSet;
4
5use semver_crate::{Version, VersionReq};
6use serde_json::{Number, Value};
7
8use crate::functions::{Function, number_value};
9use crate::interpreter::SearchResult;
10use crate::registry::register_if_enabled;
11use crate::{Context, Runtime, arg, defn};
12
13defn!(SemverParseFn, vec![arg!(string)], None);
18
19impl Function for SemverParseFn {
20 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
21 self.signature.validate(args, ctx)?;
22 let s = args[0].as_str().unwrap();
23
24 match Version::parse(s) {
25 Ok(v) => {
26 let pre = if v.pre.is_empty() {
27 Value::Null
28 } else {
29 Value::String(v.pre.to_string())
30 };
31 let build = if v.build.is_empty() {
32 Value::Null
33 } else {
34 Value::String(v.build.to_string())
35 };
36
37 let obj = serde_json::json!({
38 "major": v.major,
39 "minor": v.minor,
40 "patch": v.patch,
41 "pre": pre,
42 "build": build
43 });
44
45 Ok(obj)
46 }
47 Err(_) => Ok(Value::Null),
48 }
49 }
50}
51
52defn!(SemverMajorFn, vec![arg!(string)], None);
57
58impl Function for SemverMajorFn {
59 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
60 self.signature.validate(args, ctx)?;
61 let s = args[0].as_str().unwrap();
62
63 match Version::parse(s) {
64 Ok(v) => Ok(Value::Number(Number::from(v.major))),
65 Err(_) => Ok(Value::Null),
66 }
67 }
68}
69
70defn!(SemverMinorFn, vec![arg!(string)], None);
75
76impl Function for SemverMinorFn {
77 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
78 self.signature.validate(args, ctx)?;
79 let s = args[0].as_str().unwrap();
80
81 match Version::parse(s) {
82 Ok(v) => Ok(Value::Number(Number::from(v.minor))),
83 Err(_) => Ok(Value::Null),
84 }
85 }
86}
87
88defn!(SemverPatchFn, vec![arg!(string)], None);
93
94impl Function for SemverPatchFn {
95 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
96 self.signature.validate(args, ctx)?;
97 let s = args[0].as_str().unwrap();
98
99 match Version::parse(s) {
100 Ok(v) => Ok(Value::Number(Number::from(v.patch))),
101 Err(_) => Ok(Value::Null),
102 }
103 }
104}
105
106defn!(SemverCompareFn, vec![arg!(string), arg!(string)], None);
111
112impl Function for SemverCompareFn {
113 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
114 self.signature.validate(args, ctx)?;
115 let s1 = args[0].as_str().unwrap();
116 let s2 = args[1].as_str().unwrap();
117
118 let v1 = match Version::parse(s1) {
119 Ok(v) => v,
120 Err(_) => return Ok(Value::Null),
121 };
122 let v2 = match Version::parse(s2) {
123 Ok(v) => v,
124 Err(_) => return Ok(Value::Null),
125 };
126
127 let result = match v1.cmp(&v2) {
128 std::cmp::Ordering::Less => -1,
129 std::cmp::Ordering::Equal => 0,
130 std::cmp::Ordering::Greater => 1,
131 };
132
133 Ok(number_value(result as f64))
134 }
135}
136
137defn!(SemverSatisfiesFn, vec![arg!(string), arg!(string)], None);
142
143impl Function for SemverSatisfiesFn {
144 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
145 self.signature.validate(args, ctx)?;
146 let version_str = args[0].as_str().unwrap();
147 let req_str = args[1].as_str().unwrap();
148
149 let version = match Version::parse(version_str) {
150 Ok(v) => v,
151 Err(_) => return Ok(Value::Null),
152 };
153 let req = match VersionReq::parse(req_str) {
154 Ok(r) => r,
155 Err(_) => return Ok(Value::Null),
156 };
157
158 Ok(Value::Bool(req.matches(&version)))
159 }
160}
161
162defn!(SemverIsValidFn, vec![arg!(string)], None);
167
168impl Function for SemverIsValidFn {
169 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
170 self.signature.validate(args, ctx)?;
171 let s = args[0].as_str().unwrap();
172 let is_valid = Version::parse(s).is_ok();
173 Ok(Value::Bool(is_valid))
174 }
175}
176
177pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
179 register_if_enabled(
180 runtime,
181 "semver_parse",
182 enabled,
183 Box::new(SemverParseFn::new()),
184 );
185 register_if_enabled(
186 runtime,
187 "semver_major",
188 enabled,
189 Box::new(SemverMajorFn::new()),
190 );
191 register_if_enabled(
192 runtime,
193 "semver_minor",
194 enabled,
195 Box::new(SemverMinorFn::new()),
196 );
197 register_if_enabled(
198 runtime,
199 "semver_patch",
200 enabled,
201 Box::new(SemverPatchFn::new()),
202 );
203 register_if_enabled(
204 runtime,
205 "semver_compare",
206 enabled,
207 Box::new(SemverCompareFn::new()),
208 );
209 register_if_enabled(
210 runtime,
211 "semver_satisfies",
212 enabled,
213 Box::new(SemverSatisfiesFn::new()),
214 );
215 register_if_enabled(
216 runtime,
217 "semver_is_valid",
218 enabled,
219 Box::new(SemverIsValidFn::new()),
220 );
221}
222
223#[cfg(test)]
224mod tests {
225 use crate::Runtime;
226 use serde_json::json;
227
228 fn setup_runtime() -> Runtime {
229 Runtime::builder()
230 .with_standard()
231 .with_all_extensions()
232 .build()
233 }
234
235 #[test]
236 fn test_semver_parse() {
237 let runtime = setup_runtime();
238 let data = json!("1.2.3");
239 let expr = runtime.compile("semver_parse(@)").unwrap();
240 let result = expr.search(&data).unwrap();
241 let obj = result.as_object().unwrap();
242 assert_eq!(obj.get("major").unwrap().as_f64().unwrap(), 1.0);
243 assert_eq!(obj.get("minor").unwrap().as_f64().unwrap(), 2.0);
244 assert_eq!(obj.get("patch").unwrap().as_f64().unwrap(), 3.0);
245 }
246
247 #[test]
248 fn test_semver_parse_with_pre() {
249 let runtime = setup_runtime();
250 let data = json!("1.0.0-alpha.1");
251 let expr = runtime.compile("semver_parse(@)").unwrap();
252 let result = expr.search(&data).unwrap();
253 let obj = result.as_object().unwrap();
254 assert_eq!(obj.get("major").unwrap().as_f64().unwrap(), 1.0);
255 assert_eq!(obj.get("pre").unwrap().as_str().unwrap(), "alpha.1");
256 }
257
258 #[test]
259 fn test_semver_major() {
260 let runtime = setup_runtime();
261 let data = json!("2.3.4");
262 let expr = runtime.compile("semver_major(@)").unwrap();
263 let result = expr.search(&data).unwrap();
264 assert_eq!(result.as_f64().unwrap(), 2.0);
265 }
266
267 #[test]
268 fn test_semver_minor() {
269 let runtime = setup_runtime();
270 let data = json!("2.3.4");
271 let expr = runtime.compile("semver_minor(@)").unwrap();
272 let result = expr.search(&data).unwrap();
273 assert_eq!(result.as_f64().unwrap(), 3.0);
274 }
275
276 #[test]
277 fn test_semver_patch_fn() {
278 let runtime = setup_runtime();
279 let data = json!("2.3.4");
280 let expr = runtime.compile("semver_patch(@)").unwrap();
281 let result = expr.search(&data).unwrap();
282 assert_eq!(result.as_f64().unwrap(), 4.0);
283 }
284
285 #[test]
286 fn test_semver_compare_less() {
287 let runtime = setup_runtime();
288 let data = json!(["1.0.0", "2.0.0"]);
289 let expr = runtime.compile("semver_compare(@[0], @[1])").unwrap();
290 let result = expr.search(&data).unwrap();
291 assert_eq!(result.as_f64().unwrap(), -1.0);
292 }
293
294 #[test]
295 fn test_semver_compare_equal() {
296 let runtime = setup_runtime();
297 let data = json!(["1.0.0", "1.0.0"]);
298 let expr = runtime.compile("semver_compare(@[0], @[1])").unwrap();
299 let result = expr.search(&data).unwrap();
300 assert_eq!(result.as_f64().unwrap(), 0.0);
301 }
302
303 #[test]
304 fn test_semver_compare_greater() {
305 let runtime = setup_runtime();
306 let data = json!(["2.0.0", "1.0.0"]);
307 let expr = runtime.compile("semver_compare(@[0], @[1])").unwrap();
308 let result = expr.search(&data).unwrap();
309 assert_eq!(result.as_f64().unwrap(), 1.0);
310 }
311
312 #[test]
313 fn test_semver_satisfies_true() {
314 let runtime = setup_runtime();
315 let data = json!(["1.2.3", "^1.0.0"]);
316 let expr = runtime.compile("semver_satisfies(@[0], @[1])").unwrap();
317 let result = expr.search(&data).unwrap();
318 assert_eq!(result, json!(true));
319 }
320
321 #[test]
322 fn test_semver_satisfies_false() {
323 let runtime = setup_runtime();
324 let data = json!(["2.0.0", "^1.0.0"]);
325 let expr = runtime.compile("semver_satisfies(@[0], @[1])").unwrap();
326 let result = expr.search(&data).unwrap();
327 assert_eq!(result, json!(false));
328 }
329
330 #[test]
331 fn test_semver_satisfies_tilde() {
332 let runtime = setup_runtime();
333 let data = json!(["1.2.5", "~1.2.0"]);
334 let expr = runtime.compile("semver_satisfies(@[0], @[1])").unwrap();
335 let result = expr.search(&data).unwrap();
336 assert_eq!(result, json!(true));
337 }
338
339 #[test]
340 fn test_semver_is_valid_true() {
341 let runtime = setup_runtime();
342 let data = json!("1.2.3");
343 let expr = runtime.compile("semver_is_valid(@)").unwrap();
344 let result = expr.search(&data).unwrap();
345 assert_eq!(result, json!(true));
346 }
347
348 #[test]
349 fn test_semver_is_valid_false() {
350 let runtime = setup_runtime();
351 let data = json!("not-a-version");
352 let expr = runtime.compile("semver_is_valid(@)").unwrap();
353 let result = expr.search(&data).unwrap();
354 assert_eq!(result, json!(false));
355 }
356}