mockforge_http/handlers/
pr_generation.rs1use 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#[derive(Clone)]
17pub struct PRGenerationState {
18 pub generator: Option<Arc<PRGenerator>>,
20}
21
22#[derive(Debug, Deserialize, Serialize)]
24pub struct GeneratePRRequest {
25 pub endpoint: String,
27 pub method: String,
29 pub breaking_changes: u32,
31 pub non_breaking_changes: u32,
33 pub change_summary: String,
35 pub affected_files: Vec<String>,
37 pub file_changes: Vec<PRFileChangeRequest>,
39 pub labels: Option<Vec<String>>,
41 pub reviewers: Option<Vec<String>>,
43}
44
45#[derive(Debug, Deserialize, Serialize)]
47pub struct PRFileChangeRequest {
48 pub path: String,
50 pub content: String,
52 pub change_type: String,
54}
55
56#[derive(Debug, Serialize)]
58pub struct GeneratePRResponse {
59 pub pr_number: Option<u64>,
61 pub pr_url: Option<String>,
63 pub branch: Option<String>,
65 pub success: bool,
67 pub error: Option<String>,
69}
70
71pub 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 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 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 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
139pub 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;