use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ContributionType {
Feature,
Bugfix,
Enhancement,
Documentation,
}
impl std::fmt::Display for ContributionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContributionType::Feature => write!(f, "feature"),
ContributionType::Bugfix => write!(f, "bugfix"),
ContributionType::Enhancement => write!(f, "enhancement"),
ContributionType::Documentation => write!(f, "documentation"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum ContributionStatus {
#[default]
Submitted,
Analyzing,
Generating,
Validating,
Review,
Approved,
Rejected,
Merged,
Cancelled,
}
impl std::fmt::Display for ContributionStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContributionStatus::Submitted => write!(f, "submitted"),
ContributionStatus::Analyzing => write!(f, "analyzing"),
ContributionStatus::Generating => write!(f, "generating"),
ContributionStatus::Validating => write!(f, "validating"),
ContributionStatus::Review => write!(f, "review"),
ContributionStatus::Approved => write!(f, "approved"),
ContributionStatus::Rejected => write!(f, "rejected"),
ContributionStatus::Merged => write!(f, "merged"),
ContributionStatus::Cancelled => write!(f, "cancelled"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Default)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
Low,
#[default]
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ContributionContext {
#[serde(default)]
pub related_collections: Vec<String>,
#[serde(default)]
pub priority: Priority,
#[serde(default)]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Contribution {
#[serde(rename = "_key")]
pub id: String,
pub contribution_type: ContributionType,
pub description: String,
pub requester: String,
pub status: ContributionStatus,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
#[serde(default)]
pub context: ContributionContext,
#[serde(default)]
pub feedback: Option<String>,
#[serde(default)]
pub risk_score: Option<f64>,
#[serde(default)]
pub affected_files: Vec<String>,
}
impl Contribution {
pub fn new(
contribution_type: ContributionType,
description: String,
requester: String,
context: Option<ContributionContext>,
) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
contribution_type,
description,
requester,
status: ContributionStatus::Submitted,
created_at: now,
updated_at: now,
context: context.unwrap_or_default(),
feedback: None,
risk_score: None,
affected_files: Vec::new(),
}
}
pub fn set_status(&mut self, status: ContributionStatus) {
self.status = status;
self.updated_at = Utc::now();
}
pub fn requires_human_review(&self) -> bool {
if let Some(risk) = self.risk_score {
if risk > 0.7 {
return true;
}
}
const CORE_MODULES: &[&str] = &[
"storage/engine",
"transaction/manager",
"cluster/manager",
"server/auth",
"sync/worker",
];
for file in &self.affected_files {
for core in CORE_MODULES {
if file.contains(core) {
return true;
}
}
}
false
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct SubmitContributionRequest {
#[serde(rename = "type")]
pub contribution_type: ContributionType,
pub description: String,
#[serde(default)]
pub context: Option<ContributionContext>,
}
#[derive(Debug, Serialize)]
pub struct SubmitContributionResponse {
pub status: String,
pub id: String,
pub message: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ReviewContributionRequest {
#[serde(default)]
pub feedback: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct ListContributionsResponse {
pub contributions: Vec<Contribution>,
pub total: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contribution_creation() {
let contrib = Contribution::new(
ContributionType::Feature,
"Add dark mode support".to_string(),
"user@example.com".to_string(),
None,
);
assert_eq!(contrib.contribution_type, ContributionType::Feature);
assert_eq!(contrib.status, ContributionStatus::Submitted);
assert!(!contrib.id.is_empty());
}
#[test]
fn test_contribution_status_update() {
let mut contrib = Contribution::new(
ContributionType::Bugfix,
"Fix login issue".to_string(),
"dev@example.com".to_string(),
None,
);
let original_updated = contrib.updated_at;
std::thread::sleep(std::time::Duration::from_millis(10));
contrib.set_status(ContributionStatus::Analyzing);
assert_eq!(contrib.status, ContributionStatus::Analyzing);
assert!(contrib.updated_at > original_updated);
}
#[test]
fn test_requires_human_review_high_risk() {
let mut contrib = Contribution::new(
ContributionType::Feature,
"Add new auth method".to_string(),
"user@example.com".to_string(),
None,
);
contrib.risk_score = Some(0.8);
assert!(contrib.requires_human_review());
contrib.risk_score = Some(0.5);
assert!(!contrib.requires_human_review());
}
#[test]
fn test_requires_human_review_core_module() {
let mut contrib = Contribution::new(
ContributionType::Enhancement,
"Optimize storage".to_string(),
"user@example.com".to_string(),
None,
);
contrib.affected_files = vec!["src/storage/engine.rs".to_string()];
assert!(contrib.requires_human_review());
contrib.affected_files = vec!["src/utils/helpers.rs".to_string()];
assert!(!contrib.requires_human_review());
}
#[test]
fn test_contribution_type_serialization() {
let json = serde_json::to_string(&ContributionType::Feature).unwrap();
assert_eq!(json, "\"feature\"");
let parsed: ContributionType = serde_json::from_str("\"bugfix\"").unwrap();
assert_eq!(parsed, ContributionType::Bugfix);
}
}