1use super::{Lockfile, ResolveVersion};
12use crate::{
13 metadata, Checksum, Dependency, Error, Metadata, Name, Package, Patch, Result, SourceId,
14 Version,
15};
16use serde::{de, ser, Deserialize, Serialize};
17use std::{collections::HashMap, collections::HashSet, fmt, fmt::Write, str::FromStr};
18
19impl<'de> Deserialize<'de> for Lockfile {
20 fn deserialize<D: de::Deserializer<'de>>(
21 deserializer: D,
22 ) -> std::result::Result<Self, D::Error> {
23 EncodableLockfile::deserialize(deserializer)?
24 .try_into()
25 .map_err(de::Error::custom)
26 }
27}
28
29impl Serialize for Lockfile {
30 fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
31 EncodableLockfile::from(self).serialize(serializer)
32 }
33}
34
35#[derive(Debug, Deserialize, Serialize)]
37pub(super) struct EncodableLockfile {
38 pub(super) version: Option<u32>,
40
41 #[serde(default)]
43 pub(super) package: Vec<EncodablePackage>,
44
45 pub(super) root: Option<EncodablePackage>,
47
48 #[serde(default, skip_serializing_if = "Metadata::is_empty")]
50 pub(super) metadata: Metadata,
51
52 #[serde(default, skip_serializing_if = "Patch::is_empty")]
54 pub(super) patch: Patch,
55}
56
57impl EncodableLockfile {
58 pub fn find_checksum(&self, package: &Package) -> Option<Checksum> {
60 for (key, value) in &self.metadata {
61 if let Ok(dep) = key.checksum_dependency() {
62 if dep.name == package.name && dep.version == package.version {
63 return value.checksum().ok();
64 }
65 }
66 }
67
68 None
69 }
70}
71
72impl TryFrom<EncodableLockfile> for Lockfile {
73 type Error = Error;
74
75 fn try_from(raw_lockfile: EncodableLockfile) -> Result<Lockfile> {
76 let version = match raw_lockfile.version {
77 Some(n) => n.try_into()?,
78 None => ResolveVersion::detect(&raw_lockfile.package, &raw_lockfile.metadata)?,
79 };
80
81 let mut packages = Vec::with_capacity(raw_lockfile.package.len());
82
83 for raw_package in &raw_lockfile.package {
84 packages.push(if version == ResolveVersion::V1 {
85 let mut pkg = Package::try_from(raw_package)?;
88 pkg.checksum = raw_lockfile.find_checksum(&pkg);
89 pkg
90 } else {
91 raw_package.resolve(&raw_lockfile.package)?
94 })
95 }
96
97 Ok(Lockfile {
98 version,
99 packages,
100 root: raw_lockfile
101 .root
102 .as_ref()
103 .map(|root| root.try_into())
104 .transpose()?,
105 metadata: raw_lockfile.metadata,
106 patch: raw_lockfile.patch,
107 })
108 }
109}
110
111impl From<&Lockfile> for EncodableLockfile {
112 fn from(lockfile: &Lockfile) -> EncodableLockfile {
113 let mut packages = Vec::with_capacity(lockfile.packages.len());
114 let mut metadata = lockfile.metadata.clone();
115
116 let mut package_to_registries: HashMap<_, HashSet<&SourceId>> = HashMap::new();
117 for package in &lockfile.packages {
118 if let Some(source) = package.source.as_ref() {
119 if source.is_registry() {
120 package_to_registries
121 .entry(&package.name)
122 .or_default()
123 .insert(source);
124 }
125 }
126 }
127
128 for package in &lockfile.packages {
129 let mut raw_pkg = EncodablePackage::from_package(package, lockfile.version);
130 let checksum_key = metadata::MetadataKey::for_checksum(&Dependency::from(package));
131
132 if lockfile.version == ResolveVersion::V1 {
133 if let Some(checksum) = raw_pkg.checksum.take() {
136 let value = checksum
137 .to_string()
138 .parse::<metadata::MetadataValue>()
139 .unwrap();
140 metadata.insert(checksum_key, value);
141 }
142 } else {
143 raw_pkg.v2_deps(&lockfile.packages);
147 metadata.remove(&checksum_key);
148
149 for dep in raw_pkg.dependencies.iter_mut() {
152 if let Some(registries) = package_to_registries.get(&dep.name) {
153 if registries.len() == 1 {
154 dep.source = None;
155 }
156 }
157 }
158 }
159
160 packages.push(raw_pkg);
161 }
162
163 let version = if lockfile.version.is_explicit() {
164 Some(lockfile.version.into())
165 } else {
166 None
167 };
168
169 EncodableLockfile {
170 version,
171 package: packages,
172 root: lockfile
173 .root
174 .as_ref()
175 .map(|root| EncodablePackage::from_package(root, lockfile.version)),
176 metadata,
177 patch: lockfile.patch.clone(),
178 }
179 }
180}
181
182#[allow(clippy::to_string_trait_impl)]
183impl ToString for EncodableLockfile {
184 fn to_string(&self) -> String {
187 let toml = toml::Value::try_from(self).unwrap();
188 let mut out = String::new();
189
190 let marker_line = "# This file is automatically @generated by Cargo.";
193 let extra_line = "# It is not intended for manual editing.";
194 out.push_str(marker_line);
195 out.push('\n');
196 out.push_str(extra_line);
197 out.push('\n');
198
199 if let Some(value) = toml.get("version") {
200 if let Some(version) = value.as_integer() {
201 if version >= 3 {
202 writeln!(out, "version = {version}").unwrap();
203 }
204 }
205 }
206
207 out.push('\n');
208
209 let deps = toml["package"].as_array().unwrap();
210 for dep in deps {
211 let dep = dep.as_table().unwrap();
212
213 out.push_str("[[package]]\n");
214 emit_package(dep, &mut out);
215 }
216
217 if let Some(patch) = toml.get("patch") {
218 let list = patch["unused"].as_array().unwrap();
219 for entry in list {
220 out.push_str("[[patch.unused]]\n");
221 emit_package(entry.as_table().unwrap(), &mut out);
222 out.push('\n');
223 }
224 }
225
226 if let Some(meta) = toml.get("metadata") {
227 out.push_str("[metadata]\n");
228 out.push_str(&toml::to_string_pretty(&meta).unwrap());
229 }
230
231 if out.ends_with("\n\n") {
233 out.pop();
234 }
235
236 out
237 }
238}
239
240fn emit_package(dep: &toml::value::Table, out: &mut String) {
245 writeln!(out, "name = {}", &dep["name"]).unwrap();
246 writeln!(out, "version = {}", &dep["version"]).unwrap();
247
248 if dep.contains_key("source") {
249 writeln!(out, "source = {}", &dep["source"]).unwrap();
250 }
251 if dep.contains_key("checksum") {
252 writeln!(out, "checksum = {}", &dep["checksum"]).unwrap();
253 }
254
255 if let Some(s) = dep.get("dependencies") {
256 let slice = s.as_array().unwrap();
257
258 if !slice.is_empty() {
259 out.push_str("dependencies = [\n");
260
261 for child in slice.iter() {
262 writeln!(out, " {child},").unwrap();
263 }
264
265 out.push_str("]\n");
266 }
267 } else if dep.contains_key("replace") {
268 writeln!(out, "replace = {}", &dep["replace"]).unwrap();
269 }
270
271 out.push('\n');
272}
273
274#[derive(Debug, Deserialize, Serialize)]
276pub(crate) struct EncodablePackage {
277 pub(super) name: Name,
279
280 pub(super) version: Version,
282
283 pub(super) source: Option<EncodableSourceId>,
285
286 pub(super) checksum: Option<Checksum>,
288
289 #[serde(default, skip_serializing_if = "Vec::is_empty")]
291 pub(super) dependencies: Vec<EncodableDependency>,
292
293 pub(super) replace: Option<EncodableDependency>,
295}
296
297impl EncodablePackage {
298 fn resolve(&self, packages: &[EncodablePackage]) -> Result<Package> {
301 let mut dependencies = Vec::with_capacity(self.dependencies.len());
302
303 for dep in &self.dependencies {
304 dependencies.push(dep.resolve(packages)?);
305 }
306
307 Ok(Package {
308 name: self.name.clone(),
309 version: self.version.clone(),
310 source: self.source.as_ref().map(|s| s.inner.clone()),
311 checksum: self.checksum.clone(),
312 dependencies,
313 replace: self
314 .replace
315 .as_ref()
316 .map(|rep| rep.try_into())
317 .transpose()?,
318 })
319 }
320
321 fn v2_deps(&mut self, packages: &[Package]) {
323 for dependency in &mut self.dependencies {
324 dependency.v2(packages);
325 }
326 }
327
328 fn from_package(package: &Package, version: ResolveVersion) -> EncodablePackage {
329 EncodablePackage {
330 name: package.name.clone(),
331 version: package.version.clone(),
332 source: package
333 .source
334 .clone()
335 .and_then(|id| encodable_source_id(id, version)),
336 checksum: package.checksum.clone(),
337 dependencies: package
338 .dependencies
339 .iter()
340 .map(|dep| EncodableDependency::from_dependency(dep, version))
341 .collect::<Vec<_>>(),
342 replace: package
343 .replace
344 .as_ref()
345 .map(|rep| EncodableDependency::from_dependency(rep, version)),
346 }
347 }
348}
349
350fn encodable_source_id(id: SourceId, version: ResolveVersion) -> Option<EncodableSourceId> {
351 if id.is_path() {
352 None
353 } else {
354 Some(if version >= ResolveVersion::V4 {
355 EncodableSourceId::new(id)
356 } else {
357 EncodableSourceId::without_url_encoded(id)
358 })
359 }
360}
361
362impl TryFrom<&EncodablePackage> for Package {
364 type Error = Error;
365
366 fn try_from(raw_package: &EncodablePackage) -> Result<Package> {
367 raw_package.resolve(&[])
368 }
369}
370
371#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
373pub(crate) struct EncodableDependency {
374 pub(super) name: Name,
376
377 pub(super) version: Option<Version>,
379
380 pub(super) source: Option<EncodableSourceId>,
382}
383
384impl EncodableDependency {
385 pub fn resolve(&self, packages: &[EncodablePackage]) -> Result<Dependency> {
388 for pkg in packages {
389 if pkg.name == self.name
390 && (self.version.is_none() || self.version.as_ref() == Some(&pkg.version))
391 && self.source.is_none()
392 {
393 return Ok(Dependency {
394 name: pkg.name.clone(),
395 version: pkg.version.clone(),
396 source: pkg
397 .source
398 .clone()
399 .map(|x| x.normalize_git_source_for_dependency()),
400 });
401 }
402 }
403
404 let version = self
405 .version
406 .clone()
407 .ok_or_else(|| Error::Parse(format!("couldn't resolve dependency: {}", self.name)))?;
408
409 Ok(Dependency {
410 name: self.name.clone(),
411 version,
412 source: self
413 .source
414 .clone()
415 .map(|x| x.normalize_git_source_for_dependency()),
416 })
417 }
418
419 pub fn v2(&mut self, packages: &[Package]) {
421 let mut matching = vec![];
422
423 for package in packages {
424 if package.name == self.name {
425 matching.push(package);
426 }
427 }
428
429 if matching.len() == 1 {
431 self.version = None;
432 self.source = None;
433 }
434 }
435
436 pub fn from_dependency(dep: &Dependency, version: ResolveVersion) -> EncodableDependency {
437 EncodableDependency {
438 name: dep.name.clone(),
439 version: Some(dep.version.clone()),
440 source: dep
441 .source
442 .clone()
443 .and_then(|id| encodable_source_id(id, version)),
444 }
445 }
446}
447
448impl FromStr for EncodableDependency {
450 type Err = Error;
451
452 fn from_str(s: &str) -> Result<Self> {
453 let mut parts = s.split_whitespace();
454
455 let name = parts
456 .next()
457 .ok_or_else(|| Error::Parse("empty dependency string".to_owned()))?
458 .parse()?;
459
460 let version = parts.next().map(FromStr::from_str).transpose()?;
461
462 let source = parts
463 .next()
464 .map(|s| {
465 if s.len() < 2 || !s.starts_with('(') || !s.ends_with(')') {
466 Err(Error::Parse(format!("malformed source in dependency: {s}")))
467 } else {
468 s[1..(s.len() - 1)].parse::<SourceId>()
469 }
470 })
471 .transpose()?;
472
473 if parts.next().is_some() {
474 return Err(Error::Parse(format!("malformed dependency: {s}")));
475 }
476
477 Ok(Self {
478 name,
479 version,
480 source: source.map(EncodableSourceId::without_url_encoded),
484 })
485 }
486}
487
488impl fmt::Display for EncodableDependency {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 write!(f, "{}", &self.name)?;
491
492 if let Some(version) = &self.version {
493 write!(f, " {version}")?;
494 }
495
496 if let Some(source) = &self.source {
497 write!(f, " ({})", source.as_url())?;
498 }
499
500 Ok(())
501 }
502}
503
504impl TryFrom<&EncodableDependency> for Dependency {
506 type Error = Error;
507
508 fn try_from(raw_dependency: &EncodableDependency) -> Result<Dependency> {
509 raw_dependency.resolve(&[])
510 }
511}
512
513impl<'de> Deserialize<'de> for EncodableDependency {
514 fn deserialize<D: de::Deserializer<'de>>(
515 deserializer: D,
516 ) -> std::result::Result<Self, D::Error> {
517 String::deserialize(deserializer)?
518 .parse()
519 .map_err(de::Error::custom)
520 }
521}
522
523impl Serialize for EncodableDependency {
524 fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
525 self.to_string().serialize(serializer)
526 }
527}
528
529#[derive(Deserialize, Debug, PartialOrd, Ord, Clone)]
535#[serde(transparent)]
536pub(super) struct EncodableSourceId {
537 inner: SourceId,
538 #[serde(skip)]
541 encoded: bool,
542}
543
544impl EncodableSourceId {
545 fn new(inner: SourceId) -> Self {
547 Self {
548 inner,
549 encoded: true,
550 }
551 }
552
553 fn without_url_encoded(inner: SourceId) -> Self {
556 Self {
557 inner,
558 encoded: false,
559 }
560 }
561
562 fn as_url(&self) -> impl fmt::Display + '_ {
564 self.inner.as_url(self.encoded)
565 }
566}
567
568impl std::ops::Deref for EncodableSourceId {
569 type Target = SourceId;
570
571 fn deref(&self) -> &Self::Target {
572 &self.inner
573 }
574}
575
576impl Serialize for EncodableSourceId {
577 fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
578 serializer.collect_str(&self.as_url())
579 }
580}
581
582impl std::hash::Hash for EncodableSourceId {
583 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
584 self.inner.hash(state)
585 }
586}
587
588impl PartialEq for EncodableSourceId {
589 fn eq(&self, other: &Self) -> bool {
590 self.inner == other.inner
591 }
592}
593
594impl Eq for EncodableSourceId {}