Skip to main content

type_bridge_server/crud/
handlers.rs

1//! Axum CRUD handlers for entity and relation endpoints.
2//!
3//! Each handler extracts the path/body, builds AST clauses via the builder,
4//! passes them through the [`QueryPipeline`], and returns a [`CrudResponse`].
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use axum::extract::{Path, Query, State};
10use axum::Json;
11
12use crate::error::PipelineError;
13use crate::pipeline::{QueryInput, QueryPipeline};
14
15use super::builder;
16use super::types::*;
17
18/// POST /entities/{type_name}
19///
20/// Insert a new entity with the given attributes.
21pub async fn handle_entity_insert(
22    State(pipeline): State<Arc<QueryPipeline>>,
23    Path(type_name): Path<String>,
24    Json(req): Json<EntityInsertRequest>,
25) -> Result<Json<CrudResponse>, PipelineError> {
26    let schema = pipeline
27        .schema()
28        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
29
30    let clauses = builder::build_entity_insert(&type_name, &req.attributes, schema)?;
31
32    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
33
34    let output = pipeline
35        .execute_query(QueryInput {
36            database: req.database,
37            transaction_type: "write".to_string(),
38            clauses,
39            metadata: HashMap::new(),
40        })
41        .await?;
42
43    Ok(Json(CrudResponse {
44        status: "ok".to_string(),
45        results: output.results,
46        metadata: CrudMetadata {
47            request_id: output.request_id,
48            execution_time_ms: output.execution_time_ms,
49            typeql,
50        },
51    }))
52}
53
54/// GET /entities/{type_name}
55///
56/// Fetch entities with optional filters, sort, limit, and offset.
57pub async fn handle_entity_fetch(
58    State(pipeline): State<Arc<QueryPipeline>>,
59    Path(type_name): Path<String>,
60    Query(req): Query<EntityFetchQuery>,
61) -> Result<Json<CrudResponse>, PipelineError> {
62    let schema = pipeline
63        .schema()
64        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
65
66    let clauses = builder::build_entity_fetch(
67        &type_name,
68        &[],
69        &[],
70        req.limit,
71        req.offset,
72        schema,
73    )?;
74
75    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
76
77    let output = pipeline
78        .execute_query(QueryInput {
79            database: req.database,
80            transaction_type: "read".to_string(),
81            clauses,
82            metadata: HashMap::new(),
83        })
84        .await?;
85
86    Ok(Json(CrudResponse {
87        status: "ok".to_string(),
88        results: output.results,
89        metadata: CrudMetadata {
90            request_id: output.request_id,
91            execution_time_ms: output.execution_time_ms,
92            typeql,
93        },
94    }))
95}
96
97/// Query parameters for GET /entities/{type_name}.
98#[derive(Debug, serde::Deserialize)]
99pub struct EntityFetchQuery {
100    pub database: Option<String>,
101    pub limit: Option<u64>,
102    pub offset: Option<u64>,
103}
104
105/// GET /entities/{type_name}/{iid}
106///
107/// Fetch a single entity by its internal identifier.
108pub async fn handle_entity_get_by_iid(
109    State(pipeline): State<Arc<QueryPipeline>>,
110    Path((type_name, iid)): Path<(String, String)>,
111    Query(params): Query<DatabaseParam>,
112) -> Result<Json<CrudResponse>, PipelineError> {
113    let schema = pipeline
114        .schema()
115        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
116
117    let clauses = builder::build_entity_fetch_by_iid(&type_name, &iid, schema)?;
118
119    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
120
121    let output = pipeline
122        .execute_query(QueryInput {
123            database: params.database,
124            transaction_type: "read".to_string(),
125            clauses,
126            metadata: HashMap::new(),
127        })
128        .await?;
129
130    Ok(Json(CrudResponse {
131        status: "ok".to_string(),
132        results: output.results,
133        metadata: CrudMetadata {
134            request_id: output.request_id,
135            execution_time_ms: output.execution_time_ms,
136            typeql,
137        },
138    }))
139}
140
141/// PUT /entities/{type_name}/{iid}
142///
143/// Update an entity's attributes by IID.
144pub async fn handle_entity_update(
145    State(pipeline): State<Arc<QueryPipeline>>,
146    Path((type_name, iid)): Path<(String, String)>,
147    Json(req): Json<EntityUpdateRequest>,
148) -> Result<Json<CrudResponse>, PipelineError> {
149    let schema = pipeline
150        .schema()
151        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
152
153    let clauses =
154        builder::build_entity_update_by_iid(&type_name, &iid, &req.attributes, schema)?;
155
156    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
157
158    let output = pipeline
159        .execute_query(QueryInput {
160            database: req.database,
161            transaction_type: "write".to_string(),
162            clauses,
163            metadata: HashMap::new(),
164        })
165        .await?;
166
167    Ok(Json(CrudResponse {
168        status: "ok".to_string(),
169        results: output.results,
170        metadata: CrudMetadata {
171            request_id: output.request_id,
172            execution_time_ms: output.execution_time_ms,
173            typeql,
174        },
175    }))
176}
177
178/// DELETE /entities/{type_name}/{iid}
179///
180/// Delete an entity by its internal identifier.
181pub async fn handle_entity_delete(
182    State(pipeline): State<Arc<QueryPipeline>>,
183    Path((type_name, iid)): Path<(String, String)>,
184    Query(params): Query<DatabaseParam>,
185) -> Result<Json<CrudResponse>, PipelineError> {
186    let schema = pipeline
187        .schema()
188        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
189
190    let clauses = builder::build_entity_delete_by_iid(&type_name, &iid, schema)?;
191
192    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
193
194    let output = pipeline
195        .execute_query(QueryInput {
196            database: params.database,
197            transaction_type: "write".to_string(),
198            clauses,
199            metadata: HashMap::new(),
200        })
201        .await?;
202
203    Ok(Json(CrudResponse {
204        status: "ok".to_string(),
205        results: output.results,
206        metadata: CrudMetadata {
207            request_id: output.request_id,
208            execution_time_ms: output.execution_time_ms,
209            typeql,
210        },
211    }))
212}
213
214/// POST /relations/{type_name}
215///
216/// Insert a new relation with role players and optional attributes.
217pub async fn handle_relation_insert(
218    State(pipeline): State<Arc<QueryPipeline>>,
219    Path(type_name): Path<String>,
220    Json(req): Json<RelationInsertRequest>,
221) -> Result<Json<CrudResponse>, PipelineError> {
222    let schema = pipeline
223        .schema()
224        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
225
226    let clauses = builder::build_relation_insert(
227        &type_name,
228        &req.role_players,
229        &req.attributes,
230        schema,
231    )?;
232
233    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
234
235    let output = pipeline
236        .execute_query(QueryInput {
237            database: req.database,
238            transaction_type: "write".to_string(),
239            clauses,
240            metadata: HashMap::new(),
241        })
242        .await?;
243
244    Ok(Json(CrudResponse {
245        status: "ok".to_string(),
246        results: output.results,
247        metadata: CrudMetadata {
248            request_id: output.request_id,
249            execution_time_ms: output.execution_time_ms,
250            typeql,
251        },
252    }))
253}
254
255/// GET /relations/{type_name}
256///
257/// Fetch relations with optional limit and offset.
258pub async fn handle_relation_fetch(
259    State(pipeline): State<Arc<QueryPipeline>>,
260    Path(type_name): Path<String>,
261    Query(req): Query<EntityFetchQuery>,
262) -> Result<Json<CrudResponse>, PipelineError> {
263    let schema = pipeline
264        .schema()
265        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
266
267    let clauses = builder::build_relation_fetch(
268        &type_name,
269        &[],
270        &[],
271        req.limit,
272        req.offset,
273        schema,
274    )?;
275
276    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
277
278    let output = pipeline
279        .execute_query(QueryInput {
280            database: req.database,
281            transaction_type: "read".to_string(),
282            clauses,
283            metadata: HashMap::new(),
284        })
285        .await?;
286
287    Ok(Json(CrudResponse {
288        status: "ok".to_string(),
289        results: output.results,
290        metadata: CrudMetadata {
291            request_id: output.request_id,
292            execution_time_ms: output.execution_time_ms,
293            typeql,
294        },
295    }))
296}
297
298/// DELETE /relations/{type_name}/{iid}
299///
300/// Delete a relation by its internal identifier.
301pub async fn handle_relation_delete(
302    State(pipeline): State<Arc<QueryPipeline>>,
303    Path((type_name, iid)): Path<(String, String)>,
304    Query(params): Query<DatabaseParam>,
305) -> Result<Json<CrudResponse>, PipelineError> {
306    let schema = pipeline
307        .schema()
308        .ok_or_else(|| PipelineError::Schema("No schema loaded".to_string()))?;
309
310    let clauses = builder::build_relation_delete_by_iid(&type_name, &iid, schema)?;
311
312    let typeql = type_bridge_core_lib::compiler::QueryCompiler::new().compile(&clauses);
313
314    let output = pipeline
315        .execute_query(QueryInput {
316            database: params.database,
317            transaction_type: "write".to_string(),
318            clauses,
319            metadata: HashMap::new(),
320        })
321        .await?;
322
323    Ok(Json(CrudResponse {
324        status: "ok".to_string(),
325        results: output.results,
326        metadata: CrudMetadata {
327            request_id: output.request_id,
328            execution_time_ms: output.execution_time_ms,
329            typeql,
330        },
331    }))
332}
333
334/// Optional database query parameter.
335#[derive(Debug, serde::Deserialize)]
336pub struct DatabaseParam {
337    pub database: Option<String>,
338}