From 708d86b233ac2853cafa80d688d2714aabb3792e Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Wed, 23 Jan 2019 21:16:29 -0500 Subject: [PATCH 01/30] git-checks-config: add a crate for handling check configuration --- Cargo.toml | 1 + git-checks-config/CHANGELOG.md | 3 + git-checks-config/Cargo.toml | 19 +++++ git-checks-config/src/lib.rs | 79 +++++++++++++++++++ git-checks-config/src/macros.rs | 74 ++++++++++++++++++ git-checks-config/src/registry.rs | 124 ++++++++++++++++++++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 git-checks-config/CHANGELOG.md create mode 100644 git-checks-config/Cargo.toml create mode 100644 git-checks-config/src/lib.rs create mode 100644 git-checks-config/src/macros.rs create mode 100644 git-checks-config/src/registry.rs diff --git a/Cargo.toml b/Cargo.toml index 5405efb4..169a7605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ "git-checks-core", + "git-checks-config", "git-checks", ] diff --git a/git-checks-config/CHANGELOG.md b/git-checks-config/CHANGELOG.md new file mode 100644 index 00000000..682839b5 --- /dev/null +++ b/git-checks-config/CHANGELOG.md @@ -0,0 +1,3 @@ +# v0.1.0 (unreleased) + + * Initial release. diff --git a/git-checks-config/Cargo.toml b/git-checks-config/Cargo.toml new file mode 100644 index 00000000..6cf1545c --- /dev/null +++ b/git-checks-config/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "git-checks-config" +version = "0.1.0" +authors = ["Ben Boeckel "] +license = "MIT/Apache-2.0" +description = """ +Configuration parsing for checks. +""" +repository = "https://gitlab.kitware.com/utils/rust-git-checks" +documentation = "https://docs.rs/git-checks-config/~0.1" +keywords = ["git", "code-review"] +workspace = ".." + +[dependencies] +erased-serde = "~0.3" +failure = "~0.1" +inventory = "~0.1" +git-checks-core = { path = "../git-checks-core" } +serde = "^1.0" diff --git a/git-checks-config/src/lib.rs b/git-checks-config/src/lib.rs new file mode 100644 index 00000000..f39884ed --- /dev/null +++ b/git-checks-config/src/lib.rs @@ -0,0 +1,79 @@ +// Copyright Kitware, Inc. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(unsize)] + +//! Configurations for Git checks +//! +//! When using git checks, it is often useful to store what checks to read within +//! configuration files. This crate allows for checks to also offer support for reading +//! structures from configuration files and turning them into instances of checks. Another point +//! is that adding another check to a crate requires consumers to update to consume that new check. +//! Here, the [`inventory`][inventory] is used to create a global registry that consuming +//! applications can use to automatically discover new checks added to the application from +//! anywhere. +//! +//!## Caveats +//! +//! One downside of this is that there is then one "blessed" serialization for a check. +//! This crate aims to not preclude such uses and as such, checks themselves should generally +//! not implement `Deserialize`. Instead, a separate structure should be created which then +//! creates the check. +//! +//! ## Example +//! +//! ```rust,ignore +//! struct MyCheck { +//! field1: bool, +//! } +//! +//! impl Check for MyCheck { +//! // implementation +//! } +//! +//! struct MyCheckConfig { +//! #[serde(default)] +//! field1: bool +//! } +//! +//! impl IntoCheck for MyCheckConfig { +//! type Check = MyCheck; +//! +//! fn into_check(self) -> Self::Check { +//! MyCheck { +//! field1: self.field1, +//! } +//! } +//! } +//! +//! register_checks! { +//! MyCheckConfig { +//! "name_of_check" => CommitCheckConfig, +//! } +//! } +//! ``` + +mod crates { + pub extern crate erased_serde; + pub extern crate failure; + pub extern crate git_checks_core; + pub extern crate inventory; + pub extern crate serde; +} + +mod registry; +pub use registry::*; + +#[macro_use] +mod macros; +pub use macros::IntoCheck; + +#[doc(hidden)] +pub mod _detail { + pub use macros::parse_check; +} diff --git a/git-checks-config/src/macros.rs b/git-checks-config/src/macros.rs new file mode 100644 index 00000000..53516913 --- /dev/null +++ b/git-checks-config/src/macros.rs @@ -0,0 +1,74 @@ +// Copyright Kitware, Inc. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crates::erased_serde::{self, Deserializer}; +use crates::failure::{Fail, Fallible}; +use crates::serde::de::DeserializeOwned; + +use std::marker::Unsize; + +use CheckConfig; + +/// Trait for a deserialization structure of a check. +/// +/// This trait should be implemented for any structure which can be deserialized and construct a +/// check. +pub trait IntoCheck: DeserializeOwned { + /// The check parsed by this configuration. + type Check; + + /// Create a new instance of the check from the configuration. + fn into_check(self) -> Self::Check; +} + +/// Internal function for use by the `register_checks` macro implementation. +/// +/// This is the implementation for `CheckCtor` type generated by the macro. +#[doc(hidden)] +pub fn parse_check(conf: &mut dyn Deserializer) -> Fallible> +where + C: IntoCheck, + CT: CheckConfig, + C::Check: Unsize, +{ + erased_serde::deserialize(conf) + .map(|c: C| Box::new(c.into_check()) as Box) + .map_err(|err| err.context("...").into()) +} + +/// Register configuration structures with `inventory`. +/// +/// A single check can implement multiple check traits, so this macro accepts multiple checks and +/// multiple registrations with the same check. Ideally, check names should be unique globally, but +/// nothing enforces this at the registration level. +/// +/// ## Example +/// +/// ```rust,ignore +/// register_checks! { +/// CheckConfig { +/// "name" => CommitCheckConfig, +/// "name/topic" => TopicCheckConfig, +/// }, +/// OtherCheckConfig { +/// "other_check" => BranchCheckConfig, +/// }, +/// } +/// ``` +#[macro_export] +macro_rules! register_checks { + { $( $ty:ty { $( $name:expr => $tr:ty, )* }, )* } => { + $( + $( + inventory::submit! { + $tr::new($name, $crate::_detail::parse_check::<$ty, $tr>) + } + )* + )* + }; +} diff --git a/git-checks-config/src/registry.rs b/git-checks-config/src/registry.rs new file mode 100644 index 00000000..a6905832 --- /dev/null +++ b/git-checks-config/src/registry.rs @@ -0,0 +1,124 @@ +// Copyright Kitware, Inc. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crates::erased_serde::Deserializer; +use crates::failure::Fallible; +use crates::git_checks_core::{BranchCheck, Check, TopicCheck}; +use crates::inventory; + +/// A constructor for a check from a deserialization structure. +pub type CheckCtor = fn(&mut dyn Deserializer) -> Fallible>; + +/// Internal trait for use by the `register_checks` macro implementation. +#[doc(hidden)] +pub trait CheckConfig { + type CheckTrait: ?Sized; +} + +/// Registry type for branch checks. +/// +/// Query `inventory` using this type to find all branch checks. +pub struct BranchCheckConfig { + name: &'static str, + ctor: CheckCtor, +} + +impl BranchCheckConfig { + /// This structure should only be created by the `register_checks` macro. + #[doc(hidden)] + pub fn new(name: &'static str, ctor: CheckCtor) -> Self { + Self { + name, + ctor, + } + } + + /// The name of the branch check. + pub fn name(&self) -> &'static str { + self.name + } + + /// Create an instance of this check from a deserialization structure. + pub fn create(&self, conf: &mut dyn Deserializer) -> Fallible> { + (self.ctor)(conf) + } +} + +impl CheckConfig for BranchCheckConfig { + type CheckTrait = dyn BranchCheck; +} + +/// Registry type for commit checks. +/// +/// Query `inventory` using this type to find all commit checks. +pub struct CommitCheckConfig { + name: &'static str, + ctor: CheckCtor, +} + +impl CommitCheckConfig { + /// This structure should only be created by the `register_checks` macro. + #[doc(hidden)] + pub fn new(name: &'static str, ctor: CheckCtor) -> Self { + Self { + name, + ctor, + } + } + + /// The name of the commit check. + pub fn name(&self) -> &'static str { + self.name + } + + /// Create an instance of this check from a deserialization structure. + pub fn create(&self, conf: &mut dyn Deserializer) -> Fallible> { + (self.ctor)(conf) + } +} + +impl CheckConfig for CommitCheckConfig { + type CheckTrait = dyn Check; +} + +/// Registry type for topic checks. +/// +/// Query `inventory` using this type to find all topic checks. +pub struct TopicCheckConfig { + name: &'static str, + ctor: CheckCtor, +} + +impl TopicCheckConfig { + /// This structure should only be created by the `register_checks` macro. + #[doc(hidden)] + pub fn new(name: &'static str, ctor: CheckCtor) -> Self { + Self { + name, + ctor, + } + } + + /// The name of the topic check. + pub fn name(&self) -> &'static str { + self.name + } + + /// Create an instance of this check from a deserialization structure. + pub fn create(&self, conf: &mut dyn Deserializer) -> Fallible> { + (self.ctor)(conf) + } +} + +impl CheckConfig for TopicCheckConfig { + type CheckTrait = dyn TopicCheck; +} + +inventory::collect!(BranchCheckConfig); +inventory::collect!(CommitCheckConfig); +inventory::collect!(TopicCheckConfig); -- GitLab From 725cb1cac4b6906165de96707868a8888382ef6c Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Fri, 27 Sep 2019 05:09:21 -0400 Subject: [PATCH 02/30] registry: remove the need for the 'unsize' trait This allows the code to now work on non-nightly compilers as well. There's a bit of code duplication, but it's local and not that much code. --- git-checks-config/Cargo.toml | 1 - git-checks-config/src/lib.rs | 9 ----- git-checks-config/src/macros.rs | 23 +---------- git-checks-config/src/registry.rs | 64 ++++++++++++++++++++++++++++--- 4 files changed, 59 insertions(+), 38 deletions(-) diff --git a/git-checks-config/Cargo.toml b/git-checks-config/Cargo.toml index 6cf1545c..50bd83b6 100644 --- a/git-checks-config/Cargo.toml +++ b/git-checks-config/Cargo.toml @@ -13,7 +13,6 @@ workspace = ".." [dependencies] erased-serde = "~0.3" -failure = "~0.1" inventory = "~0.1" git-checks-core = { path = "../git-checks-core" } serde = "^1.0" diff --git a/git-checks-config/src/lib.rs b/git-checks-config/src/lib.rs index f39884ed..8d1fd2bc 100644 --- a/git-checks-config/src/lib.rs +++ b/git-checks-config/src/lib.rs @@ -6,8 +6,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(unsize)] - //! Configurations for Git checks //! //! When using git checks, it is often useful to store what checks to read within @@ -60,7 +58,6 @@ mod crates { pub extern crate erased_serde; - pub extern crate failure; pub extern crate git_checks_core; pub extern crate inventory; pub extern crate serde; @@ -71,9 +68,3 @@ pub use registry::*; #[macro_use] mod macros; -pub use macros::IntoCheck; - -#[doc(hidden)] -pub mod _detail { - pub use macros::parse_check; -} diff --git a/git-checks-config/src/macros.rs b/git-checks-config/src/macros.rs index 53516913..d95a0022 100644 --- a/git-checks-config/src/macros.rs +++ b/git-checks-config/src/macros.rs @@ -6,14 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crates::erased_serde::{self, Deserializer}; -use crates::failure::{Fail, Fallible}; use crates::serde::de::DeserializeOwned; -use std::marker::Unsize; - -use CheckConfig; - /// Trait for a deserialization structure of a check. /// /// This trait should be implemented for any structure which can be deserialized and construct a @@ -26,21 +20,6 @@ pub trait IntoCheck: DeserializeOwned { fn into_check(self) -> Self::Check; } -/// Internal function for use by the `register_checks` macro implementation. -/// -/// This is the implementation for `CheckCtor` type generated by the macro. -#[doc(hidden)] -pub fn parse_check(conf: &mut dyn Deserializer) -> Fallible> -where - C: IntoCheck, - CT: CheckConfig, - C::Check: Unsize, -{ - erased_serde::deserialize(conf) - .map(|c: C| Box::new(c.into_check()) as Box) - .map_err(|err| err.context("...").into()) -} - /// Register configuration structures with `inventory`. /// /// A single check can implement multiple check traits, so this macro accepts multiple checks and @@ -66,7 +45,7 @@ macro_rules! register_checks { $( $( inventory::submit! { - $tr::new($name, $crate::_detail::parse_check::<$ty, $tr>) + $tr::new($name, <$tr>::ctor::<$ty>) } )* )* diff --git a/git-checks-config/src/registry.rs b/git-checks-config/src/registry.rs index a6905832..0193cdc3 100644 --- a/git-checks-config/src/registry.rs +++ b/git-checks-config/src/registry.rs @@ -6,13 +6,29 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crates::erased_serde::Deserializer; -use crates::failure::Fallible; +use std::error::Error; + +use crates::erased_serde::{self, Deserializer}; use crates::git_checks_core::{BranchCheck, Check, TopicCheck}; use crates::inventory; +use crates::serde::de::DeserializeOwned; + +/// Trait for a deserialization structure of a check. +/// +/// This trait should be implemented for any structure which can be deserialized and construct a +/// check. +pub trait IntoCheck: DeserializeOwned { + /// The check parsed by this configuration. + type Check; + + /// Create a new instance of the check from the configuration. + fn into_check(self) -> Self::Check; +} + +type CtorResult = Result>; /// A constructor for a check from a deserialization structure. -pub type CheckCtor = fn(&mut dyn Deserializer) -> Fallible>; +type CheckCtor = fn(&mut dyn Deserializer) -> CtorResult>; /// Internal trait for use by the `register_checks` macro implementation. #[doc(hidden)] @@ -44,9 +60,21 @@ impl BranchCheckConfig { } /// Create an instance of this check from a deserialization structure. - pub fn create(&self, conf: &mut dyn Deserializer) -> Fallible> { + pub fn create(&self, conf: &mut dyn Deserializer) -> CtorResult> { (self.ctor)(conf) } + + /// Internal function for use by the `register_checks` macro implementation. + #[doc(hidden)] + pub fn ctor(conf: &mut dyn Deserializer) -> CtorResult> + where + C: IntoCheck, + C::Check: BranchCheck + 'static, + { + erased_serde::deserialize(conf) + .map(|c: C| Box::new(c.into_check()) as Box) + .map_err(Into::into) + } } impl CheckConfig for BranchCheckConfig { @@ -77,9 +105,21 @@ impl CommitCheckConfig { } /// Create an instance of this check from a deserialization structure. - pub fn create(&self, conf: &mut dyn Deserializer) -> Fallible> { + pub fn create(&self, conf: &mut dyn Deserializer) -> CtorResult> { (self.ctor)(conf) } + + /// Internal function for use by the `register_checks` macro implementation. + #[doc(hidden)] + pub fn ctor(conf: &mut dyn Deserializer) -> CtorResult> + where + C: IntoCheck, + C::Check: Check + 'static, + { + erased_serde::deserialize(conf) + .map(|c: C| Box::new(c.into_check()) as Box) + .map_err(Into::into) + } } impl CheckConfig for CommitCheckConfig { @@ -110,9 +150,21 @@ impl TopicCheckConfig { } /// Create an instance of this check from a deserialization structure. - pub fn create(&self, conf: &mut dyn Deserializer) -> Fallible> { + pub fn create(&self, conf: &mut dyn Deserializer) -> CtorResult> { (self.ctor)(conf) } + + /// Internal function for use by the `register_checks` macro implementation. + #[doc(hidden)] + pub fn ctor(conf: &mut dyn Deserializer) -> CtorResult> + where + C: IntoCheck, + C::Check: TopicCheck + 'static, + { + erased_serde::deserialize(conf) + .map(|c: C| Box::new(c.into_check()) as Box) + .map_err(Into::into) + } } impl CheckConfig for TopicCheckConfig { -- GitLab From 1e5f4cf9b1b1053f1a70c8f5508d0c1e8a162b87 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sun, 3 Feb 2019 11:10:06 -0500 Subject: [PATCH 03/30] git-checks: add a `config` feature --- git-checks/CHANGELOG.md | 3 +++ git-checks/Cargo.toml | 13 +++++++++++++ git-checks/src/lib.rs | 29 +++++++++++++++++++++++++++++ git-checks/src/test.rs | 18 ++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/git-checks/CHANGELOG.md b/git-checks/CHANGELOG.md index b860f630..0b948bc2 100644 --- a/git-checks/CHANGELOG.md +++ b/git-checks/CHANGELOG.md @@ -18,6 +18,9 @@ associated with the call on the builder. - Consistency. All checks now support the builder pattern, but offer an `impl Default` if available. + * There is now a `config` feature which registers deserialization for all + checks in the crate through the mechanisms provided by `inventory` and + `git-checks-config`. ## New checks diff --git a/git-checks/Cargo.toml b/git-checks/Cargo.toml index 014eb7d3..83e7e896 100644 --- a/git-checks/Cargo.toml +++ b/git-checks/Cargo.toml @@ -12,6 +12,7 @@ keywords = ["git", "code-review"] workspace = ".." [dev-dependencies] +serde_json = "^1.0" tempdir = "~0.3.6" [dependencies] @@ -27,3 +28,15 @@ wait-timeout = "~0.2" git-checks-core = { path = "../git-checks-core" } git-workarea = "^4.0" regex = "^1.0" + +git-checks-config = { path = "../git-checks-config", optional = true } +inventory = { version = "~0.1", optional = true } +serde = { version = "^1.0", optional = true } +serde_derive = { version = "^1.0", optional = true } + +[features] +default = [] +config = ["git-checks-config", "inventory", "serde", "serde_derive"] + +[package.metadata.docs.rs] +all-features = true diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 4c7030af..46154bbc 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -12,6 +12,10 @@ //! //! Simple checks useful in a wide variety of projects. +#[cfg(feature = "config")] +#[macro_use] +extern crate git_checks_config; + #[macro_use] extern crate derive_builder; @@ -21,11 +25,26 @@ extern crate lazy_static; #[macro_use] extern crate log; +#[cfg(feature = "config")] +#[macro_use] +extern crate serde_derive; + +#[cfg(all(feature = "config", test))] +#[macro_use] +extern crate serde_json; + mod crates { // public pub extern crate git_checks_core; pub extern crate git_workarea; + #[cfg(feature = "config")] + pub extern crate git_checks_config; + #[cfg(feature = "config")] + pub extern crate inventory; + #[cfg(feature = "config")] + pub extern crate serde; + // private // pub extern crate derive_builder; pub extern crate itertools; @@ -35,6 +54,8 @@ mod crates { pub extern crate ttl_cache; pub extern crate wait_timeout; + #[cfg(test)] + pub extern crate serde_json; #[cfg(test)] pub extern crate tempdir; } @@ -150,5 +171,13 @@ pub mod builders { pub use valid_name::ValidNameBuilder; } +/// Configuration structures for checks. +/// +/// These structures are registered using `inventory` using the `git-checks-config` crate. They +/// are offered here for documentation purposes mainly, but also in case their specific +/// implementations are useful. +#[cfg(feature = "config")] +pub mod config {} + #[cfg(test)] pub mod test; diff --git a/git-checks/src/test.rs b/git-checks/src/test.rs index a17760a5..543dcdad 100644 --- a/git-checks/src/test.rs +++ b/git-checks/src/test.rs @@ -15,6 +15,8 @@ use std::process::Command; use crates::git_checks_core::{BranchCheck, Check, CheckResult, TopicCheck}; use crates::git_workarea::{CommitId, GitContext, Identity}; use crates::itertools; +#[cfg(feature = "config")] +use crates::serde_json; use crates::tempdir::TempDir; pub use crates::git_checks_core::GitCheckConfiguration; @@ -269,3 +271,19 @@ where { test_result_ok(run_topic_check(name, commit, check)); } + +#[cfg(feature = "config")] +pub fn check_missing_json_field(err: serde_json::Error, field: &str) { + assert!(!err.is_io()); + assert!(!err.is_syntax()); + assert!(err.is_data()); + assert!(!err.is_eof()); + + let msg = format!("{}", err); + if msg != format!("missing field `{}`", field) { + println!( + "Error message doesn't match. Was a new required field added? ({})", + msg, + ); + } +} -- GitLab From 2cc62d2400ba82562536be51bc04ce4e181d5117 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Wed, 16 Oct 2019 16:52:25 -0400 Subject: [PATCH 04/30] gitlab-ci: test the new `config` feature --- .gitlab-ci.yml | 100 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dd25d277..39161a34 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,14 +15,14 @@ before_script: - cargo fetch --locked .cargo_build: &cargo_build - - cargo build --frozen --all --verbose - - cargo test --frozen --all --no-run --verbose + - cargo build --frozen $CARGO_FEATURES --all --verbose + - cargo test --frozen $CARGO_FEATURES --all --no-run --verbose .cargo_test: &cargo_test - apt-get install -yqq --no-install-recommends git bind9-host - git config --global user.name "Ghostflow Testing" - git config --global user.email "ghostflow@example.invalid" - - cargo test --frozen --all --verbose + - cargo test --frozen $CARGO_FEATURES --all --verbose .rust_minimum: &rust_minimum image: "rust:1.36.0" @@ -41,6 +41,24 @@ before_script: image: "rustlang/rust:nightly" +.rust_minimum_features: &rust_minimum_features + extends: .rust_minimum + + variables: + CARGO_FEATURES: --all-features + +.rust_stable_features: &rust_stable_features + extends: .rust_stable + + variables: + CARGO_FEATURES: --all-features + +.rust_nightly_features: &rust_nightly_features + extends: .rust_nightly + + variables: + CARGO_FEATURES: --all-features + .cargo_fetch_job: &cargo_fetch_job stage: prepare tags: *rust_tags @@ -110,7 +128,16 @@ build:cargo-clippy: - *cargo_cache_newest script: - rustup component add clippy - - cargo clippy --frozen --tests --all --verbose -- -D warnings + - cargo clippy --frozen $CARGO_FEATURES --tests --all --verbose -- -D warnings + +build:cargo-clippy-features: + <<: + - *cargo_build_job + - *rust_stable_features + - *cargo_cache_newest + script: + - rustup component add clippy + - cargo clippy --frozen $CARGO_FEATURES --tests --all --verbose -- -D warnings build:cargo-minimum: <<: @@ -175,6 +202,69 @@ test:cargo-nightly: # needs: # - build:cargo-mindeps +build:cargo-minimum-features: + <<: + - *cargo_build_job + - *rust_minimum_features + - *cargo_cache_newest + +test:cargo-minimum-features: + <<: + - *cargo_test_job + - *rust_minimum_features + dependencies: + - build:cargo-minimum-features + needs: + - build:cargo-minimum-features + +build:cargo-stable-features: + <<: + - *cargo_build_job + - *rust_stable_features + - *cargo_cache_newest + +test:cargo-stable-features: + <<: + - *cargo_test_job + - *rust_stable_features + dependencies: + - build:cargo-stable-features + needs: + - build:cargo-stable-features + +build:cargo-nightly-features: + <<: + - *cargo_build_job + - *rust_nightly_features + - *cargo_cache_newest + +test:cargo-nightly-features: + <<: + - *cargo_test_job + - *rust_nightly_features + dependencies: + - build:cargo-nightly-features + needs: + - build:cargo-nightly-features + +# build:cargo-mindeps-features: +# <<: +# - *cargo_build_job +# - *rust_minimum_features +# dependencies: +# - prepare:cargo-cache-mindeps +# needs: +# - prepare:cargo-cache-mindeps + +# test:cargo-mindeps-features: +# <<: +# - *cargo_test_job +# - *rust_minimum_features +# dependencies: +# - build:cargo-mindeps-features +# needs: +# - build:cargo-mindeps-features + prepare:git: image: "rust:latest" @@ -208,7 +298,7 @@ test:git-master: - apt-get install -yqq --no-install-recommends bind9-host - git config --global user.name "Ghostflow Testing" - git config --global user.email "ghostflow@example.invalid" - - PATH=$PWD/git/root/bin:$PATH cargo test --frozen --all --verbose + - PATH=$PWD/git/root/bin:$PATH cargo test --frozen $CARGO_FEATURES --all --verbose dependencies: - prepare:git - build:cargo-stable -- GitLab From cba7f3e77144e8b21fe5ffb34782c933d54382ed Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:17:33 -0400 Subject: [PATCH 05/30] allow_robot: add configuration parsing --- git-checks/src/allow_robot.rs | 92 +++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 4 +- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/git-checks/src/allow_robot.rs b/git-checks/src/allow_robot.rs index 2243b07f..5c7315c9 100644 --- a/git-checks/src/allow_robot.rs +++ b/git-checks/src/allow_robot.rs @@ -43,6 +43,98 @@ impl BranchCheck for AllowRobot { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{BranchCheckConfig, IntoCheck}; + use crates::git_workarea::Identity; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use AllowRobot; + + /// Configuration for the `AllowRobot` check. + /// + /// The `name` and `email` fields are required and are both strings. + /// + /// This check is registered as a branch check with the name `"allow_robot"`. + /// + /// # Example + /// + /// ```json + /// { + /// "name": "Robot Name", + /// "email": "robot@email.invalid" + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct AllowRobotConfig { + name: String, + email: String, + } + + impl IntoCheck for AllowRobotConfig { + type Check = AllowRobot; + + fn into_check(self) -> Self::Check { + let identity = Identity::new(self.name, self.email); + AllowRobot::builder() + .identity(identity) + .build() + .expect("configuration mismatch for `AllowRobot`") + } + } + + register_checks! { + AllowRobotConfig { + "allow_robot" => BranchCheckConfig, + }, + } + + #[test] + fn test_allow_robot_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "name"); + } + + #[test] + fn test_allow_robot_config_name_is_required() { + let email = "robot@email.invalid"; + let json = json!({ + "email": email, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "name"); + } + + #[test] + fn test_allow_robot_config_email_is_required() { + let name = "Robot Name"; + let json = json!({ + "name": name, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "email"); + } + + #[test] + fn test_allow_robot_config_minimum_fields() { + let name = "Robot Name"; + let email = "robot@email.invalid"; + let json = json!({ + "name": name, + "email": email, + }); + let check: AllowRobotConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.name, name); + assert_eq!(check.email, email); + } +} + #[cfg(test)] mod tests { use crates::git_workarea::Identity; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 46154bbc..7c7a93bf 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -177,7 +177,9 @@ pub mod builders { /// are offered here for documentation purposes mainly, but also in case their specific /// implementations are useful. #[cfg(feature = "config")] -pub mod config {} +pub mod config { + pub use allow_robot::config::AllowRobotConfig; +} #[cfg(test)] pub mod test; -- GitLab From e6abfb9e0dad3ab1f0c5962cc1216ff0c1781dff Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:19:17 -0400 Subject: [PATCH 06/30] bad_commit: add configuration parsing --- git-checks/src/bad_commit.rs | 96 ++++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 97 insertions(+) diff --git a/git-checks/src/bad_commit.rs b/git-checks/src/bad_commit.rs index 72a7da14..6142a791 100644 --- a/git-checks/src/bad_commit.rs +++ b/git-checks/src/bad_commit.rs @@ -114,6 +114,102 @@ impl TopicCheck for BadCommit { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::git_workarea::CommitId; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use BadCommit; + + /// Configuration for the `BadCommit` check. + /// + /// The `commit` and `reason` fields are required and are both strings. The commit must be a + /// full commit hash. + /// + /// This check is registered as a commit check with the name `"bad_commit"` and as a topic + /// check with the name `"bad_commit/topic"`. + /// + /// # Example + /// + /// ```json + /// { + /// "commit": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + /// "reason": "it's a bad commit" + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct BadCommitConfig { + commit: String, + reason: String, + } + + impl IntoCheck for BadCommitConfig { + type Check = BadCommit; + + fn into_check(self) -> Self::Check { + let mut builder = BadCommit::builder(); + builder.commit(CommitId::new(self.commit)); + builder.reason(self.reason); + builder + .build() + .expect("configuration mismatch for `BadCommit`") + } + } + + register_checks! { + BadCommitConfig { + "bad_commit" => CommitCheckConfig, + "bad_commit/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_bad_commit_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "commit"); + } + + #[test] + fn test_bad_commit_config_commit_is_required() { + let reason = "because"; + let json = json!({ + "reason": reason, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "commit"); + } + + #[test] + fn test_bad_commit_config_reason_is_required() { + let commit = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let json = json!({ + "commit": commit, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "reason"); + } + + #[test] + fn test_bad_commit_config_minimum_fields() { + let commit = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let reason = "because"; + let json = json!({ + "commit": commit, + "reason": reason, + }); + let check: BadCommitConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.commit, commit); + assert_eq!(check.reason, reason); + } +} + #[cfg(test)] mod tests { use crates::git_workarea::CommitId; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 7c7a93bf..04d6aa5f 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -179,6 +179,7 @@ pub mod builders { #[cfg(feature = "config")] pub mod config { pub use allow_robot::config::AllowRobotConfig; + pub use bad_commit::config::BadCommitConfig; } #[cfg(test)] -- GitLab From e71a5183b76121da44046470bdd0645147f271c6 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:21:06 -0400 Subject: [PATCH 07/30] bad_commits: add configuration parsing --- git-checks/src/bad_commits.rs | 72 +++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 73 insertions(+) diff --git a/git-checks/src/bad_commits.rs b/git-checks/src/bad_commits.rs index 87b038cb..ce77623f 100644 --- a/git-checks/src/bad_commits.rs +++ b/git-checks/src/bad_commits.rs @@ -129,6 +129,78 @@ impl TopicCheck for BadCommits { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::git_workarea::CommitId; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use BadCommits; + + /// Configuration for the `BadCommits` check. + /// + /// The `bad_commits` field is required and is a list of strings. Full hashes must be used. + /// + /// This check is registered as a commit check with the name `"bad_commits"` and as a topic + /// check with the name `"bad_commits/topic"`. It is recommended to use the topic variant due + /// to its better performance. + /// + /// # Example + /// + /// ```json + /// { + /// "bad_commits": [ + /// "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + /// "abadcafeabadcafeabadcafeabadcafeabadcafeabadcafe" + /// ] + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct BadCommitsConfig { + bad_commits: Vec, + } + + impl IntoCheck for BadCommitsConfig { + type Check = BadCommits; + + fn into_check(self) -> Self::Check { + BadCommits::builder() + .bad_commits(self.bad_commits.into_iter().map(CommitId::new)) + .build() + .expect("configuration mismatch for `BadCommits`") + } + } + + register_checks! { + BadCommitsConfig { + "bad_commits" => CommitCheckConfig, + "bad_commits/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_bad_commits_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "bad_commits"); + } + + #[test] + fn test_bad_commits_config_minimum_fields() { + let commit1: String = "commit hash 1".into(); + let json = json!({ + "bad_commits": [commit1.clone()], + }); + let check: BadCommitsConfig = serde_json::from_value(json).unwrap(); + + itertools::assert_equal(&check.bad_commits, &[commit1]); + } +} + #[cfg(test)] mod tests { use crates::git_workarea::CommitId; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 04d6aa5f..067bfbc5 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -180,6 +180,7 @@ pub mod builders { pub mod config { pub use allow_robot::config::AllowRobotConfig; pub use bad_commit::config::BadCommitConfig; + pub use bad_commits::config::BadCommitsConfig; } #[cfg(test)] -- GitLab From 848b8d491bfec14166c6b7d7674a7240f0a87640 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:21:30 -0400 Subject: [PATCH 08/30] changelog: add configuration parsing --- git-checks/src/changelog.rs | 315 ++++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 316 insertions(+) diff --git a/git-checks/src/changelog.rs b/git-checks/src/changelog.rs index d3df33c4..f038ccaf 100644 --- a/git-checks/src/changelog.rs +++ b/git-checks/src/changelog.rs @@ -222,6 +222,321 @@ impl ContentCheck for Changelog { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use Changelog; + use ChangelogStyle; + + /// Configuration for the `Changelog` check. + /// + /// Requires the `style` key which indicates what style of changelog is used. Must be one of + /// `"directory"` or `"file"`. + /// + /// For both styles, the `path` key is required. This is the path to the file or directory + /// containing changelog information. The `required` key is a boolean that defaults to `false`. + /// The directory style also has an optional `extension` key which is a string that changelog + /// files in the directory are expected to have. + /// + /// This check is registered as a commit check with the name `"changelog"` and a topic check + /// with the name `"changelog/topic"`. + /// + /// # Examples + /// + /// ```json + /// { + /// "style": "directory", + /// "path": "path/to/directory", + /// "extension": "md", + /// "required": false + /// } + /// ``` + /// + /// ```json + /// { + /// "style": "file", + /// "path": "path/to/changelog.file", + /// "required": false + /// } + /// ``` + /// + /// ```json + /// { + /// "style": "files", + /// "paths": [ + /// "path/to/first/changelog.file" + /// "path/to/second/changelog.file" + /// ], + /// "required": false + /// } + /// ``` + #[serde(tag = "style")] + #[derive(Deserialize, Debug)] + pub enum ChangelogConfig { + #[serde(rename = "directory")] + #[doc(hidden)] + Directory { + path: String, + #[serde(default)] + extension: Option, + + required: Option, + }, + #[serde(rename = "file")] + #[doc(hidden)] + File { + path: String, + + required: Option, + }, + #[serde(rename = "files")] + #[doc(hidden)] + Files { + paths: Vec, + + required: Option, + }, + } + + impl IntoCheck for ChangelogConfig { + type Check = Changelog; + + fn into_check(self) -> Self::Check { + let (style, required) = match self { + ChangelogConfig::Directory { + path, + extension, + required, + } => (ChangelogStyle::directory(path, extension), required), + ChangelogConfig::File { + path, + required, + } => (ChangelogStyle::file(path), required), + ChangelogConfig::Files { + paths, + required, + } => (ChangelogStyle::files(paths), required), + }; + + let mut builder = Changelog::builder(); + builder.style(style); + + if let Some(required) = required { + builder.required(required); + } + + builder + .build() + .expect("configuration mismatch for `Changelog`") + } + } + + register_checks! { + ChangelogConfig { + "changelog" => CommitCheckConfig, + "changelog/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_changelog_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "style"); + } + + #[test] + fn test_changelog_config_directory_path_is_required() { + let json = json!({ + "style": "directory", + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "path"); + } + + #[test] + fn test_changelog_config_directory_minimum_fields() { + let exp_path = "path/to/directory"; + let json = json!({ + "style": "directory", + "path": exp_path, + }); + let check: ChangelogConfig = serde_json::from_value(json).unwrap(); + + if let ChangelogConfig::Directory { + ref path, + ref extension, + required, + } = &check + { + assert_eq!(path, exp_path); + assert_eq!(extension, &None); + assert_eq!(required, &None); + } else { + panic!("did not create a directory config: {:?}", check); + } + } + + #[test] + fn test_changelog_config_directory_all_fields() { + let exp_path = "path/to/directory"; + let exp_ext: String = "md".into(); + let json = json!({ + "style": "directory", + "path": exp_path, + "extension": exp_ext.clone(), + "required": true, + }); + let check: ChangelogConfig = serde_json::from_value(json).unwrap(); + + if let ChangelogConfig::Directory { + ref path, + ref extension, + required, + } = &check + { + assert_eq!(path, exp_path); + assert_eq!(extension, &Some(exp_ext)); + assert_eq!(required, &Some(true)); + } else { + panic!("did not create a directory config: {:?}", check); + } + } + + #[test] + fn test_changelog_config_file_path_is_required() { + let json = json!({ + "style": "file", + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "path"); + } + + #[test] + fn test_changelog_config_file_minimum_fields() { + let exp_path = "path/to/changelog.file"; + let json = json!({ + "style": "file", + "path": exp_path, + }); + let check: ChangelogConfig = serde_json::from_value(json).unwrap(); + + if let ChangelogConfig::File { + ref path, + required, + } = &check + { + assert_eq!(path, exp_path); + assert_eq!(required, &None); + } else { + panic!("did not create a file config: {:?}", check); + } + } + + #[test] + fn test_changelog_config_file_all_fields() { + let exp_path = "path/to/changelog.file"; + let json = json!({ + "style": "file", + "path": exp_path, + "required": true, + }); + let check: ChangelogConfig = serde_json::from_value(json).unwrap(); + + if let ChangelogConfig::File { + ref path, + required, + } = &check + { + assert_eq!(path, exp_path); + assert_eq!(required, &Some(true)); + } else { + panic!("did not create a file config: {:?}", check); + } + } + + #[test] + fn test_changelog_config_files_paths_is_required() { + let json = json!({ + "style": "files", + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "paths"); + } + + #[test] + fn test_changelog_config_files_minimum_fields() { + let exp_path1 = "path/to/first/changelog.file"; + let exp_path2 = "path/to/second/changelog.file"; + let json = json!({ + "style": "files", + "paths": &[exp_path1, exp_path2], + }); + let check: ChangelogConfig = serde_json::from_value(json).unwrap(); + + if let ChangelogConfig::Files { + ref paths, + required, + } = &check + { + assert_eq!(paths, &[exp_path1, exp_path2]); + assert_eq!(required, &None); + } else { + panic!("did not create a files config: {:?}", check); + } + } + + #[test] + fn test_changelog_config_files_all_fields() { + let exp_path1 = "path/to/first/changelog.file"; + let exp_path2 = "path/to/second/changelog.file"; + let json = json!({ + "style": "files", + "paths": &[exp_path1, exp_path2], + "required": true, + }); + let check: ChangelogConfig = serde_json::from_value(json).unwrap(); + + if let ChangelogConfig::Files { + ref paths, + required, + } = &check + { + assert_eq!(paths, &[exp_path1, exp_path2]); + assert_eq!(required, &Some(true)); + } else { + panic!("did not create a files config: {:?}", check); + } + } + + #[test] + fn test_changelog_config_invalid() { + let json = json!({ + "style": "invalid", + }); + let err = serde_json::from_value::(json).unwrap_err(); + + assert!(!err.is_io()); + assert!(!err.is_syntax()); + assert!(err.is_data()); + assert!(!err.is_eof()); + + let msg = format!("{}", err); + if msg != "unknown variant `invalid`, expected `directory` or `file`" { + println!( + "Error message doesn't match. Was a new style added? ({})", + msg, + ); + } + } +} + #[cfg(test)] mod tests { use builders::ChangelogBuilder; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 067bfbc5..7ac52daa 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -181,6 +181,7 @@ pub mod config { pub use allow_robot::config::AllowRobotConfig; pub use bad_commit::config::BadCommitConfig; pub use bad_commits::config::BadCommitsConfig; + pub use changelog::config::ChangelogConfig; } #[cfg(test)] -- GitLab From fc3a8ff0edcc84694b7a45d09c7a1a5cce180337 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:21:44 -0400 Subject: [PATCH 09/30] check_end_of_line: add configuration parsing --- git-checks/src/check_end_of_line.rs | 40 +++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 41 insertions(+) diff --git a/git-checks/src/check_end_of_line.rs b/git-checks/src/check_end_of_line.rs index 9982ea7e..3a606d0b 100644 --- a/git-checks/src/check_end_of_line.rs +++ b/git-checks/src/check_end_of_line.rs @@ -78,6 +78,46 @@ impl ContentCheck for CheckEndOfLine { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use CheckEndOfLine; + + /// Configuration for the `CheckEndOfLine` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"check_end_of_line"` and a topic + /// check with the name `"check_end_of_line/topic"`. + #[derive(Deserialize, Debug)] + pub struct CheckEndOfLineConfig {} + + impl IntoCheck for CheckEndOfLineConfig { + type Check = CheckEndOfLine; + + fn into_check(self) -> Self::Check { + CheckEndOfLine::default() + } + } + + register_checks! { + CheckEndOfLineConfig { + "check_end_of_line" => CommitCheckConfig, + "check_end_of_line/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_check_end_of_line_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 7ac52daa..406cb835 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -182,6 +182,7 @@ pub mod config { pub use bad_commit::config::BadCommitConfig; pub use bad_commits::config::BadCommitsConfig; pub use changelog::config::ChangelogConfig; + pub use check_end_of_line::config::CheckEndOfLineConfig; } #[cfg(test)] -- GitLab From 1323b3e5435d44ae8758217be0e53c6c7f40de9b Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:22:00 -0400 Subject: [PATCH 10/30] check_executable_permissions: add configuration parsing --- .../src/check_executable_permissions.rs | 78 +++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 79 insertions(+) diff --git a/git-checks/src/check_executable_permissions.rs b/git-checks/src/check_executable_permissions.rs index 7f0b6ccf..bc31a491 100644 --- a/git-checks/src/check_executable_permissions.rs +++ b/git-checks/src/check_executable_permissions.rs @@ -133,6 +133,84 @@ impl ContentCheck for CheckExecutablePermissions { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use CheckExecutablePermissions; + + /// Configuration for the `CheckExecutablePermissions` check. + /// + /// The `extensions` key is a list of strings, defaulting to an empty list. These extensions + /// are used to detect executable files on Windows since other platforms can usually be + /// detected by the file contents. + /// + /// This check is registered as a commit check with the name `"check_executable_permissions" + /// and as a topic check with the name `"check_executable_permissions/topic"`. + /// + /// # Example + /// + /// ```json + /// { + /// "extensions": [ + /// "bat", + /// "exe", + /// "cmd" + /// ] + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct CheckExecutablePermissionsConfig { + #[serde(default)] + extensions: Option>, + } + + impl IntoCheck for CheckExecutablePermissionsConfig { + type Check = CheckExecutablePermissions; + + fn into_check(self) -> Self::Check { + let mut builder = CheckExecutablePermissions::builder(); + + if let Some(extensions) = self.extensions { + builder.extensions(extensions); + } + + builder + .build() + .expect("configuration mismatch for `CheckExecutablePermissions`") + } + } + + register_checks! { + CheckExecutablePermissionsConfig { + "check_executable_permissions" => CommitCheckConfig, + "check_executable_permissions/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_check_executable_permissions_config_empty() { + let json = json!({}); + let check: CheckExecutablePermissionsConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.extensions, None); + } + + #[test] + fn test_check_executable_permissions_config_all_fields() { + let exp_ext: String = "md".into(); + let json = json!({ + "extensions": [exp_ext.clone()], + }); + let check: CheckExecutablePermissionsConfig = serde_json::from_value(json).unwrap(); + + itertools::assert_equal(&check.extensions, &Some([exp_ext])); + } +} + #[cfg(test)] mod tests { use test::*; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 406cb835..7b2ee60f 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -183,6 +183,7 @@ pub mod config { pub use bad_commits::config::BadCommitsConfig; pub use changelog::config::ChangelogConfig; pub use check_end_of_line::config::CheckEndOfLineConfig; + pub use check_executable_permissions::config::CheckExecutablePermissionsConfig; } #[cfg(test)] -- GitLab From 5decc7bad5b66f21449614e6117c8d490358e778 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:22:12 -0400 Subject: [PATCH 11/30] check_size: add configuration parsing --- git-checks/src/check_size.rs | 72 ++++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 73 insertions(+) diff --git a/git-checks/src/check_size.rs b/git-checks/src/check_size.rs index a3542785..80e23a98 100644 --- a/git-checks/src/check_size.rs +++ b/git-checks/src/check_size.rs @@ -141,6 +141,78 @@ impl ContentCheck for CheckSize { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use CheckSize; + + /// Configuration for the `CheckSize` check. + /// + /// The `max_size` key is a non-negative integer for the default maximum size if an attribute + /// does not specify a different size. Defaults to 1048576 (2²⁰) bytes or 1 megabyte. + /// + /// This check is registered as a commit check with the name `"check_size"` and a topic check + /// with the name `"check_size/topic"`. + /// + /// # Example + /// + /// ```json + /// { + /// "max_size": 1048576 + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct CheckSizeConfig { + #[serde(default)] + max_size: Option, + } + + impl IntoCheck for CheckSizeConfig { + type Check = CheckSize; + + fn into_check(self) -> Self::Check { + let mut builder = CheckSize::builder(); + + if let Some(max_size) = self.max_size { + builder.max_size(max_size); + } + + builder + .build() + .expect("configuration mismatch for `CheckSize`") + } + } + + register_checks! { + CheckSizeConfig { + "check_size" => CommitCheckConfig, + "check_size/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_check_size_config_empty() { + let json = json!({}); + let check: CheckSizeConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.max_size, None); + } + + #[test] + fn test_check_size_config_all_fields() { + let json = json!({ + "max_size": 1000, + }); + let check: CheckSizeConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.max_size, Some(1000)); + } +} + #[cfg(test)] mod tests { use test::*; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 7b2ee60f..b86fdcbe 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -184,6 +184,7 @@ pub mod config { pub use changelog::config::ChangelogConfig; pub use check_end_of_line::config::CheckEndOfLineConfig; pub use check_executable_permissions::config::CheckExecutablePermissionsConfig; + pub use check_size::config::CheckSizeConfig; } #[cfg(test)] -- GitLab From 61d965ae229979b6a40732a16a0525e53839c473 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:22:25 -0400 Subject: [PATCH 12/30] check_whitespace: add configuration parsing --- git-checks/src/check_whitespace.rs | 40 ++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 41 insertions(+) diff --git a/git-checks/src/check_whitespace.rs b/git-checks/src/check_whitespace.rs index db972fbb..0e063b7e 100644 --- a/git-checks/src/check_whitespace.rs +++ b/git-checks/src/check_whitespace.rs @@ -107,6 +107,46 @@ impl TopicCheck for CheckWhitespace { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use CheckWhitespace; + + /// Configuration for the `CheckWhitespace` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"check_whitespace"` and a topic + /// check with the name `"check_whitespace/topic"`. + #[derive(Deserialize, Debug)] + pub struct CheckWhitespaceConfig {} + + impl IntoCheck for CheckWhitespaceConfig { + type Check = CheckWhitespace; + + fn into_check(self) -> Self::Check { + CheckWhitespace::default() + } + } + + register_checks! { + CheckWhitespaceConfig { + "check_whitespace" => CommitCheckConfig, + "check_whitespace/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_check_whitespace_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index b86fdcbe..ff08caa9 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -185,6 +185,7 @@ pub mod config { pub use check_end_of_line::config::CheckEndOfLineConfig; pub use check_executable_permissions::config::CheckExecutablePermissionsConfig; pub use check_size::config::CheckSizeConfig; + pub use check_whitespace::config::CheckWhitespaceConfig; } #[cfg(test)] -- GitLab From 20367c24703462e612841cd035af97a4d2f03e83 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:22:35 -0400 Subject: [PATCH 13/30] commit_subject: add configuration parsing --- git-checks/src/commit_subject.rs | 182 +++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 183 insertions(+) diff --git a/git-checks/src/commit_subject.rs b/git-checks/src/commit_subject.rs index a44166be..9fd13204 100644 --- a/git-checks/src/commit_subject.rs +++ b/git-checks/src/commit_subject.rs @@ -273,6 +273,188 @@ impl Check for CommitSubject { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + use crates::regex::Regex; + use crates::serde::de::{Deserialize, Deserializer, Error as SerdeError}; + #[cfg(test)] + use crates::serde_json; + + use CommitSubject; + + #[derive(Debug)] + struct RegexConfig(Regex); + + impl<'de> Deserialize<'de> for RegexConfig { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'de>, + { + let regex_str = ::deserialize(deserializer)?; + let regex = Regex::new(®ex_str) + .map_err(|err| D::Error::custom(format!("'{}': {}", regex_str, err)))?; + Ok(RegexConfig(regex)) + } + } + + impl From for Regex { + fn from(regex_config: RegexConfig) -> Self { + regex_config.0 + } + } + + /// Configuration for the `CommitSubject` check. + /// + /// No configuration is necessary. The defaults are guided by common commit message guidelines. + /// + /// | Field | Type | Default | + /// | ----- | ---- | ------- | + /// | `min_summary` | positive integer | 8 | + /// | `max_summary` | positive integer | 78 | + /// | `check_work_in_progress` | boolean | true | + /// | `check_rebase_commands` | boolean | true | + /// + /// The prefix configurations are lists of strings that are by default empty lists. The + /// `tolerated_prefixes` key is interpreted as a list of regular expressions. + /// + /// This check is registered as a commit check with the name `"commit_subject". + /// + /// # Example + /// + /// ```json + /// { + /// "min_summary": 8, + /// "max_summary": 78, + /// + /// "check_work_in_progress": true, + /// "check_rebase_commands": true, + /// + /// "tolerated_prefixes": [ + /// "regex" + /// ], + /// "allowed_prefixes": [ + /// "literal" + /// ], + /// "disallowed_prefixes": [ + /// "literal" + /// ] + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct CommitSubjectConfig { + #[serde(default)] + min_summary: Option, + #[serde(default)] + max_summary: Option, + + #[serde(default)] + check_work_in_progress: Option, + #[serde(default)] + check_rebase_commands: Option, + + #[serde(default)] + tolerated_prefixes: Option>, + #[serde(default)] + allowed_prefixes: Option>, + #[serde(default)] + disallowed_prefixes: Option>, + } + + impl IntoCheck for CommitSubjectConfig { + type Check = CommitSubject; + + fn into_check(self) -> Self::Check { + let mut builder = CommitSubject::builder(); + + if let Some(min_summary) = self.min_summary { + builder.min_summary(min_summary); + } + + if let Some(max_summary) = self.max_summary { + builder.max_summary(max_summary); + } + + if let Some(check_work_in_progress) = self.check_work_in_progress { + builder.check_work_in_progress(check_work_in_progress); + } + + if let Some(check_rebase_commands) = self.check_rebase_commands { + builder.check_rebase_commands(check_rebase_commands); + } + + if let Some(tolerated_prefixes) = self.tolerated_prefixes { + builder.tolerated_prefixes(tolerated_prefixes); + } + + if let Some(allowed_prefixes) = self.allowed_prefixes { + builder.allowed_prefixes(allowed_prefixes); + } + + if let Some(disallowed_prefixes) = self.disallowed_prefixes { + builder.disallowed_prefixes(disallowed_prefixes); + } + + builder + .build() + .expect("configuration mismatch for `CommitSubject`") + } + } + + register_checks! { + CommitSubjectConfig { + "commit_subject" => CommitCheckConfig, + }, + } + + #[test] + fn test_commit_subject_config_empty() { + let json = json!({}); + let check: CommitSubjectConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.min_summary, None); + assert_eq!(check.max_summary, None); + assert_eq!(check.check_work_in_progress, None); + assert_eq!(check.check_rebase_commands, None); + assert!(check.tolerated_prefixes.is_none()); + assert_eq!(check.allowed_prefixes, None); + assert_eq!(check.disallowed_prefixes, None); + } + + #[test] + fn test_commit_subject_config_all_fields() { + let exp_tprefix: String = "tolerated".into(); + let exp_aprefix: String = "allowed".into(); + let exp_dprefix: String = "disallowed".into(); + let json = json!({ + "min_summary": 1, + "max_summary": 100, + "check_work_in_progress": false, + "check_rebase_commands": false, + "tolerated_prefixes": [exp_tprefix.clone()], + "allowed_prefixes": [exp_aprefix.clone()], + "disallowed_prefixes": [exp_dprefix.clone()], + }); + let check: CommitSubjectConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.min_summary, Some(1)); + assert_eq!(check.max_summary, Some(100)); + assert_eq!(check.check_work_in_progress, Some(false)); + assert_eq!(check.check_rebase_commands, Some(false)); + itertools::assert_equal( + check + .tolerated_prefixes + .unwrap() + .iter() + .map(|re| re.0.as_str()), + &[exp_tprefix], + ); + itertools::assert_equal(&check.allowed_prefixes, &Some([exp_aprefix])); + itertools::assert_equal(&check.disallowed_prefixes, &Some([exp_dprefix])); + } +} + #[cfg(test)] mod tests { use crates::regex::Regex; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index ff08caa9..3397d0fd 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -186,6 +186,7 @@ pub mod config { pub use check_executable_permissions::config::CheckExecutablePermissionsConfig; pub use check_size::config::CheckSizeConfig; pub use check_whitespace::config::CheckWhitespaceConfig; + pub use commit_subject::config::CommitSubjectConfig; } #[cfg(test)] -- GitLab From 35d4de4fa5f1cb61b7034886a17a8faf5c7e136c Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:22:46 -0400 Subject: [PATCH 14/30] formatting: add configuration parsing --- git-checks/src/formatting.rs | 167 +++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 168 insertions(+) diff --git a/git-checks/src/formatting.rs b/git-checks/src/formatting.rs index 417e439f..08f7cc0a 100644 --- a/git-checks/src/formatting.rs +++ b/git-checks/src/formatting.rs @@ -447,6 +447,173 @@ impl ContentCheck for Formatting { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use std::time::Duration; + + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use Formatting; + + /// Configuration for the `Formatting` check. + /// + /// The `kind` key is required and is a string. This is used to construct the name of the Git + /// attribute to look for to find files which are handled by this formatter. The `name` key is + /// optional, but is a string and defaults to the given `kind`. The `formatter` key is a string + /// containing the path to the formatter on the system running the checks. Some formatters may + /// work with configuration files committed to the repository. These will also be checked out + /// when using this formatter. These may be valid Git path specifications with globs. If + /// problems are found, the optional `fix_message` key (a string) will be added to the message. + /// This should describe how to fix the issues found by the formatter. The `timeout` key is an + /// optional positive integer. If given, formatters not completing within the specified time + /// are considered failures. Without a timeout, formatters which do not exit will cause the + /// formatting check to wait forever. + /// + /// This check is registered as a commit check with the name `"formatting"` and a topic check + /// with the name `"formatting/topic"`. + /// + /// # Example + /// + /// ```json + /// { + /// "name": "formatter name", + /// "kind": "kind", + /// "formatter": "/path/to/formatter", + /// "config_files": [ + /// "path/to/config/file" + /// ], + /// "fix_message": "instructions for fixing", + /// "timeout": 10, + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct FormattingConfig { + #[serde(default)] + name: Option, + kind: String, + formatter: String, + #[serde(default)] + config_files: Option>, + #[serde(default)] + fix_message: Option, + #[serde(default)] + timeout: Option, + } + + impl IntoCheck for FormattingConfig { + type Check = Formatting; + + fn into_check(self) -> Self::Check { + let mut builder = Formatting::builder(); + + builder.kind(self.kind).formatter(self.formatter); + + if let Some(name) = self.name { + builder.name(name); + } + + if let Some(config_files) = self.config_files { + builder.config_files(config_files); + } + + if let Some(fix_message) = self.fix_message { + builder.fix_message(fix_message); + } + + if let Some(timeout) = self.timeout { + builder.timeout(Duration::from_secs(timeout)); + } + + builder + .build() + .expect("configuration mismatch for `Formatting`") + } + } + + register_checks! { + FormattingConfig { + "formatting" => CommitCheckConfig, + "formatting/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_formatting_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "kind"); + } + + #[test] + fn test_formatting_config_kind_is_required() { + let exp_formatter = "/path/to/formatter"; + let json = json!({ + "formatter": exp_formatter, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "kind"); + } + + #[test] + fn test_formatting_config_formatter_is_required() { + let exp_kind = "kind"; + let json = json!({ + "kind": exp_kind, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "formatter"); + } + + #[test] + fn test_formatting_config_minimum_fields() { + let exp_kind = "kind"; + let exp_formatter = "/path/to/formatter"; + let json = json!({ + "kind": exp_kind, + "formatter": exp_formatter, + }); + let check: FormattingConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.name, None); + assert_eq!(check.kind, exp_kind); + assert_eq!(check.formatter, exp_formatter); + assert_eq!(check.config_files, None); + assert_eq!(check.fix_message, None); + assert_eq!(check.timeout, None); + } + + #[test] + fn test_formatting_config_all_fields() { + let exp_name: String = "formatter name".into(); + let exp_kind = "kind"; + let exp_formatter = "/path/to/formatter"; + let exp_config: String = "path/to/config/file".into(); + let exp_fix_message: String = "instructions for fixing".into(); + let exp_timeout = 10; + let json = json!({ + "name": exp_name.clone(), + "kind": exp_kind, + "formatter": exp_formatter, + "config_files": [exp_config.clone()], + "fix_message": exp_fix_message.clone(), + "timeout": exp_timeout, + }); + let check: FormattingConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.name, Some(exp_name)); + assert_eq!(check.kind, exp_kind); + assert_eq!(check.formatter, exp_formatter); + itertools::assert_equal(&check.config_files.unwrap(), &[exp_config]); + assert_eq!(check.fix_message, Some(exp_fix_message)); + assert_eq!(check.timeout, Some(exp_timeout)); + } +} + #[cfg(test)] mod tests { use std::time::Duration; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 3397d0fd..f50390ac 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -187,6 +187,7 @@ pub mod config { pub use check_size::config::CheckSizeConfig; pub use check_whitespace::config::CheckWhitespaceConfig; pub use commit_subject::config::CommitSubjectConfig; + pub use formatting::config::FormattingConfig; } #[cfg(test)] -- GitLab From 13fa621bed3c64567bd7a35f9ab2b8821260edd5 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:23:02 -0400 Subject: [PATCH 15/30] invalid_paths: add configuration parsing --- git-checks/src/invalid_paths.rs | 95 ++++++++++++++++++++++++++++++++- git-checks/src/lib.rs | 1 + 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/git-checks/src/invalid_paths.rs b/git-checks/src/invalid_paths.rs index 10288c22..3913b0bc 100644 --- a/git-checks/src/invalid_paths.rs +++ b/git-checks/src/invalid_paths.rs @@ -17,7 +17,7 @@ pub struct InvalidPaths { /// In addition to whitespace, control, and non-ASCII characters, which are always disallowed. /// /// Configuration: Optional - /// Default: `String::new()` + /// Default: `String::new() #[builder(setter(into))] #[builder(default)] invalid_characters: String, @@ -139,6 +139,99 @@ impl ContentCheck for InvalidPaths { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use InvalidPaths; + + /// Configuration for the `InvalidPaths` check. + /// + /// The `invalid_characters` key is a string containing characters which are not allowed to + /// appear in paths. By default, its value is `"<>:\"|?*"` which excludes characters not + /// allowed in paths on Windows. The `allow_space` key is a boolean defaulting to false which + /// specifies whether ASCII space is allowed (whitespace is otherwise disallowed). + /// + /// This check is registered as a commit check with the name `"invalid_paths"` and a topic + /// check with the name `"invalid_paths/topic"`. + /// + /// # Example + /// + /// ```json + /// { + /// "invalid_characters": "<>:\"|?*", + /// "allow_space": false + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct InvalidPathsConfig { + #[serde(default)] + invalid_characters: Option, + #[serde(default)] + allow_space: Option, + #[serde(default)] + enforce_windows_rules: Option, + } + + impl IntoCheck for InvalidPathsConfig { + type Check = InvalidPaths; + + fn into_check(self) -> Self::Check { + let mut builder = InvalidPaths::builder(); + + if let Some(invalid_characters) = self.invalid_characters { + builder.invalid_characters(invalid_characters); + } + + if let Some(allow_space) = self.allow_space { + builder.allow_space(allow_space); + } + + if let Some(enforce_windows_rules) = self.enforce_windows_rules { + builder.enforce_windows_rules(enforce_windows_rules); + } + + builder + .build() + .expect("configuration mismatch for `InvalidPaths`") + } + } + + register_checks! { + InvalidPathsConfig { + "invalid_paths" => CommitCheckConfig, + "invalid_paths/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_invalid_paths_config_empty() { + let json = json!({}); + let check: InvalidPathsConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.invalid_characters, None); + assert_eq!(check.allow_space, None); + assert_eq!(check.enforce_windows_rules, None); + } + + #[test] + fn test_invalid_paths_config_all_fields() { + let json = json!({ + "invalid_characters": "abc", + "allow_space": true, + "enforce_windows_rules": true, + }); + let check: InvalidPathsConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.invalid_characters, Some("abc".into())); + assert_eq!(check.allow_space, Some(true)); + assert_eq!(check.enforce_windows_rules, Some(true)); + } +} + #[cfg(test)] mod tests { use test::*; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index f50390ac..0249c9b8 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -188,6 +188,7 @@ pub mod config { pub use check_whitespace::config::CheckWhitespaceConfig; pub use commit_subject::config::CommitSubjectConfig; pub use formatting::config::FormattingConfig; + pub use invalid_paths::config::InvalidPathsConfig; } #[cfg(test)] -- GitLab From dda07858c7662daf491fb6b1d0243abf7825ccbe Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:23:14 -0400 Subject: [PATCH 16/30] invalid_utf8: add configuration parsing --- git-checks/src/invalid_utf8.rs | 40 ++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 41 insertions(+) diff --git a/git-checks/src/invalid_utf8.rs b/git-checks/src/invalid_utf8.rs index eef5099d..00b490f9 100644 --- a/git-checks/src/invalid_utf8.rs +++ b/git-checks/src/invalid_utf8.rs @@ -84,6 +84,46 @@ impl ContentCheck for InvalidUtf8 { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use InvalidUtf8; + + /// Configuration for the `InvalidUtf8` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"invalid_utf8"` and a topic check + /// with the name `"invalid_utf8/topic"`. + #[derive(Deserialize, Debug)] + pub struct InvalidUtf8Config {} + + impl IntoCheck for InvalidUtf8Config { + type Check = InvalidUtf8; + + fn into_check(self) -> Self::Check { + InvalidUtf8::default() + } + } + + register_checks! { + InvalidUtf8Config { + "invalid_utf8" => CommitCheckConfig, + "invalid_utf8/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_invalid_utf8_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 0249c9b8..9eb9b0b4 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -189,6 +189,7 @@ pub mod config { pub use commit_subject::config::CommitSubjectConfig; pub use formatting::config::FormattingConfig; pub use invalid_paths::config::InvalidPathsConfig; + pub use invalid_utf8::config::InvalidUtf8Config; } #[cfg(test)] -- GitLab From 0a55779e143c19ebaa11385a5fd0e6f9df53ad07 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:23:24 -0400 Subject: [PATCH 17/30] lfs_pointer: add configuration parsing --- git-checks/src/lfs_pointer.rs | 40 +++++++++++++++++++++++++++++++++++ git-checks/src/lib.rs | 1 + 2 files changed, 41 insertions(+) diff --git a/git-checks/src/lfs_pointer.rs b/git-checks/src/lfs_pointer.rs index 8530e905..c3856ade 100644 --- a/git-checks/src/lfs_pointer.rs +++ b/git-checks/src/lfs_pointer.rs @@ -237,6 +237,46 @@ impl ContentCheck for LfsPointer { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use LfsPointer; + + /// Configuration for the `LfsPointer` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"lfs_pointer"` and a topic check + /// with the name `"lfs_pointer/topic"`. + #[derive(Deserialize, Debug)] + pub struct LfsPointerConfig {} + + impl IntoCheck for LfsPointerConfig { + type Check = LfsPointer; + + fn into_check(self) -> Self::Check { + LfsPointer::default() + } + } + + register_checks! { + LfsPointerConfig { + "lfs_pointer" => CommitCheckConfig, + "lfs_pointer/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_lfs_pointer_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 9eb9b0b4..0315166f 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -190,6 +190,7 @@ pub mod config { pub use formatting::config::FormattingConfig; pub use invalid_paths::config::InvalidPathsConfig; pub use invalid_utf8::config::InvalidUtf8Config; + pub use lfs_pointer::config::LfsPointerConfig; } #[cfg(test)] -- GitLab From e0df13a5577ac6d4f800f0742a23a814a4c8adea Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:23:39 -0400 Subject: [PATCH 18/30] reject_binaries: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/reject_binaries.rs | 40 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 0315166f..b32a0b27 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -191,6 +191,7 @@ pub mod config { pub use invalid_paths::config::InvalidPathsConfig; pub use invalid_utf8::config::InvalidUtf8Config; pub use lfs_pointer::config::LfsPointerConfig; + pub use reject_binaries::config::RejectBinariesConfig; } #[cfg(test)] diff --git a/git-checks/src/reject_binaries.rs b/git-checks/src/reject_binaries.rs index b74fc5d4..36ece11b 100644 --- a/git-checks/src/reject_binaries.rs +++ b/git-checks/src/reject_binaries.rs @@ -95,6 +95,46 @@ impl ContentCheck for RejectBinaries { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use RejectBinaries; + + /// Configuration for the `RejectBinaries` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"reject_binaries"` and a topic + /// check with the name `"reject_binaries/topic"`. + #[derive(Deserialize, Debug)] + pub struct RejectBinariesConfig {} + + impl IntoCheck for RejectBinariesConfig { + type Check = RejectBinaries; + + fn into_check(self) -> Self::Check { + RejectBinaries::default() + } + } + + register_checks! { + RejectBinariesConfig { + "reject_binaries" => CommitCheckConfig, + "reject_binaries/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_reject_binaries_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From d5fff9310875fe6a79d6b67c84da68328e73e506 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:24:02 -0400 Subject: [PATCH 19/30] reject_conflict_paths: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/reject_conflict_paths.rs | 59 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index b32a0b27..ef8d74d6 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -192,6 +192,7 @@ pub mod config { pub use invalid_utf8::config::InvalidUtf8Config; pub use lfs_pointer::config::LfsPointerConfig; pub use reject_binaries::config::RejectBinariesConfig; + pub use reject_conflict_paths::config::RejectConflictPathsConfig; } #[cfg(test)] diff --git a/git-checks/src/reject_conflict_paths.rs b/git-checks/src/reject_conflict_paths.rs index 7ca88094..bf398580 100644 --- a/git-checks/src/reject_conflict_paths.rs +++ b/git-checks/src/reject_conflict_paths.rs @@ -143,6 +143,65 @@ impl ContentCheck for RejectConflictPaths { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use RejectConflictPaths; + + /// Configuration for the `CheckEndOfLine` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"check_end_of_line"` and a topic + /// check with the name `"check_end_of_line/topic"`. + #[derive(Deserialize, Debug)] + pub struct RejectConflictPathsConfig { + #[serde(default)] + require_base_exist: Option, + } + + impl IntoCheck for RejectConflictPathsConfig { + type Check = RejectConflictPaths; + + fn into_check(self) -> Self::Check { + let mut builder = RejectConflictPaths::builder(); + + if let Some(require_base_exist) = self.require_base_exist { + builder.require_base_exist(require_base_exist); + } + + builder + .build() + .expect("configuration mismatch for `RejectConflictPaths`") + } + } + + register_checks! { + RejectConflictPathsConfig { + "reject_conflict_paths" => CommitCheckConfig, + "reject_conflict_paths/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_reject_conflict_paths_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } + + #[test] + fn test_reject_conflict_paths_config_all_fields() { + let json = json!({ + "require_base_exist": false, + }); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 517da2366700bb4d550a54f5f252973d6f8dd9a8 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:24:12 -0400 Subject: [PATCH 20/30] reject_merges: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/reject_merges.rs | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index ef8d74d6..dae43449 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -193,6 +193,7 @@ pub mod config { pub use lfs_pointer::config::LfsPointerConfig; pub use reject_binaries::config::RejectBinariesConfig; pub use reject_conflict_paths::config::RejectConflictPathsConfig; + pub use reject_merges::config::RejectMergesConfig; } #[cfg(test)] diff --git a/git-checks/src/reject_merges.rs b/git-checks/src/reject_merges.rs index 8c3e442e..93c97bdc 100644 --- a/git-checks/src/reject_merges.rs +++ b/git-checks/src/reject_merges.rs @@ -39,6 +39,45 @@ impl Check for RejectMerges { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use RejectMerges; + + /// Configuration for the `RejectMerges` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"reject_merges"` and a topic + /// check with the name `"reject_merges/topic"`. + #[derive(Deserialize, Debug)] + pub struct RejectMergesConfig {} + + impl IntoCheck for RejectMergesConfig { + type Check = RejectMerges; + + fn into_check(self) -> Self::Check { + RejectMerges::default() + } + } + + register_checks! { + RejectMergesConfig { + "reject_merges" => CommitCheckConfig, + }, + } + + #[test] + fn test_reject_merges_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 2850e9604a8e562d072b68b742b97626bfb1ddab Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:24:23 -0400 Subject: [PATCH 21/30] reject_separate_root: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/reject_separate_root.rs | 38 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index dae43449..d29f50b0 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -194,6 +194,7 @@ pub mod config { pub use reject_binaries::config::RejectBinariesConfig; pub use reject_conflict_paths::config::RejectConflictPathsConfig; pub use reject_merges::config::RejectMergesConfig; + pub use reject_separate_root::config::RejectSeparateRootConfig; } #[cfg(test)] diff --git a/git-checks/src/reject_separate_root.rs b/git-checks/src/reject_separate_root.rs index bdfc6195..043b1b51 100644 --- a/git-checks/src/reject_separate_root.rs +++ b/git-checks/src/reject_separate_root.rs @@ -39,6 +39,44 @@ impl Check for RejectSeparateRoot { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use RejectSeparateRoot; + + /// Configuration for the `RejectSeparateRoot` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"reject_separate_root"`. + #[derive(Deserialize, Debug)] + pub struct RejectSeparateRootConfig {} + + impl IntoCheck for RejectSeparateRootConfig { + type Check = RejectSeparateRoot; + + fn into_check(self) -> Self::Check { + RejectSeparateRoot::default() + } + } + + register_checks! { + RejectSeparateRootConfig { + "reject_separate_root" => CommitCheckConfig, + }, + } + + #[test] + fn test_reject_separate_root_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 17b6d8b81f02cbd120cd00260c655b6b877a3ff0 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:24:34 -0400 Subject: [PATCH 22/30] reject_syminks: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/reject_symlinks.rs | 40 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index d29f50b0..8d33c665 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -195,6 +195,7 @@ pub mod config { pub use reject_conflict_paths::config::RejectConflictPathsConfig; pub use reject_merges::config::RejectMergesConfig; pub use reject_separate_root::config::RejectSeparateRootConfig; + pub use reject_symlinks::config::RejectSymlinksConfig; } #[cfg(test)] diff --git a/git-checks/src/reject_symlinks.rs b/git-checks/src/reject_symlinks.rs index e8562bfa..2dc14e9a 100644 --- a/git-checks/src/reject_symlinks.rs +++ b/git-checks/src/reject_symlinks.rs @@ -51,6 +51,46 @@ impl ContentCheck for RejectSymlinks { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use RejectSymlinks; + + /// Configuration for the `RejectSymlinks` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"reject_symlinks"` and a topic + /// check with the name `"reject_symlinks/topic"`. + #[derive(Deserialize, Debug)] + pub struct RejectSymlinksConfig {} + + impl IntoCheck for RejectSymlinksConfig { + type Check = RejectSymlinks; + + fn into_check(self) -> Self::Check { + RejectSymlinks::default() + } + } + + register_checks! { + RejectSymlinksConfig { + "reject_symlinks" => CommitCheckConfig, + "reject_symlinks/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_reject_symlinks_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From c27423916b0abab2efb6c5d9b1f5e0a5a840922a Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:24:46 -0400 Subject: [PATCH 23/30] release_branch: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/release_branch.rs | 124 +++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 8d33c665..4ee76b5c 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -196,6 +196,7 @@ pub mod config { pub use reject_merges::config::RejectMergesConfig; pub use reject_separate_root::config::RejectSeparateRootConfig; pub use reject_symlinks::config::RejectSymlinksConfig; + pub use release_branch::config::ReleaseBranchConfig; } #[cfg(test)] diff --git a/git-checks/src/release_branch.rs b/git-checks/src/release_branch.rs index 9d108004..afa8d464 100644 --- a/git-checks/src/release_branch.rs +++ b/git-checks/src/release_branch.rs @@ -117,6 +117,130 @@ impl BranchCheck for ReleaseBranch { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{BranchCheckConfig, IntoCheck}; + use crates::git_workarea::CommitId; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use ReleaseBranch; + + /// Configuration for the `ReleaseBranch` check. + /// + /// The `branch` key is a string which defaults to `release`. This is the name of the branch + /// which contains the version which is being checked. The `disallowed_commit` is a string with + /// the full hash of the first commit which happened on the original branch after the release + /// branch was created. The `required` key is a boolean defaulting to `false` which indicates + /// whether the check is a hard failure or not. + /// + /// This check is registered as a branch check with the name `"branch_check"`. + /// + /// # Example + /// + /// ```json + /// { + /// "branch": "v1.x", + /// "disallowed_commit": "post-branch commit hash", + /// "required": true + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct ReleaseBranchConfig { + branch: String, + disallowed_commit: String, + #[serde(default)] + required: Option, + } + + impl IntoCheck for ReleaseBranchConfig { + type Check = ReleaseBranch; + + fn into_check(self) -> Self::Check { + let mut builder = ReleaseBranch::builder(); + + builder + .branch(self.branch) + .disallowed_commit(CommitId::new(self.disallowed_commit)); + + if let Some(required) = self.required { + builder.required(required); + } + + builder + .build() + .expect("configuration mismatch for `ReleaseBranch`") + } + } + + register_checks! { + ReleaseBranchConfig { + "release_branch" => BranchCheckConfig, + }, + } + + #[test] + fn test_release_branch_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "branch"); + } + + #[test] + fn test_release_branch_config_branch_is_required() { + let exp_disallowed_commit = "post-branch commit hash"; + let json = json!({ + "disallowed_commit": exp_disallowed_commit, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "branch"); + } + + #[test] + fn test_release_branch_config_disallowed_commit_is_required() { + let exp_branch = "v1.x"; + let json = json!({ + "branch": exp_branch, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "disallowed_commit"); + } + + #[test] + fn test_release_branch_config_minimum_fields() { + let exp_branch = "v1.x"; + let exp_disallowed_commit = "post-branch commit hash"; + let json = json!({ + "branch": exp_branch, + "disallowed_commit": exp_disallowed_commit, + }); + let check: ReleaseBranchConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.branch, exp_branch); + assert_eq!(check.disallowed_commit, exp_disallowed_commit); + assert_eq!(check.required, None); + } + + #[test] + fn test_release_branch_config_all_fields() { + let exp_branch = "v1.x"; + let exp_disallowed_commit = "post-branch commit hash"; + let json = json!({ + "branch": exp_branch, + "disallowed_commit": exp_disallowed_commit, + "required": true, + }); + let check: ReleaseBranchConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.branch, exp_branch); + assert_eq!(check.disallowed_commit, exp_disallowed_commit); + assert_eq!(check.required, Some(true)); + } +} + #[cfg(test)] mod tests { use crates::git_workarea::CommitId; -- GitLab From 87049bd36ea152858c36b8345a55bfca92529035 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:24:59 -0400 Subject: [PATCH 24/30] restricted_path: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/restricted_path.rs | 93 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 4ee76b5c..560ffac5 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -197,6 +197,7 @@ pub mod config { pub use reject_separate_root::config::RejectSeparateRootConfig; pub use reject_symlinks::config::RejectSymlinksConfig; pub use release_branch::config::ReleaseBranchConfig; + pub use restricted_path::config::RestrictedPathConfig; } #[cfg(test)] diff --git a/git-checks/src/restricted_path.rs b/git-checks/src/restricted_path.rs index 3d2218b4..c584dc55 100644 --- a/git-checks/src/restricted_path.rs +++ b/git-checks/src/restricted_path.rs @@ -70,6 +70,99 @@ impl ContentCheck for RestrictedPath { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck, TopicCheckConfig}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use RestrictedPath; + + /// Configuration for the `RestrictedPath` check. + /// + /// The `restricted_path` key is a string with the path to the content which should be watched. + /// The `required` key is a boolean which defaults to `true` which indicates whether modifying + /// the path is an error or a warning. + /// + /// This check is registered as a commit check with the name `"restricted_path"` and as a topic + /// check with the name `"restricted_path/topic"`. + /// + /// # Example + /// + /// ```json + /// { + /// "restricted_path": "path/to/restricted/content", + /// "required": false + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct RestrictedPathConfig { + path: String, + #[serde(default)] + required: Option, + } + + impl IntoCheck for RestrictedPathConfig { + type Check = RestrictedPath; + + fn into_check(self) -> Self::Check { + let mut builder = RestrictedPath::builder(); + + builder.path(self.path); + + if let Some(required) = self.required { + builder.required(required); + } + + builder + .build() + .expect("configuration mismatch for `RestrictedPath`") + } + } + + register_checks! { + RestrictedPathConfig { + "restricted_path" => CommitCheckConfig, + "restricted_path/topic" => TopicCheckConfig, + }, + } + + #[test] + fn test_restricted_path_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "path"); + } + + #[test] + fn test_restricted_path_config_minimum_fields() { + let exp_restricted_path = "path/to/restricted/content"; + let json = json!({ + "path": exp_restricted_path, + }); + let check: RestrictedPathConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.path, exp_restricted_path); + assert_eq!(check.required, None); + } + + #[test] + fn test_restricted_path_config_all_fields() { + let exp_restricted_path = "path/to/restricted/content"; + let json = json!({ + "path": exp_restricted_path, + "required": false, + }); + let check: RestrictedPathConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.path, exp_restricted_path); + assert_eq!(check.required, Some(false)); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 5eaf94f088abdb88bc3e8135de0ad62535a64e7b Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:25:15 -0400 Subject: [PATCH 25/30] submodule_available: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/submodule_available.rs | 69 +++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 560ffac5..274800dd 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -198,6 +198,7 @@ pub mod config { pub use reject_symlinks::config::RejectSymlinksConfig; pub use release_branch::config::ReleaseBranchConfig; pub use restricted_path::config::RestrictedPathConfig; + pub use submodule_available::config::SubmoduleAvailableConfig; } #[cfg(test)] diff --git a/git-checks/src/submodule_available.rs b/git-checks/src/submodule_available.rs index aa4c8c29..48863f11 100644 --- a/git-checks/src/submodule_available.rs +++ b/git-checks/src/submodule_available.rs @@ -215,6 +215,75 @@ impl Check for SubmoduleAvailable { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use SubmoduleAvailable; + + /// Configuration for the `SubmoduleAvailable` check. + /// + /// The `require_first_parent` key is a boolean which defaults to `false`. + /// + /// This check is registered as a commit check with the name `"submodule_available"`. + /// + /// # Example + /// + /// ```json + /// { + /// "require_first_parent": false + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct SubmoduleAvailableConfig { + #[serde(default)] + require_first_parent: Option, + } + + impl IntoCheck for SubmoduleAvailableConfig { + type Check = SubmoduleAvailable; + + fn into_check(self) -> Self::Check { + let mut builder = SubmoduleAvailable::builder(); + + if let Some(require_first_parent) = self.require_first_parent { + builder.require_first_parent(require_first_parent); + } + + builder + .build() + .expect("configuration mismatch for `SubmoduleAvailable`") + } + } + + register_checks! { + SubmoduleAvailableConfig { + "submodule_available" => CommitCheckConfig, + }, + } + + #[test] + fn test_submodule_available_config_empty() { + let json = json!({}); + let check: SubmoduleAvailableConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.require_first_parent, None); + } + + #[test] + fn test_submodule_available_config_all_fields() { + let json = json!({ + "require_first_parent": true, + }); + let check: SubmoduleAvailableConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.require_first_parent, Some(true)); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 87c4f53ac3cc438a36c532ac23020bbc68cea029 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:25:24 -0400 Subject: [PATCH 26/30] submodule_rewind: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/submodule_rewind.rs | 38 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 274800dd..4b8b7ab0 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -199,6 +199,7 @@ pub mod config { pub use release_branch::config::ReleaseBranchConfig; pub use restricted_path::config::RestrictedPathConfig; pub use submodule_available::config::SubmoduleAvailableConfig; + pub use submodule_rewind::config::SubmoduleRewindConfig; } #[cfg(test)] diff --git a/git-checks/src/submodule_rewind.rs b/git-checks/src/submodule_rewind.rs index b2ae2faa..e646a21c 100644 --- a/git-checks/src/submodule_rewind.rs +++ b/git-checks/src/submodule_rewind.rs @@ -138,6 +138,44 @@ impl Check for SubmoduleRewind { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use SubmoduleRewind; + + /// Configuration for the `SubmoduleRewind` check. + /// + /// No configuration available. + /// + /// This check is registered as a commit check with the name `"submodule_rewind"`. + #[derive(Deserialize, Debug)] + pub struct SubmoduleRewindConfig {} + + impl IntoCheck for SubmoduleRewindConfig { + type Check = SubmoduleRewind; + + fn into_check(self) -> Self::Check { + SubmoduleRewind::default() + } + } + + register_checks! { + SubmoduleRewindConfig { + "submodule_rewind" => CommitCheckConfig, + }, + } + + #[test] + fn test_submodule_rewind_config_empty() { + let json = json!({}); + serde_json::from_value::(json).unwrap(); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 512e9c3bf017d1066bea15b9e02afd5c6d013e12 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:25:34 -0400 Subject: [PATCH 27/30] submodule_watch: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/submodule_watch.rs | 80 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 4b8b7ab0..01c5762d 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -200,6 +200,7 @@ pub mod config { pub use restricted_path::config::RestrictedPathConfig; pub use submodule_available::config::SubmoduleAvailableConfig; pub use submodule_rewind::config::SubmoduleRewindConfig; + pub use submodule_watch::config::SubmoduleWatchConfig; } #[cfg(test)] diff --git a/git-checks/src/submodule_watch.rs b/git-checks/src/submodule_watch.rs index 80453859..9c758ee3 100644 --- a/git-checks/src/submodule_watch.rs +++ b/git-checks/src/submodule_watch.rs @@ -117,6 +117,86 @@ impl Check for SubmoduleWatch { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + use SubmoduleWatch; + + /// Configuration for the `SubmoduleWatch` check. + /// + /// The `reject_additions` and `reject_removals` keys are both booleans which default to + /// `false`. + /// + /// This check is registered as a commit check with the name `"submodule_watch"`. + /// + /// # Example + /// + /// ```json + /// { + /// "reject_additions": false, + /// "reject_removals": false + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct SubmoduleWatchConfig { + #[serde(default)] + reject_additions: Option, + #[serde(default)] + reject_removals: Option, + } + + impl IntoCheck for SubmoduleWatchConfig { + type Check = SubmoduleWatch; + + fn into_check(self) -> Self::Check { + let mut builder = SubmoduleWatch::builder(); + + if let Some(reject_additions) = self.reject_additions { + builder.reject_additions(reject_additions); + } + + if let Some(reject_removals) = self.reject_removals { + builder.reject_removals(reject_removals); + } + + builder + .build() + .expect("configuration mismatch for `SubmoduleWatch`") + } + } + + register_checks! { + SubmoduleWatchConfig { + "submodule_watch" => CommitCheckConfig, + }, + } + + #[test] + fn test_submodule_watch_config_empty() { + let json = json!({}); + let check: SubmoduleWatchConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.reject_additions, None); + assert_eq!(check.reject_removals, None); + } + + #[test] + fn test_submodule_watch_config_all_fields() { + let json = json!({ + "reject_additions": true, + "reject_removals": true, + }); + let check: SubmoduleWatchConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.reject_additions, Some(true)); + assert_eq!(check.reject_removals, Some(true)); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From cac4a2294b4b765e978e5a2b5c0b45f0ca27b9a1 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:25:45 -0400 Subject: [PATCH 28/30] third_party: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/third_party.rs | 141 ++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 01c5762d..760cf730 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -201,6 +201,7 @@ pub mod config { pub use submodule_available::config::SubmoduleAvailableConfig; pub use submodule_rewind::config::SubmoduleRewindConfig; pub use submodule_watch::config::SubmoduleWatchConfig; + pub use third_party::config::ThirdPartyConfig; } #[cfg(test)] diff --git a/git-checks/src/third_party.rs b/git-checks/src/third_party.rs index 9dc951a5..84e5c1c8 100644 --- a/git-checks/src/third_party.rs +++ b/git-checks/src/third_party.rs @@ -279,6 +279,147 @@ impl Check for ThirdParty { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + #[cfg(test)] + use crates::serde_json; + + #[cfg(test)] + use test; + use ThirdParty; + + /// Configuration for the `ThirdParty` check. + /// + /// All keys are required and strings. The `name` and `script` keys are informational and only + /// appear in messages. Any modifications at `path` are checked to ensure that they are tracked + /// on an "import branch" rooted with the given commit specified by the `root` key. + /// + /// This check is registered as a commit check with the name `"third_party"`. + /// + /// # Example + /// + /// ```json + /// { + /// "name": "extlib", + /// "path": "path/to/import/of/extlib", + /// "root": "root commit", + /// "script": "path/to/update/script" + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct ThirdPartyConfig { + name: String, + path: String, + root: String, + utility: String, + } + + impl IntoCheck for ThirdPartyConfig { + type Check = ThirdParty; + + fn into_check(self) -> Self::Check { + ThirdParty::builder() + .name(self.name) + .path(self.path) + .root(self.root) + .utility(self.utility) + .build() + .expect("configuration mismatch for `ThirdParty`") + } + } + + register_checks! { + ThirdPartyConfig { + "third_party" => CommitCheckConfig, + }, + } + + #[test] + fn test_third_party_config_empty() { + let json = json!({}); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "name"); + } + + #[test] + fn test_third_party_config_name_is_required() { + let exp_path = "path/to/import/of/extlib"; + let exp_root = "root commit"; + let exp_utility = "path/to/update/utility"; + let json = json!({ + "path": exp_path, + "root": exp_root, + "utility": exp_utility, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "name"); + } + + #[test] + fn test_third_party_config_path_is_required() { + let exp_name = "extlib"; + let exp_root = "root commit"; + let exp_utility = "path/to/update/utility"; + let json = json!({ + "name": exp_name, + "root": exp_root, + "utility": exp_utility, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "path"); + } + + #[test] + fn test_third_party_config_root_is_required() { + let exp_name = "extlib"; + let exp_path = "path/to/import/of/extlib"; + let exp_utility = "path/to/update/utility"; + let json = json!({ + "name": exp_name, + "path": exp_path, + "utility": exp_utility, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "root"); + } + + #[test] + fn test_third_party_config_utility_is_required() { + let exp_name = "extlib"; + let exp_path = "path/to/import/of/extlib"; + let exp_root = "root commit"; + let json = json!({ + "name": exp_name, + "path": exp_path, + "root": exp_root, + }); + let err = serde_json::from_value::(json).unwrap_err(); + test::check_missing_json_field(err, "utility"); + } + + #[test] + fn test_third_party_config_minimum_fields() { + let exp_name = "extlib"; + let exp_path = "path/to/import/of/extlib"; + let exp_root = "root commit"; + let exp_utility = "path/to/update/utility"; + let json = json!({ + "name": exp_name, + "path": exp_path, + "root": exp_root, + "utility": exp_utility, + }); + let check: ThirdPartyConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.name, exp_name); + assert_eq!(check.path, exp_path); + assert_eq!(check.root, exp_root); + assert_eq!(check.utility, exp_utility); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 774322273e07177bb236c964636a1a24e88d3a82 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:26:03 -0400 Subject: [PATCH 29/30] valid_name: add configuration parsing --- git-checks/src/lib.rs | 1 + git-checks/src/valid_name.rs | 147 +++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/git-checks/src/lib.rs b/git-checks/src/lib.rs index 760cf730..3906737a 100644 --- a/git-checks/src/lib.rs +++ b/git-checks/src/lib.rs @@ -202,6 +202,7 @@ pub mod config { pub use submodule_rewind::config::SubmoduleRewindConfig; pub use submodule_watch::config::SubmoduleWatchConfig; pub use third_party::config::ThirdPartyConfig; + pub use valid_name::config::ValidNameConfig; } #[cfg(test)] diff --git a/git-checks/src/valid_name.rs b/git-checks/src/valid_name.rs index b0adaae5..ac5cbccb 100644 --- a/git-checks/src/valid_name.rs +++ b/git-checks/src/valid_name.rs @@ -264,6 +264,153 @@ impl BranchCheck for ValidName { } } +#[cfg(feature = "config")] +pub(crate) mod config { + use crates::git_checks_config::{CommitCheckConfig, IntoCheck}; + use crates::inventory; + #[cfg(test)] + use crates::serde::Deserialize; + #[cfg(test)] + use crates::serde_json; + + use ValidName; + use ValidNameFullNamePolicy; + + /// Configuration for full name policies. + #[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)] + pub enum ValidNameFullNamePolicyIO { + /// Full names are required and trigger errors if not found. + #[serde(rename = "required")] + Required, + /// Full names are preferred and trigger warnings if not found. + #[serde(rename = "preferred")] + Preferred, + /// Full names are optional and are not checked. + #[serde(rename = "optional")] + Optional, + } + + impl From for ValidNameFullNamePolicy { + fn from(policy: ValidNameFullNamePolicyIO) -> Self { + match policy { + ValidNameFullNamePolicyIO::Required => ValidNameFullNamePolicy::Required, + ValidNameFullNamePolicyIO::Preferred => ValidNameFullNamePolicy::Preferred, + ValidNameFullNamePolicyIO::Optional => ValidNameFullNamePolicy::Optional, + } + } + } + + /// Configuration for the `ValidName` check. + /// + /// The `full_name_policy` key is a string which must be one of `"optional"`, `"preferred"`, or + /// `"required"` (the default). The `whitelisted_domains` is a list of strings which defaults + /// to empty for domains which are assumed to be valid in email addresses. This should contain + /// addresses which are common to the project being watched to avoid false positives when DNS + /// lookup failures occur. + /// + /// This check is registered as a commit check with the name `"valid_name"`. + /// + /// # Example + /// + /// ```json + /// { + /// "full_name_policy": "required", + /// "whitelisted_domains": [ + /// "mycompany.invalid" + /// ] + /// } + /// ``` + #[derive(Deserialize, Debug)] + pub struct ValidNameConfig { + #[serde(default)] + full_name_policy: Option, + #[serde(default)] + whitelisted_domains: Option>, + } + + impl IntoCheck for ValidNameConfig { + type Check = ValidName; + + fn into_check(self) -> Self::Check { + let mut builder = ValidName::builder(); + + if let Some(full_name_policy) = self.full_name_policy { + builder.full_name_policy(full_name_policy.into()); + } + + if let Some(whitelisted_domains) = self.whitelisted_domains { + builder.whitelisted_domains(whitelisted_domains); + } + + builder + .build() + .expect("configuration mismatch for `ValidName`") + } + } + + register_checks! { + ValidNameConfig { + "valid_name" => CommitCheckConfig, + }, + } + + #[test] + fn test_valid_name_full_name_policy_deserialize() { + let value = json!("required"); + let policy = ValidNameFullNamePolicyIO::deserialize(value).unwrap(); + assert_eq!(policy, ValidNameFullNamePolicyIO::Required); + + let value = json!("optional"); + let policy = ValidNameFullNamePolicyIO::deserialize(value).unwrap(); + assert_eq!(policy, ValidNameFullNamePolicyIO::Optional); + + let value = json!("preferred"); + let policy = ValidNameFullNamePolicyIO::deserialize(value).unwrap(); + assert_eq!(policy, ValidNameFullNamePolicyIO::Preferred); + + let value = json!("invalid"); + let err = ValidNameFullNamePolicyIO::deserialize(value).unwrap_err(); + + assert!(!err.is_io()); + assert!(!err.is_syntax()); + assert!(err.is_data()); + assert!(!err.is_eof()); + + let msg = format!("{}", err); + if msg != "unknown variant `invalid`, expected one of `required`, `preferred`, `optional`" { + println!( + "Error message doesn't match. Was a new style added? ({})", + msg, + ); + } + } + + #[test] + fn test_valid_name_config_empty() { + let json = json!({}); + let check: ValidNameConfig = serde_json::from_value(json).unwrap(); + + assert_eq!(check.full_name_policy, None); + assert_eq!(check.whitelisted_domains, None); + } + + #[test] + fn test_valid_name_config_all_fields() { + let exp_domain: String = "mycompany.invalid".into(); + let json = json!({ + "full_name_policy": "optional", + "whitelisted_domains": [exp_domain.clone()], + }); + let check: ValidNameConfig = serde_json::from_value(json).unwrap(); + + assert_eq!( + check.full_name_policy, + Some(ValidNameFullNamePolicyIO::Optional), + ); + itertools::assert_equal(&check.whitelisted_domains, &Some([exp_domain])); + } +} + #[cfg(test)] mod tests { use test::*; -- GitLab From 49e97ac426cd2a84b12f2b6d73a533efed69358e Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Oct 2019 15:54:22 -0400 Subject: [PATCH 30/30] gitlab-ci: disable --all-features on nightly for now Needs a new release for it to work again. Already fixed upstream though. https://github.com/rust-lang/cargo/pull/7525 --- .gitlab-ci.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 39161a34..b835446b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -232,20 +232,21 @@ test:cargo-stable-features: needs: - build:cargo-stable-features -build:cargo-nightly-features: - <<: - - *cargo_build_job - - *rust_nightly_features - - *cargo_cache_newest +# Need a new cargo release with: https://github.com/rust-lang/cargo/pull/7525 +# build:cargo-nightly-features: +# <<: +# - *cargo_build_job +# - *rust_nightly_features +# - *cargo_cache_newest -test:cargo-nightly-features: - <<: - - *cargo_test_job - - *rust_nightly_features - dependencies: - - build:cargo-nightly-features - needs: - - build:cargo-nightly-features +# test:cargo-nightly-features: +# <<: +# - *cargo_test_job +# - *rust_nightly_features +# dependencies: +# - build:cargo-nightly-features +# needs: +# - build:cargo-nightly-features # build:cargo-mindeps-features: # <<: -- GitLab