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