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