1use super::{Lockfile, ResolveVersion};
12use crate::{
13 Checksum, Dependency, Error, Metadata, Name, Package, Patch, Result, SourceId, Version,
14 metadata,
15};
16use serde::{Deserialize, Serialize, de, ser};
17use std::{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 for package in &lockfile.packages {
117 let mut raw_pkg = EncodablePackage::from_package(package, lockfile.version);
118 let checksum_key = metadata::MetadataKey::for_checksum(&Dependency::from(package));
119
120 if lockfile.version == ResolveVersion::V1 {
121 if let Some(checksum) = raw_pkg.checksum.take() {
124 let value = checksum
125 .to_string()
126 .parse::<metadata::MetadataValue>()
127 .unwrap();
128 metadata.insert(checksum_key, value);
129 }
130 } else {
131 raw_pkg.v2_deps(&lockfile.packages);
135 metadata.remove(&checksum_key);
136 }
137
138 packages.push(raw_pkg);
139 }
140
141 let version = if lockfile.version.is_explicit() {
142 Some(lockfile.version.into())
143 } else {
144 None
145 };
146
147 EncodableLockfile {
148 version,
149 package: packages,
150 root: lockfile
151 .root
152 .as_ref()
153 .map(|root| EncodablePackage::from_package(root, lockfile.version)),
154 metadata,
155 patch: lockfile.patch.clone(),
156 }
157 }
158}
159
160#[allow(clippy::to_string_trait_impl)]
161impl ToString for EncodableLockfile {
162 fn to_string(&self) -> String {
165 let toml = toml::Value::try_from(self).unwrap();
166 let mut out = String::new();
167
168 let marker_line = "# This file is automatically @generated by Cargo.";
171 let extra_line = "# It is not intended for manual editing.";
172 out.push_str(marker_line);
173 out.push('\n');
174 out.push_str(extra_line);
175 out.push('\n');
176
177 if let Some(value) = toml.get("version") {
178 if let Some(version) = value.as_integer() {
179 if version >= 3 {
180 writeln!(out, "version = {version}").unwrap();
181 }
182 }
183 }
184
185 out.push('\n');
186
187 let deps = toml["package"].as_array().unwrap();
188 for dep in deps {
189 let dep = dep.as_table().unwrap();
190
191 out.push_str("[[package]]\n");
192 emit_package(dep, &mut out);
193 }
194
195 if let Some(patch) = toml.get("patch") {
196 let list = patch["unused"].as_array().unwrap();
197 for entry in list {
198 out.push_str("[[patch.unused]]\n");
199 emit_package(entry.as_table().unwrap(), &mut out);
200 out.push('\n');
201 }
202 }
203
204 if let Some(meta) = toml.get("metadata") {
205 out.push_str("[metadata]\n");
206 out.push_str(&toml::to_string_pretty(&meta).unwrap());
207 }
208
209 if out.ends_with("\n\n") {
211 out.pop();
212 }
213
214 out
215 }
216}
217
218fn emit_package(dep: &toml::value::Table, out: &mut String) {
223 writeln!(out, "name = {}", &dep["name"]).unwrap();
224 writeln!(out, "version = {}", &dep["version"]).unwrap();
225
226 if dep.contains_key("source") {
227 writeln!(out, "source = {}", &dep["source"]).unwrap();
228 }
229 if dep.contains_key("checksum") {
230 writeln!(out, "checksum = {}", &dep["checksum"]).unwrap();
231 }
232
233 if let Some(s) = dep.get("dependencies") {
234 let slice = s.as_array().unwrap();
235
236 if !slice.is_empty() {
237 out.push_str("dependencies = [\n");
238
239 for child in slice.iter() {
240 writeln!(out, " {child},").unwrap();
241 }
242
243 out.push_str("]\n");
244 }
245 } else if dep.contains_key("replace") {
246 writeln!(out, "replace = {}", &dep["replace"]).unwrap();
247 }
248
249 out.push('\n');
250}
251
252#[derive(Debug, Deserialize, Serialize)]
254pub(crate) struct EncodablePackage {
255 pub(super) name: Name,
257
258 pub(super) version: Version,
260
261 pub(super) source: Option<EncodableSourceId>,
263
264 pub(super) checksum: Option<Checksum>,
266
267 #[serde(default, skip_serializing_if = "Vec::is_empty")]
269 pub(super) dependencies: Vec<EncodableDependency>,
270
271 pub(super) replace: Option<EncodableDependency>,
273}
274
275impl EncodablePackage {
276 fn resolve(&self, packages: &[EncodablePackage]) -> Result<Package> {
279 let mut dependencies = Vec::with_capacity(self.dependencies.len());
280
281 for dep in &self.dependencies {
282 dependencies.push(dep.resolve(packages)?);
283 }
284
285 Ok(Package {
286 name: self.name.clone(),
287 version: self.version.clone(),
288 source: self.source.as_ref().map(|s| s.inner.clone()),
289 checksum: self.checksum.clone(),
290 dependencies,
291 replace: self
292 .replace
293 .as_ref()
294 .map(|rep| rep.try_into())
295 .transpose()?,
296 })
297 }
298
299 fn v2_deps(&mut self, packages: &[Package]) {
301 for dependency in &mut self.dependencies {
302 dependency.v2(packages);
303 }
304 }
305
306 fn from_package(package: &Package, version: ResolveVersion) -> EncodablePackage {
307 EncodablePackage {
308 name: package.name.clone(),
309 version: package.version.clone(),
310 source: package
311 .source
312 .clone()
313 .and_then(|id| encodable_source_id(id, version)),
314 checksum: package.checksum.clone(),
315 dependencies: package
316 .dependencies
317 .iter()
318 .map(|dep| EncodableDependency::from_dependency(dep, version))
319 .collect::<Vec<_>>(),
320 replace: package
321 .replace
322 .as_ref()
323 .map(|rep| EncodableDependency::from_dependency(rep, version)),
324 }
325 }
326}
327
328fn encodable_source_id(id: SourceId, version: ResolveVersion) -> Option<EncodableSourceId> {
329 if id.is_path() {
330 None
331 } else {
332 Some(if version >= ResolveVersion::V4 {
333 EncodableSourceId::new(id)
334 } else {
335 EncodableSourceId::without_url_encoded(id)
336 })
337 }
338}
339
340impl TryFrom<&EncodablePackage> for Package {
342 type Error = Error;
343
344 fn try_from(raw_package: &EncodablePackage) -> Result<Package> {
345 raw_package.resolve(&[])
346 }
347}
348
349#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
351pub(crate) struct EncodableDependency {
352 pub(super) name: Name,
354
355 pub(super) version: Option<Version>,
357
358 pub(super) source: Option<EncodableSourceId>,
360}
361
362impl EncodableDependency {
363 pub fn resolve(&self, packages: &[EncodablePackage]) -> Result<Dependency> {
366 for pkg in packages {
367 if pkg.name == self.name
368 && (self.version.is_none() || self.version.as_ref() == Some(&pkg.version))
369 && self.source.is_none()
370 {
371 return Ok(Dependency {
372 name: pkg.name.clone(),
373 version: pkg.version.clone(),
374 source: pkg.source.clone().map(|x| x.inner),
375 });
376 }
377 }
378
379 let version = self
380 .version
381 .clone()
382 .ok_or_else(|| Error::Parse(format!("couldn't resolve dependency: {}", self.name)))?;
383
384 Ok(Dependency {
385 name: self.name.clone(),
386 version,
387 source: self.source.clone().map(|x| x.inner),
388 })
389 }
390
391 pub fn v2(&mut self, packages: &[Package]) {
393 let mut matching = vec![];
394
395 for package in packages {
396 if package.name == self.name {
397 matching.push(package);
398 }
399 }
400
401 if matching.len() == 1 {
402 self.version = None;
404 self.source = None;
405 return;
406 }
407
408 let Some(version) = self.version.as_ref() else {
409 return;
412 };
413
414 if matching
415 .iter()
416 .filter(|package| &package.version == version)
417 .count()
418 == 1
419 {
420 self.source = None;
422 }
423 }
424
425 pub fn from_dependency(dep: &Dependency, version: ResolveVersion) -> EncodableDependency {
426 EncodableDependency {
427 name: dep.name.clone(),
428 version: Some(dep.version.clone()),
429 source: dep
430 .source
431 .clone()
432 .and_then(|id| encodable_source_id(id, version)),
433 }
434 }
435}
436
437impl FromStr for EncodableDependency {
439 type Err = Error;
440
441 fn from_str(s: &str) -> Result<Self> {
442 let mut parts = s.split_whitespace();
443
444 let name = parts
445 .next()
446 .ok_or_else(|| Error::Parse("empty dependency string".to_owned()))?
447 .parse()?;
448
449 let version = parts.next().map(FromStr::from_str).transpose()?;
450
451 let source = parts
452 .next()
453 .map(|s| {
454 if s.len() < 2 || !s.starts_with('(') || !s.ends_with(')') {
455 Err(Error::Parse(format!("malformed source in dependency: {s}")))
456 } else {
457 s[1..(s.len() - 1)].parse::<SourceId>()
458 }
459 })
460 .transpose()?;
461
462 if parts.next().is_some() {
463 return Err(Error::Parse(format!("malformed dependency: {s}")));
464 }
465
466 Ok(Self {
467 name,
468 version,
469 source: source.map(EncodableSourceId::without_url_encoded),
473 })
474 }
475}
476
477impl fmt::Display for EncodableDependency {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 write!(f, "{}", &self.name)?;
480
481 if let Some(version) = &self.version {
482 write!(f, " {version}")?;
483 }
484
485 if let Some(source) = &self.source {
486 write!(f, " ({})", source.as_url())?;
487 }
488
489 Ok(())
490 }
491}
492
493impl TryFrom<&EncodableDependency> for Dependency {
495 type Error = Error;
496
497 fn try_from(raw_dependency: &EncodableDependency) -> Result<Dependency> {
498 raw_dependency.resolve(&[])
499 }
500}
501
502impl<'de> Deserialize<'de> for EncodableDependency {
503 fn deserialize<D: de::Deserializer<'de>>(
504 deserializer: D,
505 ) -> std::result::Result<Self, D::Error> {
506 String::deserialize(deserializer)?
507 .parse()
508 .map_err(de::Error::custom)
509 }
510}
511
512impl Serialize for EncodableDependency {
513 fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
514 self.to_string().serialize(serializer)
515 }
516}
517
518#[derive(Deserialize, Debug, PartialOrd, Ord, Clone)]
524#[serde(transparent)]
525pub(super) struct EncodableSourceId {
526 inner: SourceId,
527 #[serde(skip)]
530 encoded: bool,
531}
532
533impl EncodableSourceId {
534 fn new(inner: SourceId) -> Self {
536 Self {
537 inner,
538 encoded: true,
539 }
540 }
541
542 fn without_url_encoded(inner: SourceId) -> Self {
545 Self {
546 inner,
547 encoded: false,
548 }
549 }
550
551 fn as_url(&self) -> impl fmt::Display + '_ {
553 self.inner.as_url(self.encoded)
554 }
555}
556
557impl std::ops::Deref for EncodableSourceId {
558 type Target = SourceId;
559
560 fn deref(&self) -> &Self::Target {
561 &self.inner
562 }
563}
564
565impl Serialize for EncodableSourceId {
566 fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
567 serializer.collect_str(&self.as_url())
568 }
569}
570
571impl std::hash::Hash for EncodableSourceId {
572 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
573 self.inner.hash(state)
574 }
575}
576
577impl PartialEq for EncodableSourceId {
578 fn eq(&self, other: &Self) -> bool {
579 self.inner == other.inner
580 }
581}
582
583impl Eq for EncodableSourceId {}