1use async_graphql::{Context, EmptySubscription, Object, Schema, SimpleObject, ID};
8use fcdb_graph::{GraphDB, Rid, LabelId, Timestamp};
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use tokio::sync::RwLock;
12
13#[derive(SimpleObject, Serialize, Deserialize)]
15pub struct Node {
16 pub id: ID,
18 pub data: String,
20 pub created_at: String,
22}
23
24#[derive(SimpleObject, Serialize, Deserialize)]
26pub struct GraphEdge {
27 pub from: ID,
29 pub to: ID,
31 pub label: String,
33 pub properties: String,
35}
36
37#[derive(SimpleObject, Serialize, Deserialize)]
39pub struct TraversalResult {
40 pub node: Node,
42 pub depth: i32,
44}
45
46#[derive(SimpleObject, Serialize, Deserialize)]
48pub struct SearchResult {
49 pub node: Node,
51 pub score: f32,
53}
54
55#[derive(async_graphql::InputObject)]
57pub struct CreateNodeInput {
58 pub data: String,
60}
61
62#[derive(async_graphql::InputObject)]
64pub struct UpdateNodeInput {
65 pub id: ID,
67 pub data: String,
69}
70
71#[derive(async_graphql::InputObject)]
73pub struct CreateEdgeInput {
74 pub from: ID,
76 pub to: ID,
78 pub label: String,
80 pub properties: String,
82}
83
84#[derive(async_graphql::InputObject)]
86pub struct TraverseInput {
87 pub from: ID,
89 pub labels: Option<Vec<String>>,
91 pub max_depth: i32,
93 pub as_of: Option<String>,
95}
96
97pub struct Query;
99
100#[Object]
101impl Query {
102 async fn node(&self, ctx: &Context<'_>, id: ID) -> async_graphql::Result<Option<Node>> {
104 let graph = ctx.data::<Arc<RwLock<GraphDB>>>()?;
105 let graph = graph.read().await;
106
107 let rid = Rid(id.parse().map_err(|_| "Invalid node ID")?);
108
109 match graph.get_node(rid).await {
110 Ok(Some(data)) => {
111 let data_str = String::from_utf8(data)
112 .map_err(|_| "Invalid UTF-8 data")?;
113 Ok(Some(Node {
114 id,
115 data: data_str,
116 created_at: "2024-01-01T00:00:00Z".to_string(), }))
118 }
119 Ok(None) => Ok(None),
120 Err(e) => Err(async_graphql::Error::new(format!("Database error: {}", e))),
121 }
122 }
123
124 async fn node_at(&self, ctx: &Context<'_>, id: ID, as_of: String) -> async_graphql::Result<Option<Node>> {
126 let graph = ctx.data::<Arc<RwLock<GraphDB>>>()?;
127 let graph = graph.read().await;
128
129 let rid = Rid(id.parse().map_err(|_| "Invalid node ID")?);
130 let timestamp = Timestamp(as_of.parse().map_err(|_| "Invalid timestamp")?);
131
132 match graph.get_node_at(rid, timestamp).await {
133 Ok(Some(data)) => {
134 let data_str = String::from_utf8(data)
135 .map_err(|_| "Invalid UTF-8 data")?;
136 Ok(Some(Node {
137 id,
138 data: data_str,
139 created_at: as_of,
140 }))
141 }
142 Ok(None) => Ok(None),
143 Err(e) => Err(async_graphql::Error::new(format!("Database error: {}", e))),
144 }
145 }
146
147 async fn traverse(&self, ctx: &Context<'_>, input: TraverseInput) -> async_graphql::Result<Vec<TraversalResult>> {
149 let graph = ctx.data::<Arc<RwLock<GraphDB>>>()?;
150 let graph = graph.read().await;
151
152 let from_rid = Rid(input.from.parse().map_err(|_| "Invalid node ID")?);
153 let labels: Option<Vec<LabelId>> = input.labels.map(|ls|
154 ls.into_iter().map(|l| LabelId(l.parse().unwrap_or(0))).collect()
155 );
156 let max_depth = input.max_depth as usize;
157 let as_of = input.as_of.map(|ts| Timestamp(ts.parse().unwrap_or(0)));
158
159 let traversal = graph.traverse(from_rid, labels.as_deref(), max_depth, as_of).await
160 .map_err(|e| async_graphql::Error::new(format!("Traversal error: {}", e)))?;
161
162 let mut results = Vec::new();
163 for (rid, depth) in traversal {
164 if let Ok(Some(data)) = graph.get_node(rid).await {
166 if let Ok(data_str) = String::from_utf8(data) {
167 results.push(TraversalResult {
168 node: Node {
169 id: ID::from(rid.0.to_string()),
170 data: data_str,
171 created_at: "2024-01-01T00:00:00Z".to_string(),
172 },
173 depth: depth as i32,
174 });
175 }
176 }
177 }
178
179 Ok(results)
180 }
181
182 async fn search(&self, ctx: &Context<'_>, query: String) -> async_graphql::Result<Vec<SearchResult>> {
184 let graph = ctx.data::<Arc<RwLock<GraphDB>>>()?;
185 let graph = graph.read().await;
186
187 let search_results = graph.search(&query).await
188 .map_err(|e| async_graphql::Error::new(format!("Search error: {}", e)))?;
189
190 let mut results = Vec::new();
191 for (rid, score) in search_results {
192 if let Ok(Some(data)) = graph.get_node(rid).await {
193 if let Ok(data_str) = String::from_utf8(data) {
194 results.push(SearchResult {
195 node: Node {
196 id: ID::from(rid.0.to_string()),
197 data: data_str,
198 created_at: "2024-01-01T00:00:00Z".to_string(),
199 },
200 score,
201 });
202 }
203 }
204 }
205
206 Ok(results)
207 }
208}
209
210pub struct Mutation;
212
213#[Object]
214impl Mutation {
215 async fn create_node(&self, ctx: &Context<'_>, input: CreateNodeInput) -> async_graphql::Result<Node> {
217 let graph = ctx.data::<Arc<RwLock<GraphDB>>>()?;
218 let mut graph = graph.write().await;
219
220 let data_bytes = input.data.as_bytes();
221 let rid = graph.create_node(data_bytes).await
222 .map_err(|e| async_graphql::Error::new(format!("Create node error: {}", e)))?;
223
224 Ok(Node {
225 id: ID::from(rid.0.to_string()),
226 data: input.data,
227 created_at: "2024-01-01T00:00:00Z".to_string(),
228 })
229 }
230
231 async fn update_node(&self, ctx: &Context<'_>, input: UpdateNodeInput) -> async_graphql::Result<Node> {
233 let graph = ctx.data::<Arc<RwLock<GraphDB>>>()?;
234 let mut graph = graph.write().await;
235
236 let rid = Rid(input.id.parse().map_err(|_| "Invalid node ID")?);
237 let data_bytes = input.data.as_bytes();
238
239 graph.update_node(rid, data_bytes).await
240 .map_err(|e| async_graphql::Error::new(format!("Update node error: {}", e)))?;
241
242 Ok(Node {
243 id: input.id,
244 data: input.data,
245 created_at: "2024-01-01T00:00:00Z".to_string(),
246 })
247 }
248
249 async fn create_edge(&self, ctx: &Context<'_>, input: CreateEdgeInput) -> async_graphql::Result<GraphEdge> {
251 let graph = ctx.data::<Arc<RwLock<GraphDB>>>()?;
252 let mut graph = graph.write().await;
253
254 let from_rid = Rid(input.from.parse().map_err(|_| "Invalid from ID")?);
255 let to_rid = Rid(input.to.parse().map_err(|_| "Invalid to ID")?);
256 let label_id = LabelId(input.label.parse().map_err(|_| "Invalid label")?);
257 let prop_bytes = input.properties.as_bytes();
258
259 graph.create_edge(from_rid, to_rid, label_id, prop_bytes).await
260 .map_err(|e| async_graphql::Error::new(format!("Create edge error: {}", e)))?;
261
262 Ok(GraphEdge {
263 from: input.from,
264 to: input.to,
265 label: input.label,
266 properties: input.properties,
267 })
268 }
269}
270
271pub type EnishiSchema = Schema<Query, Mutation, EmptySubscription>;
273
274pub fn create_schema(graph: Arc<RwLock<GraphDB>>) -> EnishiSchema {
276 Schema::build(Query, Mutation, EmptySubscription)
277 .data(graph)
278 .finish()
279}
280
281pub const GRAPHQL_SCHEMA: &str = r#"
283 type Node {
284 id: ID!
285 data: String!
286 createdAt: String!
287 }
288
289 type GraphEdge {
290 from: ID!
291 to: ID!
292 label: String!
293 properties: String!
294 }
295
296 type TraversalResult {
297 node: Node!
298 depth: Int!
299 }
300
301 type SearchResult {
302 node: Node!
303 score: Float!
304 }
305
306 input CreateNodeInput {
307 data: String!
308 }
309
310 input UpdateNodeInput {
311 id: ID!
312 data: String!
313 }
314
315 input CreateEdgeInput {
316 from: ID!
317 to: ID!
318 label: String!
319 properties: String!
320 }
321
322 input TraverseInput {
323 from: ID!
324 labels: [String!]
325 maxDepth: Int!
326 asOf: String
327 }
328
329 type Query {
330 node(id: ID!): Node
331 nodeAt(id: ID!, asOf: String!): Node
332 traverse(input: TraverseInput!): [TraversalResult!]!
333 search(query: String!): [SearchResult!]!
334 }
335
336 type Mutation {
337 createNode(input: CreateNodeInput!): Node!
338 updateNode(input: UpdateNodeInput!): Node!
339 createEdge(input: CreateEdgeInput!): GraphEdge!
340 }
341"#;
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use tempfile::tempdir;
347
348 #[tokio::test]
349 async fn test_graphql_schema_creation() {
350 let temp_dir = tempdir().unwrap();
351 let cas = enishi_cas::PackCAS::open(temp_dir.path()).await.unwrap();
352 let graph = GraphDB::new(cas).await;
353 let graph = Arc::new(RwLock::new(graph));
354
355 let schema = create_schema(graph);
356 assert!(schema.query(&"query { __typename }").await.is_ok());
357 }
358}