Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

deserialize [package.metadata] and [workspace.metadata] tables #798

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 161 additions & 2 deletions src/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use std::collections::BTreeMap;

use anyhow::Context;
use serde::Deserialize;

use crate::{LintLevel, OverrideMap, QueryOverride, RequiredSemverUpdate};

#[derive(Debug, Clone)]
pub(crate) struct Manifest {
pub(crate) path: std::path::PathBuf,
pub(crate) parsed: cargo_toml::Manifest,
pub(crate) parsed: cargo_toml::Manifest<LintTable>,
}

impl Manifest {
pub(crate) fn parse(path: std::path::PathBuf) -> anyhow::Result<Self> {
// Parsing via `cargo_toml::Manifest::from_path()` is preferable to parsing from a string,
// because inspection of surrounding files is sometimes necessary to determine
// the existence of lib targets and ensure proper handling of workspace inheritance.
let parsed = cargo_toml::Manifest::from_path(&path)
let parsed = cargo_toml::Manifest::from_path_with_metadata(&path)
.with_context(|| format!("failed when reading {}", path.display()))?;

Ok(Self { path, parsed })
Expand Down Expand Up @@ -57,3 +62,157 @@ pub(crate) fn get_project_dir_from_manifest_path(
.context("manifest path doesn't have a parent")?;
Ok(dir_path.to_path_buf())
}

/// A [package.metadata] or [workspace.metadata] table with
/// `cargo-semver-checks` lint entries stored in `config`
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct LintTable {
#[serde(default, rename = "cargo-semver-checks")]
pub(crate) config: Option<BTreeMap<String, OverrideConfig>>,
}

impl LintTable {
#[allow(dead_code)] // TODO: remove when integrated
pub fn into_overrides(self) -> Option<OverrideMap> {
self.config
.map(|config| config.into_iter().map(|(k, v)| (k, v.into())).collect())
}
}

/// Different valid representations of a [`QueryOverride`] in the Cargo.toml configuration table
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged, rename_all = "kebab-case")]
pub(crate) enum OverrideConfig {
/// Specify members by name, e.g.
/// `lint_name = { lint-level = "deny", required-update = "major" }
/// Any omitted members will default to `None`
Structure(QueryOverride),
/// Shorthand for specifying just a lint level and leaving
/// the other members as default: e.g.,
/// `lint_name = "deny"`
LintLevel(LintLevel),
/// Shorthand for specifying just a required version bump and leaving
/// the other members as default: e.g.,
/// `lint_name = "allow"`
RequiredUpdate(RequiredSemverUpdate),
}

impl From<OverrideConfig> for QueryOverride {
fn from(value: OverrideConfig) -> Self {
match value {
OverrideConfig::Structure(x) => x,
OverrideConfig::LintLevel(lint_level) => Self {
lint_level: Some(lint_level),
required_update: None,
},
OverrideConfig::RequiredUpdate(required_update) => Self {
lint_level: None,
required_update: Some(required_update),
},
}
}
}

#[cfg(test)]
mod tests {
use crate::{manifest::OverrideConfig, QueryOverride};

use super::LintTable;

#[test]
fn test_deserialize_config() {
use crate::LintLevel::*;
use crate::RequiredSemverUpdate::*;
use OverrideConfig::*;
let manifest = r#"[package]
name = "cargo-semver-checks"
version = "1.2.3"
edition = "2021"

[package.metadata.cargo-semver-checks]
one = "major"
two = "deny"
three = { lint-level = "warn" }
four = { required-update = "major" }
five = { required-update = "minor", lint-level = "allow" }

[workspace.metadata.cargo-semver-checks]
six = "allow"
"#;

let parsed = cargo_toml::Manifest::from_slice_with_metadata(manifest.as_bytes())
.expect("Cargo.toml should be valid");
let package_metadata: LintTable = parsed
.package
.expect("Cargo.toml should contain a package")
.metadata
.expect("Package metadata should be present");

let workspace_metadata = parsed
.workspace
.expect("Cargo.toml should contain a workspace")
.metadata
.expect("Workspace metadata should be present");

let pkg = package_metadata
.config
.expect("Lint table should be present");
let wks = workspace_metadata
.config
.expect("Lint table should be present");
assert!(
matches!(pkg.get("one"), Some(&RequiredUpdate(Major))),
"got {:?}",
pkg.get("one")
);

assert!(
matches!(pkg.get("two"), Some(&LintLevel(Deny))),
"got {:?}",
pkg.get("two")
);

assert!(
matches!(
pkg.get("three"),
Some(&Structure(QueryOverride {
required_update: None,
lint_level: Some(Warn)
}))
),
"got {:?}",
pkg.get("three")
);

assert!(
matches!(
pkg.get("four"),
Some(&Structure(QueryOverride {
required_update: Some(Major),
lint_level: None,
}))
),
"got {:?}",
pkg.get("four")
);

//
assert!(
matches!(
pkg.get("five"),
Some(&Structure(QueryOverride {
required_update: Some(Minor),
lint_level: Some(Allow)
}))
),
"got {:?}",
pkg.get("five")
);

assert!(
matches!(wks.get("six"), Some(&LintLevel(Allow))),
"got {:?}",
wks.get("six")
);
}
}
10 changes: 9 additions & 1 deletion src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use crate::ReleaseType;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RequiredSemverUpdate {
#[serde(alias = "major")]
Major,
#[serde(alias = "minor")]
Minor,
}

Expand All @@ -34,10 +36,13 @@ impl From<RequiredSemverUpdate> for ReleaseType {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum LintLevel {
/// If this lint occurs, do nothing.
#[serde(alias = "allow")]
Allow,
/// If this lint occurs, print a warning.
#[serde(alias = "warn")]
Warn,
/// If this lint occurs, raise an error.
#[serde(alias = "deny")]
Deny,
}

Expand Down Expand Up @@ -142,18 +147,21 @@ Failed to parse a query: {e}
}

/// Configured values for a [`SemverQuery`] that differ from the lint's defaults.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct QueryOverride {
/// The required version bump for this lint; see [`SemverQuery`].`required_update`.
///
/// If this is `None`, use the query's default `required_update` when calculating
/// the effective required version bump.
#[serde(default)]
pub required_update: Option<RequiredSemverUpdate>,

/// The lint level for this lint; see [`SemverQuery`].`lint_level`.
///
/// If this is `None`, use the query's default `lint_level` when calculating
/// the effective lint level.
#[serde(default)]
pub lint_level: Option<LintLevel>,
}

Expand Down
Loading