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