1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4use indexmap::IndexSet;
5use toml_edit::KeyMut;
6
7use super::manifest::str_or_1_len_table;
8use crate::CargoResult;
9
10#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
14#[non_exhaustive]
15pub struct Dependency {
16 pub name: String,
18 pub optional: Option<bool>,
20
21 pub features: Option<Vec<String>>,
23 pub default_features: Option<bool>,
25 pub inherited_features: Option<Vec<String>>,
27
28 pub source: Option<Source>,
30 pub registry: Option<String>,
32
33 pub rename: Option<String>,
36
37 pub available_features: BTreeMap<String, Vec<String>>,
39}
40
41impl Dependency {
42 pub fn new(name: &str) -> Self {
44 Self {
45 name: name.into(),
46 optional: None,
47 features: None,
48 default_features: None,
49 inherited_features: None,
50 source: None,
51 registry: None,
52 rename: None,
53 available_features: Default::default(),
54 }
55 }
56
57 pub fn set_source(mut self, source: impl Into<Source>) -> Self {
59 self.source = Some(source.into());
60 self
61 }
62
63 pub fn set_available_features(
65 mut self,
66 available_features: BTreeMap<String, Vec<String>>,
67 ) -> Self {
68 self.available_features = available_features;
69 self
70 }
71
72 #[allow(dead_code)]
74 pub fn set_optional(mut self, opt: bool) -> Self {
75 self.optional = Some(opt);
76 self
77 }
78
79 #[allow(dead_code)]
81 pub fn set_features(mut self, features: Vec<String>) -> Self {
82 self.features = Some(features);
83 self
84 }
85 pub fn extend_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
87 self.features
88 .get_or_insert_with(Default::default)
89 .extend(features);
90 self
91 }
92
93 #[allow(dead_code)]
95 pub fn set_default_features(mut self, default_features: bool) -> Self {
96 self.default_features = Some(default_features);
97 self
98 }
99
100 pub fn set_rename(mut self, rename: &str) -> Self {
102 self.rename = Some(rename.into());
103 self
104 }
105
106 pub fn set_registry(mut self, registry: impl Into<String>) -> Self {
108 self.registry = Some(registry.into());
109 self
110 }
111
112 pub fn set_inherited_features(mut self, features: Vec<String>) -> Self {
114 self.inherited_features = Some(features);
115 self
116 }
117
118 pub fn source(&self) -> Option<&Source> {
120 self.source.as_ref()
121 }
122
123 pub fn version(&self) -> Option<&str> {
125 match self.source()? {
126 Source::Registry(src) => Some(src.version.as_str()),
127 Source::Path(src) => src.version.as_deref(),
128 Source::Git(src) => src.version.as_deref(),
129 Source::Workspace(_) => None,
130 }
131 }
132
133 pub fn registry(&self) -> Option<&str> {
135 self.registry.as_deref()
136 }
137
138 pub fn rename(&self) -> Option<&str> {
140 self.rename.as_deref()
141 }
142
143 pub fn default_features(&self) -> Option<bool> {
145 self.default_features
146 }
147
148 pub fn optional(&self) -> Option<bool> {
150 self.optional
151 }
152}
153
154impl Dependency {
155 pub fn from_toml(crate_root: &Path, key: &str, item: &toml_edit::Item) -> CargoResult<Self> {
157 if let Some(version) = item.as_str() {
158 let dep = Self::new(key).set_source(RegistrySource::new(version));
159 Ok(dep)
160 } else if let Some(table) = item.as_table_like() {
161 let (name, rename) = if let Some(value) = table.get("package") {
162 (
163 value
164 .as_str()
165 .ok_or_else(|| invalid_type(key, "package", value.type_name(), "string"))?
166 .to_owned(),
167 Some(key.to_owned()),
168 )
169 } else {
170 (key.to_owned(), None)
171 };
172
173 let source: Source =
174 if let Some(git) = table.get("git") {
175 let mut src = GitSource::new(
176 git.as_str()
177 .ok_or_else(|| invalid_type(key, "git", git.type_name(), "string"))?,
178 );
179 if let Some(value) = table.get("branch") {
180 src = src.set_branch(value.as_str().ok_or_else(|| {
181 invalid_type(key, "branch", value.type_name(), "string")
182 })?);
183 }
184 if let Some(value) = table.get("tag") {
185 src = src.set_tag(value.as_str().ok_or_else(|| {
186 invalid_type(key, "tag", value.type_name(), "string")
187 })?);
188 }
189 if let Some(value) = table.get("rev") {
190 src = src.set_rev(value.as_str().ok_or_else(|| {
191 invalid_type(key, "rev", value.type_name(), "string")
192 })?);
193 }
194 if let Some(value) = table.get("version") {
195 src = src.set_version(value.as_str().ok_or_else(|| {
196 invalid_type(key, "version", value.type_name(), "string")
197 })?);
198 }
199 src.into()
200 } else if let Some(path) = table.get("path") {
201 let path = crate_root
202 .join(path.as_str().ok_or_else(|| {
203 invalid_type(key, "path", path.type_name(), "string")
204 })?);
205 let mut src = PathSource::new(path);
206 if let Some(value) = table.get("version") {
207 src = src.set_version(value.as_str().ok_or_else(|| {
208 invalid_type(key, "version", value.type_name(), "string")
209 })?);
210 }
211 src.into()
212 } else if let Some(version) = table.get("version") {
213 let src = RegistrySource::new(version.as_str().ok_or_else(|| {
214 invalid_type(key, "version", version.type_name(), "string")
215 })?);
216 src.into()
217 } else if let Some(workspace) = table.get("workspace") {
218 let workspace_bool = workspace.as_bool().ok_or_else(|| {
219 invalid_type(key, "workspace", workspace.type_name(), "bool")
220 })?;
221 if !workspace_bool {
222 anyhow::bail!("`{key}.workspace = false` is unsupported")
223 }
224 let src = WorkspaceSource::new();
225 src.into()
226 } else {
227 anyhow::bail!("Unrecognized dependency source for `{key}`");
228 };
229 let registry = if let Some(value) = table.get("registry") {
230 Some(
231 value
232 .as_str()
233 .ok_or_else(|| invalid_type(key, "registry", value.type_name(), "string"))?
234 .to_owned(),
235 )
236 } else {
237 None
238 };
239
240 let default_features = table.get("default-features").and_then(|v| v.as_bool());
241 if table.contains_key("default_features") {
242 anyhow::bail!(
243 "Use of `default_features` in `{key}` is unsupported, please switch to `default-features`"
244 );
245 }
246
247 let features = if let Some(value) = table.get("features") {
248 Some(
249 value
250 .as_array()
251 .ok_or_else(|| invalid_type(key, "features", value.type_name(), "array"))?
252 .iter()
253 .map(|v| {
254 v.as_str().map(|s| s.to_owned()).ok_or_else(|| {
255 invalid_type(key, "features", v.type_name(), "string")
256 })
257 })
258 .collect::<CargoResult<Vec<String>>>()?,
259 )
260 } else {
261 None
262 };
263
264 let available_features = BTreeMap::default();
265
266 let optional = table.get("optional").and_then(|v| v.as_bool());
267
268 let dep = Self {
269 name,
270 rename,
271 source: Some(source),
272 registry,
273 default_features,
274 features,
275 available_features,
276 optional,
277 inherited_features: None,
278 };
279 Ok(dep)
280 } else {
281 anyhow::bail!("Unrecognized` dependency entry format for `{key}");
282 }
283 }
284
285 pub fn toml_key(&self) -> &str {
289 self.rename().unwrap_or(&self.name)
290 }
291
292 pub fn to_toml(&self, crate_root: &Path) -> toml_edit::Item {
303 assert!(
304 crate_root.is_absolute(),
305 "Absolute path needed, got: {}",
306 crate_root.display()
307 );
308 let table: toml_edit::Item = match (
309 self.optional.unwrap_or(false),
310 self.features.as_ref(),
311 self.default_features.unwrap_or(true),
312 self.source.as_ref(),
313 self.registry.as_ref(),
314 self.rename.as_ref(),
315 ) {
316 (
318 false,
319 None,
320 true,
321 Some(Source::Registry(RegistrySource { version: v })),
322 None,
323 None,
324 ) => toml_edit::value(v),
325 (false, None, true, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
326 let mut table = toml_edit::InlineTable::default();
327 table.set_dotted(true);
328 table.insert("workspace", true.into());
329 toml_edit::value(toml_edit::Value::InlineTable(table))
330 }
331 (_, _, _, _, _, _) => {
333 let mut table = toml_edit::InlineTable::default();
334
335 match &self.source {
336 Some(Source::Registry(src)) => {
337 table.insert("version", src.version.as_str().into());
338 }
339 Some(Source::Path(src)) => {
340 let relpath = path_field(crate_root, &src.path);
341 if let Some(r) = src.version.as_deref() {
342 table.insert("version", r.into());
343 }
344 table.insert("path", relpath.into());
345 }
346 Some(Source::Git(src)) => {
347 table.insert("git", src.git.as_str().into());
348 if let Some(branch) = src.branch.as_deref() {
349 table.insert("branch", branch.into());
350 }
351 if let Some(tag) = src.tag.as_deref() {
352 table.insert("tag", tag.into());
353 }
354 if let Some(rev) = src.rev.as_deref() {
355 table.insert("rev", rev.into());
356 }
357 if let Some(r) = src.version.as_deref() {
358 table.insert("version", r.into());
359 }
360 }
361 Some(Source::Workspace(_)) => {
362 table.insert("workspace", true.into());
363 }
364 None => {}
365 }
366 if table.contains_key("version") {
367 if let Some(r) = self.registry.as_deref() {
368 table.insert("registry", r.into());
369 }
370 }
371
372 if self.rename.is_some() {
373 table.insert("package", self.name.as_str().into());
374 }
375 if let Some(v) = self.default_features {
376 table.insert("default-features", v.into());
377 }
378 if let Some(features) = self.features.as_ref() {
379 let features: toml_edit::Value = features.iter().cloned().collect();
380 table.insert("features", features);
381 }
382 if let Some(v) = self.optional {
383 table.insert("optional", v.into());
384 }
385
386 toml_edit::value(toml_edit::Value::InlineTable(table))
387 }
388 };
389
390 table
391 }
392
393 pub fn update_toml(&self, crate_root: &Path, key: &mut KeyMut, item: &mut toml_edit::Item) {
395 if str_or_1_len_table(item) {
396 *item = self.to_toml(crate_root);
398 key.fmt();
399 } else if let Some(table) = item.as_table_like_mut() {
400 match &self.source {
401 Some(Source::Registry(src)) => {
402 overwrite_value(table, "version", src.version.as_str());
403
404 for key in ["path", "git", "branch", "tag", "rev", "workspace"] {
405 table.remove(key);
406 }
407 }
408 Some(Source::Path(src)) => {
409 let relpath = path_field(crate_root, &src.path);
410 overwrite_value(table, "path", relpath);
411 if let Some(r) = src.version.as_deref() {
412 overwrite_value(table, "version", r);
413 } else {
414 table.remove("version");
415 }
416
417 for key in ["git", "branch", "tag", "rev", "workspace"] {
418 table.remove(key);
419 }
420 }
421 Some(Source::Git(src)) => {
422 overwrite_value(table, "git", src.git.as_str());
423 if let Some(branch) = src.branch.as_deref() {
424 overwrite_value(table, "branch", branch);
425 } else {
426 table.remove("branch");
427 }
428 if let Some(tag) = src.tag.as_deref() {
429 overwrite_value(table, "tag", tag);
430 } else {
431 table.remove("tag");
432 }
433 if let Some(rev) = src.rev.as_deref() {
434 overwrite_value(table, "rev", rev);
435 } else {
436 table.remove("rev");
437 }
438 if let Some(r) = src.version.as_deref() {
439 overwrite_value(table, "version", r);
440 } else {
441 table.remove("version");
442 }
443
444 for key in ["path", "workspace"] {
445 table.remove(key);
446 }
447 }
448 Some(Source::Workspace(_)) => {
449 overwrite_value(table, "workspace", true);
450 table.set_dotted(true);
451 key.fmt();
452 for key in [
453 "version",
454 "registry",
455 "registry-index",
456 "path",
457 "git",
458 "branch",
459 "tag",
460 "rev",
461 "package",
462 "default-features",
463 ] {
464 table.remove(key);
465 }
466 }
467 None => {}
468 }
469 if table.contains_key("version") {
470 if let Some(r) = self.registry.as_deref() {
471 overwrite_value(table, "registry", r);
472 } else {
473 table.remove("registry");
474 }
475 } else {
476 table.remove("registry");
477 }
478
479 if self.rename.is_some() {
480 overwrite_value(table, "package", self.name.as_str());
481 }
482 match self.default_features {
483 Some(v) => {
484 overwrite_value(table, "default-features", v);
485 }
486 None => {
487 table.remove("default-features");
488 }
489 }
490 if let Some(new_features) = self.features.as_ref() {
491 let mut features = table
492 .get("features")
493 .and_then(|i| i.as_value())
494 .and_then(|v| v.as_array())
495 .and_then(|a| {
496 a.iter()
497 .map(|v| v.as_str())
498 .collect::<Option<IndexSet<_>>>()
499 })
500 .unwrap_or_default();
501 features.extend(new_features.iter().map(|s| s.as_str()));
502 let features = features.into_iter().collect::<toml_edit::Value>();
503 table.set_dotted(false);
504 overwrite_value(table, "features", features);
505 } else {
506 table.remove("features");
507 }
508 match self.optional {
509 Some(v) => {
510 table.set_dotted(false);
511 overwrite_value(table, "optional", v);
512 }
513 None => {
514 table.remove("optional");
515 }
516 }
517 } else {
518 unreachable!("Invalid dependency type: {}", item.type_name());
519 }
520 }
521}
522
523impl Default for WorkspaceSource {
524 fn default() -> Self {
525 Self::new()
526 }
527}
528
529fn overwrite_value(
531 table: &mut dyn toml_edit::TableLike,
532 key: &str,
533 value: impl Into<toml_edit::Value>,
534) {
535 let mut value = value.into();
536
537 let existing = table.entry(key).or_insert(toml_edit::Item::None);
538 let existing_decor = existing
539 .as_value()
540 .map(|v| v.decor().clone())
541 .unwrap_or_default();
542
543 *value.decor_mut() = existing_decor;
544
545 *existing = toml_edit::Item::Value(value);
546}
547
548fn invalid_type(dep: &str, key: &str, actual: &str, expected: &str) -> anyhow::Error {
549 anyhow::format_err!("Found {actual} for {key} when {expected} was expected for {dep}")
550}
551
552impl std::fmt::Display for Dependency {
553 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554 if let Some(source) = self.source() {
555 write!(f, "{}@{}", self.name, source)
556 } else {
557 self.toml_key().fmt(f)
558 }
559 }
560}
561
562fn path_field(crate_root: &Path, abs_path: &Path) -> String {
563 let relpath = pathdiff::diff_paths(abs_path, crate_root).expect("both paths are absolute");
564 let relpath = relpath.to_str().unwrap().replace('\\', "/");
565 relpath
566}
567
568#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
570pub enum Source {
571 Registry(RegistrySource),
573 Path(PathSource),
575 Git(GitSource),
577 Workspace(WorkspaceSource),
579}
580
581impl Source {
582 pub fn as_registry(&self) -> Option<&RegistrySource> {
584 match self {
585 Self::Registry(src) => Some(src),
586 _ => None,
587 }
588 }
589
590 #[allow(dead_code)]
592 pub fn as_path(&self) -> Option<&PathSource> {
593 match self {
594 Self::Path(src) => Some(src),
595 _ => None,
596 }
597 }
598
599 #[allow(dead_code)]
601 pub fn as_git(&self) -> Option<&GitSource> {
602 match self {
603 Self::Git(src) => Some(src),
604 _ => None,
605 }
606 }
607
608 #[allow(dead_code)]
610 pub fn as_workspace(&self) -> Option<&WorkspaceSource> {
611 match self {
612 Self::Workspace(src) => Some(src),
613 _ => None,
614 }
615 }
616}
617
618impl std::fmt::Display for Source {
619 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
620 match self {
621 Self::Registry(src) => src.fmt(f),
622 Self::Path(src) => src.fmt(f),
623 Self::Git(src) => src.fmt(f),
624 Self::Workspace(src) => src.fmt(f),
625 }
626 }
627}
628
629impl<'s> From<&'s Source> for Source {
630 fn from(inner: &'s Source) -> Self {
631 inner.clone()
632 }
633}
634
635impl From<RegistrySource> for Source {
636 fn from(inner: RegistrySource) -> Self {
637 Self::Registry(inner)
638 }
639}
640
641impl From<PathSource> for Source {
642 fn from(inner: PathSource) -> Self {
643 Self::Path(inner)
644 }
645}
646
647impl From<GitSource> for Source {
648 fn from(inner: GitSource) -> Self {
649 Self::Git(inner)
650 }
651}
652
653impl From<WorkspaceSource> for Source {
654 fn from(inner: WorkspaceSource) -> Self {
655 Self::Workspace(inner)
656 }
657}
658
659#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
661#[non_exhaustive]
662pub struct RegistrySource {
663 pub version: String,
665}
666
667impl RegistrySource {
668 pub fn new(version: impl AsRef<str>) -> Self {
670 let version = version.as_ref().split('+').next().unwrap();
674 Self {
675 version: version.to_owned(),
676 }
677 }
678}
679
680impl std::fmt::Display for RegistrySource {
681 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
682 self.version.fmt(f)
683 }
684}
685
686#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
688#[non_exhaustive]
689pub struct PathSource {
690 pub path: PathBuf,
692 pub version: Option<String>,
694}
695
696impl PathSource {
697 pub fn new(path: impl Into<PathBuf>) -> Self {
699 Self {
700 path: path.into(),
701 version: None,
702 }
703 }
704
705 pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
707 let version = version.as_ref().split('+').next().unwrap();
711 self.version = Some(version.to_owned());
712 self
713 }
714}
715
716impl std::fmt::Display for PathSource {
717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 self.path.display().fmt(f)
719 }
720}
721
722#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
724#[non_exhaustive]
725pub struct GitSource {
726 pub git: String,
728 pub branch: Option<String>,
730 pub tag: Option<String>,
732 pub rev: Option<String>,
734 pub version: Option<String>,
736}
737
738impl GitSource {
739 pub fn new(git: impl Into<String>) -> Self {
741 Self {
742 git: git.into(),
743 branch: None,
744 tag: None,
745 rev: None,
746 version: None,
747 }
748 }
749
750 pub fn set_branch(mut self, branch: impl Into<String>) -> Self {
752 self.branch = Some(branch.into());
753 self.tag = None;
754 self.rev = None;
755 self
756 }
757
758 pub fn set_tag(mut self, tag: impl Into<String>) -> Self {
760 self.branch = None;
761 self.tag = Some(tag.into());
762 self.rev = None;
763 self
764 }
765
766 pub fn set_rev(mut self, rev: impl Into<String>) -> Self {
768 self.branch = None;
769 self.tag = None;
770 self.rev = Some(rev.into());
771 self
772 }
773
774 pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
776 let version = version.as_ref().split('+').next().unwrap();
780 self.version = Some(version.to_owned());
781 self
782 }
783}
784
785impl std::fmt::Display for GitSource {
786 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
787 write!(f, "{}", self.git)?;
788 if let Some(branch) = &self.branch {
789 write!(f, "?branch={branch}")?;
790 } else if let Some(tag) = &self.tag {
791 write!(f, "?tag={tag}")?;
792 } else if let Some(rev) = &self.rev {
793 write!(f, "?rev={rev}")?;
794 }
795 Ok(())
796 }
797}
798
799#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
801#[non_exhaustive]
802pub struct WorkspaceSource;
803
804impl WorkspaceSource {
805 pub fn new() -> Self {
806 Self
807 }
808}
809
810impl std::fmt::Display for WorkspaceSource {
811 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812 "workspace".fmt(f)
813 }
814}
815
816#[cfg(test)]
817mod tests {
818 use std::path::Path;
819
820 use super::*;
821
822 #[test]
823 fn to_toml_simple_dep() {
824 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
825 .expect("root exists");
826 let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
827 let key = dep.toml_key();
828 let item = dep.to_toml(&crate_root);
829
830 assert_eq!(key, "dep".to_owned());
831
832 verify_roundtrip(&crate_root, key, &item);
833 }
834
835 #[test]
836 fn to_toml_simple_dep_with_version() {
837 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
838 .expect("root exists");
839 let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
840 let key = dep.toml_key();
841 let item = dep.to_toml(&crate_root);
842
843 assert_eq!(key, "dep".to_owned());
844 assert_eq!(item.as_str(), Some("1.0"));
845
846 verify_roundtrip(&crate_root, key, &item);
847 }
848
849 #[test]
850 fn to_toml_optional_dep() {
851 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
852 .expect("root exists");
853 let dep = Dependency::new("dep")
854 .set_source(RegistrySource::new("1.0"))
855 .set_optional(true);
856 let key = dep.toml_key();
857 let item = dep.to_toml(&crate_root);
858
859 assert_eq!(key, "dep".to_owned());
860 assert!(item.is_inline_table());
861
862 let dep = item.as_inline_table().unwrap();
863 assert_eq!(dep.get("optional").unwrap().as_bool(), Some(true));
864
865 verify_roundtrip(&crate_root, key, &item);
866 }
867
868 #[test]
869 fn to_toml_dep_without_default_features() {
870 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
871 .expect("root exists");
872 let dep = Dependency::new("dep")
873 .set_source(RegistrySource::new("1.0"))
874 .set_default_features(false);
875 let key = dep.toml_key();
876 let item = dep.to_toml(&crate_root);
877
878 assert_eq!(key, "dep".to_owned());
879 assert!(item.is_inline_table());
880
881 let dep = item.as_inline_table().unwrap();
882 assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
883
884 verify_roundtrip(&crate_root, key, &item);
885 }
886
887 #[test]
888 fn to_toml_dep_with_path_source() {
889 let root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
890 .expect("root exists");
891 let crate_root = root.join("foo");
892 let dep = Dependency::new("dep").set_source(PathSource::new(root.join("bar")));
893 let key = dep.toml_key();
894 let item = dep.to_toml(&crate_root);
895
896 assert_eq!(key, "dep".to_owned());
897 assert!(item.is_inline_table());
898
899 let dep = item.as_inline_table().unwrap();
900 assert_eq!(dep.get("path").unwrap().as_str(), Some("../bar"));
901
902 verify_roundtrip(&crate_root, key, &item);
903 }
904
905 #[test]
906 fn to_toml_dep_with_git_source() {
907 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
908 .expect("root exists");
909 let dep = Dependency::new("dep").set_source(GitSource::new("https://foor/bar.git"));
910 let key = dep.toml_key();
911 let item = dep.to_toml(&crate_root);
912
913 assert_eq!(key, "dep".to_owned());
914 assert!(item.is_inline_table());
915
916 let dep = item.as_inline_table().unwrap();
917 assert_eq!(
918 dep.get("git").unwrap().as_str(),
919 Some("https://foor/bar.git")
920 );
921
922 verify_roundtrip(&crate_root, key, &item);
923 }
924
925 #[test]
926 fn to_toml_renamed_dep() {
927 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
928 .expect("root exists");
929 let dep = Dependency::new("dep")
930 .set_source(RegistrySource::new("1.0"))
931 .set_rename("d");
932 let key = dep.toml_key();
933 let item = dep.to_toml(&crate_root);
934
935 assert_eq!(key, "d".to_owned());
936 assert!(item.is_inline_table());
937
938 let dep = item.as_inline_table().unwrap();
939 assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
940
941 verify_roundtrip(&crate_root, key, &item);
942 }
943
944 #[test]
945 fn to_toml_dep_from_alt_registry() {
946 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
947 .expect("root exists");
948 let dep = Dependency::new("dep")
949 .set_source(RegistrySource::new("1.0"))
950 .set_registry("alternative");
951 let key = dep.toml_key();
952 let item = dep.to_toml(&crate_root);
953
954 assert_eq!(key, "dep".to_owned());
955 assert!(item.is_inline_table());
956
957 let dep = item.as_inline_table().unwrap();
958 assert_eq!(dep.get("registry").unwrap().as_str(), Some("alternative"));
959
960 verify_roundtrip(&crate_root, key, &item);
961 }
962
963 #[test]
964 fn to_toml_complex_dep() {
965 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
966 .expect("root exists");
967 let dep = Dependency::new("dep")
968 .set_source(RegistrySource::new("1.0"))
969 .set_default_features(false)
970 .set_rename("d");
971 let key = dep.toml_key();
972 let item = dep.to_toml(&crate_root);
973
974 assert_eq!(key, "d".to_owned());
975 assert!(item.is_inline_table());
976
977 let dep = item.as_inline_table().unwrap();
978 assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
979 assert_eq!(dep.get("version").unwrap().as_str(), Some("1.0"));
980 assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
981
982 verify_roundtrip(&crate_root, key, &item);
983 }
984
985 #[test]
986 fn paths_with_forward_slashes_are_left_as_is() {
987 let crate_root = dunce::canonicalize(std::env::current_dir().unwrap().join(Path::new("/")))
988 .expect("root exists");
989 let path = crate_root.join("sibling/crate");
990 let relpath = "sibling/crate";
991 let dep = Dependency::new("dep").set_source(PathSource::new(path));
992 let key = dep.toml_key();
993 let item = dep.to_toml(&crate_root);
994
995 let table = item.as_inline_table().unwrap();
996 let got = table.get("path").unwrap().as_str().unwrap();
997 assert_eq!(got, relpath);
998
999 verify_roundtrip(&crate_root, key, &item);
1000 }
1001
1002 #[test]
1003 #[cfg(windows)]
1004 fn normalise_windows_style_paths() {
1005 let crate_root =
1006 dunce::canonicalize(&std::env::current_dir().unwrap().join(Path::new("/")))
1007 .expect("root exists");
1008 let original = crate_root.join(r"sibling\crate");
1009 let should_be = "sibling/crate";
1010 let dep = Dependency::new("dep").set_source(PathSource::new(original));
1011 let key = dep.toml_key();
1012 let item = dep.to_toml(&crate_root);
1013
1014 let table = item.as_inline_table().unwrap();
1015 let got = table.get("path").unwrap().as_str().unwrap();
1016 assert_eq!(got, should_be);
1017
1018 verify_roundtrip(&crate_root, key, &item);
1019 }
1020
1021 #[track_caller]
1022 fn verify_roundtrip(crate_root: &Path, key: &str, item: &toml_edit::Item) {
1023 let roundtrip = Dependency::from_toml(crate_root, key, item).unwrap();
1024 let round_key = roundtrip.toml_key();
1025 let round_item = roundtrip.to_toml(crate_root);
1026 assert_eq!(key, round_key);
1027 assert_eq!(item.to_string(), round_item.to_string());
1028 }
1029}