package_parser/pkgs/dotnet/
nuspec.rs1use packageurl::PackageUrl;
2use quick_xml::de::from_str;
3use serde::{Deserialize, Serialize};
4
5use crate::error::SourcePkgError;
6use crate::pkgs::common::model::{DependentPackage, Package, PackageManifest};
7
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
13#[serde(rename = "package")]
14struct NuSpec {
15 pub metadata: NuSpecMetadata,
16 }
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
21struct NuSpecMetadata {
22 #[serde(rename = "id", default)]
24 pub id: String,
25 #[serde(rename = "version")]
26 pub version: String,
27 #[serde(rename = "description")]
28 pub description: String,
29 #[serde(rename = "authors")]
31 pub authors: String,
32
33 #[serde(rename = "minClientVersion")]
35 pub min_client_version: Option<String>,
36
37 #[serde(rename = "owners")]
40 pub owners: Option<String>,
41 #[serde(rename = "projectUrl")]
42 pub project_url: Option<String>,
43 #[serde(rename = "licenseUrl")]
44 pub license_url: Option<String>,
45 #[serde(rename = "iconUrl")]
46 pub icon_url: Option<String>,
47 #[serde(rename = "icon")]
48 pub icon: Option<String>,
49 #[serde(rename = "readme")]
50 pub readme: Option<String>,
51 #[serde(rename = "requireLicenseAcceptance")]
52 pub require_license_acceptance: Option<bool>,
53 #[serde(rename = "license")]
54 pub license: Option<String>,
55 #[serde(rename = "copyright")]
56 pub copyright: Option<String>,
57 #[serde(rename = "developmentDependency")]
58 pub development_dependency: Option<bool>,
59 #[serde(rename = "releaseNotes")]
60 pub release_notes: Option<String>,
61 #[serde(rename = "tags")]
63 pub tags: Option<String>,
64 #[serde(rename = "language")]
65 pub language: Option<String>,
66 #[serde(rename = "repository")]
67 pub repository: Option<NuSpecRepository>,
68
69 #[serde(rename = "dependencies")]
71 pub dependencies: Option<NuSpecDependencies>,
72 #[serde(rename = "frameworkAssemblies")]
73 pub framework_assemblies: Option<Vec<NuSpecFrameworkAssembly>>,
74 #[serde(rename = "packageTypes")]
75 pub package_types: Option<Vec<NuSpecPackageType>>,
76 #[serde(rename = "references")]
77 pub references: Option<Vec<NuSpecReference>>,
78 }
81
82#[derive(Clone, Debug, Serialize, Deserialize)]
83struct NuSpecRepository {
84 #[serde(rename = "type")]
85 pub repo_type: Option<String>,
86 pub url: Option<String>,
87 pub branch: Option<String>,
88 pub commit: Option<String>,
89}
90
91#[derive(Clone, Debug, Serialize, Deserialize)]
92struct NuSpecFile {
93 pub src: String,
94 pub target: String,
95 pub exclude: String,
96}
97
98#[derive(Clone, Debug, Serialize, Deserialize)]
99struct NuSpecDependencies {
100 #[serde(rename = "group", default)]
101 groups: Vec<NuSpecDependencyGroup>,
102 #[serde(rename = "dependency", default)]
103 dependencies: Vec<NuSpecDependency>,
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108struct NuSpecDependencyGroup {
109 target_framework: Option<String>,
110 #[serde(rename = "dependency", default)]
111 dependencies: Vec<NuSpecDependency>,
112}
113
114#[derive(Clone, Debug, Serialize, Deserialize)]
115struct NuSpecDependency {
116 pub id: String,
117 pub version: String,
118 pub exclude: Option<String>,
119 pub include: Option<String>,
120}
121
122#[derive(Clone, Debug, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124struct NuSpecFrameworkAssembly {
125 pub assembly_name: Option<String>,
126 pub target_framework: Option<String>,
127}
128
129#[derive(Clone, Debug, Serialize, Deserialize)]
130pub enum NuSpecPackageType {
131 Dependency,
132 DotnetTool,
133 Template,
134 #[serde(other)]
135 Unknown,
136}
137
138#[derive(Clone, Debug, Serialize, Deserialize)]
139enum NuSpecReferenceOrGroup {
140 Group {
141 #[serde(rename = "targetFramework")]
142 target_framework: String,
143 #[serde(rename = "reference", default)]
144 references: Vec<NuSpecReference>,
145 },
146 Reference(NuSpecReference),
147}
148
149#[derive(Clone, Debug, Serialize, Deserialize)]
150struct NuSpecReference {
151 pub file: String,
152}
153
154#[derive(Clone, Debug, Serialize, Deserialize)]
155struct NuSpecContentFiles {
156 pub include: String,
157 pub exclude: Option<String>,
158 #[serde(rename = "buildAction")]
159 pub build_action: Option<String>,
160 #[serde(rename = "copyToOutput")]
161 pub copy_to_output: Option<bool>,
162 pub flatten: Option<bool>,
163}
164
165pub struct DotnetNuSpec {}
166
167impl DotnetNuSpec {
168 pub fn new() -> Self {
169 Self {}
170 }
171
172 fn parse_dotnet_nuspec(path: impl AsRef<Path>) -> Result<NuSpec, SourcePkgError> {
173 let mut file = File::open(path)?;
174 let mut content = String::new();
175 file.read_to_string(&mut content)?;
176 let root = from_str::<NuSpec>(&content)?;
177 Ok(root)
178 }
179}
180
181#[async_trait::async_trait]
182impl PackageManifest for DotnetNuSpec {
183 fn get_name(&self) -> String {
184 "nuget".into()
185 }
186
187 async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
188 let root = Self::parse_dotnet_nuspec(path)?;
189 let package = Package {
190 name: root.metadata.id,
191 version: root.metadata.version,
192 dependencies: root
193 .metadata
194 .dependencies
195 .into_iter()
196 .map(|dependencies| {
197 let mut deps = vec![];
198 for dependency_groups in dependencies.groups {
199 let scope = dependency_groups.target_framework.unwrap_or_default();
200
201 for dependency in dependency_groups.dependencies {
202 let dep = DependentPackage {
203 purl: PackageUrl::new("nuget", dependency.id)
204 .expect("purl arguments are invalid")
205 .to_string(),
206 requirement: dependency.version,
207 scope: scope.clone(),
208 ..Default::default()
209 };
210 deps.push(dep);
211 }
212 }
213 deps
214 })
215 .collect::<Vec<_>>()
216 .concat(),
217 ..Default::default()
218 };
219
220 Ok(package)
221 }
222
223 fn file_name_patterns(&self) -> &'static [&'static str] {
224 &["*.nuspec"]
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::helper::testing::*;
232 use std::path::Path;
233
234 #[tokio::test]
235 async fn all_nuspec() {
236 let dir_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/testdata/nuget"));
237 let parser = DotnetNuSpec::new();
238
239 for result in all_files_with_extensions(dir_path, &["nuspec"]) {
240 let path = result.expect("Failed to walk");
241 println!("{}", path.display());
242 let _ = parser.recognize(&path).await.expect("Failed to parse");
243 }
244 }
245}