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