Skip to main content

provenant/parsers/nuget/
packages_lock.rs

1use std::path::Path;
2
3use crate::models::{DatasourceId, Dependency, PackageData, PackageType};
4use crate::parser_warn as warn;
5use packageurl::PackageUrl;
6
7use super::super::PackageParser;
8use super::super::utils::{MAX_ITERATION_COUNT, read_file_to_string};
9use super::default_package_data;
10
11pub struct PackagesLockParser;
12
13impl PackageParser for PackagesLockParser {
14    const PACKAGE_TYPE: PackageType = PackageType::Nuget;
15
16    fn is_match(path: &Path) -> bool {
17        path.file_name()
18            .and_then(|name| name.to_str())
19            .is_some_and(|name| name.ends_with("packages.lock.json"))
20    }
21
22    fn extract_packages(path: &Path) -> Vec<PackageData> {
23        let content = match read_file_to_string(path, None) {
24            Ok(c) => c,
25            Err(e) => {
26                warn!("Failed to read packages.lock.json at {:?}: {}", path, e);
27                return vec![default_package_data(Some(DatasourceId::NugetPackagesLock))];
28            }
29        };
30
31        let parsed: serde_json::Value = match serde_json::from_str(&content) {
32            Ok(v) => v,
33            Err(e) => {
34                warn!("Failed to parse packages.lock.json at {:?}: {}", path, e);
35                return vec![default_package_data(Some(DatasourceId::NugetPackagesLock))];
36            }
37        };
38
39        let mut dependencies = Vec::new();
40        let mut iteration_count: usize = 0;
41
42        if let Some(deps_obj) = parsed.get("dependencies").and_then(|v| v.as_object()) {
43            for (target_framework, packages) in deps_obj.iter().take(MAX_ITERATION_COUNT) {
44                if let Some(packages_obj) = packages.as_object() {
45                    for (package_name, package_info) in
46                        packages_obj.iter().take(MAX_ITERATION_COUNT)
47                    {
48                        iteration_count += 1;
49                        if iteration_count > MAX_ITERATION_COUNT {
50                            warn!(
51                                "Iteration limit exceeded in packages.lock.json at {:?}; stopping at {} dependencies",
52                                path, MAX_ITERATION_COUNT
53                            );
54                            break;
55                        }
56                        if let Some(info_obj) = package_info.as_object() {
57                            let version = info_obj
58                                .get("resolved")
59                                .and_then(|v| v.as_str())
60                                .map(|s| s.to_string());
61
62                            let requested = info_obj
63                                .get("requested")
64                                .and_then(|v| v.as_str())
65                                .map(|s| s.to_string());
66
67                            let package_type = info_obj.get("type").and_then(|v| v.as_str());
68
69                            let is_direct = match package_type {
70                                Some("Direct") => Some(true),
71                                Some("Transitive") => Some(false),
72                                _ => None,
73                            };
74
75                            let purl = version.as_ref().and_then(|v| {
76                                PackageUrl::new("nuget", package_name).ok().map(|mut p| {
77                                    let _ = p.with_version(v);
78                                    p.to_string()
79                                })
80                            });
81
82                            let mut extra_data = serde_json::Map::new();
83                            extra_data.insert(
84                                "target_framework".to_string(),
85                                serde_json::Value::String(target_framework.clone()),
86                            );
87
88                            if let Some(content_hash) =
89                                info_obj.get("contentHash").and_then(|v| v.as_str())
90                            {
91                                extra_data.insert(
92                                    "content_hash".to_string(),
93                                    serde_json::Value::String(content_hash.to_string()),
94                                );
95                            }
96
97                            dependencies.push(Dependency {
98                                purl,
99                                extracted_requirement: requested.or(version),
100                                scope: Some(target_framework.clone()),
101                                is_runtime: Some(true),
102                                is_optional: Some(false),
103                                is_pinned: Some(true),
104                                is_direct,
105                                resolved_package: None,
106                                extra_data: if extra_data.is_empty() {
107                                    None
108                                } else {
109                                    Some(extra_data.into_iter().collect())
110                                },
111                            });
112                        }
113                    }
114                }
115            }
116        }
117
118        vec![PackageData {
119            datasource_id: Some(DatasourceId::NugetPackagesLock),
120            package_type: Some(Self::PACKAGE_TYPE),
121            dependencies,
122            ..default_package_data(Some(DatasourceId::NugetPackagesLock))
123        }]
124    }
125}
126
127crate::register_parser!(
128    ".NET packages.lock.json lockfile",
129    &["**/packages.lock.json"],
130    "nuget",
131    "C#",
132    Some(
133        "https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#locking-dependencies"
134    ),
135);