use super::service::ContractService;
use super::{AnalyzeComplexityContract, AnalyzeSatdContract, AnalyzeDeadCodeContract, AnalyzeTdgContract, AnalyzeLintHotspotContract, QualityGateContract, RefactorAutoContract};
use anyhow::Result;
use axum::{
extract::{Json, State},
http::StatusCode,
response::{IntoResponse, Response},
routing::{get, post},
Router,
};
use serde_json::{json, Value};
use std::sync::Arc;
use tower_http::cors::CorsLayer;
#[derive(Clone)]
struct AppState {
service: Arc<ContractService>,
}
pub fn create_router() -> Result<Router> {
let service = Arc::new(ContractService::new()?);
let state = AppState { service };
Ok(Router::new()
.route("/api/analyze/complexity", post(analyze_complexity))
.route("/api/analyze/satd", post(analyze_satd))
.route("/api/analyze/dead-code", post(analyze_dead_code))
.route("/api/analyze/tdg", post(analyze_tdg))
.route("/api/analyze/lint-hotspot", post(analyze_lint_hotspot))
.route("/api/quality-gate", post(quality_gate))
.route("/api/refactor/auto", post(refactor_auto))
.route("/health", get(health_check))
.route("/api/openapi", get(openapi_spec))
.layer(CorsLayer::permissive())
.with_state(state))
}
async fn analyze_complexity(
State(state): State<AppState>,
Json(mut params): Json<Value>,
) -> Result<Json<Value>, AppError> {
params = super::adapter::BackwardCompatibility::map_json_params(params);
let contract: AnalyzeComplexityContract = serde_json::from_value(params)
.map_err(|e| AppError::BadRequest(format!("Invalid parameters: {e}")))?;
let result = state
.service
.analyze_complexity(contract)
.await
.map_err(AppError::from)?;
Ok(Json(result))
}
async fn analyze_satd(
State(state): State<AppState>,
Json(mut params): Json<Value>,
) -> Result<Json<Value>, AppError> {
params = super::adapter::BackwardCompatibility::map_json_params(params);
let contract: AnalyzeSatdContract = serde_json::from_value(params)
.map_err(|e| AppError::BadRequest(format!("Invalid parameters: {e}")))?;
let result = state
.service
.analyze_satd(contract)
.await
.map_err(AppError::from)?;
Ok(Json(result))
}
async fn analyze_dead_code(
State(state): State<AppState>,
Json(mut params): Json<Value>,
) -> Result<Json<Value>, AppError> {
params = super::adapter::BackwardCompatibility::map_json_params(params);
let contract: AnalyzeDeadCodeContract = serde_json::from_value(params)
.map_err(|e| AppError::BadRequest(format!("Invalid parameters: {e}")))?;
let result = state
.service
.analyze_dead_code(contract)
.await
.map_err(AppError::from)?;
Ok(Json(result))
}
async fn analyze_tdg(
State(state): State<AppState>,
Json(mut params): Json<Value>,
) -> Result<Json<Value>, AppError> {
params = super::adapter::BackwardCompatibility::map_json_params(params);
let contract: AnalyzeTdgContract = serde_json::from_value(params)
.map_err(|e| AppError::BadRequest(format!("Invalid parameters: {e}")))?;
let result = state
.service
.analyze_tdg(contract)
.await
.map_err(AppError::from)?;
Ok(Json(result))
}
async fn analyze_lint_hotspot(
State(state): State<AppState>,
Json(mut params): Json<Value>,
) -> Result<Json<Value>, AppError> {
params = super::adapter::BackwardCompatibility::map_json_params(params);
let contract: AnalyzeLintHotspotContract = serde_json::from_value(params)
.map_err(|e| AppError::BadRequest(format!("Invalid parameters: {e}")))?;
let result = state
.service
.analyze_lint_hotspot(contract)
.await
.map_err(AppError::from)?;
Ok(Json(result))
}
async fn quality_gate(
State(state): State<AppState>,
Json(mut params): Json<Value>,
) -> Result<Json<Value>, AppError> {
params = super::adapter::BackwardCompatibility::map_json_params(params);
let contract: QualityGateContract = serde_json::from_value(params)
.map_err(|e| AppError::BadRequest(format!("Invalid parameters: {e}")))?;
let result = state
.service
.quality_gate(contract)
.await
.map_err(AppError::from)?;
Ok(Json(result))
}
async fn refactor_auto(
State(state): State<AppState>,
Json(mut params): Json<Value>,
) -> Result<Json<Value>, AppError> {
params = super::adapter::BackwardCompatibility::map_json_params(params);
let contract: RefactorAutoContract = serde_json::from_value(params)
.map_err(|e| AppError::BadRequest(format!("Invalid parameters: {e}")))?;
let result = state
.service
.refactor_auto(contract)
.await
.map_err(AppError::from)?;
Ok(Json(result))
}
async fn health_check() -> Json<Value> {
Json(json!({
"status": "healthy",
"service": "pmat",
"version": env!("CARGO_PKG_VERSION"),
"contracts": "uniform"
}))
}
async fn openapi_spec() -> Json<Value> {
Json(generate_openapi_spec())
}
fn generate_openapi_spec() -> Value {
json!({
"openapi": "3.0.0",
"info": {
"title": "PMAT API",
"version": env!("CARGO_PKG_VERSION"),
"description": "Professional project quantitative analysis toolkit with uniform contracts"
},
"servers": [
{
"url": "http://localhost:8080",
"description": "Local server"
}
],
"paths": {
"/api/analyze/complexity": {
"post": {
"summary": "Analyze code complexity",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AnalyzeComplexityContract"
}
}
}
},
"responses": {
"200": {
"description": "Analysis results"
}
}
}
},
"/api/analyze/satd": {
"post": {
"summary": "Analyze SATD",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AnalyzeSatdContract"
}
}
}
}
}
},
"/api/analyze/dead-code": {
"post": {
"summary": "Analyze dead code",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AnalyzeDeadCodeContract"
}
}
}
}
}
},
"/api/analyze/tdg": {
"post": {
"summary": "Analyze TDG",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AnalyzeTdgContract"
}
}
}
}
}
},
"/api/analyze/lint-hotspot": {
"post": {
"summary": "Analyze lint hotspots",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AnalyzeLintHotspotContract"
}
}
}
}
}
},
"/api/quality-gate": {
"post": {
"summary": "Run quality gate",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QualityGateContract"
}
}
}
}
}
},
"/api/refactor/auto": {
"post": {
"summary": "Auto refactor",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RefactorAutoContract"
}
}
}
}
}
}
},
"components": {
"schemas": {
"BaseAnalysisContract": {
"type": "object",
"required": ["path"],
"properties": {
"path": {
"type": "string",
"description": "Path to analyze"
},
"format": {
"type": "string",
"enum": ["table", "json", "yaml", "markdown", "csv", "summary"],
"default": "table"
},
"output": {
"type": "string",
"description": "Output file path"
},
"top_files": {
"type": "integer",
"description": "Number of top files",
"default": 10
},
"include_tests": {
"type": "boolean",
"default": false
},
"timeout": {
"type": "integer",
"default": 60
}
}
},
"AnalyzeComplexityContract": {
"allOf": [
{ "$ref": "#/components/schemas/BaseAnalysisContract" },
{
"type": "object",
"properties": {
"max_cyclomatic": { "type": "integer" },
"max_cognitive": { "type": "integer" },
"max_halstead": { "type": "number" }
}
}
]
},
"AnalyzeSatdContract": {
"allOf": [
{ "$ref": "#/components/schemas/BaseAnalysisContract" },
{
"type": "object",
"properties": {
"severity": {
"type": "string",
"enum": ["low", "medium", "high", "critical"]
},
"critical_only": { "type": "boolean" },
"strict": { "type": "boolean" },
"fail_on_violation": { "type": "boolean" }
}
}
]
},
"AnalyzeDeadCodeContract": {
"allOf": [
{ "$ref": "#/components/schemas/BaseAnalysisContract" },
{
"type": "object",
"properties": {
"include_unreachable": { "type": "boolean" },
"min_dead_lines": { "type": "integer" },
"max_percentage": { "type": "number" },
"fail_on_violation": { "type": "boolean" }
}
}
]
},
"AnalyzeTdgContract": {
"allOf": [
{ "$ref": "#/components/schemas/BaseAnalysisContract" },
{
"type": "object",
"properties": {
"threshold": { "type": "number" },
"include_components": { "type": "boolean" },
"critical_only": { "type": "boolean" }
}
}
]
},
"AnalyzeLintHotspotContract": {
"allOf": [
{ "$ref": "#/components/schemas/BaseAnalysisContract" },
{
"type": "object",
"properties": {
"file": { "type": "string" },
"max_density": { "type": "number" },
"min_confidence": { "type": "number" },
"enforce": { "type": "boolean" },
"dry_run": { "type": "boolean" }
}
}
]
},
"QualityGateContract": {
"allOf": [
{ "$ref": "#/components/schemas/BaseAnalysisContract" },
{
"type": "object",
"properties": {
"profile": {
"type": "string",
"enum": ["standard", "strict", "extreme", "toyota"]
},
"file": { "type": "string" },
"fail_on_violation": { "type": "boolean" },
"verbose": { "type": "boolean" }
}
}
]
},
"RefactorAutoContract": {
"type": "object",
"required": ["file"],
"properties": {
"file": { "type": "string" },
"format": {
"type": "string",
"enum": ["table", "json", "yaml", "markdown", "csv", "summary"]
},
"output": { "type": "string" },
"target_complexity": { "type": "integer" },
"dry_run": { "type": "boolean" },
"timeout": { "type": "integer" }
}
}
}
}
})
}
#[derive(Debug)]
enum AppError {
BadRequest(String),
Internal(anyhow::Error),
}
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
AppError::Internal(err)
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::Internal(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
};
let body = Json(json!({
"error": message
}));
(status, body).into_response()
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}