Skip to main content

lux_lib/rockspec/
lua_dependency.rs

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