agm_core/model/
imports.rs1use serde::{Deserialize, Serialize};
4use std::str::FromStr;
5
6#[derive(Debug, Clone, PartialEq, thiserror::Error)]
7#[error("invalid import entry: {input:?}")]
8pub struct ParseImportError {
9 pub input: String,
10}
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct ImportEntry {
14 pub package: String,
15 pub version_constraint: Option<String>,
16}
17
18impl ImportEntry {
19 #[must_use]
20 pub fn new(package: String, version_constraint: Option<String>) -> Self {
21 Self {
22 package,
23 version_constraint,
24 }
25 }
26}
27
28impl FromStr for ImportEntry {
29 type Err = ParseImportError;
30
31 fn from_str(s: &str) -> Result<Self, Self::Err> {
32 let s = s.trim();
33 if s.is_empty() {
34 return Err(ParseImportError {
35 input: s.to_owned(),
36 });
37 }
38 if let Some((package, constraint)) = s.split_once('@') {
39 let package = package.trim();
40 let constraint = constraint.trim();
41 if package.is_empty() {
42 return Err(ParseImportError {
43 input: s.to_owned(),
44 });
45 }
46 Ok(Self {
47 package: package.to_owned(),
48 version_constraint: if constraint.is_empty() {
49 None
50 } else {
51 Some(constraint.to_owned())
52 },
53 })
54 } else {
55 Ok(Self {
56 package: s.to_owned(),
57 version_constraint: None,
58 })
59 }
60 }
61}
62
63impl std::fmt::Display for ImportEntry {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "{}", self.package)?;
66 if let Some(ref c) = self.version_constraint {
67 write!(f, "@{c}")?;
68 }
69 Ok(())
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_import_entry_from_str_package_only() {
79 let entry: ImportEntry = "shared.security".parse().unwrap();
80 assert_eq!(entry.package, "shared.security");
81 assert_eq!(entry.version_constraint, None);
82 }
83
84 #[test]
85 fn test_import_entry_from_str_with_caret_constraint() {
86 let entry: ImportEntry = "shared.security@^1.0.0".parse().unwrap();
87 assert_eq!(entry.package, "shared.security");
88 assert_eq!(entry.version_constraint, Some("^1.0.0".to_owned()));
89 }
90
91 #[test]
92 fn test_import_entry_from_str_with_exact_version() {
93 let entry: ImportEntry = "shared.http@2.0.0".parse().unwrap();
94 assert_eq!(entry.package, "shared.http");
95 assert_eq!(entry.version_constraint, Some("2.0.0".to_owned()));
96 }
97
98 #[test]
99 fn test_import_entry_from_str_with_tilde() {
100 let entry: ImportEntry = "core.utils@~1.2.0".parse().unwrap();
101 assert_eq!(entry.version_constraint, Some("~1.2.0".to_owned()));
102 }
103
104 #[test]
105 fn test_import_entry_from_str_with_wildcard() {
106 let entry: ImportEntry = "core.utils@1.*".parse().unwrap();
107 assert_eq!(entry.version_constraint, Some("1.*".to_owned()));
108 }
109
110 #[test]
111 fn test_import_entry_from_str_empty_returns_error() {
112 assert!("".parse::<ImportEntry>().is_err());
113 }
114
115 #[test]
116 fn test_import_entry_from_str_no_package_returns_error() {
117 assert!("@^1.0.0".parse::<ImportEntry>().is_err());
118 }
119
120 #[test]
121 fn test_import_entry_display_without_constraint() {
122 let entry = ImportEntry::new("shared.http".to_owned(), None);
123 assert_eq!(entry.to_string(), "shared.http");
124 }
125
126 #[test]
127 fn test_import_entry_display_with_constraint() {
128 let entry = ImportEntry::new("shared.http".to_owned(), Some("^1.0.0".to_owned()));
129 assert_eq!(entry.to_string(), "shared.http@^1.0.0");
130 }
131
132 #[test]
133 fn test_import_entry_roundtrip() {
134 let input = "shared.security@^1.0.0";
135 let entry: ImportEntry = input.parse().unwrap();
136 assert_eq!(entry.to_string(), input);
137 }
138
139 #[test]
140 fn test_import_entry_serde_roundtrip() {
141 let entry = ImportEntry::new("shared.security".to_owned(), Some("^1.0.0".to_owned()));
142 let json = serde_json::to_string(&entry).unwrap();
143 let back: ImportEntry = serde_json::from_str(&json).unwrap();
144 assert_eq!(entry, back);
145 }
146}