Skip to main content

deps_dart/
types.rs

1//! Domain types for Dart/Pub dependencies.
2
3use std::any::Any;
4use tower_lsp_server::ls_types::Range;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct DartDependency {
8    pub name: deps_core::PackageName,
9    pub name_range: Range,
10    pub version_req: Option<deps_core::VersionReq>,
11    pub version_range: Option<Range>,
12    pub section: DependencySection,
13    pub source: DependencySource,
14    /// Dart-specific Git sub-path (e.g., `path: packages/pkg` inside a repo).
15    /// Only meaningful when `source` is `DependencySource::Git`.
16    pub git_path: Option<String>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Default)]
20pub enum DependencySection {
21    #[default]
22    Dependencies,
23    DevDependencies,
24    DependencyOverrides,
25}
26
27pub use deps_core::parser::DependencySource;
28
29#[derive(Debug, Clone)]
30pub struct DartVersion {
31    pub version: deps_core::ConcreteVersion,
32    pub retracted: bool,
33    /// Publish timestamp, parsed eagerly from the API's `published` field.
34    ///
35    /// `None` when the response omits it or the value fails to parse as
36    /// RFC 3339 — degrades gracefully, per
37    /// [US-003](https://github.com/bug-ops/deps-lsp/issues/145).
38    pub published_at: Option<deps_core::PublishTime>,
39}
40
41#[derive(Debug, Clone)]
42pub struct PackageInfo {
43    pub name: deps_core::PackageName,
44    pub description: Option<String>,
45    pub homepage: Option<String>,
46    pub repository: Option<String>,
47    pub documentation: Option<String>,
48    pub version: deps_core::ConcreteVersion,
49    pub license: Option<String>,
50}
51
52// deps-core trait implementations
53
54impl deps_core::Dependency for DartDependency {
55    fn name(&self) -> &deps_core::PackageName {
56        &self.name
57    }
58
59    fn name_range(&self) -> Range {
60        self.name_range
61    }
62
63    fn version_requirement(&self) -> Option<&deps_core::VersionReq> {
64        self.version_req.as_ref()
65    }
66
67    fn version_range(&self) -> Option<Range> {
68        self.version_range
69    }
70
71    fn source(&self) -> deps_core::parser::DependencySource {
72        self.source.clone()
73    }
74
75    fn features(&self) -> &[String] {
76        &[]
77    }
78
79    fn as_any(&self) -> &dyn Any {
80        self
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use std::assert_matches;
88    use tower_lsp_server::ls_types::Position;
89
90    fn test_dep(source: DependencySource) -> DartDependency {
91        DartDependency {
92            name: "flutter_bloc".into(),
93            name_range: Range::new(Position::new(5, 2), Position::new(5, 14)),
94            version_req: Some("^8.1.0".into()),
95            version_range: Some(Range::new(Position::new(5, 16), Position::new(5, 22))),
96            section: DependencySection::Dependencies,
97            source,
98            git_path: None,
99        }
100    }
101
102    #[test]
103    fn test_dependency_source_variants() {
104        assert_matches!(DependencySource::Registry, DependencySource::Registry);
105        assert_matches!(
106            DependencySource::Git {
107                url: "u".into(),
108                rev: None
109            },
110            DependencySource::Git { .. }
111        );
112        assert_matches!(
113            DependencySource::Path { path: "p".into() },
114            DependencySource::Path { .. }
115        );
116        assert_matches!(
117            DependencySource::Sdk {
118                sdk: "flutter".into()
119            },
120            DependencySource::Sdk { .. }
121        );
122    }
123
124    #[test]
125    fn test_dependency_section_default() {
126        assert_matches!(
127            DependencySection::default(),
128            DependencySection::Dependencies
129        );
130    }
131
132    #[test]
133    fn test_dependency_trait() {
134        use deps_core::Dependency;
135
136        let dep = test_dep(DependencySource::Registry);
137        assert_eq!(dep.name(), "flutter_bloc");
138        assert_eq!(
139            dep.version_requirement().map(deps_core::VersionReq::as_str),
140            Some("^8.1.0")
141        );
142        assert!(dep.as_any().is::<DartDependency>());
143    }
144
145    #[test]
146    fn test_dependency_info_source_registry() {
147        use deps_core::Dependency;
148        let dep = test_dep(DependencySource::Registry);
149        assert!(dep.source().is_registry());
150    }
151
152    #[test]
153    fn test_dependency_info_source_sdk() {
154        use deps_core::Dependency;
155        let dep = test_dep(DependencySource::Sdk {
156            sdk: "flutter".into(),
157        });
158        assert!(!dep.source().is_registry());
159        assert_matches!(dep.source(), DependencySource::Sdk { sdk } if sdk == "flutter");
160    }
161
162    #[test]
163    fn test_dependency_info_source_git() {
164        use deps_core::Dependency;
165        let dep = test_dep(DependencySource::Git {
166            url: "https://github.com/test/repo".into(),
167            rev: Some("main".into()),
168        });
169        match dep.source() {
170            deps_core::parser::DependencySource::Git { url, rev } => {
171                assert_eq!(url, "https://github.com/test/repo");
172                assert_eq!(rev, Some("main".to_string()));
173            }
174            _ => panic!("Expected Git source"),
175        }
176    }
177
178    #[test]
179    fn test_dependency_info_source_path() {
180        use deps_core::Dependency;
181        let dep = test_dep(DependencySource::Path {
182            path: "../local".into(),
183        });
184        match dep.source() {
185            deps_core::parser::DependencySource::Path { path } => {
186                assert_eq!(path, "../local");
187            }
188            _ => panic!("Expected Path source"),
189        }
190    }
191
192    #[test]
193    fn test_version_trait() {
194        use deps_core::Version;
195        let ver = DartVersion {
196            version: "1.0.0".into(),
197            retracted: false,
198            published_at: Some(deps_core::PublishTime::from_unix_secs(1_704_067_200)),
199        };
200        assert_eq!(ver.version_string(), "1.0.0");
201        assert!(!ver.removal_status().blocks_resolution());
202        assert!(ver.features().is_empty());
203        assert!(ver.as_any().is::<DartVersion>());
204    }
205
206    #[test]
207    fn test_version_retracted() {
208        use deps_core::Version;
209        let ver = DartVersion {
210            version: "0.9.0".into(),
211            retracted: true,
212            published_at: None,
213        };
214        assert!(ver.removal_status().blocks_resolution());
215    }
216
217    #[test]
218    fn test_version_is_prerelease() {
219        use deps_core::Version;
220
221        let stable = DartVersion {
222            version: "1.0.0".into(),
223            retracted: false,
224            published_at: None,
225        };
226        let prerelease = DartVersion {
227            version: "2.10.0-nullsafety.1".into(),
228            retracted: false,
229            published_at: None,
230        };
231
232        assert!(!stable.is_prerelease());
233        assert!(stable.is_stable());
234        assert!(prerelease.is_prerelease());
235        assert!(!prerelease.is_stable());
236    }
237
238    #[test]
239    fn test_metadata_trait() {
240        use deps_core::Metadata;
241        let info = PackageInfo {
242            name: "provider".into(),
243            description: Some("A wrapper around InheritedWidget".into()),
244            homepage: Some("https://pub.dev/packages/provider".into()),
245            repository: Some("https://github.com/rrousselGit/provider".into()),
246            documentation: Some("https://pub.dev/documentation/provider".into()),
247            version: "6.1.2".into(),
248            license: Some("MIT".into()),
249        };
250        assert_eq!(info.name(), "provider");
251        assert!(info.description().is_some());
252        assert_eq!(info.latest_version(), "6.1.2");
253        assert!(info.as_any().is::<PackageInfo>());
254    }
255
256    #[test]
257    fn test_metadata_minimal() {
258        use deps_core::Metadata;
259        let info = PackageInfo {
260            name: "minimal".into(),
261            description: None,
262            homepage: None,
263            repository: None,
264            documentation: None,
265            version: "0.1.0".into(),
266            license: None,
267        };
268        assert!(info.description().is_none());
269        assert!(info.repository().is_none());
270        assert!(info.documentation().is_none());
271    }
272
273    #[test]
274    fn test_dependency_without_version() {
275        use deps_core::Dependency;
276        let dep = DartDependency {
277            name: "test".into(),
278            name_range: Range::default(),
279            version_req: None,
280            version_range: None,
281            section: DependencySection::Dependencies,
282            source: DependencySource::Registry,
283            git_path: None,
284        };
285        assert!(dep.version_requirement().is_none());
286        assert!(dep.version_range().is_none());
287    }
288}