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#[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
103impl 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}