1#![allow(unused_assignments)]
41
42pub mod affected;
43pub mod base;
44pub mod ci;
45pub mod config;
46pub mod contributors;
47pub mod cue;
48pub mod environment;
49pub mod http;
50pub mod lockfile;
51pub mod manifest;
52pub mod module;
53pub mod owners;
54pub mod paths;
55pub mod rules;
56pub mod runtime;
57pub mod secrets;
58pub mod shell;
59pub mod sync;
60pub mod tasks;
61pub mod tools;
62
63pub use affected::{AffectedBy, matches_pattern};
65
66pub use module::{Instance, InstanceKind, ModuleEvaluation};
68
69pub const VERSION: &str = env!("CARGO_PKG_VERSION");
71
72#[cfg(test)]
73pub mod test_utils;
74
75use miette::{Diagnostic, SourceSpan};
76use serde::{Deserialize, Serialize};
77use std::path::{Path, PathBuf};
78use thiserror::Error;
79
80#[derive(Error, Debug, Diagnostic)]
82pub enum Error {
83 #[error("Configuration error: {message}")]
84 #[diagnostic(
85 code(cuenv::config::invalid),
86 help("Check your cuenv.cue configuration file for syntax errors or invalid values")
87 )]
88 Configuration {
89 #[source_code]
90 src: String,
91 #[label("invalid configuration")]
92 span: Option<SourceSpan>,
93 message: String,
94 },
95
96 #[error("FFI operation failed in {function}: {message}")]
97 #[diagnostic(code(cuenv::ffi::error))]
98 Ffi {
99 function: &'static str,
100 message: String,
101 #[help]
102 help: Option<String>,
103 },
104
105 #[error("CUE parsing failed: {message}")]
106 #[diagnostic(code(cuenv::cue::parse_error))]
107 CueParse {
108 path: Box<Path>,
109 #[source_code]
110 src: Option<String>,
111 #[label("parsing failed here")]
112 span: Option<SourceSpan>,
113 message: String,
114 suggestions: Option<Vec<String>>,
115 },
116
117 #[error("I/O {operation} failed{}", path.as_ref().map_or(String::new(), |p| format!(": {}", p.display())))]
118 #[diagnostic(
119 code(cuenv::io::error),
120 help("Check file permissions and ensure the path exists")
121 )]
122 Io {
123 #[source]
124 source: std::io::Error,
125 path: Option<Box<Path>>,
126 operation: String,
127 },
128
129 #[error("Text encoding error")]
130 #[diagnostic(
131 code(cuenv::encoding::utf8),
132 help("The file contains invalid UTF-8. Ensure your files use UTF-8 encoding.")
133 )]
134 Utf8 {
135 #[source]
136 source: std::str::Utf8Error,
137 file: Option<Box<Path>>,
138 },
139
140 #[error("Operation timed out after {seconds} seconds")]
141 #[diagnostic(
142 code(cuenv::timeout),
143 help("Try increasing the timeout or check if the operation is stuck")
144 )]
145 Timeout { seconds: u64 },
146
147 #[error("Validation failed: {message}")]
148 #[diagnostic(code(cuenv::validation::failed))]
149 Validation {
150 #[source_code]
151 src: Option<String>,
152 #[label("validation failed")]
153 span: Option<SourceSpan>,
154 message: String,
155 #[related]
156 related: Vec<Error>,
157 },
158
159 #[error("Task execution failed: {message}")]
160 #[diagnostic(code(cuenv::task::execution))]
161 Execution {
162 message: String,
163 #[help]
164 help: Option<String>,
165 },
166
167 #[error("Tool resolution failed: {message}")]
168 #[diagnostic(code(cuenv::tool::resolution))]
169 ToolResolution {
170 message: String,
171 #[help]
172 help: Option<String>,
173 },
174
175 #[error("Platform error: {message}")]
176 #[diagnostic(
177 code(cuenv::platform::error),
178 help("This platform may not be supported by the tool provider")
179 )]
180 Platform { message: String },
181
182 #[error("Task '{task_name}' failed with exit code {exit_code}")]
183 #[diagnostic(code(cuenv::task::failed))]
184 TaskFailed {
185 task_name: String,
186 exit_code: i32,
187 stdout: String,
188 stderr: String,
189 #[help]
190 help: Option<String>,
191 },
192
193 #[error("Task graph error: {message}")]
194 #[diagnostic(code(cuenv::task::graph))]
195 TaskGraph {
196 message: String,
197 #[help]
198 help: Option<String>,
199 },
200
201 #[error("Secret resolution failed: {message}")]
202 #[diagnostic(code(cuenv::secret::resolution))]
203 SecretResolution {
204 message: String,
205 #[help]
206 help: Option<String>,
207 },
208}
209
210impl Error {
211 #[must_use]
212 pub fn configuration(msg: impl Into<String>) -> Self {
213 Error::Configuration {
214 src: String::new(),
215 span: None,
216 message: msg.into(),
217 }
218 }
219
220 #[must_use]
221 pub fn configuration_with_source(
222 msg: impl Into<String>,
223 src: impl Into<String>,
224 span: Option<SourceSpan>,
225 ) -> Self {
226 Error::Configuration {
227 src: src.into(),
228 span,
229 message: msg.into(),
230 }
231 }
232
233 #[must_use]
234 pub fn ffi(function: &'static str, message: impl Into<String>) -> Self {
235 Error::Ffi {
236 function,
237 message: message.into(),
238 help: None,
239 }
240 }
241
242 #[must_use]
243 pub fn ffi_with_help(
244 function: &'static str,
245 message: impl Into<String>,
246 help: impl Into<String>,
247 ) -> Self {
248 Error::Ffi {
249 function,
250 message: message.into(),
251 help: Some(help.into()),
252 }
253 }
254
255 #[must_use]
256 pub fn cue_parse(path: &Path, message: impl Into<String>) -> Self {
257 Error::CueParse {
258 path: path.into(),
259 src: None,
260 span: None,
261 message: message.into(),
262 suggestions: None,
263 }
264 }
265
266 #[must_use]
267 pub fn cue_parse_with_source(
268 path: &Path,
269 message: impl Into<String>,
270 src: impl Into<String>,
271 span: Option<SourceSpan>,
272 suggestions: Option<Vec<String>>,
273 ) -> Self {
274 Error::CueParse {
275 path: path.into(),
276 src: Some(src.into()),
277 span,
278 message: message.into(),
279 suggestions,
280 }
281 }
282
283 #[must_use]
284 pub fn validation(msg: impl Into<String>) -> Self {
285 Error::Validation {
286 src: None,
287 span: None,
288 message: msg.into(),
289 related: Vec::new(),
290 }
291 }
292
293 #[must_use]
294 pub fn validation_with_source(
295 msg: impl Into<String>,
296 src: impl Into<String>,
297 span: Option<SourceSpan>,
298 ) -> Self {
299 Error::Validation {
300 src: Some(src.into()),
301 span,
302 message: msg.into(),
303 related: Vec::new(),
304 }
305 }
306
307 #[must_use]
308 pub fn execution(msg: impl Into<String>) -> Self {
309 Error::Execution {
310 message: msg.into(),
311 help: None,
312 }
313 }
314
315 #[must_use]
316 pub fn execution_with_help(msg: impl Into<String>, help: impl Into<String>) -> Self {
317 Error::Execution {
318 message: msg.into(),
319 help: Some(help.into()),
320 }
321 }
322
323 #[must_use]
324 pub fn tool_resolution(msg: impl Into<String>) -> Self {
325 Error::ToolResolution {
326 message: msg.into(),
327 help: None,
328 }
329 }
330
331 #[must_use]
332 pub fn tool_resolution_with_help(msg: impl Into<String>, help: impl Into<String>) -> Self {
333 Error::ToolResolution {
334 message: msg.into(),
335 help: Some(help.into()),
336 }
337 }
338
339 #[must_use]
340 pub fn platform(msg: impl Into<String>) -> Self {
341 Error::Platform {
342 message: msg.into(),
343 }
344 }
345
346 #[must_use]
347 pub fn task_failed(
348 task_name: impl Into<String>,
349 exit_code: i32,
350 stdout: impl Into<String>,
351 stderr: impl Into<String>,
352 ) -> Self {
353 Error::TaskFailed {
354 task_name: task_name.into(),
355 exit_code,
356 stdout: stdout.into(),
357 stderr: stderr.into(),
358 help: None,
359 }
360 }
361
362 #[must_use]
363 pub fn task_failed_with_help(
364 task_name: impl Into<String>,
365 exit_code: i32,
366 stdout: impl Into<String>,
367 stderr: impl Into<String>,
368 help: impl Into<String>,
369 ) -> Self {
370 Error::TaskFailed {
371 task_name: task_name.into(),
372 exit_code,
373 stdout: stdout.into(),
374 stderr: stderr.into(),
375 help: Some(help.into()),
376 }
377 }
378
379 #[must_use]
380 pub fn task_graph(message: impl Into<String>) -> Self {
381 Error::TaskGraph {
382 message: message.into(),
383 help: None,
384 }
385 }
386
387 #[must_use]
388 pub fn task_graph_with_help(message: impl Into<String>, help: impl Into<String>) -> Self {
389 Error::TaskGraph {
390 message: message.into(),
391 help: Some(help.into()),
392 }
393 }
394
395 #[must_use]
396 pub fn secret_resolution(message: impl Into<String>) -> Self {
397 Error::SecretResolution {
398 message: message.into(),
399 help: None,
400 }
401 }
402
403 #[must_use]
404 pub fn secret_resolution_with_help(
405 message: impl Into<String>,
406 help: impl Into<String>,
407 ) -> Self {
408 Error::SecretResolution {
409 message: message.into(),
410 help: Some(help.into()),
411 }
412 }
413}
414
415impl From<std::io::Error> for Error {
417 fn from(source: std::io::Error) -> Self {
418 Error::Io {
419 source,
420 path: None,
421 operation: "unknown (unmapped error conversion)".to_string(),
422 }
423 }
424}
425
426impl From<std::str::Utf8Error> for Error {
427 fn from(source: std::str::Utf8Error) -> Self {
428 Error::Utf8 { source, file: None }
429 }
430}
431
432impl From<cuenv_hooks::Error> for Error {
433 fn from(source: cuenv_hooks::Error) -> Self {
434 Error::Execution {
435 message: source.to_string(),
436 help: None,
437 }
438 }
439}
440
441impl From<cuenv_task_graph::Error> for Error {
442 fn from(err: cuenv_task_graph::Error) -> Self {
443 let help = match &err {
444 cuenv_task_graph::Error::CycleDetected { .. } => {
445 Some("Check for circular dependencies between tasks".into())
446 }
447 cuenv_task_graph::Error::MissingDependency { task, dependency } => Some(format!(
448 "Add task '{}' or remove it from {}'s dependsOn",
449 dependency, task
450 )),
451 cuenv_task_graph::Error::MissingDependencies { missing } => {
452 let suggestions: Vec<String> = missing
453 .iter()
454 .map(|(task, dep)| {
455 format!(" - Add '{}' or remove from {}'s dependsOn", dep, task)
456 })
457 .collect();
458 Some(format!(
459 "Fix missing dependencies:\n{}",
460 suggestions.join("\n")
461 ))
462 }
463 cuenv_task_graph::Error::TopologicalSortFailed { .. } => None,
464 cuenv_task_graph::Error::DuplicateNodeName {
465 name,
466 existing_kind,
467 new_kind,
468 } => Some(format!(
469 "Rename the {new_kind} '{name}' to avoid collision with the existing {existing_kind}"
470 )),
471 };
472 Error::TaskGraph {
473 message: err.to_string(),
474 help,
475 }
476 }
477}
478
479pub type Result<T> = std::result::Result<T, Error>;
481
482#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
484pub enum DryRun {
485 #[default]
487 No,
488 Yes,
490}
491
492impl DryRun {
493 #[must_use]
495 pub const fn is_dry_run(self) -> bool {
496 matches!(self, Self::Yes)
497 }
498}
499
500impl From<bool> for DryRun {
501 fn from(v: bool) -> Self {
502 if v { Self::Yes } else { Self::No }
503 }
504}
505
506#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
508pub enum OutputCapture {
509 #[default]
511 Capture,
512 Stream,
514}
515
516impl OutputCapture {
517 #[must_use]
519 pub const fn should_capture(self) -> bool {
520 matches!(self, Self::Capture)
521 }
522}
523
524impl From<bool> for OutputCapture {
525 fn from(v: bool) -> Self {
526 if v { Self::Capture } else { Self::Stream }
527 }
528}
529
530pub struct Limits {
532 pub max_path_length: usize,
533 pub max_package_name_length: usize,
534 pub max_output_size: usize,
535}
536
537impl Default for Limits {
538 fn default() -> Self {
539 Self {
540 max_path_length: 4096,
541 max_package_name_length: 256,
542 max_output_size: 100 * 1024 * 1024, }
544 }
545}
546
547#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
567pub struct PackageDir(PathBuf);
568
569impl PackageDir {
570 #[must_use]
572 pub fn as_path(&self) -> &Path {
573 &self.0
574 }
575
576 #[must_use]
578 pub fn into_path_buf(self) -> PathBuf {
579 self.0
580 }
581}
582
583impl AsRef<Path> for PackageDir {
584 fn as_ref(&self) -> &Path {
585 &self.0
586 }
587}
588
589#[derive(Error, Debug, Clone, Diagnostic)]
591pub enum PackageDirError {
592 #[error("path does not exist: {0}")]
594 #[diagnostic(
595 code(cuenv::package_dir::not_found),
596 help("Make sure the directory exists and you have permission to access it")
597 )]
598 NotFound(String),
599
600 #[error("path is not a directory: {0}")]
602 #[diagnostic(
603 code(cuenv::package_dir::not_directory),
604 help("The path must point to a directory, not a file")
605 )]
606 NotADirectory(String),
607
608 #[error("io error accessing path: {0}")]
610 #[diagnostic(
611 code(cuenv::package_dir::io_error),
612 help("Check file permissions and ensure you have access to the path")
613 )]
614 Io(String),
615}
616
617impl TryFrom<&Path> for PackageDir {
618 type Error = PackageDirError;
619
620 fn try_from(input: &Path) -> std::result::Result<Self, Self::Error> {
634 match std::fs::metadata(input) {
635 Ok(meta) => {
636 if meta.is_dir() {
637 Ok(PackageDir(input.to_path_buf()))
638 } else {
639 Err(PackageDirError::NotADirectory(input.display().to_string()))
640 }
641 }
642 Err(e) => {
643 if e.kind() == std::io::ErrorKind::NotFound {
644 Err(PackageDirError::NotFound(input.display().to_string()))
645 } else {
646 Err(PackageDirError::Io(e.to_string()))
647 }
648 }
649 }
650 }
651}
652
653#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
676pub struct PackageName(String);
677
678impl PackageName {
679 #[must_use]
681 pub fn as_str(&self) -> &str {
682 &self.0
683 }
684
685 #[must_use]
687 pub fn into_string(self) -> String {
688 self.0
689 }
690}
691
692impl AsRef<str> for PackageName {
693 fn as_ref(&self) -> &str {
694 &self.0
695 }
696}
697
698impl std::fmt::Display for PackageName {
699 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
700 write!(f, "{}", self.0)
701 }
702}
703
704#[derive(Error, Debug, Clone, Diagnostic)]
706pub enum PackageNameError {
707 #[error("invalid package name: {0}")]
709 #[diagnostic(
710 code(cuenv::package_name::invalid),
711 help(
712 "Package names must be 1-64 characters, start with alphanumeric, and contain only alphanumeric, hyphen, or underscore characters"
713 )
714 )]
715 Invalid(String),
716}
717
718impl TryFrom<&str> for PackageName {
719 type Error = PackageNameError;
720
721 fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
734 let bytes = s.as_bytes();
735
736 if bytes.is_empty() || bytes.len() > 64 {
738 return Err(PackageNameError::Invalid(s.to_string()));
739 }
740
741 let first = bytes[0];
743 let is_alnum =
744 |b: u8| b.is_ascii_uppercase() || b.is_ascii_lowercase() || b.is_ascii_digit();
745
746 if !is_alnum(first) {
747 return Err(PackageNameError::Invalid(s.to_string()));
748 }
749
750 let valid = |b: u8| is_alnum(b) || b == b'-' || b == b'_';
752 for &b in bytes {
753 if !valid(b) {
754 return Err(PackageNameError::Invalid(s.to_string()));
755 }
756 }
757
758 Ok(PackageName(s.to_string()))
759 }
760}
761
762impl TryFrom<String> for PackageName {
763 type Error = PackageNameError;
764
765 fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
767 Self::try_from(s.as_str())
768 }
769}
770
771#[cfg(test)]
772mod tests {
773 use super::*;
774 use miette::SourceSpan;
775 use std::path::Path;
776
777 #[test]
778 fn test_error_configuration() {
779 let err = Error::configuration("test message");
780 assert_eq!(err.to_string(), "Configuration error: test message");
781
782 if let Error::Configuration { message, .. } = err {
783 assert_eq!(message, "test message");
784 } else {
785 panic!("Expected Configuration error");
786 }
787 }
788
789 #[test]
790 fn test_error_configuration_with_source() {
791 let src = "test source code";
792 let span = SourceSpan::from(0..4);
793 let err = Error::configuration_with_source("config error", src, Some(span));
794
795 if let Error::Configuration {
796 src: source,
797 span: s,
798 message,
799 } = err
800 {
801 assert_eq!(source, "test source code");
802 assert_eq!(s, Some(SourceSpan::from(0..4)));
803 assert_eq!(message, "config error");
804 } else {
805 panic!("Expected Configuration error");
806 }
807 }
808
809 #[test]
810 fn test_error_ffi() {
811 let err = Error::ffi("test_function", "FFI failed");
812 assert_eq!(
813 err.to_string(),
814 "FFI operation failed in test_function: FFI failed"
815 );
816
817 if let Error::Ffi {
818 function,
819 message,
820 help,
821 } = err
822 {
823 assert_eq!(function, "test_function");
824 assert_eq!(message, "FFI failed");
825 assert!(help.is_none());
826 } else {
827 panic!("Expected Ffi error");
828 }
829 }
830
831 #[test]
832 fn test_error_ffi_with_help() {
833 let err = Error::ffi_with_help("test_func", "error msg", "try this instead");
834
835 if let Error::Ffi {
836 function,
837 message,
838 help,
839 } = err
840 {
841 assert_eq!(function, "test_func");
842 assert_eq!(message, "error msg");
843 assert_eq!(help, Some("try this instead".to_string()));
844 } else {
845 panic!("Expected Ffi error");
846 }
847 }
848
849 #[test]
850 fn test_error_cue_parse() {
851 let path = Path::new("/test/path.cue");
852 let err = Error::cue_parse(path, "parsing failed");
853 assert_eq!(err.to_string(), "CUE parsing failed: parsing failed");
854
855 if let Error::CueParse {
856 path: p, message, ..
857 } = err
858 {
859 assert_eq!(p.as_ref(), Path::new("/test/path.cue"));
860 assert_eq!(message, "parsing failed");
861 } else {
862 panic!("Expected CueParse error");
863 }
864 }
865
866 #[test]
867 fn test_error_cue_parse_with_source() {
868 let path = Path::new("/test/file.cue");
869 let src = "package test";
870 let span = SourceSpan::from(0..7);
871 let suggestions = vec!["Check syntax".to_string(), "Verify imports".to_string()];
872
873 let err = Error::cue_parse_with_source(
874 path,
875 "parse error",
876 src,
877 Some(span),
878 Some(suggestions.clone()),
879 );
880
881 if let Error::CueParse {
882 path: p,
883 src: source,
884 span: s,
885 message,
886 suggestions: sugg,
887 } = err
888 {
889 assert_eq!(p.as_ref(), Path::new("/test/file.cue"));
890 assert_eq!(source, Some("package test".to_string()));
891 assert_eq!(s, Some(SourceSpan::from(0..7)));
892 assert_eq!(message, "parse error");
893 assert_eq!(sugg, Some(suggestions));
894 } else {
895 panic!("Expected CueParse error");
896 }
897 }
898
899 #[test]
900 fn test_error_validation() {
901 let err = Error::validation("validation failed");
902 assert_eq!(err.to_string(), "Validation failed: validation failed");
903
904 if let Error::Validation {
905 message, related, ..
906 } = err
907 {
908 assert_eq!(message, "validation failed");
909 assert!(related.is_empty());
910 } else {
911 panic!("Expected Validation error");
912 }
913 }
914
915 #[test]
916 fn test_error_validation_with_source() {
917 let src = "test validation source";
918 let span = SourceSpan::from(5..15);
919 let err = Error::validation_with_source("validation error", src, Some(span));
920
921 if let Error::Validation {
922 src: source,
923 span: s,
924 message,
925 ..
926 } = err
927 {
928 assert_eq!(source, Some("test validation source".to_string()));
929 assert_eq!(s, Some(SourceSpan::from(5..15)));
930 assert_eq!(message, "validation error");
931 } else {
932 panic!("Expected Validation error");
933 }
934 }
935
936 #[test]
937 fn test_error_timeout() {
938 let err = Error::Timeout { seconds: 30 };
939 assert_eq!(err.to_string(), "Operation timed out after 30 seconds");
940 }
941
942 #[test]
943 fn test_error_from_io_error() {
944 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
945 let err: Error = io_err.into();
946
947 if let Error::Io { operation, .. } = err {
948 assert_eq!(operation, "unknown (unmapped error conversion)");
949 } else {
950 panic!("Expected Io error");
951 }
952 }
953
954 #[test]
955 fn test_error_from_utf8_error() {
956 let bytes = vec![0xFF, 0xFE];
957 let utf8_err = std::str::from_utf8(&bytes).unwrap_err();
958 let err: Error = utf8_err.into();
959
960 assert!(matches!(err, Error::Utf8 { .. }));
961 }
962
963 #[test]
964 fn test_limits_default() {
965 let limits = Limits::default();
966 assert_eq!(limits.max_path_length, 4096);
967 assert_eq!(limits.max_package_name_length, 256);
968 assert_eq!(limits.max_output_size, 100 * 1024 * 1024);
969 }
970
971 #[test]
972 fn test_result_type_alias() {
973 let ok_result: Result<i32> = Ok(42);
974 assert!(ok_result.is_ok());
975 if let Ok(value) = ok_result {
976 assert_eq!(value, 42);
977 }
978
979 let err_result: Result<i32> = Err(Error::configuration("test"));
980 assert!(err_result.is_err());
981 }
982
983 #[test]
984 fn test_error_display() {
985 let errors = vec![
986 (Error::configuration("test"), "Configuration error: test"),
987 (
988 Error::ffi("func", "msg"),
989 "FFI operation failed in func: msg",
990 ),
991 (
992 Error::cue_parse(Path::new("/test"), "msg"),
993 "CUE parsing failed: msg",
994 ),
995 (Error::validation("msg"), "Validation failed: msg"),
996 (
997 Error::Timeout { seconds: 10 },
998 "Operation timed out after 10 seconds",
999 ),
1000 ];
1001
1002 for (error, expected) in errors {
1003 assert_eq!(error.to_string(), expected);
1004 }
1005 }
1006
1007 #[test]
1008 fn test_error_diagnostic_codes() {
1009 use miette::Diagnostic;
1010
1011 let config_err = Error::configuration("test");
1012 assert_eq!(
1013 config_err.code().unwrap().to_string(),
1014 "cuenv::config::invalid"
1015 );
1016
1017 let ffi_err = Error::ffi("func", "msg");
1018 assert_eq!(ffi_err.code().unwrap().to_string(), "cuenv::ffi::error");
1019
1020 let cue_err = Error::cue_parse(Path::new("/test"), "msg");
1021 assert_eq!(
1022 cue_err.code().unwrap().to_string(),
1023 "cuenv::cue::parse_error"
1024 );
1025
1026 let validation_err = Error::validation("msg");
1027 assert_eq!(
1028 validation_err.code().unwrap().to_string(),
1029 "cuenv::validation::failed"
1030 );
1031
1032 let timeout_err = Error::Timeout { seconds: 5 };
1033 assert_eq!(timeout_err.code().unwrap().to_string(), "cuenv::timeout");
1034 }
1035
1036 #[test]
1037 fn test_package_dir_validation() {
1038 let result = PackageDir::try_from(Path::new("."));
1040 assert!(result.is_ok(), "Current directory should be valid");
1041
1042 let pkg_dir = result.unwrap();
1044 assert_eq!(pkg_dir.as_path(), Path::new("."));
1045 assert_eq!(pkg_dir.as_ref(), Path::new("."));
1046 assert_eq!(pkg_dir.into_path_buf(), PathBuf::from("."));
1047
1048 let result = PackageDir::try_from(Path::new("/path/does/not/exist"));
1050 assert!(result.is_err());
1051 match result.unwrap_err() {
1052 PackageDirError::NotFound(_) => {} other => panic!("Expected NotFound error, got: {:?}", other),
1054 }
1055
1056 let temp_path = std::env::temp_dir().join("cuenv_test_file");
1059 let file = std::fs::File::create(&temp_path).unwrap();
1060 drop(file);
1061
1062 let result = PackageDir::try_from(temp_path.as_path());
1063 assert!(result.is_err());
1064 match result.unwrap_err() {
1065 PackageDirError::NotADirectory(_) => {} other => panic!("Expected NotADirectory error, got: {:?}", other),
1067 }
1068
1069 std::fs::remove_file(temp_path).ok();
1071 }
1072
1073 #[test]
1074 fn test_package_name_validation() {
1075 let max_len_string = "a".repeat(64);
1077 let valid_names = vec![
1078 "my-package",
1079 "package_123",
1080 "a", "A", "0package", "package-with-hyphens",
1084 "package_with_underscores",
1085 max_len_string.as_str(), ];
1087
1088 for name in valid_names {
1089 let result = PackageName::try_from(name);
1090 assert!(result.is_ok(), "'{}' should be valid", name);
1091
1092 let result = PackageName::try_from(name.to_string());
1094 assert!(result.is_ok(), "'{}' as String should be valid", name);
1095
1096 let pkg_name = result.unwrap();
1098 assert_eq!(pkg_name.as_str(), name);
1099 assert_eq!(pkg_name.as_ref(), name);
1100 assert_eq!(pkg_name.to_string(), name);
1101 assert_eq!(pkg_name.into_string(), name.to_string());
1102 }
1103
1104 let too_long_string = "a".repeat(65);
1106 let invalid_names = vec![
1107 "", "-invalid", "_invalid", "invalid.name", "invalid/name", "invalid:name", too_long_string.as_str(), "invalid@name", "invalid#name", "invalid name", ];
1118
1119 for name in invalid_names {
1120 let result = PackageName::try_from(name);
1121 assert!(result.is_err(), "'{}' should be invalid", name);
1122
1123 assert!(matches!(result.unwrap_err(), PackageNameError::Invalid(_)));
1125 }
1126 }
1127}