use axum::{ Json, body::Bytes, extract::{Path, Query, State}, http::{HeaderMap, StatusCode, header}, response::IntoResponse, }; use serde::Deserialize; use serde_json::{Value, json}; use crate::{ error::ApiError, service::{ ExportQuery, GenerateDraftPayload, ImportQuery, NewVersionPayload, OperationPayload, PublishPayload, TestRunPayload, }, state::AppState, }; #[derive(Deserialize)] pub struct WorkspacePath { pub workspace_id: String, } #[derive(Deserialize)] pub struct WorkspaceOperationPath { pub workspace_id: String, pub operation_id: String, } #[derive(Deserialize)] pub struct WorkspaceOperationVersionPath { pub workspace_id: String, pub operation_id: String, pub version: u32, } pub async fn list_operations( Path(path): Path, State(state): State, ) -> Result, ApiError> { let items = state .service .list_operations(&path.workspace_id.as_str().into()) .await?; Ok(Json(json!({ "items": items }))) } pub async fn create_operation( Path(path): Path, State(state): State, Json(payload): Json, ) -> Result, ApiError> { let created = state .service .create_operation(&path.workspace_id.as_str().into(), payload) .await?; Ok(Json(json!(created))) } pub async fn get_operation( Path(path): Path, State(state): State, ) -> Result, ApiError> { let operation = state .service .get_operation( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), ) .await?; Ok(Json(json!(operation))) } pub async fn get_operation_version( Path(path): Path, State(state): State, ) -> Result, ApiError> { let version = state .service .get_operation_version( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), path.version, ) .await?; Ok(Json(json!(version))) } pub async fn create_version( Path(path): Path, State(state): State, Json(payload): Json, ) -> Result, ApiError> { let created = state .service .create_version( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), payload, ) .await?; Ok(Json(json!(created))) } pub async fn publish_operation( Path(path): Path, State(state): State, Json(payload): Json, ) -> Result, ApiError> { let published = state .service .publish_operation( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), payload.version, ) .await?; Ok(Json(json!(published))) } pub async fn run_test( Path(path): Path, State(state): State, Json(payload): Json, ) -> Result, ApiError> { let result = state .service .run_test( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), payload, ) .await?; Ok(Json(json!(result))) } pub async fn upload_input_json( Path(path): Path, State(state): State, Json(payload): Json, ) -> Result, ApiError> { let sample = state .service .save_json_sample( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), crank_registry::SampleKind::InputJson, &payload, ) .await?; Ok(Json(json!({ "sample_id": sample.id.as_str(), "sample_kind": sample.sample_kind, "version": sample.version }))) } pub async fn upload_output_json( Path(path): Path, State(state): State, Json(payload): Json, ) -> Result, ApiError> { let sample = state .service .save_json_sample( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), crank_registry::SampleKind::OutputJson, &payload, ) .await?; Ok(Json(json!({ "sample_id": sample.id.as_str(), "sample_kind": sample.sample_kind, "version": sample.version }))) } pub async fn upload_proto_descriptor( Path(path): Path, State(state): State, headers: HeaderMap, body: Bytes, ) -> Result, ApiError> { let source_name = header_file_name(&headers); let descriptor = state .service .upload_proto_file( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), source_name.as_deref(), body.as_ref(), ) .await?; Ok(Json(json!(descriptor))) } pub async fn upload_descriptor_set( Path(path): Path, State(state): State, headers: HeaderMap, body: Bytes, ) -> Result, ApiError> { let source_name = header_file_name(&headers); let descriptor = state .service .upload_descriptor_set( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), source_name.as_deref(), body.as_ref(), ) .await?; Ok(Json(json!(descriptor))) } pub async fn list_grpc_services( Path(path): Path, Query(query): Query, State(state): State, ) -> Result, ApiError> { let services = state .service .list_grpc_services( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), query.version, ) .await?; Ok(Json(json!({ "services": services }))) } pub async fn generate_draft( Path(path): Path, State(state): State, Json(payload): Json, ) -> Result, ApiError> { let draft = state .service .generate_draft( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), payload, ) .await?; Ok(Json(json!(draft))) } pub async fn export_operation( Path(path): Path, Query(query): Query, State(state): State, ) -> Result { let yaml = state .service .export_operation( &path.workspace_id.as_str().into(), &path.operation_id.as_str().into(), query, ) .await?; let mut headers = HeaderMap::new(); headers.insert( header::CONTENT_TYPE, header::HeaderValue::from_static("application/yaml"), ); Ok((StatusCode::OK, headers, yaml)) } pub async fn import_operation( Path(path): Path, Query(query): Query, State(state): State, body: String, ) -> Result, ApiError> { let imported = state .service .import_operation(&path.workspace_id.as_str().into(), query, &body) .await?; Ok(Json(json!(imported))) } fn header_file_name(headers: &HeaderMap) -> Option { headers .get("x-file-name") .and_then(|value| value.to_str().ok()) .map(ToOwned::to_owned) }