1use std::path::PathBuf;
19
20use crate::loader::LeanLibraryDependency;
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct LeanBuiltCapability {
25 dylib_path: Option<PathBuf>,
26 env_var: Option<String>,
27 manifest_path: Option<PathBuf>,
28 manifest_env_var: Option<String>,
29 package: Option<String>,
30 module: Option<String>,
31 dependencies: Vec<LeanLibraryDependency>,
32}
33
34impl LeanBuiltCapability {
35 #[must_use]
47 pub fn path(path: impl Into<PathBuf>) -> Self {
48 Self {
49 dylib_path: Some(path.into()),
50 env_var: None,
51 manifest_path: None,
52 manifest_env_var: None,
53 package: None,
54 module: None,
55 dependencies: Vec::new(),
56 }
57 }
58
59 #[must_use]
66 pub fn env(env_var: impl Into<String>) -> Self {
67 Self {
68 dylib_path: None,
69 env_var: Some(env_var.into()),
70 manifest_path: None,
71 manifest_env_var: None,
72 package: None,
73 module: None,
74 dependencies: Vec::new(),
75 }
76 }
77
78 #[must_use]
89 pub fn manifest_path(path: impl Into<PathBuf>) -> Self {
90 Self {
91 dylib_path: None,
92 env_var: None,
93 manifest_path: Some(path.into()),
94 manifest_env_var: None,
95 package: None,
96 module: None,
97 dependencies: Vec::new(),
98 }
99 }
100
101 #[must_use]
108 pub fn manifest_env(env_var: impl Into<String>) -> Self {
109 Self {
110 dylib_path: None,
111 env_var: None,
112 manifest_path: None,
113 manifest_env_var: Some(env_var.into()),
114 package: None,
115 module: None,
116 dependencies: Vec::new(),
117 }
118 }
119
120 #[must_use]
122 pub fn env_var(mut self, env_var: impl Into<String>) -> Self {
123 self.env_var = Some(env_var.into());
124 self
125 }
126
127 #[must_use]
129 pub fn manifest_env_var(mut self, env_var: impl Into<String>) -> Self {
130 self.manifest_env_var = Some(env_var.into());
131 self
132 }
133
134 #[must_use]
136 pub fn package(mut self, package: impl Into<String>) -> Self {
137 self.package = Some(package.into());
138 self
139 }
140
141 #[must_use]
143 pub fn module(mut self, module: impl Into<String>) -> Self {
144 self.module = Some(module.into());
145 self
146 }
147
148 #[must_use]
150 pub fn dependency(mut self, dependency: LeanLibraryDependency) -> Self {
151 self.dependencies.push(dependency);
152 self
153 }
154
155 #[must_use]
158 pub fn dependencies(mut self, dependencies: impl IntoIterator<Item = LeanLibraryDependency>) -> Self {
159 self.dependencies.extend(dependencies);
160 self
161 }
162
163 #[must_use]
165 pub fn package_name(&self) -> Option<&str> {
166 self.package.as_deref()
167 }
168
169 #[must_use]
171 pub fn module_name(&self) -> Option<&str> {
172 self.module.as_deref()
173 }
174
175 pub fn take_package_name(&mut self) -> Option<String> {
177 self.package.take()
178 }
179
180 pub fn take_module_name(&mut self) -> Option<String> {
182 self.module.take()
183 }
184
185 pub fn take_dependencies(&mut self) -> Vec<LeanLibraryDependency> {
187 std::mem::take(&mut self.dependencies)
188 }
189
190 #[must_use]
192 pub fn dependency_descriptors(&self) -> &[LeanLibraryDependency] {
193 &self.dependencies
194 }
195
196 pub fn dylib_path(&self) -> Result<PathBuf, LeanBuiltCapabilityError> {
205 if let Some(path) = &self.dylib_path {
206 return Ok(path.clone());
207 }
208 let env_var = self
209 .env_var
210 .as_deref()
211 .ok_or(LeanBuiltCapabilityError::MissingDylibSource)?;
212 std::env::var_os(env_var)
213 .map(PathBuf::from)
214 .ok_or_else(|| LeanBuiltCapabilityError::EnvVarNotSet {
215 env_var: env_var.to_owned(),
216 kind: BuiltCapabilityArtifact::Dylib,
217 })
218 }
219
220 pub fn resolved_manifest_path(&self) -> Result<PathBuf, LeanBuiltCapabilityError> {
229 if let Some(path) = &self.manifest_path {
230 return Ok(path.clone());
231 }
232 let env_var = self
233 .manifest_env_var
234 .as_deref()
235 .ok_or(LeanBuiltCapabilityError::MissingManifestSource)?;
236 std::env::var_os(env_var)
237 .map(PathBuf::from)
238 .ok_or_else(|| LeanBuiltCapabilityError::EnvVarNotSet {
239 env_var: env_var.to_owned(),
240 kind: BuiltCapabilityArtifact::Manifest,
241 })
242 }
243}
244
245impl From<&crate::build_helpers::BuiltLeanCapability> for LeanBuiltCapability {
246 fn from(value: &crate::build_helpers::BuiltLeanCapability) -> Self {
247 Self {
248 dylib_path: Some(value.dylib_path().to_path_buf()),
249 env_var: Some(value.env_var().to_owned()),
250 manifest_path: Some(value.manifest_path().to_path_buf()),
251 manifest_env_var: Some(value.manifest_env_var().to_owned()),
252 package: Some(value.package().to_owned()),
253 module: Some(value.module().to_owned()),
254 dependencies: Vec::new(),
255 }
256 }
257}
258
259#[derive(Clone, Copy, Debug, Eq, PartialEq)]
261pub enum BuiltCapabilityArtifact {
262 Dylib,
264 Manifest,
266}
267
268impl BuiltCapabilityArtifact {
269 fn as_str(self) -> &'static str {
270 match self {
271 Self::Dylib => "Lean capability dylib",
272 Self::Manifest => "Lean capability manifest",
273 }
274 }
275}
276
277#[derive(Clone, Debug, Eq, PartialEq)]
279pub enum LeanBuiltCapabilityError {
280 MissingDylibSource,
282 MissingManifestSource,
284 EnvVarNotSet {
286 env_var: String,
288 kind: BuiltCapabilityArtifact,
290 },
291}
292
293impl std::fmt::Display for LeanBuiltCapabilityError {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 match self {
296 Self::MissingDylibSource => {
297 f.write_str("LeanBuiltCapability needs either a dylib path or an environment variable")
298 }
299 Self::MissingManifestSource => {
300 f.write_str("LeanBuiltCapability needs either a manifest path or manifest environment variable")
301 }
302 Self::EnvVarNotSet { env_var, kind } => {
303 write!(f, "environment variable {env_var} is not set for {}", kind.as_str())
304 }
305 }
306 }
307}
308
309impl std::error::Error for LeanBuiltCapabilityError {}
310
311#[cfg(test)]
312#[allow(clippy::expect_used, clippy::panic)]
313mod tests {
314 use super::{BuiltCapabilityArtifact, LeanBuiltCapability, LeanBuiltCapabilityError};
315 use std::path::PathBuf;
316
317 #[test]
318 fn path_descriptor_resolves_without_runtime_env() {
319 let spec = LeanBuiltCapability::path("/tmp/libcap.so")
320 .env_var("LEAN_RS_CAPABILITY_CAP_DYLIB")
321 .package("pkg")
322 .module("Cap");
323
324 let path = match spec.dylib_path() {
325 Ok(path) => path,
326 Err(err) => panic!("expected path, got {err}"),
327 };
328 assert_eq!(path, PathBuf::from("/tmp/libcap.so"));
329 assert_eq!(spec.package_name(), Some("pkg"));
330 assert_eq!(spec.module_name(), Some("Cap"));
331 }
332
333 #[test]
334 fn missing_runtime_env_is_typed() {
335 let spec = LeanBuiltCapability::env("LEAN_TC_TEST_MISSING_CAPABILITY_DYLIB")
336 .package("pkg")
337 .module("Cap");
338 let err = match spec.dylib_path() {
339 Ok(path) => panic!("expected missing env error, got {}", path.display()),
340 Err(err) => err,
341 };
342 assert!(matches!(
343 err,
344 LeanBuiltCapabilityError::EnvVarNotSet {
345 kind: BuiltCapabilityArtifact::Dylib,
346 ..
347 }
348 ));
349 }
350
351 #[test]
352 fn missing_runtime_manifest_env_is_typed() {
353 let spec = LeanBuiltCapability::manifest_env("LEAN_TC_TEST_MISSING_CAPABILITY_MANIFEST");
354 let err = match spec.resolved_manifest_path() {
355 Ok(path) => panic!("expected missing env error, got {}", path.display()),
356 Err(err) => err,
357 };
358 assert!(matches!(
359 err,
360 LeanBuiltCapabilityError::EnvVarNotSet {
361 kind: BuiltCapabilityArtifact::Manifest,
362 ..
363 }
364 ));
365 }
366
367 #[test]
368 fn missing_dylib_source_is_typed() {
369 let spec = LeanBuiltCapability::manifest_path("/tmp/manifest.json");
370 let err = match spec.dylib_path() {
371 Ok(path) => panic!("expected missing dylib source error, got {}", path.display()),
372 Err(err) => err,
373 };
374 assert_eq!(err, LeanBuiltCapabilityError::MissingDylibSource);
375 }
376
377 #[test]
378 fn missing_manifest_source_is_typed() {
379 let spec = LeanBuiltCapability::path("/tmp/libcap.so").package("pkg").module("Cap");
380 let err = match spec.resolved_manifest_path() {
381 Ok(path) => panic!("expected missing manifest source error, got {}", path.display()),
382 Err(err) => err,
383 };
384 assert_eq!(err, LeanBuiltCapabilityError::MissingManifestSource);
385 }
386}