Skip to main content

jpx_core/extensions/
semver_fns.rs

1//! Semantic versioning functions.
2
3use 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
13// =============================================================================
14// semver_parse(s) -> object
15// =============================================================================
16
17defn!(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
52// =============================================================================
53// semver_major(s) -> number
54// =============================================================================
55
56defn!(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
70// =============================================================================
71// semver_minor(s) -> number
72// =============================================================================
73
74defn!(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
88// =============================================================================
89// semver_patch(s) -> number
90// =============================================================================
91
92defn!(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
106// =============================================================================
107// semver_compare(v1, v2) -> number (-1, 0, 1)
108// =============================================================================
109
110defn!(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
137// =============================================================================
138// semver_satisfies(version, requirement) -> bool
139// =============================================================================
140
141defn!(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
162// =============================================================================
163// semver_is_valid(s) -> bool
164// =============================================================================
165
166defn!(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
177/// Register semver functions filtered by the enabled set.
178pub 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}