Skip to main content

mockforge_intelligence/handlers/drift_budget/
scoped_budgets.rs

1//! Workspace and service budget creation responses.
2
3use super::DriftBudgetSource;
4use super::DriftBudgetStateProvider;
5use axum::{extract::State, http::StatusCode, response::Json};
6use mockforge_contracts::contract_drift::types::DriftBudget;
7use serde::{Deserialize, Serialize};
8
9/// Request to create workspace/service/tag budget
10#[derive(Debug, Deserialize, Serialize)]
11pub struct CreateWorkspaceBudgetRequest {
12    /// Workspace ID
13    pub workspace_id: String,
14    /// Maximum allowed breaking changes
15    pub max_breaking_changes: Option<u32>,
16    /// Maximum allowed non-breaking changes
17    pub max_non_breaking_changes: Option<u32>,
18    /// Maximum field churn percentage
19    pub max_field_churn_percent: Option<f64>,
20    /// Time window in days
21    pub time_window_days: Option<u32>,
22    /// Whether the budget is enabled
23    pub enabled: Option<bool>,
24}
25
26/// Request to create a service-level drift budget
27#[derive(Debug, Deserialize, Serialize)]
28pub struct CreateServiceBudgetRequest {
29    /// Service name
30    pub service_name: String,
31    /// Maximum allowed breaking changes
32    pub max_breaking_changes: Option<u32>,
33    /// Maximum allowed non-breaking changes
34    pub max_non_breaking_changes: Option<u32>,
35    /// Maximum field churn percentage
36    pub max_field_churn_percent: Option<f64>,
37    /// Time window in days
38    pub time_window_days: Option<u32>,
39    /// Whether the budget is enabled
40    pub enabled: Option<bool>,
41}
42
43/// Create or update workspace budget
44///
45/// POST /api/v1/drift/budgets/workspace
46pub async fn create_workspace_budget<S: DriftBudgetStateProvider>(
47    State(state): State<S>,
48    Json(request): Json<CreateWorkspaceBudgetRequest>,
49) -> Result<Json<serde_json::Value>, StatusCode> {
50    let budget = DriftBudget {
51        max_breaking_changes: request.max_breaking_changes.unwrap_or(0),
52        max_non_breaking_changes: request.max_non_breaking_changes.unwrap_or(10),
53        max_field_churn_percent: request.max_field_churn_percent,
54        time_window_days: request.time_window_days,
55        severity_threshold: crate::ai_contract_diff::MismatchSeverity::High,
56        enabled: request.enabled.unwrap_or(true),
57    };
58
59    let mut config = state.engine().config().clone();
60    config
61        .per_workspace_budgets
62        .insert(request.workspace_id.clone(), budget.clone());
63
64    // Note: In a full implementation, this would persist to database
65    // state.engine().update_config(config);
66
67    Ok(Json(serde_json::json!({
68        "workspace_id": request.workspace_id,
69        "budget": budget,
70    })))
71}
72
73/// Create or update service budget
74///
75/// POST /api/v1/drift/budgets/service
76pub async fn create_service_budget<S: DriftBudgetStateProvider>(
77    State(state): State<S>,
78    Json(request): Json<CreateServiceBudgetRequest>,
79) -> Result<Json<serde_json::Value>, StatusCode> {
80    let budget = DriftBudget {
81        max_breaking_changes: request.max_breaking_changes.unwrap_or(0),
82        max_non_breaking_changes: request.max_non_breaking_changes.unwrap_or(10),
83        max_field_churn_percent: request.max_field_churn_percent,
84        time_window_days: request.time_window_days,
85        severity_threshold: crate::ai_contract_diff::MismatchSeverity::High,
86        enabled: request.enabled.unwrap_or(true),
87    };
88
89    let mut config = state.engine().config().clone();
90    config.per_service_budgets.insert(request.service_name.clone(), budget.clone());
91
92    // Note: In a full implementation, this would persist to database
93    // state.engine().update_config(config);
94
95    Ok(Json(serde_json::json!({
96        "service_name": request.service_name,
97        "budget": budget,
98    })))
99}