Skip to main content

mockforge_intelligence/handlers/
pr_generation.rs

1//! PR generation handlers
2//!
3//! This module provides HTTP handlers for triggering PR generation when contract changes are detected.
4
5use axum::extract::State;
6use axum::http::StatusCode;
7use axum::response::Json;
8use serde::{Deserialize, Serialize};
9use std::sync::Arc;
10
11use crate::pr_generation::{PRFileChange, PRFileChangeType, PRGenerator, PRTemplateContext};
12
13/// State for PR generation handlers
14#[derive(Clone)]
15pub struct PRGenerationState {
16    /// PR generator
17    pub generator: Option<Arc<PRGenerator>>,
18}
19
20/// Request to generate a PR
21#[derive(Debug, Deserialize, Serialize)]
22pub struct GeneratePRRequest {
23    /// Endpoint path
24    pub endpoint: String,
25    /// HTTP method
26    pub method: String,
27    /// Number of breaking changes
28    pub breaking_changes: u32,
29    /// Number of non-breaking changes
30    pub non_breaking_changes: u32,
31    /// Change summary
32    pub change_summary: String,
33    /// Affected files
34    pub affected_files: Vec<String>,
35    /// File changes
36    pub file_changes: Vec<PRFileChangeRequest>,
37    /// Labels to add
38    pub labels: Option<Vec<String>>,
39    /// Reviewers to request
40    pub reviewers: Option<Vec<String>>,
41}
42
43/// Request for a file change
44#[derive(Debug, Deserialize, Serialize)]
45pub struct PRFileChangeRequest {
46    /// File path
47    pub path: String,
48    /// File content
49    pub content: String,
50    /// Change type
51    pub change_type: String,
52}
53
54/// Response for PR generation
55#[derive(Debug, Serialize)]
56pub struct GeneratePRResponse {
57    /// PR number
58    pub pr_number: Option<u64>,
59    /// PR URL
60    pub pr_url: Option<String>,
61    /// Branch name
62    pub branch: Option<String>,
63    /// Success status
64    pub success: bool,
65    /// Error message (if any)
66    pub error: Option<String>,
67}
68
69/// Generate a PR from contract changes
70///
71/// POST /api/v1/pr/generate
72pub async fn generate_pr(
73    State(state): State<PRGenerationState>,
74    Json(request): Json<GeneratePRRequest>,
75) -> Result<Json<GeneratePRResponse>, StatusCode> {
76    let generator = state.generator.as_ref().ok_or(StatusCode::SERVICE_UNAVAILABLE)?;
77
78    // Convert file change requests to PR file changes
79    let file_changes: Vec<PRFileChange> = request
80        .file_changes
81        .into_iter()
82        .map(|fc| {
83            let change_type = match fc.change_type.as_str() {
84                "create" => PRFileChangeType::Create,
85                "update" => PRFileChangeType::Update,
86                "delete" => PRFileChangeType::Delete,
87                _ => PRFileChangeType::Update,
88            };
89
90            PRFileChange {
91                path: fc.path,
92                content: fc.content,
93                change_type,
94            }
95        })
96        .collect();
97
98    // Create template context
99    let context = PRTemplateContext {
100        endpoint: request.endpoint,
101        method: request.method,
102        breaking_changes: request.breaking_changes,
103        non_breaking_changes: request.non_breaking_changes,
104        affected_files: request.affected_files,
105        change_summary: request.change_summary,
106        is_breaking: request.breaking_changes > 0,
107        metadata: std::collections::HashMap::new(),
108    };
109
110    // Generate PR
111    match generator
112        .create_pr_from_context(
113            context,
114            file_changes,
115            request.labels.unwrap_or_default(),
116            request.reviewers.unwrap_or_default(),
117        )
118        .await
119    {
120        Ok(pr_result) => Ok(Json(GeneratePRResponse {
121            pr_number: Some(pr_result.number),
122            pr_url: Some(pr_result.url),
123            branch: Some(pr_result.branch),
124            success: true,
125            error: None,
126        })),
127        Err(e) => Ok(Json(GeneratePRResponse {
128            pr_number: None,
129            pr_url: None,
130            branch: None,
131            success: false,
132            error: Some(e.to_string()),
133        })),
134    }
135}
136
137/// Create PR generation router
138pub fn pr_generation_router(state: PRGenerationState) -> axum::Router {
139    use axum::routing::post;
140
141    axum::Router::new()
142        .route("/api/v1/pr/generate", post(generate_pr))
143        .with_state(state)
144}