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