1use omnigraph::db::{GraphCommit, MergeOutcome, ReadTarget, SchemaApplyResult, Snapshot};
7use omnigraph::error::{MergeConflict, MergeConflictKind};
8use omnigraph::loader::{LoadMode, LoadResult};
9use omnigraph_compiler::SchemaMigrationStep;
10use omnigraph_compiler::query::ast::Param;
11use omnigraph_compiler::result::QueryResult;
12use omnigraph_compiler::types::{PropType, ScalarType};
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use utoipa::{IntoParams, ToSchema};
16
17#[derive(ToSchema)]
19#[schema(as = LoadMode)]
20#[allow(dead_code)]
21enum LoadModeSchema {
22 #[schema(rename = "overwrite")]
24 Overwrite,
25 #[schema(rename = "append")]
27 Append,
28 #[schema(rename = "merge")]
30 Merge,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
34pub struct SnapshotTableOutput {
35 pub table_key: String,
36 pub table_path: String,
37 pub table_version: u64,
38 pub table_branch: Option<String>,
39 pub row_count: u64,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
43pub struct SnapshotOutput {
44 pub branch: String,
45 pub manifest_version: u64,
46 pub tables: Vec<SnapshotTableOutput>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
50pub struct BranchCreateRequest {
51 pub from: Option<String>,
53 pub name: String,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
58pub struct BranchCreateOutput {
59 pub uri: String,
60 pub from: String,
61 pub name: String,
62 pub actor_id: Option<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
66pub struct BranchListOutput {
67 pub branches: Vec<String>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
71pub struct BranchDeleteOutput {
72 pub uri: String,
73 pub name: String,
74 pub actor_id: Option<String>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
78pub struct BranchMergeRequest {
79 pub source: String,
81 pub target: Option<String>,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
86#[serde(rename_all = "snake_case")]
87pub enum BranchMergeOutcome {
88 AlreadyUpToDate,
89 FastForward,
90 Merged,
91}
92
93impl From<MergeOutcome> for BranchMergeOutcome {
94 fn from(value: MergeOutcome) -> Self {
95 match value {
96 MergeOutcome::AlreadyUpToDate => Self::AlreadyUpToDate,
97 MergeOutcome::FastForward => Self::FastForward,
98 MergeOutcome::Merged => Self::Merged,
99 }
100 }
101}
102
103impl BranchMergeOutcome {
104 pub fn as_str(self) -> &'static str {
105 match self {
106 Self::AlreadyUpToDate => "already_up_to_date",
107 Self::FastForward => "fast_forward",
108 Self::Merged => "merged",
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
114pub struct BranchMergeOutput {
115 pub source: String,
116 pub target: String,
117 pub outcome: BranchMergeOutcome,
118 pub actor_id: Option<String>,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
122#[serde(rename_all = "snake_case")]
123pub enum MergeConflictKindOutput {
124 DivergentInsert,
125 DivergentUpdate,
126 DeleteVsUpdate,
127 OrphanEdge,
128 UniqueViolation,
129 CardinalityViolation,
130 ValueConstraintViolation,
131}
132
133impl MergeConflictKindOutput {
134 pub fn as_str(self) -> &'static str {
135 match self {
136 Self::DivergentInsert => "divergent_insert",
137 Self::DivergentUpdate => "divergent_update",
138 Self::DeleteVsUpdate => "delete_vs_update",
139 Self::OrphanEdge => "orphan_edge",
140 Self::UniqueViolation => "unique_violation",
141 Self::CardinalityViolation => "cardinality_violation",
142 Self::ValueConstraintViolation => "value_constraint_violation",
143 }
144 }
145}
146
147impl From<MergeConflictKind> for MergeConflictKindOutput {
148 fn from(value: MergeConflictKind) -> Self {
149 match value {
150 MergeConflictKind::DivergentInsert => Self::DivergentInsert,
151 MergeConflictKind::DivergentUpdate => Self::DivergentUpdate,
152 MergeConflictKind::DeleteVsUpdate => Self::DeleteVsUpdate,
153 MergeConflictKind::OrphanEdge => Self::OrphanEdge,
154 MergeConflictKind::UniqueViolation => Self::UniqueViolation,
155 MergeConflictKind::CardinalityViolation => Self::CardinalityViolation,
156 MergeConflictKind::ValueConstraintViolation => Self::ValueConstraintViolation,
157 }
158 }
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
162pub struct MergeConflictOutput {
163 pub table_key: String,
164 pub row_id: Option<String>,
165 pub kind: MergeConflictKindOutput,
166 pub message: String,
167}
168
169impl From<&MergeConflict> for MergeConflictOutput {
170 fn from(value: &MergeConflict) -> Self {
171 Self {
172 table_key: value.table_key.clone(),
173 row_id: value.row_id.clone(),
174 kind: value.kind.into(),
175 message: value.message.clone(),
176 }
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
181pub struct ReadTargetOutput {
182 pub branch: Option<String>,
183 pub snapshot: Option<String>,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
187pub struct ReadOutput {
188 pub query_name: String,
189 pub target: ReadTargetOutput,
190 pub row_count: usize,
191 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 pub columns: Vec<String>,
193 pub rows: Value,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
197pub struct ChangeOutput {
198 pub branch: String,
199 pub query_name: String,
200 pub affected_nodes: usize,
201 pub affected_edges: usize,
202 pub actor_id: Option<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
206pub struct IngestTableOutput {
207 pub table_key: String,
208 pub rows_loaded: usize,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
212pub struct IngestOutput {
213 pub uri: String,
214 pub branch: String,
215 pub base_branch: Option<String>,
218 pub branch_created: bool,
219 #[schema(value_type = LoadModeSchema)]
220 pub mode: LoadMode,
221 pub tables: Vec<IngestTableOutput>,
222 pub actor_id: Option<String>,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
226pub struct CommitOutput {
227 pub graph_commit_id: String,
228 pub manifest_branch: Option<String>,
229 pub manifest_version: u64,
230 pub parent_commit_id: Option<String>,
231 pub merged_parent_commit_id: Option<String>,
232 pub actor_id: Option<String>,
233 #[schema(example = 1714000000000000i64)]
235 pub created_at: i64,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
239pub struct CommitListOutput {
240 pub commits: Vec<CommitOutput>,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
244pub struct ReadRequest {
245 #[schema(
248 example = "query get_person($name: String) {\n match {\n $p: Person { name: $name }\n }\n return { $p.name, $p.age }\n}"
249 )]
250 pub query_source: String,
251 pub query_name: Option<String>,
254 pub params: Option<Value>,
256 pub branch: Option<String>,
258 pub snapshot: Option<String>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
270pub struct QueryRequest {
271 #[schema(example = "query get_person($name: String) {\n match {\n $p: Person { name: $name }\n }\n return { $p.name, $p.age }\n}")]
276 pub query: String,
277 pub name: Option<String>,
280 pub params: Option<Value>,
282 pub branch: Option<String>,
284 pub snapshot: Option<String>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
289pub struct ChangeRequest {
290 #[schema(
295 example = "query insert_person($name: String, $age: I32) {\n insert Person { name: $name, age: $age }\n}"
296 )]
297 #[serde(alias = "query_source")]
298 pub query: String,
299 #[serde(default, alias = "query_name")]
303 pub name: Option<String>,
304 #[serde(default)]
306 pub params: Option<Value>,
307 #[serde(default)]
309 pub branch: Option<String>,
310}
311
312#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
316pub struct InvokeStoredQueryRequest {
317 #[serde(default)]
319 pub params: Option<Value>,
320 #[serde(default)]
323 pub branch: Option<String>,
324 #[serde(default)]
327 pub snapshot: Option<String>,
328 #[serde(default)]
334 pub expect_mutation: Option<bool>,
335}
336
337#[derive(Debug, Serialize, ToSchema)]
343#[serde(untagged)]
344pub enum InvokeStoredQueryResponse {
345 Read(ReadOutput),
346 Change(ChangeOutput),
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
356#[serde(rename_all = "snake_case")]
357pub enum ParamKind {
358 String,
359 Bool,
360 Int,
361 #[serde(rename = "bigint")]
362 BigInt,
363 Float,
364 Date,
365 #[serde(rename = "datetime")]
366 DateTime,
367 Blob,
368 Vector,
369 List,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
374pub struct ParamDescriptor {
375 pub name: String,
376 pub kind: ParamKind,
377 #[serde(skip_serializing_if = "Option::is_none")]
380 pub item_kind: Option<ParamKind>,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub vector_dim: Option<u32>,
384 pub nullable: bool,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
390pub struct QueryCatalogEntry {
391 pub name: String,
393 pub tool_name: String,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub description: Option<String>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub instruction: Option<String>,
399 pub mutation: bool,
401 pub params: Vec<ParamDescriptor>,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
407pub struct QueriesCatalogOutput {
408 pub queries: Vec<QueryCatalogEntry>,
409}
410
411fn scalar_kind(scalar: ScalarType) -> ParamKind {
414 match scalar {
415 ScalarType::String => ParamKind::String,
416 ScalarType::Bool => ParamKind::Bool,
417 ScalarType::I32 | ScalarType::U32 => ParamKind::Int,
418 ScalarType::I64 | ScalarType::U64 => ParamKind::BigInt,
419 ScalarType::F32 | ScalarType::F64 => ParamKind::Float,
420 ScalarType::Date => ParamKind::Date,
421 ScalarType::DateTime => ParamKind::DateTime,
422 ScalarType::Blob => ParamKind::Blob,
423 ScalarType::Vector(_) => ParamKind::Vector,
424 }
425}
426
427pub fn param_descriptor(param: &Param) -> ParamDescriptor {
428 match PropType::from_param_type_name(¶m.type_name, param.nullable) {
429 Some(pt) if pt.list => ParamDescriptor {
430 name: param.name.clone(),
431 kind: ParamKind::List,
432 item_kind: Some(scalar_kind(pt.scalar)),
433 vector_dim: None,
434 nullable: param.nullable,
435 },
436 Some(pt) => {
437 let (kind, vector_dim) = match pt.scalar {
438 ScalarType::Vector(dim) => (ParamKind::Vector, Some(dim)),
439 other => (scalar_kind(other), None),
440 };
441 ParamDescriptor {
442 name: param.name.clone(),
443 kind,
444 item_kind: None,
445 vector_dim,
446 nullable: param.nullable,
447 }
448 }
449 None => ParamDescriptor {
453 name: param.name.clone(),
454 kind: ParamKind::String,
455 item_kind: None,
456 vector_dim: None,
457 nullable: param.nullable,
458 },
459 }
460}
461
462
463#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
464pub struct SchemaApplyRequest {
465 #[schema(
468 example = "node Person {\n name: String @key\n age: I32?\n}\n\nedge Knows: Person -> Person"
469 )]
470 pub schema_source: String,
471 #[serde(default)]
476 pub allow_data_loss: bool,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
480pub struct SchemaApplyOutput {
481 pub uri: String,
482 pub supported: bool,
483 pub applied: bool,
484 pub step_count: usize,
485 pub manifest_version: u64,
486 #[schema(value_type = Vec<Value>)]
487 pub steps: Vec<SchemaMigrationStep>,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
491pub struct SchemaOutput {
492 pub schema_source: String,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
496pub struct IngestRequest {
497 pub branch: Option<String>,
500 pub from: Option<String>,
504 #[schema(value_type = Option<LoadModeSchema>)]
506 pub mode: Option<LoadMode>,
507 #[schema(
510 example = "{\"type\": \"Person\", \"data\": {\"name\": \"Alice\", \"age\": 30}}\n{\"type\": \"Person\", \"data\": {\"name\": \"Bob\", \"age\": 25}}"
511 )]
512 pub data: String,
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
516pub struct ExportRequest {
517 pub branch: Option<String>,
519 #[serde(default)]
521 pub type_names: Vec<String>,
522 #[serde(default)]
524 pub table_keys: Vec<String>,
525}
526
527#[derive(Debug, Clone, Deserialize, IntoParams)]
528pub struct SnapshotQuery {
529 pub branch: Option<String>,
530}
531
532#[derive(Debug, Clone, Deserialize, IntoParams)]
533pub struct CommitListQuery {
534 pub branch: Option<String>,
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
538pub struct HealthOutput {
539 pub status: String,
540 pub version: String,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 pub source_version: Option<String>,
543}
544
545#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
546#[serde(rename_all = "snake_case")]
547pub enum ErrorCode {
548 Unauthorized,
549 Forbidden,
550 BadRequest,
551 NotFound,
552 MethodNotAllowed,
557 Conflict,
558 TooManyRequests,
561 Internal,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
569pub struct ManifestConflictOutput {
570 pub table_key: String,
571 pub expected: u64,
572 pub actual: u64,
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
576pub struct ErrorOutput {
577 pub error: String,
578 #[serde(skip_serializing_if = "Option::is_none")]
579 pub code: Option<ErrorCode>,
580 #[serde(default, skip_serializing_if = "Vec::is_empty")]
581 pub merge_conflicts: Vec<MergeConflictOutput>,
582 #[serde(skip_serializing_if = "Option::is_none")]
587 pub manifest_conflict: Option<ManifestConflictOutput>,
588}
589
590pub fn snapshot_payload(branch: &str, snapshot: &Snapshot) -> SnapshotOutput {
591 let mut entries: Vec<_> = snapshot.entries().cloned().collect();
592 entries.sort_by(|a, b| a.table_key.cmp(&b.table_key));
593 let tables = entries
594 .iter()
595 .map(|entry| SnapshotTableOutput {
596 table_key: entry.table_key.clone(),
597 table_path: entry.table_path.clone(),
598 table_version: entry.table_version,
599 table_branch: entry.table_branch.clone(),
600 row_count: entry.row_count,
601 })
602 .collect::<Vec<_>>();
603 SnapshotOutput {
604 branch: branch.to_string(),
605 manifest_version: snapshot.version(),
606 tables,
607 }
608}
609
610pub fn schema_apply_output(uri: &str, result: SchemaApplyResult) -> SchemaApplyOutput {
611 SchemaApplyOutput {
612 uri: uri.to_string(),
613 supported: result.supported,
614 applied: result.applied,
615 step_count: result.steps.len(),
616 manifest_version: result.manifest_version,
617 steps: result.steps,
618 }
619}
620
621pub fn commit_output(commit: &GraphCommit) -> CommitOutput {
622 CommitOutput {
623 graph_commit_id: commit.graph_commit_id.clone(),
624 manifest_branch: commit.manifest_branch.clone(),
625 manifest_version: commit.manifest_version,
626 parent_commit_id: commit.parent_commit_id.clone(),
627 merged_parent_commit_id: commit.merged_parent_commit_id.clone(),
628 actor_id: commit.actor_id.clone(),
629 created_at: commit.created_at,
630 }
631}
632
633pub fn read_output(query_name: String, target: &ReadTarget, result: QueryResult) -> ReadOutput {
634 let columns = result
635 .schema()
636 .fields()
637 .iter()
638 .map(|field| field.name().clone())
639 .collect();
640 ReadOutput {
641 query_name,
642 target: read_target_output(target),
643 row_count: result.num_rows(),
644 columns,
645 rows: result.to_rust_json(),
646 }
647}
648
649pub fn ingest_output(
650 uri: &str,
651 result: &LoadResult,
652 mode: LoadMode,
653 actor_id: Option<String>,
654) -> IngestOutput {
655 IngestOutput {
656 uri: uri.to_string(),
657 branch: result.branch.clone(),
658 base_branch: result.base_branch.clone(),
659 branch_created: result.branch_created,
660 mode,
661 tables: result
662 .to_ingest_tables()
663 .into_iter()
664 .map(|table| IngestTableOutput {
665 table_key: table.table_key,
666 rows_loaded: table.rows_loaded,
667 })
668 .collect(),
669 actor_id,
670 }
671}
672
673pub fn read_target_output(target: &ReadTarget) -> ReadTargetOutput {
674 match target {
675 ReadTarget::Branch(branch) => ReadTargetOutput {
676 branch: Some(branch.clone()),
677 snapshot: None,
678 },
679 ReadTarget::Snapshot(snapshot) => ReadTargetOutput {
680 branch: None,
681 snapshot: Some(snapshot.as_str().to_string()),
682 },
683 }
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
693pub struct GraphInfo {
694 pub graph_id: String,
695 pub uri: String,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
702pub struct GraphListResponse {
703 pub graphs: Vec<GraphInfo>,
704}