use std::fmt;
use std::path::PathBuf;
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, ValueEnum,
)]
#[serde(rename_all = "kebab-case")]
pub enum Category {
RustTarget,
NodeModules,
FrameworkCache,
BuildOutput,
TestCache,
PythonCache,
PythonEnvironment,
GlobalCache,
ExpensiveGlobalCache,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Confidence {
#[default]
Safe,
Review,
Protected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ReviewRule {
SwiftPackageBuild,
XcodeDerivedData,
GradleBuild,
CocoaPods,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CustomRule {
pub name: String,
pub category: Category,
pub directory_names: Vec<String>,
pub required_markers: Vec<String>,
pub reason: String,
}
impl ReviewRule {
#[must_use]
pub fn all() -> [Self; 4] {
[
Self::SwiftPackageBuild,
Self::XcodeDerivedData,
Self::GradleBuild,
Self::CocoaPods,
]
}
}
impl Category {
#[must_use]
pub fn all() -> [Self; 9] {
[
Self::RustTarget,
Self::NodeModules,
Self::FrameworkCache,
Self::BuildOutput,
Self::TestCache,
Self::PythonCache,
Self::PythonEnvironment,
Self::GlobalCache,
Self::ExpensiveGlobalCache,
]
}
#[must_use]
pub fn safe_defaults() -> [Self; 5] {
[
Self::RustTarget,
Self::NodeModules,
Self::FrameworkCache,
Self::PythonCache,
Self::PythonEnvironment,
]
}
}
impl fmt::Display for Category {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
Self::RustTarget => "rust-target",
Self::NodeModules => "node-modules",
Self::FrameworkCache => "framework-cache",
Self::BuildOutput => "build-output",
Self::TestCache => "test-cache",
Self::PythonCache => "python-cache",
Self::PythonEnvironment => "python-environment",
Self::GlobalCache => "global-cache",
Self::ExpensiveGlobalCache => "expensive-global-cache",
};
formatter.pad(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Candidate {
pub category: Category,
pub path: PathBuf,
pub bytes: u64,
pub reason: String,
pub modified_at_unix: Option<u64>,
#[serde(default)]
pub confidence: Confidence,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approved_rule: Option<ReviewRule>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_rule: Option<CustomRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewCandidate {
pub path: PathBuf,
pub bytes: u64,
pub reason: String,
pub modified_at_unix: Option<u64>,
pub confidence: Confidence,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggested_rule: Option<ReviewRule>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_root: Option<PathBuf>,
#[serde(default)]
pub approved: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningObservation {
pub path: PathBuf,
pub category: Option<Category>,
pub bytes: u64,
pub reason: String,
pub modified_at_unix: Option<u64>,
pub confidence: Confidence,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanReport {
pub roots: Vec<PathBuf>,
pub candidates: Vec<Candidate>,
#[serde(default)]
pub review_candidates: Vec<ReviewCandidate>,
#[serde(default)]
pub learning_observations: Vec<LearningObservation>,
pub warnings: Vec<String>,
pub total_bytes: u64,
#[serde(default)]
pub review_total_bytes: u64,
#[serde(default)]
pub observed_total_bytes: u64,
#[serde(default = "default_true")]
pub protect_git_tracked: bool,
}
const fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum OutputFormat {
Table,
Json,
Jsonl,
Html,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RenderOptions {
pub redact_paths: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn review_rule_wire_names_should_match_the_macos_app_contract() {
let encoded: Vec<String> = ReviewRule::all()
.iter()
.map(|rule| serde_json::to_string(rule).expect("rule serializes"))
.collect();
assert_eq!(
encoded,
[
r#""swift-package-build""#,
r#""xcode-derived-data""#,
r#""gradle-build""#,
r#""cocoa-pods""#,
]
);
}
}