Skip to main content

lux_lib/rockspec/
lua_dependency.rs

1use std::{collections::HashMap, convert::Infallible, fmt::Display, str::FromStr};
2
3use crate::{
4    lockfile::{OptState, PinnedState},
5    lua_rockspec::{
6        ExternalDependencySpec, PartialOverride, PerPlatform, PlatformOverridable, RockSourceSpec,
7    },
8    package::{PackageName, PackageReq, PackageReqParseError, PackageSpec, PackageVersionReq},
9};
10use miette::Diagnostic;
11use serde::{Deserialize, Deserializer};
12use thiserror::Error;
13
14#[derive(Error, Debug, Diagnostic)]
15#[non_exhaustive]
16pub enum LuaDependencySpecParseError {
17    #[error(transparent)]
18    #[diagnostic(transparent)]
19    PackageReq(#[from] PackageReqParseError),
20}
21
22/// Specification for a Lua dependency in a Lux project
23#[derive(Debug, Clone, PartialEq)]
24pub struct LuaDependencySpec {
25    pub(crate) package_req: PackageReq,
26    pub(crate) pin: PinnedState,
27    pub(crate) opt: OptState,
28    pub(crate) source: Option<RockSourceSpec>,
29}
30
31impl LuaDependencySpec {
32    pub fn new(
33        package_req: PackageReq,
34        pin: PinnedState,
35        opt: OptState,
36        source: Option<RockSourceSpec>,
37    ) -> Self {
38        Self {
39            package_req,
40            pin,
41            opt,
42            source,
43        }
44    }
45
46    pub fn package_req(&self) -> &PackageReq {
47        &self.package_req
48    }
49    pub fn pin(&self) -> &PinnedState {
50        &self.pin
51    }
52    pub fn opt(&self) -> &OptState {
53        &self.opt
54    }
55    pub fn source(&self) -> &Option<RockSourceSpec> {
56        &self.source
57    }
58    pub fn into_package_req(self) -> PackageReq {
59        self.package_req
60    }
61    pub fn name(&self) -> &PackageName {
62        self.package_req.name()
63    }
64    pub fn version_req(&self) -> &PackageVersionReq {
65        self.package_req.version_req()
66    }
67    pub fn matches(&self, package: &PackageSpec) -> bool {
68        self.package_req.matches(package)
69    }
70}
71
72impl From<PackageName> for LuaDependencySpec {
73    fn from(name: PackageName) -> Self {
74        Self {
75            package_req: PackageReq::from(name),
76            pin: PinnedState::default(),
77            opt: OptState::default(),
78            source: None,
79        }
80    }
81}
82
83impl From<PackageReq> for LuaDependencySpec {
84    fn from(package_req: PackageReq) -> Self {
85        Self {
86            package_req,
87            pin: PinnedState::default(),
88            opt: OptState::default(),
89            source: None,
90        }
91    }
92}
93
94impl FromStr for LuaDependencySpec {
95    type Err = LuaDependencySpecParseError;
96
97    fn from_str(str: &str) -> Result<Self, LuaDependencySpecParseError> {
98        let package_req = PackageReq::from_str(str)?;
99        Ok(Self {
100            package_req,
101            pin: PinnedState::default(),
102            opt: OptState::default(),
103            source: None,
104        })
105    }
106}
107
108impl Display for LuaDependencySpec {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        if self.version_req().is_any() {
111            self.name().fmt(f)
112        } else {
113            f.write_str(format!("{}{}", self.name(), self.version_req()).as_str())
114        }
115    }
116}
117
118/// Override `base_deps` with `override_deps`
119/// - Adds missing dependencies
120/// - Replaces dependencies with the same name
121impl PartialOverride for Vec<LuaDependencySpec> {
122    type Err = Infallible;
123
124    fn apply_overrides(&self, override_vec: &Self) -> Result<Self, Self::Err> {
125        let mut result_map: HashMap<String, LuaDependencySpec> = self
126            .iter()
127            .map(|dep| (dep.name().clone().to_string(), dep.clone()))
128            .collect();
129        for override_dep in override_vec {
130            result_map.insert(
131                override_dep.name().clone().to_string(),
132                override_dep.clone(),
133            );
134        }
135        Ok(result_map.into_values().collect())
136    }
137}
138
139impl PlatformOverridable for Vec<LuaDependencySpec> {
140    type Err = Infallible;
141
142    fn on_nil<T>() -> Result<super::PerPlatform<T>, <Self as PlatformOverridable>::Err>
143    where
144        T: PlatformOverridable,
145        T: Default,
146    {
147        Ok(PerPlatform::default())
148    }
149}
150
151impl<'de> Deserialize<'de> for LuaDependencySpec {
152    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153    where
154        D: Deserializer<'de>,
155    {
156        let package_req = PackageReq::deserialize(deserializer)?;
157        Ok(Self {
158            package_req,
159            pin: PinnedState::default(),
160            opt: OptState::default(),
161            source: None,
162        })
163    }
164}
165
166#[derive(Debug, Deserialize)]
167#[serde(rename_all = "lowercase")]
168pub enum DependencyType<T> {
169    Regular(Vec<T>),
170    Build(Vec<T>),
171    Test(Vec<T>),
172    External(HashMap<String, ExternalDependencySpec>),
173}
174
175impl<T> DependencyType<T> {
176    pub fn as_ref(&self) -> DependencyType<&T> {
177        match *self {
178            Self::Regular(ref x) => DependencyType::Regular(x.iter().collect()),
179            Self::Build(ref x) => DependencyType::Build(x.iter().collect()),
180            Self::Test(ref x) => DependencyType::Test(x.iter().collect()),
181            Self::External(ref x) => DependencyType::External(x.clone()),
182        }
183    }
184}
185
186#[derive(Debug, Deserialize)]
187#[serde(rename_all = "lowercase")]
188pub enum LuaDependencyType<T> {
189    Regular(Vec<T>),
190    Build(Vec<T>),
191    Test(Vec<T>),
192}
193
194#[cfg(test)]
195mod tests {
196
197    use ottavino::{Closure, Executor, Fuel, Lua, Value};
198    use ottavino_util::serde::from_value;
199    use path_slash::PathBufExt;
200
201    use super::*;
202
203    fn eval_lua<T: serde::de::DeserializeOwned>(code: &str) -> Result<T, ottavino::ExternError> {
204        Lua::core().try_enter(|ctx| {
205            let closure = Closure::load(ctx, None, code.as_bytes())?;
206            let executor = Executor::start(ctx, closure.into(), ());
207            executor.step(ctx, &mut Fuel::with(i32::MAX))?;
208            from_value(executor.take_result::<Value<'_>>(ctx)??).map_err(ottavino::Error::from)
209        })
210    }
211
212    #[tokio::test]
213    async fn test_override_lua_dependency_spec() {
214        let neorg_a: LuaDependencySpec = "neorg 1.0.0".parse().unwrap();
215        let neorg_b: LuaDependencySpec = "neorg 2.0.0".parse().unwrap();
216        let foo: LuaDependencySpec = "foo 1.0.0".parse().unwrap();
217        let bar: LuaDependencySpec = "bar 1.0.0".parse().unwrap();
218        let base_vec = vec![neorg_a, foo.clone()];
219        let override_vec = vec![neorg_b.clone(), bar.clone()];
220        let result = base_vec.apply_overrides(&override_vec).unwrap();
221        assert_eq!(result.clone().len(), 3);
222        assert_eq!(
223            result
224                .into_iter()
225                .filter(|dep| *dep == neorg_b || *dep == foo || *dep == bar)
226                .count(),
227            3
228        );
229    }
230
231    #[test]
232    fn test_dependency_type_from_lua() {
233        let regular_deps: DependencyType<LuaDependencySpec> =
234            eval_lua(r#"return { regular = {"neorg 1.0.0", "foo 1.0.0"} }"#).unwrap();
235        let build_deps: DependencyType<LuaDependencySpec> =
236            eval_lua(r#"return { build = {"neorg 1.0.0", "foo 1.0.0"} }"#).unwrap();
237        let test_deps: DependencyType<LuaDependencySpec> =
238            eval_lua(r#"return { test = {"neorg 1.0.0", "foo 1.0.0"} }"#).unwrap();
239        let external_deps: DependencyType<ExternalDependencySpec> = eval_lua(
240            r#"return { external = { foo = { header = "foo.h", library = "libfoo.so" }, bar = { header = "bar.h" } } }"#,
241        )
242        .unwrap();
243
244        match regular_deps {
245            DependencyType::Regular(deps) => {
246                assert_eq!(deps.len(), 2);
247                assert_eq!(deps[0].to_string(), "neorg==1.0.0");
248                assert_eq!(deps[1].to_string(), "foo==1.0.0");
249            }
250            _ => panic!("Expected regular dependencies"),
251        }
252
253        match build_deps {
254            DependencyType::Build(deps) => {
255                assert_eq!(deps.len(), 2);
256                assert_eq!(deps[0].to_string(), "neorg==1.0.0");
257                assert_eq!(deps[1].to_string(), "foo==1.0.0");
258            }
259            _ => panic!("Expected build dependencies"),
260        }
261
262        match test_deps {
263            DependencyType::Test(deps) => {
264                assert_eq!(deps.len(), 2);
265                assert_eq!(deps[0].to_string(), "neorg==1.0.0");
266                assert_eq!(deps[1].to_string(), "foo==1.0.0");
267            }
268            _ => panic!("Expected test dependencies"),
269        }
270
271        match external_deps {
272            DependencyType::External(deps) => {
273                assert_eq!(deps.len(), 2);
274                assert_eq!(
275                    deps["foo"].header.as_ref().unwrap().to_slash_lossy(),
276                    "foo.h"
277                );
278                assert_eq!(
279                    deps["foo"].library.as_ref().unwrap().to_slash_lossy(),
280                    "libfoo.so"
281                );
282
283                assert_eq!(
284                    deps["bar"].header.as_ref().unwrap().to_slash_lossy(),
285                    "bar.h"
286                );
287                assert!(deps["bar"].library.is_none());
288            }
289            _ => panic!("Expected external dependencies"),
290        }
291
292        let _err: ottavino::ExternError =
293            eval_lua::<DependencyType<ExternalDependencySpec>>("return {}").unwrap_err();
294    }
295
296    #[test]
297    fn test_lua_dependency_type_from_lua() {
298        let regular_deps: LuaDependencyType<LuaDependencySpec> =
299            eval_lua(r#"return { regular = {"neorg 1.0.0", "foo 1.0.0"} }"#).unwrap();
300        let build_deps: LuaDependencyType<LuaDependencySpec> =
301            eval_lua(r#"return { build = {"neorg 1.0.0", "foo 1.0.0"} }"#).unwrap();
302        let test_deps: LuaDependencyType<LuaDependencySpec> =
303            eval_lua(r#"return { test = {"neorg 1.0.0", "foo 1.0.0"} }"#).unwrap();
304
305        match regular_deps {
306            LuaDependencyType::Regular(deps) => {
307                assert_eq!(deps.len(), 2);
308                assert_eq!(deps[0].to_string(), "neorg==1.0.0");
309                assert_eq!(deps[1].to_string(), "foo==1.0.0");
310            }
311            _ => panic!("Expected regular dependencies"),
312        }
313
314        match build_deps {
315            LuaDependencyType::Build(deps) => {
316                assert_eq!(deps.len(), 2);
317                assert_eq!(deps[0].to_string(), "neorg==1.0.0");
318                assert_eq!(deps[1].to_string(), "foo==1.0.0");
319            }
320            _ => panic!("Expected build dependencies"),
321        }
322
323        match test_deps {
324            LuaDependencyType::Test(deps) => {
325                assert_eq!(deps.len(), 2);
326                assert_eq!(deps[0].to_string(), "neorg==1.0.0");
327                assert_eq!(deps[1].to_string(), "foo==1.0.0");
328            }
329            _ => panic!("Expected test dependencies"),
330        }
331
332        eval_lua::<LuaDependencyType<LuaDependencySpec>>("return {}").unwrap_err();
333    }
334}