1#![allow(clippy::large_enum_variant)]
2#![doc = include_str!("../README.md")]
3
4use serde::Deserializer;
5use serde::{Deserialize, Serialize, Serializer};
6use std::collections::BTreeMap;
7use std::fs;
8use std::io;
9use std::path::Path;
10
11pub use toml::Value;
12
13pub type DepsSet = BTreeMap<String, Dependency>;
14pub type TargetDepsSet = BTreeMap<String, Target>;
15pub type FeatureSet = BTreeMap<String, Vec<String>>;
16pub type PatchSet = BTreeMap<String, DepsSet>;
17pub type ToolLintsSet = BTreeMap<String, Lint>;
18pub type LintsSet = BTreeMap<String, ToolLintsSet>;
19
20mod afs;
21mod error;
22pub use crate::afs::*;
23pub use crate::error::Error;
24use serde::de::{Error as _, Unexpected};
25use std::str::FromStr;
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32#[serde(rename_all = "kebab-case")]
33pub struct Manifest<PackageMetadata = Value, WorkspaceMetadata = Value> {
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub package: Option<Package<PackageMetadata>>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub cargo_features: Option<Vec<String>>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub workspace: Option<Workspace<WorkspaceMetadata>>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub dependencies: Option<DepsSet>,
42 #[serde(skip_serializing_if = "Option::is_none", alias = "dev_dependencies")]
43 pub dev_dependencies: Option<DepsSet>,
44 #[serde(skip_serializing_if = "Option::is_none", alias = "build_dependencies")]
45 pub build_dependencies: Option<DepsSet>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub target: Option<TargetDepsSet>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub features: Option<FeatureSet>,
50 #[serde(default, skip_serializing_if = "Vec::is_empty")]
53 pub bin: Vec<Product>,
54 #[serde(default, skip_serializing_if = "Vec::is_empty")]
55 pub bench: Vec<Product>,
56 #[serde(default, skip_serializing_if = "Vec::is_empty")]
57 pub test: Vec<Product>,
58 #[serde(default, skip_serializing_if = "Vec::is_empty")]
59 pub example: Vec<Product>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub patch: Option<PatchSet>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
67 pub lib: Option<Product>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub profile: Option<Profiles>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub badges: Option<Badges>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub lints: Option<MaybeInheritedLintsSet>,
76}
77
78impl<PackageMetadata, WorkspaceMetadata> Default for Manifest<PackageMetadata, WorkspaceMetadata> {
79 #[allow(deprecated)]
80 fn default() -> Self {
81 Self {
82 package: None,
83 cargo_features: None,
84 workspace: None,
85 dependencies: None,
86 dev_dependencies: None,
87 build_dependencies: None,
88 target: None,
89 features: None,
90 patch: None,
91 lib: None,
92 profile: None,
93 lints: None,
94 badges: None,
95 bin: Default::default(),
96 bench: Default::default(),
97 test: Default::default(),
98 example: Default::default(),
99 }
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
104#[serde(rename_all = "kebab-case")]
105pub struct Workspace<Metadata = Value> {
106 #[serde(default)]
107 pub members: Vec<String>,
108
109 #[serde(skip_serializing_if = "Option::is_none", alias = "default_members")]
110 pub default_members: Option<Vec<String>>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub exclude: Option<Vec<String>>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub resolver: Option<Resolver>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub dependencies: Option<DepsSet>,
120
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub package: Option<WorkspacePackage>,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub metadata: Option<Metadata>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub lints: Option<LintsSet>,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
138#[serde(rename_all = "kebab-case")]
139pub struct WorkspacePackage {
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub edition: Option<Edition>,
142 pub version: Option<String>,
144 pub authors: Option<Vec<String>>,
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub description: Option<String>,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub homepage: Option<String>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub documentation: Option<String>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub readme: Option<StringOrBool>,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub keywords: Option<Vec<String>>,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub categories: Option<Vec<String>>,
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub license: Option<String>,
166 #[serde(rename = "license-file")]
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub license_file: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub publish: Option<Publish>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub exclude: Option<Vec<String>>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub include: Option<Vec<String>>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub repository: Option<String>,
177 #[serde(rename = "rust-version")]
179 pub rust_version: Option<String>,
180}
181
182fn default_true() -> bool {
183 true
184}
185
186fn is_true(value: &bool) -> bool {
187 *value
188}
189
190fn is_false(value: &bool) -> bool {
191 !value
192}
193
194impl Manifest<Value> {
195 pub fn from_slice(cargo_toml_content: &[u8]) -> Result<Self, Error> {
199 Self::from_slice_with_metadata(cargo_toml_content)
200 }
201
202 pub fn from_path(cargo_toml_path: impl AsRef<Path>) -> Result<Self, Error> {
206 Self::from_path_with_metadata(cargo_toml_path)
207 }
208}
209
210impl FromStr for Manifest<Value> {
211 type Err = Error;
212
213 fn from_str(cargo_toml_content: &str) -> Result<Self, Self::Err> {
219 Self::from_slice_with_metadata(cargo_toml_content.as_bytes())
220 }
221}
222
223impl<Metadata: for<'a> Deserialize<'a>> Manifest<Metadata> {
224 pub fn from_slice_with_metadata(cargo_toml_content: &[u8]) -> Result<Self, Error> {
228 let mut manifest: Self = toml_from_slice(cargo_toml_content)?;
229 if manifest.package.is_none() && manifest.workspace.is_none() {
230 let val: Value = toml_from_slice(cargo_toml_content)?;
233 if let Some(project) = val.get("project") {
234 manifest.package = Some(project.clone().try_into()?);
235 } else {
236 manifest.package = Some(val.try_into()?);
237 }
238 }
239 Ok(manifest)
240 }
241
242 pub fn from_path_with_metadata(cargo_toml_path: impl AsRef<Path>) -> Result<Self, Error> {
246 let cargo_toml_path = cargo_toml_path.as_ref();
247 let cargo_toml_content = fs::read(cargo_toml_path)?;
248 let mut manifest = Self::from_slice_with_metadata(&cargo_toml_content)?;
249 manifest.complete_from_path(cargo_toml_path)?;
250 Ok(manifest)
251 }
252
253 pub fn complete_from_path(&mut self, path: &Path) -> Result<(), Error> {
258 let manifest_dir = path.parent().ok_or_else(|| io::Error::other("bad path"))?;
259 self.complete_from_abstract_filesystem(&Filesystem::new(manifest_dir))
260 }
261
262 pub fn complete_from_abstract_filesystem<FS: AbstractFilesystem>(
268 &mut self,
269 fs: &FS,
270 ) -> Result<(), Error> {
271 enum ProductType {
272 #[allow(dead_code)]
273 Lib,
274 Bin,
275 Example,
276 Test,
277 Bench,
278 }
279
280 let autobins = self.autobins();
281 let autotests = self.autotests();
282 let autoexamples = self.autoexamples();
283 let autobenches = self.autobenches();
284
285 if let Some(ref mut package) = self.package {
286 let src = match fs.file_names_in("src") {
287 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(Default::default()),
288 result => result,
289 }?;
290
291 let edition = match package.edition {
292 Some(MaybeInherited::Local(edition)) => Some(edition),
293 _ => None,
294 };
295
296 if let Some(ref mut lib) = self.lib {
297 if lib.path.is_none() {
300 let fallback_name = lib.name.as_deref().unwrap_or(&package.name);
301
302 if src.contains("lib.rs") {
303 lib.path = Some("src/lib.rs".to_string());
304 } else if package.uses_legacy_auto_discovery()
305 && src.contains(format!("{fallback_name}.rs").as_str())
306 {
307 lib.path = Some(format!("src/{fallback_name}.rs"));
308 } else {
309 let msg =
310 "can't find library, rename file to `src/lib.rs` or specify lib.path";
311 return Err(Error::Other(msg.to_string()));
312 }
313 }
314
315 if lib.name.is_none() {
318 lib.name = Some(package.name.replace('-', "_"));
319 }
320
321 if lib.edition.is_none() {
324 lib.edition = edition;
325 }
326
327 if lib.crate_type.is_none() {
330 lib.crate_type = Some(vec!["lib".to_string()]);
331 }
332
333 lib.required_features.clear();
336 } else if !matches!(package.autolib, Some(false)) && src.contains("lib.rs") {
337 self.lib = Some(Product {
338 name: Some(package.name.replace('-', "_")),
339 path: Some("src/lib.rs".to_string()),
340 edition,
341 crate_type: Some(vec!["lib".to_string()]),
342 ..Product::default()
343 })
344 }
345
346 let fill_target_defaults = |targets: &mut Vec<Product>, kind: ProductType| {
347 for target in targets {
348 if target.edition.is_none() {
349 target.edition = edition;
350 }
351
352 if matches!(kind, ProductType::Example) && target.crate_type.is_none() {
353 target.crate_type = Some(vec!["bin".to_string()]);
354 }
355 }
356 };
357
358 let mut discovered_targets = discover_targets(fs, "src/bin")?;
359
360 let has_main_rs = src.contains("main.rs");
361 if has_main_rs {
362 let target = DiscoveredTarget {
363 name: package.name.clone(),
364 path: "src/main.rs".to_string(),
365 };
366 discovered_targets.push(target);
367 }
368
369 process_discovered_targets(&mut self.bin, discovered_targets, autobins)?;
370 fill_target_defaults(&mut self.bin, ProductType::Bin);
371
372 if package.uses_legacy_auto_discovery() && has_main_rs {
376 for target in self.bin.iter_mut().filter(|t| t.path.is_none()) {
377 target.path = Some("src/main.rs".to_string());
378 }
379 }
380
381 let discovered_targets = discover_targets(fs, "examples")?;
382 process_discovered_targets(&mut self.example, discovered_targets, autoexamples)?;
383 fill_target_defaults(&mut self.example, ProductType::Example);
384
385 let discovered_targets = discover_targets(fs, "tests")?;
386 process_discovered_targets(&mut self.test, discovered_targets, autotests)?;
387 fill_target_defaults(&mut self.test, ProductType::Test);
388
389 let discovered_targets = discover_targets(fs, "benches")?;
390 process_discovered_targets(&mut self.bench, discovered_targets, autobenches)?;
391 fill_target_defaults(&mut self.bench, ProductType::Bench);
392
393 if matches!(package.build, None | Some(StringOrBool::Bool(true)))
394 && fs.file_names_in(".")?.contains("build.rs")
395 {
396 package.build = Some(StringOrBool::String("build.rs".to_string()));
397 }
398 }
399 Ok(())
400 }
401
402 pub fn autobins(&self) -> bool {
403 let Some(pkg) = &self.package else {
404 return false;
405 };
406
407 let default_value = !pkg.uses_legacy_auto_discovery() || self.bin.is_empty();
408 pkg.autobins.unwrap_or(default_value)
409 }
410
411 pub fn autoexamples(&self) -> bool {
412 let Some(pkg) = &self.package else {
413 return false;
414 };
415
416 let default_value = !pkg.uses_legacy_auto_discovery() || self.example.is_empty();
417 pkg.autoexamples.unwrap_or(default_value)
418 }
419
420 pub fn autotests(&self) -> bool {
421 let Some(pkg) = &self.package else {
422 return false;
423 };
424
425 let default_value = !pkg.uses_legacy_auto_discovery() || self.test.is_empty();
426 pkg.autotests.unwrap_or(default_value)
427 }
428
429 pub fn autobenches(&self) -> bool {
430 let Some(pkg) = &self.package else {
431 return false;
432 };
433
434 let default_value = !pkg.uses_legacy_auto_discovery() || self.bench.is_empty();
435 pkg.autobenches.unwrap_or(default_value)
436 }
437}
438
439#[derive(Debug)]
440struct DiscoveredTarget {
441 name: String,
442 path: String,
443}
444
445fn process_discovered_targets(
449 targets: &mut Vec<Product>,
450 discovered_targets: Vec<DiscoveredTarget>,
451 add_discovered_targets: bool,
452) -> Result<(), Error> {
453 for target in targets.iter_mut() {
454 let Some(ref name) = target.name else {
457 continue;
458 };
459
460 if target.path.is_none() {
463 let discovered_target = discovered_targets.iter().find(|t| t.name == *name);
464 if let Some(discovered_target) = discovered_target {
465 target.path = Some(discovered_target.path.clone());
466 }
467
468 }
475 }
476
477 if add_discovered_targets {
478 for discovered_target in discovered_targets {
479 if targets
480 .iter()
481 .any(|b| b.path.as_deref() == Some(&discovered_target.path))
482 {
483 continue;
484 }
485
486 targets.push(Product {
487 name: Some(discovered_target.name),
488 path: Some(discovered_target.path),
489 edition: None,
490 ..Product::default()
491 });
492 }
493 }
494
495 Ok(())
496}
497
498fn discover_targets<FS: AbstractFilesystem>(
507 fs: &FS,
508 path: &str,
509) -> Result<Vec<DiscoveredTarget>, Error> {
510 let Ok(file_names) = fs.file_names_in(path) else {
511 return Ok(Default::default());
515 };
516
517 let mut file_names = file_names.into_iter().collect::<Vec<_>>();
519 file_names.sort_unstable();
520
521 let mut out = Vec::new();
522 for file_name in file_names {
523 let rel_path = format!("{}/{}", path, file_name);
524
525 if let Some(name) = file_name.strip_suffix(".rs") {
526 out.push(DiscoveredTarget {
527 name: name.into(),
528 path: rel_path.clone(),
529 });
530 }
531
532 let Ok(subfolder_file_names) = fs.file_names_in(&rel_path) else {
533 continue;
534 };
535
536 if subfolder_file_names.contains("main.rs") {
537 out.push(DiscoveredTarget {
538 name: file_name.into(),
539 path: rel_path + "/main.rs",
540 });
541 }
542 }
543
544 Ok(out)
545}
546
547#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
548pub struct Profiles {
549 pub release: Option<Profile>,
550 pub dev: Option<Profile>,
551 pub test: Option<Profile>,
552 pub bench: Option<Profile>,
553 pub doc: Option<Profile>,
554
555 #[serde(flatten)]
556 pub custom: BTreeMap<String, Profile>,
557}
558
559#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
560#[serde(try_from = "toml::Value")]
561pub enum StripSetting {
562 None,
564 Debuginfo,
565 Symbols,
567}
568
569impl Serialize for StripSetting {
570 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
571 match self {
572 Self::None => serializer.serialize_bool(false),
573 Self::Debuginfo => serializer.serialize_str("debuginfo"),
574 Self::Symbols => serializer.serialize_bool(true),
575 }
576 }
577}
578
579impl TryFrom<Value> for StripSetting {
580 type Error = Error;
581
582 fn try_from(v: Value) -> Result<Self, Error> {
583 Ok(match v {
584 Value::Boolean(b) => {
585 if b {
586 Self::Symbols
587 } else {
588 Self::None
589 }
590 }
591 Value::String(s) => match s.as_str() {
592 "none" => Self::None,
593 "debuginfo" => Self::Debuginfo,
594 "symbols" => Self::Symbols,
595 other => {
596 return Err(Error::Other(format!(
597 "'{other}' is not a valid value for 'strip'"
598 )))
599 }
600 },
601 _ => {
602 return Err(Error::Other(
603 "wrong data type for strip setting".to_string(),
604 ))
605 }
606 })
607 }
608}
609
610#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
611#[serde(rename_all = "kebab-case")]
612pub struct Profile {
613 #[serde(alias = "opt_level")]
614 pub opt_level: Option<Value>,
615 pub debug: Option<Value>,
616 pub rpath: Option<bool>,
617 pub inherits: Option<String>,
618 pub lto: Option<Value>,
619 #[serde(alias = "debug_assertions")]
620 pub debug_assertions: Option<bool>,
621 #[serde(alias = "codegen_units")]
622 pub codegen_units: Option<u16>,
623 pub panic: Option<String>,
624 pub incremental: Option<bool>,
625 #[serde(alias = "overflow_checks")]
626 pub overflow_checks: Option<bool>,
627 pub strip: Option<StripSetting>,
628 #[serde(default)]
629 pub package: BTreeMap<String, Value>,
630 pub split_debuginfo: Option<String>,
631 pub build_override: Option<Value>,
633}
634
635#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
636#[serde(rename_all = "kebab-case")]
637pub struct Product {
641 pub path: Option<String>,
643
644 pub name: Option<String>,
649
650 #[serde(default = "default_true", skip_serializing_if = "is_true")]
652 pub test: bool,
653
654 #[serde(default = "default_true", skip_serializing_if = "is_true")]
658 pub doctest: bool,
659
660 #[serde(default = "default_true", skip_serializing_if = "is_true")]
662 pub bench: bool,
663
664 #[serde(default = "default_true", skip_serializing_if = "is_true")]
666 pub doc: bool,
667
668 #[serde(default, skip_serializing_if = "is_false")]
671 pub plugin: bool,
672
673 #[serde(default, alias = "proc_macro", skip_serializing_if = "is_false")]
676 pub proc_macro: bool,
677
678 #[serde(default = "default_true", skip_serializing_if = "is_true")]
682 pub harness: bool,
683
684 #[serde(default)]
689 pub edition: Option<Edition>,
690
691 #[serde(default, alias = "required_features")]
696 pub required_features: Vec<String>,
697
698 #[serde(skip_serializing_if = "Option::is_none", alias = "crate_type")]
700 pub crate_type: Option<Vec<String>>,
701}
702
703impl Default for Product {
704 fn default() -> Self {
705 Self {
706 path: None,
707 name: None,
708 test: true,
709 doctest: true,
710 bench: true,
711 doc: true,
712 harness: true,
713 plugin: false,
714 proc_macro: false,
715 required_features: Vec::new(),
716 crate_type: None,
717 edition: Some(Edition::default()),
718 }
719 }
720}
721
722#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
723#[serde(rename_all = "kebab-case")]
724pub struct Target {
725 #[serde(default)]
726 pub dependencies: DepsSet,
727 #[serde(default, alias = "dev_dependencies")]
728 pub dev_dependencies: DepsSet,
729 #[serde(default, alias = "build_dependencies")]
730 pub build_dependencies: DepsSet,
731}
732
733#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
734#[serde(untagged)]
735pub enum Dependency {
736 Simple(String),
737 Inherited(InheritedDependencyDetail), Detailed(DependencyDetail),
739}
740
741impl Dependency {
742 pub fn detail(&self) -> Option<&DependencyDetail> {
743 match *self {
744 Dependency::Detailed(ref d) => Some(d),
745 _ => None,
746 }
747 }
748
749 pub fn simplify(self) -> Self {
752 match self {
753 Dependency::Detailed(details) => details.simplify(),
754 dep => dep,
755 }
756 }
757
758 pub fn req(&self) -> &str {
759 match *self {
760 Dependency::Simple(ref v) => v,
761 Dependency::Detailed(ref d) => d.version.as_deref().unwrap_or("*"),
762 Dependency::Inherited(_) => "*",
763 }
764 }
765
766 pub fn req_features(&self) -> &[String] {
767 match *self {
768 Dependency::Simple(_) => &[],
769 Dependency::Detailed(ref d) => d.features.as_deref().unwrap_or(&[]),
770 Dependency::Inherited(ref i) => i.features.as_deref().unwrap_or(&[]),
771 }
772 }
773
774 pub fn optional(&self) -> bool {
775 match *self {
776 Dependency::Simple(_) => false,
777 Dependency::Detailed(ref d) => d.optional.unwrap_or(false),
778 Dependency::Inherited(ref i) => i.optional.unwrap_or(false),
779 }
780 }
781
782 pub fn package(&self) -> Option<&str> {
785 match *self {
786 Dependency::Detailed(ref d) => d.package.as_deref(),
787 _ => None,
788 }
789 }
790
791 pub fn git(&self) -> Option<&str> {
793 self.detail().and_then(|d| d.git.as_deref())
794 }
795
796 pub fn is_crates_io(&self) -> bool {
799 match *self {
800 Dependency::Simple(_) => true,
801 Dependency::Detailed(ref d) => {
802 d.path.is_none()
804 && d.registry.is_none()
805 && d.registry_index.is_none()
806 && d.git.is_none()
807 && d.tag.is_none()
808 && d.branch.is_none()
809 && d.rev.is_none()
810 }
811 Dependency::Inherited(_) => false,
812 }
813 }
814}
815
816#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
817#[serde(rename_all = "kebab-case")]
818pub struct DependencyDetail {
819 #[serde(skip_serializing_if = "Option::is_none")]
820 pub version: Option<String>,
821 #[serde(skip_serializing_if = "Option::is_none")]
822 pub registry: Option<String>,
823 #[serde(alias = "registry_index")]
824 pub registry_index: Option<String>,
825 #[serde(skip_serializing_if = "Option::is_none")]
826 pub path: Option<String>,
827 #[serde(skip_serializing_if = "Option::is_none")]
828 pub git: Option<String>,
829 #[serde(skip_serializing_if = "Option::is_none")]
830 pub branch: Option<String>,
831 #[serde(skip_serializing_if = "Option::is_none")]
832 pub tag: Option<String>,
833 #[serde(skip_serializing_if = "Option::is_none")]
834 pub rev: Option<String>,
835 #[serde(default)]
836 #[serde(skip_serializing_if = "Option::is_none")]
837 pub features: Option<Vec<String>>,
838 #[serde(default)]
839 #[serde(skip_serializing_if = "Option::is_none")]
840 pub optional: Option<bool>,
841 #[serde(default, alias = "default_features")]
842 #[serde(skip_serializing_if = "Option::is_none")]
843 pub default_features: Option<bool>,
844 #[serde(skip_serializing_if = "Option::is_none")]
845 pub package: Option<String>,
846}
847
848impl DependencyDetail {
849 fn simplify(self) -> Dependency {
850 let Self {
851 version: _,
852 registry,
853 registry_index,
854 path,
855 git,
856 branch,
857 tag,
858 rev,
859 features,
860 optional,
861 default_features,
862 package,
863 } = &self;
864
865 if registry.is_some()
866 || registry_index.is_some()
867 || path.is_some()
868 || git.is_some()
869 || branch.is_some()
870 || tag.is_some()
871 || rev.is_some()
872 || features.is_some()
873 || optional.is_some()
874 || default_features.is_some()
875 || package.is_some()
876 {
877 return Dependency::Detailed(self);
878 }
879
880 match self.version {
881 None => Dependency::Detailed(self),
882 Some(version) => Dependency::Simple(version),
883 }
884 }
885}
886
887#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
890#[serde(rename_all = "kebab-case")]
891pub struct InheritedDependencyDetail {
892 pub workspace: True,
893
894 #[serde(default, skip_serializing_if = "Option::is_none")]
895 pub features: Option<Vec<String>>,
896
897 #[serde(default, skip_serializing_if = "Option::is_none")]
898 pub optional: Option<bool>,
899}
900
901#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq)]
909#[serde(untagged)]
910pub enum MaybeInherited<T> {
911 Inherited { workspace: True },
912 Local(T),
913}
914
915impl<T> MaybeInherited<T> {
916 pub fn inherited() -> Self {
917 Self::Inherited { workspace: True }
918 }
919
920 pub fn as_local(self) -> Option<T> {
921 match self {
922 Self::Local(x) => Some(x),
923 Self::Inherited { .. } => None,
924 }
925 }
926
927 pub const fn as_ref(&self) -> MaybeInherited<&T> {
928 match self {
929 Self::Local(ref x) => MaybeInherited::Local(x),
930 Self::Inherited { .. } => MaybeInherited::Inherited { workspace: True },
931 }
932 }
933}
934
935#[derive(Debug, Default, Clone, PartialEq, Eq)]
937#[doc(hidden)]
938pub struct True;
939
940impl Serialize for True {
941 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
942 where
943 S: Serializer,
944 {
945 serializer.serialize_bool(true)
946 }
947}
948
949impl<'de> Deserialize<'de> for True {
950 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
951 where
952 D: Deserializer<'de>,
953 {
954 if bool::deserialize(deserializer)? {
955 Ok(Self)
956 } else {
957 Err(D::Error::invalid_value(
958 Unexpected::Bool(false),
959 &"a `true` boolean value",
960 ))
961 }
962 }
963}
964
965#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
968pub struct Package<Metadata = Value> {
969 pub name: String,
971 #[serde(skip_serializing_if = "Option::is_none")]
972 pub edition: Option<MaybeInherited<Edition>>,
973 #[serde(skip_serializing_if = "Option::is_none")]
978 pub version: Option<MaybeInherited<String>>,
979 #[serde(skip_serializing_if = "Option::is_none")]
980 pub build: Option<StringOrBool>,
981 #[serde(skip_serializing_if = "Option::is_none")]
982 pub workspace: Option<String>,
983 #[serde(skip_serializing_if = "Option::is_none")]
984 pub authors: Option<MaybeInherited<Vec<String>>>,
986 #[serde(skip_serializing_if = "Option::is_none")]
987 pub links: Option<String>,
988 #[serde(skip_serializing_if = "Option::is_none")]
989 pub description: Option<MaybeInherited<String>>,
992 #[serde(skip_serializing_if = "Option::is_none")]
993 pub homepage: Option<MaybeInherited<String>>,
994 #[serde(skip_serializing_if = "Option::is_none")]
995 pub documentation: Option<MaybeInherited<String>>,
996 #[serde(skip_serializing_if = "Option::is_none")]
997 pub readme: Option<MaybeInherited<StringOrBool>>,
999 #[serde(skip_serializing_if = "Option::is_none")]
1000 pub keywords: Option<MaybeInherited<Vec<String>>>,
1001 #[serde(skip_serializing_if = "Option::is_none")]
1002 pub categories: Option<MaybeInherited<Vec<String>>>,
1005 #[serde(skip_serializing_if = "Option::is_none")]
1006 pub license: Option<MaybeInherited<String>>,
1008 #[serde(rename = "license-file")]
1009 #[serde(skip_serializing_if = "Option::is_none")]
1010 pub license_file: Option<MaybeInherited<String>>,
1011 #[serde(skip_serializing_if = "Option::is_none")]
1012 pub repository: Option<MaybeInherited<String>>,
1013 #[serde(skip_serializing_if = "Option::is_none")]
1014 pub metadata: Option<Metadata>,
1015 #[serde(rename = "rust-version")]
1017 pub rust_version: Option<MaybeInherited<String>>,
1018 #[serde(skip_serializing_if = "Option::is_none")]
1019 pub exclude: Option<MaybeInherited<Vec<String>>>,
1020 #[serde(skip_serializing_if = "Option::is_none")]
1021 pub include: Option<MaybeInherited<Vec<String>>>,
1022
1023 #[serde(rename = "default-run")]
1024 #[serde(skip_serializing_if = "Option::is_none")]
1025 pub default_run: Option<String>,
1027
1028 #[serde(skip_serializing_if = "Option::is_none")]
1030 pub autolib: Option<bool>,
1031 #[serde(skip_serializing_if = "Option::is_none")]
1035 pub autobins: Option<bool>,
1036 #[serde(skip_serializing_if = "Option::is_none")]
1040 pub autoexamples: Option<bool>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1045 pub autotests: Option<bool>,
1046 #[serde(skip_serializing_if = "Option::is_none")]
1050 pub autobenches: Option<bool>,
1051 #[serde(skip_serializing_if = "Option::is_none")]
1052 pub publish: Option<MaybeInherited<Publish>>,
1053 #[serde(skip_serializing_if = "Option::is_none")]
1054 pub resolver: Option<Resolver>,
1055}
1056
1057impl<Metadata> Package<Metadata> {
1058 pub fn new(name: String, version: String) -> Self {
1059 Self {
1060 name,
1061 edition: None,
1062 version: Some(MaybeInherited::Local(version)),
1063 build: None,
1064 workspace: None,
1065 authors: None,
1066 links: None,
1067 description: None,
1068 homepage: None,
1069 documentation: None,
1070 readme: None,
1071 keywords: None,
1072 categories: None,
1073 license: None,
1074 license_file: None,
1075 repository: None,
1076 metadata: None,
1077 rust_version: None,
1078 exclude: None,
1079 include: None,
1080 default_run: None,
1081 autolib: None,
1082 autobins: None,
1083 autoexamples: None,
1084 autotests: None,
1085 autobenches: None,
1086 publish: None,
1087 resolver: None,
1088 }
1089 }
1090
1091 pub fn version(&self) -> MaybeInherited<&str> {
1096 self.version
1097 .as_ref()
1098 .map(|v| match v {
1099 MaybeInherited::Local(v) => MaybeInherited::Local(v.as_str()),
1100 MaybeInherited::Inherited { .. } => MaybeInherited::Inherited { workspace: True },
1101 })
1102 .unwrap_or_else(|| MaybeInherited::Local("0.0.0"))
1103 }
1104
1105 fn uses_legacy_auto_discovery(&self) -> bool {
1116 matches!(
1117 self.edition,
1118 None | Some(MaybeInherited::Local(Edition::E2015))
1119 )
1120 }
1121}
1122
1123#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1124#[serde(untagged)]
1125pub enum StringOrBool {
1126 String(String),
1127 Bool(bool),
1128}
1129
1130#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
1131#[serde(untagged)]
1132pub enum Publish {
1133 Flag(bool),
1134 Registry(Vec<String>),
1135}
1136
1137impl Default for Publish {
1138 fn default() -> Self {
1139 Publish::Flag(true)
1140 }
1141}
1142
1143impl PartialEq<Publish> for bool {
1144 fn eq(&self, p: &Publish) -> bool {
1145 match *p {
1146 Publish::Flag(flag) => flag == *self,
1147 Publish::Registry(ref reg) => reg.is_empty() != *self,
1148 }
1149 }
1150}
1151
1152impl PartialEq<bool> for Publish {
1153 fn eq(&self, b: &bool) -> bool {
1154 b.eq(self)
1155 }
1156}
1157
1158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1159#[serde(rename_all = "kebab-case")]
1160pub struct Badge {
1161 pub repository: String,
1162 #[serde(default = "default_master")]
1163 pub branch: String,
1164 pub service: Option<String>,
1165 pub id: Option<String>,
1166 #[serde(alias = "project_name")]
1167 pub project_name: Option<String>,
1168}
1169
1170fn default_master() -> String {
1171 "master".to_string()
1172}
1173
1174#[allow(clippy::unnecessary_wraps)]
1175fn ok_or_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>
1176where
1177 T: Deserialize<'de> + Default,
1178 D: Deserializer<'de>,
1179{
1180 Ok(Deserialize::deserialize(deserializer).unwrap_or_default())
1181}
1182
1183fn toml_from_slice<T>(s: &'_ [u8]) -> Result<T, Error>
1184where
1185 T: serde::de::DeserializeOwned,
1186{
1187 Ok(toml::from_str(std::str::from_utf8(s)?)?)
1188}
1189
1190#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
1191#[serde(rename_all = "kebab-case")]
1192pub struct Badges {
1193 #[serde(default, deserialize_with = "ok_or_default")]
1199 pub appveyor: Option<Badge>,
1200
1201 #[serde(default, deserialize_with = "ok_or_default")]
1203 pub circle_ci: Option<Badge>,
1204
1205 #[serde(default, deserialize_with = "ok_or_default")]
1207 pub gitlab: Option<Badge>,
1208
1209 #[serde(default, deserialize_with = "ok_or_default")]
1212 pub travis_ci: Option<Badge>,
1213
1214 #[serde(default, deserialize_with = "ok_or_default")]
1218 pub codecov: Option<Badge>,
1219
1220 #[serde(default, deserialize_with = "ok_or_default")]
1223 pub coveralls: Option<Badge>,
1224
1225 #[serde(default, deserialize_with = "ok_or_default")]
1227 pub is_it_maintained_issue_resolution: Option<Badge>,
1228
1229 #[serde(default, deserialize_with = "ok_or_default")]
1231 pub is_it_maintained_open_issues: Option<Badge>,
1232
1233 #[serde(default, deserialize_with = "ok_or_default")]
1237 pub maintenance: Maintenance,
1238}
1239
1240#[derive(Debug, PartialEq, Eq, Copy, Clone, Default, Serialize, Deserialize)]
1241pub struct Maintenance {
1242 pub status: MaintenanceStatus,
1243}
1244
1245#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
1246#[serde(rename_all = "kebab-case")]
1247#[derive(Default)]
1248pub enum MaintenanceStatus {
1249 #[default]
1250 None,
1251 ActivelyDeveloped,
1252 PassivelyMaintained,
1253 AsIs,
1254 Experimental,
1255 LookingForMaintainer,
1256 Deprecated,
1257}
1258
1259#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize, Default)]
1260pub enum Edition {
1261 #[serde(rename = "2015")]
1262 #[default]
1263 E2015,
1264 #[serde(rename = "2018")]
1265 E2018,
1266 #[serde(rename = "2021")]
1267 E2021,
1268 #[serde(rename = "2024")]
1269 E2024,
1270}
1271
1272impl Edition {
1273 pub fn as_str(&self) -> &'static str {
1274 match self {
1275 Self::E2015 => "2015",
1276 Self::E2018 => "2018",
1277 Self::E2021 => "2021",
1278 Self::E2024 => "2024",
1279 }
1280 }
1281}
1282
1283#[derive(Debug, PartialEq, Eq, Default, Copy, Clone, Hash, Serialize, Deserialize)]
1284pub enum Resolver {
1285 #[serde(rename = "1")]
1286 #[default]
1287 V1,
1288 #[serde(rename = "2")]
1289 V2,
1290 #[serde(rename = "3")]
1291 V3,
1292}
1293
1294#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
1295#[serde(rename_all = "kebab-case")]
1296pub enum LintLevel {
1297 Forbid,
1298 Deny,
1299 Warn,
1300 Allow,
1301}
1302
1303impl LintLevel {
1304 pub fn as_str(&self) -> &'static str {
1305 match self {
1306 Self::Forbid => "forbid",
1307 Self::Deny => "deny",
1308 Self::Warn => "warn",
1309 Self::Allow => "allow",
1310 }
1311 }
1312}
1313
1314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1315#[serde(rename_all = "kebab-case")]
1316pub struct LintConfig {
1317 pub level: LintLevel,
1318 #[serde(default)]
1319 pub priority: i8,
1320}
1321
1322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1323#[serde(untagged)]
1324pub enum Lint {
1325 Level(LintLevel),
1326 Config(LintConfig),
1327}
1328
1329impl Lint {
1330 pub fn level(&self) -> LintLevel {
1331 match self {
1332 Self::Level(level) => *level,
1333 Self::Config(config) => config.level,
1334 }
1335 }
1336
1337 pub fn priority(&self) -> i8 {
1338 match self {
1339 Self::Level(_) => 0,
1340 Self::Config(config) => config.priority,
1341 }
1342 }
1343}
1344
1345#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1346pub struct MaybeInheritedLintsSet {
1347 #[serde(skip_serializing_if = "Option::is_none")]
1348 pub workspace: Option<True>,
1349 #[serde(flatten)]
1350 pub lints: LintsSet,
1351}
1352
1353impl MaybeInheritedLintsSet {
1354 pub fn is_inherited(&self) -> bool {
1355 matches!(&self.workspace, &Some(True))
1356 }
1357}
1358
1359#[cfg(test)]
1360mod tests {
1361 use super::*;
1362
1363 #[test]
1364 fn test_auto_discovery_defaults() {
1365 let mut manifest = Manifest {
1366 package: Some(Package::<()>::new("foo".into(), "1.0.0".into())),
1367 ..Default::default()
1368 };
1369 assert!(manifest.autobins());
1370 assert!(manifest.autotests());
1371 assert!(manifest.autoexamples());
1372 assert!(manifest.autobenches());
1373
1374 manifest.bin = vec![Product::default()];
1375 assert!(!manifest.autobins());
1376 assert!(manifest.autotests());
1377 assert!(manifest.autoexamples());
1378 assert!(manifest.autobenches());
1379
1380 manifest.package.as_mut().unwrap().autotests = Some(false);
1381 assert!(!manifest.autobins());
1382 assert!(!manifest.autotests());
1383 assert!(manifest.autoexamples());
1384 assert!(manifest.autobenches());
1385
1386 manifest.package.as_mut().unwrap().autobins = Some(true);
1387 assert!(manifest.autobins());
1388 assert!(!manifest.autotests());
1389 assert!(manifest.autoexamples());
1390 assert!(manifest.autobenches());
1391
1392 manifest.package.as_mut().unwrap().autobins = None;
1393 manifest.package.as_mut().unwrap().edition = Some(MaybeInherited::Local(Edition::E2018));
1394 assert!(manifest.autobins());
1395 assert!(!manifest.autotests());
1396 assert!(manifest.autoexamples());
1397 assert!(manifest.autobenches());
1398 }
1399
1400 #[test]
1401 fn test_legacy_auto_discovery_flag() {
1402 let mut package = Package::<()>::new("foo".into(), "1.0.0".into());
1403 assert!(package.uses_legacy_auto_discovery());
1404
1405 package.edition = Some(MaybeInherited::Local(Edition::E2015));
1406 assert!(package.uses_legacy_auto_discovery());
1407
1408 package.edition = Some(MaybeInherited::Local(Edition::E2018));
1409 assert!(!package.uses_legacy_auto_discovery());
1410
1411 package.edition = Some(MaybeInherited::Local(Edition::E2021));
1412 assert!(!package.uses_legacy_auto_discovery());
1413
1414 package.edition = Some(MaybeInherited::Inherited { workspace: True });
1415 assert!(!package.uses_legacy_auto_discovery());
1416 }
1417}