1use crate::package;
2use std::{collections::HashMap, convert::Infallible, fmt::Display, str::FromStr};
3
4use crate::{
5 lockfile::{OptState, PinnedState},
6 lua_rockspec::{
7 ExternalDependencySpec, PartialOverride, PerPlatform, PlatformOverridable, RockSourceSpec,
8 },
9};
10use miette::Diagnostic;
11use serde::{Deserialize, Deserializer};
12use thiserror::Error;
13
14#[derive(Error, Debug, Diagnostic)]
15#[non_exhaustive]
16pub enum SpecParseError {
17 #[error(transparent)]
18 #[diagnostic(transparent)]
19 Req(#[from] package::ReqParseError),
20}
21
22#[derive(Debug, Clone, PartialEq)]
24pub struct LuaDependencySpec {
25 pub(crate) package_req: package::Req,
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: package::Req,
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) -> &package::Req {
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) -> package::Req {
59 self.package_req
60 }
61 pub fn name(&self) -> &package::Name {
62 self.package_req.name()
63 }
64 pub fn version_req(&self) -> &package::VersionReq {
65 self.package_req.version_req()
66 }
67 pub fn matches(&self, package: &package::Spec) -> bool {
68 self.package_req.matches(package)
69 }
70}
71
72impl From<package::Name> for LuaDependencySpec {
73 fn from(name: package::Name) -> Self {
74 Self {
75 package_req: package::Req::from(name),
76 pin: PinnedState::default(),
77 opt: OptState::default(),
78 source: None,
79 }
80 }
81}
82
83impl From<package::Req> for LuaDependencySpec {
84 fn from(package_req: package::Req) -> 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 = SpecParseError;
96
97 fn from_str(str: &str) -> Result<Self, SpecParseError> {
98 let package_req = package::Req::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
118impl 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 = package::Req::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}