batuta/serve/banco/
handlers_recipes.rs1use axum::{extract::State, http::StatusCode, response::Json};
4use serde::Deserialize;
5
6use super::recipes::{DatasetResult, Recipe, RecipeStep};
7use super::state::BancoState;
8use super::types::ErrorResponse;
9
10pub async fn create_recipe_handler(
12 State(state): State<BancoState>,
13 Json(request): Json<CreateRecipeRequest>,
14) -> Json<Recipe> {
15 let recipe = state.recipes.create(
16 &request.name,
17 request.source_files,
18 request.steps,
19 &request.output_format.unwrap_or_else(|| "jsonl".to_string()),
20 );
21 Json(recipe)
22}
23
24pub async fn list_recipes_handler(State(state): State<BancoState>) -> Json<RecipesListResponse> {
26 Json(RecipesListResponse { recipes: state.recipes.list() })
27}
28
29pub async fn get_recipe_handler(
31 State(state): State<BancoState>,
32 axum::extract::Path(id): axum::extract::Path<String>,
33) -> Result<Json<Recipe>, (StatusCode, Json<ErrorResponse>)> {
34 state.recipes.get(&id).map(Json).ok_or((
35 StatusCode::NOT_FOUND,
36 Json(ErrorResponse::new(format!("Recipe {id} not found"), "not_found", 404)),
37 ))
38}
39
40pub async fn run_recipe_handler(
42 State(state): State<BancoState>,
43 axum::extract::Path(id): axum::extract::Path<String>,
44) -> Result<Json<DatasetResult>, (StatusCode, Json<ErrorResponse>)> {
45 let recipe = state.recipes.get(&id).ok_or((
46 StatusCode::NOT_FOUND,
47 Json(ErrorResponse::new(format!("Recipe {id} not found"), "not_found", 404)),
48 ))?;
49
50 let source_texts: Vec<(String, String)> = recipe
52 .source_files
53 .iter()
54 .filter_map(|file_id| {
55 let info = state.files.get(file_id)?;
56 let content = state
58 .files
59 .read_content(file_id)
60 .map(|bytes| String::from_utf8_lossy(&bytes).to_string())
61 .unwrap_or_default();
62 Some((info.name, content))
63 })
64 .collect();
65
66 let source_refs: Vec<(&str, &str)> =
67 source_texts.iter().map(|(n, c)| (n.as_str(), c.as_str())).collect();
68
69 state.recipes.run(&id, &source_refs).map(Json).map_err(|e| {
70 (
71 StatusCode::INTERNAL_SERVER_ERROR,
72 Json(ErrorResponse::new(e.to_string(), "recipe_error", 500)),
73 )
74 })
75}
76
77pub async fn list_datasets_handler(State(state): State<BancoState>) -> Json<DatasetsListResponse> {
79 Json(DatasetsListResponse { datasets: state.recipes.list_datasets() })
80}
81
82pub async fn preview_dataset_handler(
84 State(state): State<BancoState>,
85 axum::extract::Path(id): axum::extract::Path<String>,
86) -> Result<Json<DatasetPreview>, (StatusCode, Json<ErrorResponse>)> {
87 let dataset = state.recipes.get_dataset(&id).ok_or((
88 StatusCode::NOT_FOUND,
89 Json(ErrorResponse::new(format!("Dataset {id} not found"), "not_found", 404)),
90 ))?;
91
92 let preview_records: Vec<_> = dataset.records.iter().take(10).cloned().collect();
93 Ok(Json(DatasetPreview {
94 dataset_id: dataset.dataset_id,
95 total_records: dataset.record_count,
96 preview: preview_records,
97 }))
98}
99
100#[derive(Debug, Deserialize)]
105pub struct CreateRecipeRequest {
106 pub name: String,
107 #[serde(default)]
108 pub source_files: Vec<String>,
109 pub steps: Vec<RecipeStep>,
110 #[serde(default)]
111 pub output_format: Option<String>,
112}
113
114#[derive(Debug, serde::Serialize)]
115pub struct RecipesListResponse {
116 pub recipes: Vec<Recipe>,
117}
118
119#[derive(Debug, serde::Serialize)]
120pub struct DatasetsListResponse {
121 pub datasets: Vec<DatasetResult>,
122}
123
124#[derive(Debug, serde::Serialize)]
125pub struct DatasetPreview {
126 pub dataset_id: String,
127 pub total_records: usize,
128 pub preview: Vec<super::recipes::Record>,
129}