Skip to main content

nu_command/conversions/into/
semver_range.rs

1use crate::semver::range::SemverRangeValue;
2use nu_engine::command_prelude::*;
3use nu_protocol::shell_error::generic::GenericError;
4
5#[derive(Clone)]
6pub struct IntoSemverRange;
7
8impl Command for IntoSemverRange {
9    fn name(&self) -> &str {
10        "into semver-range"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build("into semver-range")
15            .input_output_types(vec![
16                (Type::String, Type::Custom("semver-range".into())),
17                (
18                    Type::Custom("semver-range".into()),
19                    Type::Custom("semver-range".into()),
20                ),
21            ])
22            .category(Category::Conversions)
23    }
24
25    fn description(&self) -> &str {
26        "Convert a string to a semver range."
27    }
28
29    fn search_terms(&self) -> Vec<&str> {
30        vec!["version", "requirement", "semantic"]
31    }
32
33    fn run(
34        &self,
35        engine_state: &EngineState,
36        _stack: &mut Stack,
37        call: &Call,
38        input: PipelineData,
39    ) -> Result<PipelineData, ShellError> {
40        let head = call.head;
41
42        input.map(
43            move |value| into_semver_range(&value, head),
44            engine_state.signals(),
45        )
46    }
47
48    fn examples(&self) -> Vec<Example<'static>> {
49        vec![
50            Example {
51                description: "Convert a string to a semver range",
52                example: "'>=1.0.0' | into semver-range",
53                result: None,
54            },
55            Example {
56                description: "Convert a complex range",
57                example: "'^1.2.3' | into semver-range",
58                result: None,
59            },
60        ]
61    }
62}
63
64fn into_semver_range(input: &Value, head: Span) -> Value {
65    match input {
66        Value::Custom { val, .. } if val.type_name() == "semver-range" => input.clone(),
67        Value::String { val, .. } => match semver::VersionReq::parse(val) {
68            Ok(requirement) => Value::custom(Box::new(SemverRangeValue::new(requirement)), head),
69            Err(_) => Value::error(
70                ShellError::Generic(
71                    GenericError::new(
72                        format!("Cannot convert \"{val}\" to a semver range"),
73                        "the given string is not a valid semver requirement",
74                        head,
75                    )
76                    .with_help("expected format: >=1.0.0, ^1.2.3, ~1.2, etc."),
77                ),
78                head,
79            ),
80        },
81        _ => Value::error(
82            ShellError::Generic(GenericError::new(
83                format!("Cannot convert {} to semver range", input.get_type()),
84                "expected a string or semver-range value",
85                head,
86            )),
87            head,
88        ),
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    fn get_custom_value(value: &Value) -> &SemverRangeValue {
97        match value {
98            Value::Custom { val, .. } => val.as_any().downcast_ref::<SemverRangeValue>().unwrap(),
99            _ => panic!("Expected Custom value"),
100        }
101    }
102
103    #[test]
104    fn test_into_semver_range_from_string() {
105        let value = Value::string(">=1.0.0", Span::test_data());
106        let result = into_semver_range(&value, Span::test_data());
107
108        assert!(matches!(result, Value::Custom { .. }));
109        let range_val = get_custom_value(&result);
110        assert_eq!(range_val.requirement.to_string(), ">=1.0.0");
111    }
112
113    #[test]
114    fn test_into_semver_range_caret() {
115        let value = Value::string("^1.2.3", Span::test_data());
116        let result = into_semver_range(&value, Span::test_data());
117
118        let range_val = get_custom_value(&result);
119        assert_eq!(range_val.requirement.to_string(), "^1.2.3");
120    }
121
122    #[test]
123    fn test_into_semver_range_tilde() {
124        let value = Value::string("~1.2", Span::test_data());
125        let result = into_semver_range(&value, Span::test_data());
126
127        let range_val = get_custom_value(&result);
128        assert_eq!(range_val.requirement.to_string(), "~1.2");
129    }
130
131    #[test]
132    fn test_into_semver_range_complex() {
133        let value = Value::string(">=1.0.0, <2.0.0", Span::test_data());
134        let result = into_semver_range(&value, Span::test_data());
135
136        let range_val = get_custom_value(&result);
137        assert_eq!(range_val.requirement.to_string(), ">=1.0.0, <2.0.0");
138    }
139
140    #[test]
141    fn test_into_semver_range_wildcard() {
142        let value = Value::string("*", Span::test_data());
143        let result = into_semver_range(&value, Span::test_data());
144
145        let range_val = get_custom_value(&result);
146        assert_eq!(range_val.requirement.to_string(), "*");
147    }
148
149    #[test]
150    fn test_into_semver_range_from_semver_range() {
151        let original = SemverRangeValue::new(semver::VersionReq::parse(">=1.0.0").unwrap());
152        let value = Value::custom(Box::new(original), Span::test_data());
153        let result = into_semver_range(&value, Span::test_data());
154
155        // Should return the same value
156        let range_val = get_custom_value(&result);
157        assert_eq!(range_val.requirement.to_string(), ">=1.0.0");
158    }
159
160    #[test]
161    fn test_into_semver_range_invalid() {
162        let value = Value::string("not-a-range", Span::test_data());
163        let result = into_semver_range(&value, Span::test_data());
164
165        assert!(matches!(result, Value::Error { .. }));
166    }
167
168    #[test]
169    fn test_into_semver_range_unsupported_type() {
170        let value = Value::int(42, Span::test_data());
171        let result = into_semver_range(&value, Span::test_data());
172
173        assert!(matches!(result, Value::Error { .. }));
174    }
175
176    #[test]
177    fn test_into_semver_range_wrong_custom_value() {
178        // Create a semver value instead of semver-range
179        use crate::semver::value::SemverValue;
180        let semver = SemverValue::new(semver::Version::parse("1.2.3").unwrap());
181        let value = Value::custom(Box::new(semver), Span::test_data());
182        let result = into_semver_range(&value, Span::test_data());
183
184        assert!(matches!(result, Value::Error { .. }));
185    }
186}
187
188#[cfg(test)]
189mod test {
190    use super::*;
191
192    #[test]
193    fn test_examples() -> nu_test_support::Result {
194        nu_test_support::test().examples(IntoSemverRange)
195    }
196}