Skip to main content

deps_nuget/
types.rs

1//! Domain types for NuGet/.NET project dependencies.
2
3use std::any::Any;
4use tower_lsp_server::ls_types::{Range, Uri};
5
6/// A single `PackageReference` / `PackageVersion` / `package` entry from a manifest.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct NuGetDependency {
9    pub name: deps_core::PackageName,
10    pub name_range: Range,
11    /// Absent when the manifest omits an explicit version — central package management
12    /// entries and unresolvable MSBuild property expressions (`$(...)`) both degrade to
13    /// `None` rather than a bogus or unresolved-looking requirement.
14    pub version_requirement: Option<deps_core::VersionReq>,
15    pub version_range: Option<Range>,
16    /// Resolved against the manifest's `NuGet.Config` `<packageSources>`/
17    /// `<packageSourceMapping>` (issue #523) by `NuGetEcosystem::parse_manifest`, after
18    /// parsing — every parser in `parser.rs` stays config-blind and always constructs this as
19    /// `DependencySource::Registry`.
20    pub source: deps_core::parser::DependencySource,
21}
22
23// Hand-written, not `impl_dependency!`: the macro's `source: $source:expr` arm substitutes
24// the expression into a generated `fn source(&self)` body, so `self.source.clone()` cannot be
25// passed through it — see `deps-npm/src/types.rs`'s identical precedent/comment.
26impl deps_core::Dependency for NuGetDependency {
27    fn name(&self) -> &deps_core::PackageName {
28        &self.name
29    }
30
31    fn name_range(&self) -> Range {
32        self.name_range
33    }
34
35    fn version_requirement(&self) -> Option<&deps_core::VersionReq> {
36        self.version_requirement.as_ref()
37    }
38
39    fn version_range(&self) -> Option<Range> {
40        self.version_range
41    }
42
43    fn source(&self) -> deps_core::parser::DependencySource {
44        self.source.clone()
45    }
46
47    fn as_any(&self) -> &dyn Any {
48        self
49    }
50}
51
52/// Parsed result of a single manifest file (`.csproj`, `Directory.Packages.props`,
53/// `packages.config`).
54#[derive(Debug)]
55pub struct NuGetParseResult {
56    pub dependencies: Vec<NuGetDependency>,
57    pub uri: Uri,
58    /// Every routing chain this manifest's resolved `NuGet.Config` implies (issue #523) — one
59    /// per distinct `<packageSourceMapping>` hop-set, or the single plain accumulated chain
60    /// when no mapping is declared. Registered against the shared `NuGetRegistry` by
61    /// `NuGetEcosystem::parse_manifest`; empty when nothing is registrable (no config, or
62    /// every dependency resolves to plain `Registry`/a fail-closed `CustomRegistry`).
63    pub resolved_chains: Vec<crate::config::NuGetSourceChain>,
64}
65
66deps_core::impl_parse_result!(
67    NuGetParseResult,
68    NuGetDependency {
69        dependencies: dependencies,
70        uri: uri,
71    }
72);
73
74/// A single version of a package, as returned by the NuGet flat-container endpoint.
75///
76/// Deliberately a single field. `removal_status` always reports `Available` (the
77/// trait default, no override here) because the flat container carries no `listed`
78/// flag — unlisted detection requires the registration hive and is deferred (spec
79/// §1, follow-up D1) to hover-only enrichment.
80///
81/// Hand-writes `deps_core::Version` instead of using `impl_version!` because the macro's
82/// `is_prerelease` cannot be overridden and the shared default (a keyword sniff for
83/// `-alpha|-beta|-rc|-dev|-pre|-snapshot|-canary|-nightly`) does not recognize standard
84/// .NET prerelease labels (`-rtm`, `-servicing.23`, `-CI-*`, `-final`, ...). This follows
85/// the same precedent as `deps-maven`'s and `deps-go`'s hand-written `Version` impls for
86/// ecosystems with a structural (non-keyword) prerelease convention.
87#[derive(Debug, Clone)]
88pub struct NuGetVersion {
89    pub version: deps_core::ConcreteVersion,
90    /// Publish timestamp, populated only when `Registry::get_versions_with` is called with
91    /// freshness enabled and the version was covered by the registration-hive walk.
92    pub published_at: Option<deps_core::PublishTime>,
93}
94
95impl deps_core::Version for NuGetVersion {
96    fn version_string(&self) -> &deps_core::ConcreteVersion {
97        &self.version
98    }
99
100    fn is_prerelease(&self) -> bool {
101        crate::version::is_prerelease(self.version.as_str())
102    }
103
104    fn published_at(&self) -> Option<deps_core::PublishTime> {
105        self.published_at
106    }
107
108    fn as_any(&self) -> &dyn Any {
109        self
110    }
111}
112
113/// Package metadata from a NuGet search result.
114#[derive(Debug, Clone)]
115pub struct PackageInfo {
116    pub name: deps_core::PackageName,
117    pub description: Option<String>,
118    pub repository: Option<String>,
119    pub documentation: Option<String>,
120    pub latest_version: deps_core::ConcreteVersion,
121}
122
123deps_core::impl_metadata!(PackageInfo {
124    name: name,
125    description: description,
126    repository: repository,
127    documentation: documentation,
128    latest_version: latest_version,
129});
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use std::assert_matches;
135    use tower_lsp_server::ls_types::Position;
136
137    fn test_dep() -> NuGetDependency {
138        NuGetDependency {
139            name: "Newtonsoft.Json".into(),
140            name_range: Range::new(Position::new(2, 4), Position::new(2, 20)),
141            version_requirement: Some("13.0.3".into()),
142            version_range: Some(Range::new(Position::new(2, 30), Position::new(2, 36))),
143            source: deps_core::parser::DependencySource::Registry,
144        }
145    }
146
147    #[test]
148    fn test_dependency_trait() {
149        use deps_core::Dependency;
150
151        let dep = test_dep();
152        assert_eq!(dep.name(), "Newtonsoft.Json");
153        assert_eq!(
154            dep.version_requirement().map(deps_core::VersionReq::as_str),
155            Some("13.0.3")
156        );
157        assert!(dep.features().is_empty());
158        assert!(dep.as_any().is::<NuGetDependency>());
159        assert_matches!(dep.source(), deps_core::parser::DependencySource::Registry);
160    }
161
162    #[test]
163    fn test_dependency_without_version() {
164        use deps_core::Dependency;
165
166        let dep = NuGetDependency {
167            name: "MyCompany.Shared".into(),
168            name_range: Range::default(),
169            version_requirement: None,
170            version_range: None,
171            source: deps_core::parser::DependencySource::Registry,
172        };
173        assert!(dep.version_requirement().is_none());
174        assert!(dep.version_range().is_none());
175    }
176
177    #[test]
178    fn test_parse_result_trait() {
179        use deps_core::ParseResult;
180
181        let result = NuGetParseResult {
182            dependencies: vec![test_dep()],
183            uri: deps_core::test_util::test_uri("/test/App.csproj"),
184            resolved_chains: Vec::new(),
185        };
186
187        assert_eq!(result.dependencies().len(), 1);
188        assert!(result.workspace_root().is_none());
189        assert!(result.as_any().is::<NuGetParseResult>());
190    }
191
192    #[test]
193    fn test_version_trait() {
194        use deps_core::Version;
195
196        let ver = NuGetVersion {
197            version: "13.0.3".into(),
198            published_at: None,
199        };
200        assert_eq!(ver.version_string(), "13.0.3");
201        assert!(!ver.removal_status().blocks_resolution());
202        assert!(!ver.is_prerelease());
203        assert!(ver.as_any().is::<NuGetVersion>());
204    }
205
206    #[test]
207    fn test_version_prerelease_dotnet_label() {
208        use deps_core::Version;
209
210        // Real .NET label that the shared keyword-sniff default would misclassify as stable.
211        let ver = NuGetVersion {
212            version: "13.0.0-rtm".into(),
213            published_at: None,
214        };
215        assert!(ver.is_prerelease());
216        assert!(!ver.is_stable());
217    }
218
219    #[test]
220    fn test_version_never_yanked() {
221        use deps_core::Version;
222
223        let ver = NuGetVersion {
224            version: "1.0.0".into(),
225            published_at: None,
226        };
227        assert!(!ver.removal_status().blocks_resolution());
228    }
229
230    #[test]
231    fn test_metadata_trait() {
232        use deps_core::Metadata;
233
234        let info = PackageInfo {
235            name: "Newtonsoft.Json".into(),
236            description: Some("Popular high-performance JSON framework".into()),
237            repository: None,
238            documentation: None,
239            latest_version: "13.0.3".into(),
240        };
241        assert_eq!(info.name(), "Newtonsoft.Json");
242        assert_eq!(
243            info.description(),
244            Some("Popular high-performance JSON framework")
245        );
246        assert_eq!(info.latest_version(), "13.0.3");
247        assert!(info.repository().is_none());
248        assert!(info.as_any().is::<PackageInfo>());
249    }
250}