// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Utilities for parsing and generating Cargo.toml files.
use std::collections::BTreeMap;
use semver::Version;
use serde::{Deserialize, Serialize};
/// Set of dependencies for a particular usage: final artifacts, tests, or
/// build scripts.
pub type DependencySet<Type> = BTreeMap<String, Type>;
/// A version constraint in a dependency spec. We don't use `semver::VersionReq`
/// since we only pass it through opaquely from third_party.toml to Cargo.toml.
/// Parsing it is unnecessary.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
// From serde's perspective we serialize and deserialize this as a plain string.
#[serde(transparent)]
pub struct VersionConstraint(pub String);
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct WorkspaceSpec {
pub members: Vec<String>,
}
/// A single crate dependency. Cargo.toml and third_party.toml have different
/// version formats and some different fields. This is generic to share the same
/// type between them.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum Dependency<VersionType, DepType> {
/// A dependency of the form `foo = "1.0.11"`: just the package name as key
/// and the version as value. The sole field is the crate version.
Short(VersionType),
/// A dependency that specifies other fields in the form of `foo = { ... }`
/// or `[dependencies.foo] ... `.
Full(DepType),
}
/// A single Cargo.toml dependency.
pub type CargoDependency = Dependency<String, CargoFullDependency>;
pub type CargoDependencySet = DependencySet<CargoDependency>;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct CargoFullDependency {
/// Include the package's default features. Influences Cargo behavior.
#[serde(default = "get_true", skip_serializing_if = "is_true")]
pub default_features: bool,
/// Version constraint on dependency.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<VersionConstraint>,
/// Required features.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub features: Vec<String>,
}
impl Default for CargoFullDependency {
fn default() -> Self {
Self { default_features: true, version: None, features: vec![] }
}
}
/// Representation of a Cargo.toml file.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct CargoManifest {
pub package: CargoPackage,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workspace: Option<WorkspaceSpec>,
/// Regular dependencies built into production code.
#[serde(
default,
skip_serializing_if = "DependencySet::is_empty",
serialize_with = "toml::ser::tables_last"
)]
pub dependencies: CargoDependencySet,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct CargoPackage {
pub name: String,
pub version: Version,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub authors: Vec<String>,
#[serde(default)]
pub edition: Edition,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub include: Vec<String>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub license: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(transparent)]
pub struct Edition(pub String);
impl Default for Edition {
fn default() -> Self {
Edition("2015".to_string())
}
}
impl std::fmt::Display for Edition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct CargoPatch {
pub path: String,
pub package: String,
}
// Used to set the serde default of a field to true.
fn get_true() -> bool {
true
}
fn is_true(b: &bool) -> bool {
*b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn package_manifest() {
let manifest: CargoManifest = toml::de::from_str(concat!(
"[package]
name = \"foo\"
version = \"1.2.3\"
authors = [\"[email protected]\", \"[email protected]\"]
edition = \"2021\"
description = \"A library to foo the bars\"
license = \"funtimes\"
"
))
.unwrap();
assert_eq!(
manifest.package,
CargoPackage {
name: "foo".to_string(),
version: Version::new(1, 2, 3),
authors: vec!["[email protected]".to_string(), "[email protected]".to_string()],
edition: Edition("2021".to_string()),
description: Some("A library to foo the bars".to_string()),
license: "funtimes".to_string(),
include: Vec::new(),
}
)
}
}