1use std::{
2 collections::{BTreeMap, BTreeSet},
3 fmt,
4};
5
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8
9pub type Result<T> = std::result::Result<T, GrustError>;
10pub type Props = BTreeMap<String, Value>;
11
12#[derive(Debug, thiserror::Error)]
13pub enum GrustError {
14 #[error("backend error: {0}")]
15 Backend(String),
16 #[error("schema error: {0}")]
17 Schema(String),
18 #[error("unsupported graph feature: {0}")]
19 Unsupported(String),
20 #[error("serialization error: {0}")]
21 Serialization(String),
22}
23
24macro_rules! string_newtype {
25 ($(#[$meta:meta])* $name:ident) => {
26 $(#[$meta])*
27 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
28 pub struct $name(String);
29
30 impl $name {
31 pub fn new(value: impl Into<String>) -> Self {
32 Self(value.into())
33 }
34
35 pub fn as_str(&self) -> &str {
36 &self.0
37 }
38
39 pub fn into_string(self) -> String {
40 self.0
41 }
42 }
43
44 impl fmt::Display for $name {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 f.write_str(&self.0)
47 }
48 }
49
50 impl From<String> for $name {
51 fn from(value: String) -> Self {
52 Self::new(value)
53 }
54 }
55
56 impl From<&str> for $name {
57 fn from(value: &str) -> Self {
58 Self::new(value)
59 }
60 }
61
62 impl From<&String> for $name {
63 fn from(value: &String) -> Self {
64 Self::new(value.clone())
65 }
66 }
67
68 impl From<&$name> for $name {
69 fn from(value: &$name) -> Self {
70 value.clone()
71 }
72 }
73 };
74}
75
76string_newtype!(
77 NodeId
79);
80string_newtype!(
81 EdgeId
83);
84string_newtype!(
85 Label
87);
88
89#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
90#[serde(tag = "type", content = "value", rename_all = "snake_case")]
91pub enum Value {
92 Null,
93 Bool(bool),
94 Int(i64),
95 Float(f64),
96 String(String),
97 DateTime(String),
100 StringArray(Vec<String>),
101 IntArray(Vec<i64>),
102 FloatArray(Vec<f64>),
103 Json(serde_json::Value),
104}
105
106impl Value {
107 pub fn datetime(value: impl Into<String>) -> Result<Self> {
111 let value = value.into();
112 if is_rfc3339_datetime(&value) {
113 Ok(Self::DateTime(value))
114 } else {
115 Err(GrustError::Schema(format!(
116 "'{value}' is not an RFC 3339 date-time"
117 )))
118 }
119 }
120
121 pub fn as_str(&self) -> Option<&str> {
122 match self {
123 Self::String(value) => Some(value),
124 _ => None,
125 }
126 }
127
128 pub fn as_datetime(&self) -> Option<&str> {
129 match self {
130 Self::DateTime(value) => Some(value),
131 _ => None,
132 }
133 }
134
135 pub fn as_string_array(&self) -> Option<&[String]> {
136 match self {
137 Self::StringArray(values) => Some(values),
138 _ => None,
139 }
140 }
141
142 pub fn as_int_array(&self) -> Option<&[i64]> {
143 match self {
144 Self::IntArray(values) => Some(values),
145 _ => None,
146 }
147 }
148
149 pub fn as_float_array(&self) -> Option<&[f64]> {
150 match self {
151 Self::FloatArray(values) => Some(values),
152 _ => None,
153 }
154 }
155
156 pub fn to_json(&self) -> serde_json::Value {
159 match self {
160 Self::Null => serde_json::Value::Null,
161 Self::Bool(value) => serde_json::Value::Bool(*value),
162 Self::Int(value) => serde_json::Value::from(*value),
163 Self::Float(value) => serde_json::Value::from(*value),
164 Self::String(value) | Self::DateTime(value) => serde_json::Value::String(value.clone()),
165 Self::StringArray(values) => serde_json::Value::from(values.clone()),
166 Self::IntArray(values) => serde_json::Value::from(values.clone()),
167 Self::FloatArray(values) => serde_json::Value::from(values.clone()),
168 Self::Json(value) => value.clone(),
169 }
170 }
171
172 pub fn from_json(value: serde_json::Value) -> Self {
176 if let serde_json::Value::Object(mapping) = &value
177 && mapping.contains_key("type")
178 && mapping.contains_key("value")
179 && let Ok(tagged) = serde_json::from_value(value.clone())
180 {
181 return tagged;
182 }
183 Self::from(value)
184 }
185}
186
187fn is_rfc3339_datetime(value: &str) -> bool {
189 let bytes = value.as_bytes();
190 if bytes.len() < 20 {
191 return false;
192 }
193 let digit = |i: usize| bytes[i].is_ascii_digit();
194 let all_digits = |range: std::ops::Range<usize>| range.clone().all(digit);
195 let pair = |i: usize| (bytes[i] - b'0') * 10 + (bytes[i + 1] - b'0');
196
197 if !(all_digits(0..4)
198 && bytes[4] == b'-'
199 && all_digits(5..7)
200 && bytes[7] == b'-'
201 && all_digits(8..10)
202 && bytes[10] == b'T'
203 && all_digits(11..13)
204 && bytes[13] == b':'
205 && all_digits(14..16)
206 && bytes[16] == b':'
207 && all_digits(17..19))
208 {
209 return false;
210 }
211 let year = bytes[0..4]
212 .iter()
213 .fold(0u16, |acc, digit| acc * 10 + u16::from(digit - b'0'));
214 let month = pair(5);
215 let day = pair(8);
216 let leap_year = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
217 let max_day = match month {
218 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
219 4 | 6 | 9 | 11 => 30,
220 2 if leap_year => 29,
221 2 => 28,
222 _ => return false,
223 };
224 if !(day >= 1 && day <= max_day && pair(11) < 24 && pair(14) < 60 && pair(17) <= 60) {
225 return false;
226 }
227
228 let mut i = 19;
229 if bytes[i] == b'.' {
230 let start = i + 1;
231 i = start;
232 while i < bytes.len() && bytes[i].is_ascii_digit() {
233 i += 1;
234 }
235 if i == start {
236 return false;
237 }
238 }
239 match bytes.get(i) {
240 Some(b'Z') => i + 1 == bytes.len(),
241 Some(b'+' | b'-') => {
242 i + 6 == bytes.len()
243 && all_digits(i + 1..i + 3)
244 && bytes[i + 3] == b':'
245 && all_digits(i + 4..i + 6)
246 && pair(i + 1) < 24
247 && pair(i + 4) < 60
248 }
249 _ => false,
250 }
251}
252
253impl From<String> for Value {
254 fn from(value: String) -> Self {
255 Self::String(value)
256 }
257}
258
259impl From<&str> for Value {
260 fn from(value: &str) -> Self {
261 Self::String(value.to_string())
262 }
263}
264
265impl From<&String> for Value {
266 fn from(value: &String) -> Self {
267 Self::String(value.clone())
268 }
269}
270
271impl From<Vec<String>> for Value {
272 fn from(value: Vec<String>) -> Self {
273 Self::StringArray(value)
274 }
275}
276
277impl From<Vec<i64>> for Value {
278 fn from(value: Vec<i64>) -> Self {
279 Self::IntArray(value)
280 }
281}
282
283impl From<Vec<f64>> for Value {
284 fn from(value: Vec<f64>) -> Self {
285 Self::FloatArray(value)
286 }
287}
288
289impl From<bool> for Value {
290 fn from(value: bool) -> Self {
291 Self::Bool(value)
292 }
293}
294
295impl From<i64> for Value {
296 fn from(value: i64) -> Self {
297 Self::Int(value)
298 }
299}
300
301impl From<i32> for Value {
302 fn from(value: i32) -> Self {
303 Self::Int(i64::from(value))
304 }
305}
306
307impl From<usize> for Value {
308 fn from(value: usize) -> Self {
309 Self::Int(value as i64)
310 }
311}
312
313impl From<f64> for Value {
314 fn from(value: f64) -> Self {
315 Self::Float(value)
316 }
317}
318
319impl From<serde_json::Value> for Value {
320 fn from(value: serde_json::Value) -> Self {
321 match value {
322 serde_json::Value::Null => Self::Null,
323 serde_json::Value::Bool(value) => Self::Bool(value),
324 serde_json::Value::Number(value) => {
325 if let Some(value) = value.as_i64() {
326 Self::Int(value)
327 } else if let Some(value) = value.as_f64() {
328 Self::Float(value)
329 } else {
330 Self::Json(serde_json::Value::Number(value))
331 }
332 }
333 serde_json::Value::String(value) => Self::String(value),
334 serde_json::Value::Array(values) => {
335 let ints = values
336 .iter()
337 .filter_map(serde_json::Value::as_i64)
338 .collect::<Vec<_>>();
339 if !values.is_empty() && ints.len() == values.len() {
340 return Self::IntArray(ints);
341 }
342 let floats = values
343 .iter()
344 .filter_map(serde_json::Value::as_f64)
345 .collect::<Vec<_>>();
346 if !values.is_empty() && floats.len() == values.len() {
347 return Self::FloatArray(floats);
348 }
349 let strings = values
350 .iter()
351 .filter_map(|value| value.as_str().map(ToString::to_string))
352 .collect::<Vec<_>>();
353 if strings.len() == values.len() {
354 Self::StringArray(strings)
355 } else {
356 Self::Json(serde_json::Value::Array(values))
357 }
358 }
359 serde_json::Value::Object(value) => Self::Json(serde_json::Value::Object(value)),
360 }
361 }
362}
363
364#[cfg(feature = "typed-garde")]
365pub mod typed {
366 use serde::Serialize;
367 #[cfg(feature = "typed-zod-rs")]
368 use serde::de::DeserializeOwned;
369
370 use crate::{
371 Edge, EdgeId, Graph, GraphBuilder, GrustError, Node, NodeId, Props, PutOutcome, Result,
372 Value,
373 };
374
375 pub use garde;
376 #[cfg(feature = "typed-zod-rs")]
377 pub use zod_rs;
378
379 pub trait TypedNode: garde::Validate + Serialize {
380 const LABEL: &'static str;
381
382 fn node_id(&self) -> NodeId;
383
384 fn node_props(&self) -> Result<Props> {
385 props_from_serialize(self)
386 }
387 }
388
389 pub trait TypedEdge: garde::Validate + Serialize {
390 const LABEL: &'static str;
391
392 fn source_node_id(&self) -> NodeId;
393
394 fn target_node_id(&self) -> NodeId;
395
396 fn edge_id(&self) -> Option<EdgeId> {
397 None
398 }
399
400 fn edge_props(&self) -> Result<Props> {
401 props_from_serialize(self)
402 }
403 }
404
405 #[derive(Clone, Debug, Default)]
406 pub struct TypedGraphBuilder {
407 builder: GraphBuilder,
408 }
409
410 impl TypedGraphBuilder {
411 pub fn new() -> Self {
412 Self::default()
413 }
414
415 pub fn from_builder(builder: GraphBuilder) -> Self {
416 Self { builder }
417 }
418
419 pub fn from_graph(graph: Graph) -> Self {
420 let mut builder = GraphBuilder::new();
421 for node in graph.nodes {
422 builder.add_node(node);
423 }
424 for edge in graph.edges {
425 builder.add_edge(edge);
426 }
427 Self { builder }
428 }
429
430 pub fn add_raw_node(&mut self, node: Node) -> NodeId {
431 self.builder.add_node(node)
432 }
433
434 pub fn add_raw_edge(&mut self, edge: Edge) -> PutOutcome {
435 self.builder.add_edge(edge)
436 }
437
438 pub fn add_node<T>(&mut self, node: &T) -> Result<NodeId>
439 where
440 T: TypedNode,
441 T::Context: Default,
442 {
443 node.validate()
444 .map_err(|err| validation_error(T::LABEL, err))?;
445 self.add_validated_node(node)
446 }
447
448 pub fn add_node_with<T>(&mut self, node: &T, ctx: &T::Context) -> Result<NodeId>
449 where
450 T: TypedNode,
451 {
452 node.validate_with(ctx)
453 .map_err(|err| validation_error(T::LABEL, err))?;
454 self.add_validated_node(node)
455 }
456
457 pub fn add_edge<T>(&mut self, edge: &T) -> Result<PutOutcome>
458 where
459 T: TypedEdge,
460 T::Context: Default,
461 {
462 edge.validate()
463 .map_err(|err| validation_error(T::LABEL, err))?;
464 self.add_validated_edge(edge)
465 }
466
467 pub fn add_edge_with<T>(&mut self, edge: &T, ctx: &T::Context) -> Result<PutOutcome>
468 where
469 T: TypedEdge,
470 {
471 edge.validate_with(ctx)
472 .map_err(|err| validation_error(T::LABEL, err))?;
473 self.add_validated_edge(edge)
474 }
475
476 #[cfg(feature = "typed-zod-rs")]
477 pub fn add_node_from_json<T, S>(
478 &mut self,
479 schema: &S,
480 value: &serde_json::Value,
481 ) -> Result<NodeId>
482 where
483 T: TypedNode + DeserializeOwned,
484 T::Context: Default,
485 S: zod_rs::Schema<serde_json::Value>,
486 {
487 let node = parse_typed_json::<T, S>(schema, value)?;
488 self.add_validated_node(&node)
489 }
490
491 #[cfg(feature = "typed-zod-rs")]
492 pub fn add_node_from_json_with<T, S>(
493 &mut self,
494 schema: &S,
495 value: &serde_json::Value,
496 ctx: &T::Context,
497 ) -> Result<NodeId>
498 where
499 T: TypedNode + DeserializeOwned,
500 S: zod_rs::Schema<serde_json::Value>,
501 {
502 let node = parse_typed_json_with::<T, S>(schema, value, ctx)?;
503 self.add_validated_node(&node)
504 }
505
506 #[cfg(feature = "typed-zod-rs")]
507 pub fn add_edge_from_json<T, S>(
508 &mut self,
509 schema: &S,
510 value: &serde_json::Value,
511 ) -> Result<PutOutcome>
512 where
513 T: TypedEdge + DeserializeOwned,
514 T::Context: Default,
515 S: zod_rs::Schema<serde_json::Value>,
516 {
517 let edge = parse_typed_json::<T, S>(schema, value)?;
518 self.add_validated_edge(&edge)
519 }
520
521 #[cfg(feature = "typed-zod-rs")]
522 pub fn add_edge_from_json_with<T, S>(
523 &mut self,
524 schema: &S,
525 value: &serde_json::Value,
526 ctx: &T::Context,
527 ) -> Result<PutOutcome>
528 where
529 T: TypedEdge + DeserializeOwned,
530 S: zod_rs::Schema<serde_json::Value>,
531 {
532 let edge = parse_typed_json_with::<T, S>(schema, value, ctx)?;
533 self.add_validated_edge(&edge)
534 }
535
536 pub fn build(self) -> Graph {
537 self.builder.build()
538 }
539
540 pub fn into_builder(self) -> GraphBuilder {
541 self.builder
542 }
543
544 fn add_validated_node<T>(&mut self, node: &T) -> Result<NodeId>
545 where
546 T: TypedNode,
547 {
548 let node_id = node.node_id();
549 let mut props = node.node_props()?;
550 props.insert("id".to_string(), Value::from(node_id.as_str()));
551 let graph_node = Node::new(T::LABEL, node_id, props);
552 Ok(self.builder.add_node(graph_node))
553 }
554
555 fn add_validated_edge<T>(&mut self, edge: &T) -> Result<PutOutcome>
556 where
557 T: TypedEdge,
558 {
559 let mut graph_edge = Edge::new(
560 T::LABEL,
561 edge.source_node_id(),
562 edge.target_node_id(),
563 edge.edge_props()?,
564 );
565 graph_edge.id = edge.edge_id();
566 Ok(self.builder.add_edge(graph_edge))
567 }
568 }
569
570 pub fn props_from_serialize<T>(value: &T) -> Result<Props>
571 where
572 T: Serialize + ?Sized,
573 {
574 let serialized = serde_json::to_value(value)
575 .map_err(|err| GrustError::Serialization(format!("typed props error: {err}")))?;
576 let serde_json::Value::Object(fields) = serialized else {
577 return Err(GrustError::Schema(
578 "typed graph values must serialize as JSON objects".to_string(),
579 ));
580 };
581
582 Ok(fields
583 .into_iter()
584 .map(|(key, value)| (key, Value::from(value)))
585 .collect())
586 }
587
588 #[cfg(feature = "typed-zod-rs")]
589 pub fn parse_typed_json<T, S>(schema: &S, value: &serde_json::Value) -> Result<T>
590 where
591 T: DeserializeOwned + garde::Validate,
592 T::Context: Default,
593 S: zod_rs::Schema<serde_json::Value>,
594 {
595 let ctx = T::Context::default();
596 parse_typed_json_with(schema, value, &ctx)
597 }
598
599 #[cfg(feature = "typed-zod-rs")]
600 pub fn parse_typed_json_with<T, S>(
601 schema: &S,
602 value: &serde_json::Value,
603 ctx: &T::Context,
604 ) -> Result<T>
605 where
606 T: DeserializeOwned + garde::Validate,
607 S: zod_rs::Schema<serde_json::Value>,
608 {
609 schema
610 .safe_parse(value)
611 .map_err(|err| GrustError::Schema(format!("zod-rs validation failed: {err}")))?;
612 let typed: T = serde_json::from_value(value.clone())
613 .map_err(|err| GrustError::Serialization(format!("typed JSON decode error: {err}")))?;
614 typed
615 .validate_with(ctx)
616 .map_err(|err| GrustError::Schema(format!("typed validation failed: {err}")))?;
617 Ok(typed)
618 }
619
620 fn validation_error(label: &str, err: garde::Report) -> GrustError {
621 GrustError::Schema(format!("{label} validation failed: {err}"))
622 }
623}
624
625#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
626pub struct Node {
627 pub id: NodeId,
628 pub label: Label,
629 pub props: Props,
630}
631
632impl Node {
633 pub fn new(label: impl Into<Label>, id: impl Into<NodeId>, props: impl Into<Props>) -> Self {
634 let id = id.into();
635 let mut props = props.into();
636 props
637 .entry("id".to_string())
638 .or_insert_with(|| Value::from(id.as_str()));
639 Self {
640 id,
641 label: label.into(),
642 props,
643 }
644 }
645}
646
647#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
648pub struct Edge {
649 pub id: Option<EdgeId>,
650 pub from: NodeId,
651 pub to: NodeId,
652 pub label: Label,
653 pub props: Props,
654}
655
656impl Edge {
657 pub fn new(
658 label: impl Into<Label>,
659 from: impl Into<NodeId>,
660 to: impl Into<NodeId>,
661 props: impl Into<Props>,
662 ) -> Self {
663 Self {
664 id: None,
665 from: from.into(),
666 to: to.into(),
667 label: label.into(),
668 props: props.into(),
669 }
670 }
671
672 pub fn with_id(mut self, id: impl Into<EdgeId>) -> Self {
673 self.id = Some(id.into());
674 self
675 }
676}
677
678#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
679pub struct Graph {
680 pub nodes: Vec<Node>,
681 pub edges: Vec<Edge>,
682}
683
684impl Graph {
685 pub fn new(nodes: Vec<Node>, edges: Vec<Edge>) -> Self {
686 Self { nodes, edges }
687 }
688
689 pub fn from_yaml(yaml: &str) -> Result<Self> {
690 yaml::graph_from_yaml(yaml)
691 }
692
693 pub fn to_yaml(&self) -> Result<String> {
694 yaml::graph_to_yaml(self)
695 }
696
697 pub fn from_json(json: &str) -> Result<Self> {
698 json::graph_from_json(json)
699 }
700
701 pub fn to_json(&self) -> Result<String> {
702 json::graph_to_json(self)
703 }
704
705 pub fn from_xml(xml: &str) -> Result<Self> {
706 xml::graph_from_xml(xml)
707 }
708
709 pub fn to_xml(&self) -> Result<String> {
710 xml::graph_to_xml(self)
711 }
712
713 pub fn builder() -> GraphBuilder {
714 GraphBuilder::new()
715 }
716}
717
718mod graph_doc {
719 use std::collections::{BTreeMap, BTreeSet};
720
721 use serde::{Deserialize, Serialize};
722
723 use crate::{Edge, EdgeId, Graph, GrustError, Label, Node, NodeId, Props, Value};
724
725 #[derive(Debug, Serialize, Deserialize)]
726 pub(super) struct GraphDoc {
727 #[serde(default)]
728 pub(super) nodes: Vec<NodeDoc>,
729 #[serde(default)]
730 pub(super) edges: Vec<EdgeDoc>,
731 }
732
733 #[derive(Debug, Serialize, Deserialize)]
734 pub(super) struct NodeDoc {
735 pub(super) id: NodeId,
736 pub(super) label: Label,
737 #[serde(default, deserialize_with = "deserialize_props")]
738 pub(super) props: Props,
739 }
740
741 #[derive(Debug, Serialize, Deserialize)]
742 pub(super) struct NodeDocOut {
743 pub(super) id: NodeId,
744 pub(super) label: Label,
745 #[serde(default)]
746 pub(super) props: Props,
747 }
748
749 #[derive(Debug, Serialize, Deserialize)]
750 pub(super) struct EdgeDoc {
751 #[serde(default)]
752 pub(super) id: Option<EdgeId>,
753 pub(super) label: Label,
754 pub(super) from: NodeId,
755 pub(super) to: NodeId,
756 #[serde(default, deserialize_with = "deserialize_props")]
757 pub(super) props: Props,
758 }
759
760 #[derive(Debug, Serialize, Deserialize)]
761 pub(super) struct EdgeDocOut {
762 #[serde(default)]
763 pub(super) id: Option<EdgeId>,
764 pub(super) label: Label,
765 pub(super) from: NodeId,
766 pub(super) to: NodeId,
767 #[serde(default)]
768 pub(super) props: Props,
769 }
770
771 pub(super) fn graph_from_doc(doc: GraphDoc) -> super::Result<Graph> {
772 let mut ids = BTreeSet::new();
773 for node in &doc.nodes {
774 if !ids.insert(node.id.clone()) {
775 return Err(GrustError::Schema(format!(
776 "duplicate node id '{}'",
777 node.id
778 )));
779 }
780 }
781
782 let mut edges = Vec::with_capacity(doc.edges.len());
783 for edge in doc.edges {
784 if !ids.contains(&edge.from) {
785 return Err(GrustError::Schema(format!(
786 "edge '{}' references unknown from node '{}'",
787 edge.label, edge.from
788 )));
789 }
790 if !ids.contains(&edge.to) {
791 return Err(GrustError::Schema(format!(
792 "edge '{}' references unknown to node '{}'",
793 edge.label, edge.to
794 )));
795 }
796
797 let mut graph_edge = Edge::new(edge.label, edge.from, edge.to, edge.props);
798 graph_edge.id = edge.id;
799 edges.push(graph_edge);
800 }
801
802 let nodes = doc
803 .nodes
804 .into_iter()
805 .map(|node| Node::new(node.label, node.id, node.props))
806 .collect();
807
808 Ok(Graph::new(nodes, edges))
809 }
810
811 pub(super) fn graph_to_doc(graph: &Graph) -> GraphDocOut {
812 GraphDocOut {
813 nodes: graph
814 .nodes
815 .iter()
816 .map(|node| NodeDocOut {
817 id: node.id.clone(),
818 label: node.label.clone(),
819 props: without_generated_id(&node.props, &node.id),
820 })
821 .collect(),
822 edges: graph
823 .edges
824 .iter()
825 .map(|edge| EdgeDocOut {
826 id: edge.id.clone(),
827 label: edge.label.clone(),
828 from: edge.from.clone(),
829 to: edge.to.clone(),
830 props: edge.props.clone(),
831 })
832 .collect(),
833 }
834 }
835
836 fn without_generated_id(props: &Props, id: &NodeId) -> Props {
837 let mut props = props.clone();
838 if props.get("id") == Some(&Value::from(id.as_str())) {
839 props.remove("id");
840 }
841 props
842 }
843
844 fn deserialize_props<'de, D>(deserializer: D) -> std::result::Result<Props, D::Error>
845 where
846 D: serde::Deserializer<'de>,
847 {
848 let raw = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
849 raw.into_iter()
850 .map(|(key, value)| {
851 value_from_json(value)
852 .map(|value| (key, value))
853 .map_err(serde::de::Error::custom)
854 })
855 .collect()
856 }
857
858 fn value_from_json(value: serde_json::Value) -> std::result::Result<Value, String> {
859 if let serde_json::Value::Object(mapping) = &value
860 && mapping.contains_key("type")
861 && mapping.contains_key("value")
862 {
863 return serde_json::from_value(value)
864 .map_err(|err| format!("invalid tagged Grust value: {err}"));
865 }
866
867 Ok(Value::from_json(value))
868 }
869
870 #[derive(Debug, Serialize, Deserialize)]
871 pub(super) struct GraphDocOut {
872 pub(super) nodes: Vec<NodeDocOut>,
873 pub(super) edges: Vec<EdgeDocOut>,
874 }
875}
876
877mod yaml {
878 use crate::{Graph, GrustError};
879
880 pub(super) fn graph_from_yaml(yaml: &str) -> super::Result<Graph> {
881 let doc: super::graph_doc::GraphDoc = serde_yaml::from_str(yaml)
882 .map_err(|err| GrustError::Serialization(format!("YAML parse error: {err}")))?;
883 super::graph_doc::graph_from_doc(doc)
884 }
885
886 pub(super) fn graph_to_yaml(graph: &Graph) -> super::Result<String> {
887 serde_yaml::to_string(&super::graph_doc::graph_to_doc(graph))
888 .map_err(|err| GrustError::Serialization(format!("YAML serialization error: {err}")))
889 }
890}
891
892mod json {
893 use crate::{Graph, GrustError};
894
895 pub(super) fn graph_from_json(json: &str) -> super::Result<Graph> {
896 let doc: super::graph_doc::GraphDoc = serde_json::from_str(json)
897 .map_err(|err| GrustError::Serialization(format!("JSON parse error: {err}")))?;
898 super::graph_doc::graph_from_doc(doc)
899 }
900
901 pub(super) fn graph_to_json(graph: &Graph) -> super::Result<String> {
902 serde_json::to_string_pretty(&super::graph_doc::graph_to_doc(graph))
903 .map_err(|err| GrustError::Serialization(format!("JSON serialization error: {err}")))
904 }
905}
906
907mod xml {
908 use serde::{Deserialize, Serialize};
909
910 use crate::{Edge, EdgeId, Graph, GrustError, Label, Node, NodeId, Props, Value};
911
912 #[derive(Debug, Serialize, Deserialize)]
913 #[serde(rename = "graph")]
914 struct GraphXml {
915 #[serde(default)]
916 nodes: NodesXml,
917 #[serde(default)]
918 edges: EdgesXml,
919 }
920
921 #[derive(Debug, Default, Serialize, Deserialize)]
922 struct NodesXml {
923 #[serde(rename = "node", default)]
924 items: Vec<NodeXml>,
925 }
926
927 #[derive(Debug, Default, Serialize, Deserialize)]
928 struct EdgesXml {
929 #[serde(rename = "edge", default)]
930 items: Vec<EdgeXml>,
931 }
932
933 #[derive(Debug, Serialize, Deserialize)]
934 struct NodeXml {
935 id: NodeId,
936 label: Label,
937 #[serde(default)]
938 props: PropsXml,
939 }
940
941 #[derive(Debug, Serialize, Deserialize)]
942 struct EdgeXml {
943 #[serde(default, skip_serializing_if = "Option::is_none")]
944 id: Option<EdgeId>,
945 label: Label,
946 from: NodeId,
947 to: NodeId,
948 #[serde(default)]
949 props: PropsXml,
950 }
951
952 #[derive(Debug, Default, Serialize, Deserialize)]
953 struct PropsXml {
954 #[serde(rename = "prop", default)]
955 items: Vec<PropXml>,
956 }
957
958 #[derive(Debug, Serialize, Deserialize)]
959 struct PropXml {
960 key: String,
961 value: Value,
962 }
963
964 pub(super) fn graph_from_xml(xml: &str) -> super::Result<Graph> {
965 let doc: GraphXml = quick_xml::de::from_str(xml)
966 .map_err(|err| GrustError::Serialization(format!("XML parse error: {err}")))?;
967 super::graph_doc::graph_from_doc(doc.into())
968 }
969
970 pub(super) fn graph_to_xml(graph: &Graph) -> super::Result<String> {
971 quick_xml::se::to_string(&GraphXml::from(graph))
972 .map_err(|err| GrustError::Serialization(format!("XML serialization error: {err}")))
973 }
974
975 impl From<GraphXml> for super::graph_doc::GraphDoc {
976 fn from(value: GraphXml) -> Self {
977 Self {
978 nodes: value.nodes.items.into_iter().map(Into::into).collect(),
979 edges: value.edges.items.into_iter().map(Into::into).collect(),
980 }
981 }
982 }
983
984 impl From<NodeXml> for super::graph_doc::NodeDoc {
985 fn from(value: NodeXml) -> Self {
986 Self {
987 id: value.id,
988 label: value.label,
989 props: value.props.into(),
990 }
991 }
992 }
993
994 impl From<EdgeXml> for super::graph_doc::EdgeDoc {
995 fn from(value: EdgeXml) -> Self {
996 Self {
997 id: value.id,
998 label: value.label,
999 from: value.from,
1000 to: value.to,
1001 props: value.props.into(),
1002 }
1003 }
1004 }
1005
1006 impl From<PropsXml> for Props {
1007 fn from(value: PropsXml) -> Self {
1008 value
1009 .items
1010 .into_iter()
1011 .map(|prop| (prop.key, prop.value))
1012 .collect()
1013 }
1014 }
1015
1016 impl From<&Graph> for GraphXml {
1017 fn from(graph: &Graph) -> Self {
1018 Self {
1019 nodes: NodesXml {
1020 items: graph.nodes.iter().map(NodeXml::from).collect(),
1021 },
1022 edges: EdgesXml {
1023 items: graph.edges.iter().map(EdgeXml::from).collect(),
1024 },
1025 }
1026 }
1027 }
1028
1029 impl From<&Node> for NodeXml {
1030 fn from(node: &Node) -> Self {
1031 let props = super::graph_doc::graph_to_doc(&Graph::new(vec![node.clone()], Vec::new()))
1032 .nodes
1033 .into_iter()
1034 .next()
1035 .expect("node exists")
1036 .props;
1037 Self {
1038 id: node.id.clone(),
1039 label: node.label.clone(),
1040 props: props.into(),
1041 }
1042 }
1043 }
1044
1045 impl From<&Edge> for EdgeXml {
1046 fn from(edge: &Edge) -> Self {
1047 Self {
1048 id: edge.id.clone(),
1049 label: edge.label.clone(),
1050 from: edge.from.clone(),
1051 to: edge.to.clone(),
1052 props: edge.props.clone().into(),
1053 }
1054 }
1055 }
1056
1057 impl From<Props> for PropsXml {
1058 fn from(value: Props) -> Self {
1059 Self {
1060 items: value
1061 .into_iter()
1062 .map(|(key, value)| PropXml { key, value })
1063 .collect(),
1064 }
1065 }
1066 }
1067}
1068
1069#[derive(Clone, Debug, Default, Eq, PartialEq)]
1070pub enum EdgePolicy {
1071 AllowDuplicates,
1072 #[default]
1073 DedupeByFromLabelTo,
1074}
1075
1076#[derive(Clone, Debug, Default)]
1077pub struct GraphBuilder {
1078 nodes: BTreeMap<NodeId, Node>,
1079 edges: Vec<Edge>,
1080 edge_keys: BTreeSet<(NodeId, Label, NodeId)>,
1081 edge_policy: EdgePolicy,
1082}
1083
1084impl GraphBuilder {
1085 pub fn new() -> Self {
1086 Self::default()
1087 }
1088
1089 pub fn edge_policy(mut self, edge_policy: EdgePolicy) -> Self {
1090 self.edge_policy = edge_policy;
1091 self
1092 }
1093
1094 pub fn node<'a>(
1095 &'a mut self,
1096 label: impl Into<Label>,
1097 id: impl Into<NodeId>,
1098 ) -> NodeBuilder<'a> {
1099 NodeBuilder {
1100 builder: self,
1101 label: label.into(),
1102 id: id.into(),
1103 props: Props::new(),
1104 }
1105 }
1106
1107 pub fn edge<'a>(
1108 &'a mut self,
1109 label: impl Into<Label>,
1110 from: impl Into<NodeId>,
1111 to: impl Into<NodeId>,
1112 ) -> EdgeBuilder<'a> {
1113 EdgeBuilder {
1114 builder: self,
1115 id: None,
1116 label: label.into(),
1117 from: from.into(),
1118 to: to.into(),
1119 props: Props::new(),
1120 }
1121 }
1122
1123 pub fn add_node(&mut self, node: Node) -> NodeId {
1130 let id = node.id.clone();
1131 self.nodes
1132 .entry(id.clone())
1133 .and_modify(|existing| {
1134 if existing.label == node.label {
1135 existing.props.extend(node.props.clone());
1136 } else {
1137 *existing = node.clone();
1138 }
1139 })
1140 .or_insert(node);
1141 id
1142 }
1143
1144 pub fn add_edge(&mut self, edge: Edge) -> PutOutcome {
1147 match self.edge_policy {
1148 EdgePolicy::AllowDuplicates => {
1149 self.edges.push(edge);
1150 PutOutcome::Inserted
1151 }
1152 EdgePolicy::DedupeByFromLabelTo => {
1153 let key = (edge.from.clone(), edge.label.clone(), edge.to.clone());
1154 if self.edge_keys.insert(key) {
1155 self.edges.push(edge);
1156 PutOutcome::Inserted
1157 } else {
1158 PutOutcome::Deduped
1159 }
1160 }
1161 }
1162 }
1163
1164 pub fn build(self) -> Graph {
1165 Graph {
1166 nodes: self.nodes.into_values().collect(),
1167 edges: self.edges,
1168 }
1169 }
1170}
1171
1172pub struct NodeBuilder<'a> {
1173 builder: &'a mut GraphBuilder,
1174 label: Label,
1175 id: NodeId,
1176 props: Props,
1177}
1178
1179impl<'a> NodeBuilder<'a> {
1180 pub fn prop(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1181 self.props.insert(key.into(), value.into());
1182 self
1183 }
1184
1185 pub fn props(mut self, props: Props) -> Self {
1186 self.props.extend(props);
1187 self
1188 }
1189
1190 pub fn finish(self) -> NodeId {
1191 let node = Node::new(self.label, self.id, self.props);
1192 self.builder.add_node(node)
1193 }
1194}
1195
1196pub struct EdgeBuilder<'a> {
1197 builder: &'a mut GraphBuilder,
1198 id: Option<EdgeId>,
1199 label: Label,
1200 from: NodeId,
1201 to: NodeId,
1202 props: Props,
1203}
1204
1205impl<'a> EdgeBuilder<'a> {
1206 pub fn id(mut self, id: impl Into<EdgeId>) -> Self {
1207 self.id = Some(id.into());
1208 self
1209 }
1210
1211 pub fn prop(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1212 self.props.insert(key.into(), value.into());
1213 self
1214 }
1215
1216 pub fn props(mut self, props: Props) -> Self {
1217 self.props.extend(props);
1218 self
1219 }
1220
1221 pub fn finish(self) -> PutOutcome {
1222 let mut edge = Edge::new(self.label, self.from, self.to, self.props);
1223 edge.id = self.id;
1224 self.builder.add_edge(edge)
1225 }
1226}
1227
1228#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1229pub struct GraphSchema {
1230 pub nodes: Vec<NodeType>,
1231 pub edges: Vec<EdgeType>,
1232}
1233
1234impl GraphSchema {
1235 pub fn builder() -> GraphSchemaBuilder {
1236 GraphSchemaBuilder::default()
1237 }
1238
1239 pub fn node_type(&self, label: &Label) -> Option<&NodeType> {
1240 self.nodes
1241 .iter()
1242 .find(|node_type| &node_type.label == label)
1243 }
1244
1245 pub fn edge_type(&self, label: &Label) -> Option<&EdgeType> {
1246 self.edges
1247 .iter()
1248 .find(|edge_type| &edge_type.label == label)
1249 }
1250
1251 pub fn validate_graph(&self, graph: &Graph) -> Result<()> {
1252 for node in &graph.nodes {
1253 self.validate_node(node)?;
1254 }
1255 let labels: BTreeMap<&NodeId, &Label> = graph
1256 .nodes
1257 .iter()
1258 .map(|node| (&node.id, &node.label))
1259 .collect();
1260 for edge in &graph.edges {
1261 self.validate_edge_with(edge, |id| labels.get(id).copied())?;
1262 }
1263 self.validate_edge_uniqueness(graph)
1264 }
1265
1266 fn validate_edge_uniqueness(&self, graph: &Graph) -> Result<()> {
1269 let mut seen = BTreeSet::new();
1270 for edge in &graph.edges {
1271 let Some(edge_type) = self.edge_type(&edge.label) else {
1272 continue;
1273 };
1274 if edge_type.uniqueness == EdgeUniqueness::None {
1275 continue;
1276 }
1277 let (a, b) = if edge_type.directed || edge.from <= edge.to {
1278 (&edge.from, &edge.to)
1279 } else {
1280 (&edge.to, &edge.from)
1281 };
1282 if !seen.insert((edge.label.clone(), a.clone(), b.clone())) {
1283 return Err(GrustError::Schema(format!(
1284 "duplicate edge '{}' between '{}' and '{}' violates {:?} uniqueness",
1285 edge.label.as_str(),
1286 a.as_str(),
1287 b.as_str(),
1288 edge_type.uniqueness
1289 )));
1290 }
1291 }
1292 Ok(())
1293 }
1294
1295 pub fn validate_node(&self, node: &Node) -> Result<()> {
1296 let node_type = self.node_type(&node.label).ok_or_else(|| {
1297 GrustError::Schema(format!("schema has no node type '{}'", node.label.as_str()))
1298 })?;
1299 validate_props(
1300 &node.props,
1301 &node_type.fields,
1302 &format!("node '{}'", node.id.as_str()),
1303 )
1304 }
1305
1306 pub fn validate_edge(&self, edge: &Edge, graph: &Graph) -> Result<()> {
1307 self.validate_edge_with(edge, |id| {
1308 graph
1309 .nodes
1310 .iter()
1311 .find(|node| &node.id == id)
1312 .map(|node| &node.label)
1313 })
1314 }
1315
1316 pub fn validate_edge_with<'a>(
1319 &self,
1320 edge: &Edge,
1321 lookup: impl Fn(&NodeId) -> Option<&'a Label>,
1322 ) -> Result<()> {
1323 let edge_type = self.edge_type(&edge.label).ok_or_else(|| {
1324 GrustError::Schema(format!("schema has no edge type '{}'", edge.label.as_str()))
1325 })?;
1326
1327 let from_label = lookup(&edge.from).ok_or_else(|| {
1328 GrustError::Schema(format!(
1329 "edge '{}' references unknown from node '{}'",
1330 edge.label.as_str(),
1331 edge.from.as_str()
1332 ))
1333 })?;
1334 let to_label = lookup(&edge.to).ok_or_else(|| {
1335 GrustError::Schema(format!(
1336 "edge '{}' references unknown to node '{}'",
1337 edge.label.as_str(),
1338 edge.to.as_str()
1339 ))
1340 })?;
1341
1342 let from_matches =
1343 |label: &Label| edge_type.from.is_empty() || edge_type.from.contains(label);
1344 let to_matches = |label: &Label| edge_type.to.is_empty() || edge_type.to.contains(label);
1345 let endpoints_ok = (from_matches(from_label) && to_matches(to_label))
1348 || (!edge_type.directed && from_matches(to_label) && to_matches(from_label));
1349 if !endpoints_ok {
1350 if !from_matches(from_label) {
1351 return Err(GrustError::Schema(format!(
1352 "edge '{}' cannot start from node label '{}'",
1353 edge.label.as_str(),
1354 from_label.as_str()
1355 )));
1356 }
1357 return Err(GrustError::Schema(format!(
1358 "edge '{}' cannot end at node label '{}'",
1359 edge.label.as_str(),
1360 to_label.as_str()
1361 )));
1362 }
1363
1364 validate_props(
1365 &edge.props,
1366 &edge_type.fields,
1367 &format!("edge '{}'", edge.label.as_str()),
1368 )
1369 }
1370
1371 pub fn validate_edge_props(&self, edge: &Edge) -> Result<()> {
1375 let edge_type = self.edge_type(&edge.label).ok_or_else(|| {
1376 GrustError::Schema(format!("schema has no edge type '{}'", edge.label.as_str()))
1377 })?;
1378 validate_props(
1379 &edge.props,
1380 &edge_type.fields,
1381 &format!("edge '{}'", edge.label.as_str()),
1382 )
1383 }
1384}
1385
1386#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1387pub struct NodeType {
1388 pub label: Label,
1389 pub fields: Vec<Field>,
1390}
1391
1392#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1393pub struct EdgeType {
1394 pub label: Label,
1395 pub from: Vec<Label>,
1396 pub to: Vec<Label>,
1397 pub fields: Vec<Field>,
1398 pub directed: bool,
1399 pub uniqueness: EdgeUniqueness,
1400}
1401
1402#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1403pub struct Field {
1404 pub name: String,
1405 pub ty: FieldType,
1406 pub required: bool,
1407}
1408
1409#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1410pub enum FieldType {
1411 String,
1412 Int,
1413 Float,
1414 Bool,
1415 DateTime,
1416 StringArray,
1417 IntArray,
1418 FloatArray,
1419 Json,
1420}
1421
1422#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1430pub enum EdgeUniqueness {
1431 None,
1432 FromTo,
1433 FromLabelTo,
1434}
1435
1436#[derive(Clone, Debug, Default)]
1437pub struct GraphSchemaBuilder {
1438 nodes: Vec<NodeType>,
1439 edges: Vec<EdgeType>,
1440}
1441
1442impl GraphSchemaBuilder {
1443 pub fn node(mut self, label: impl Into<Label>, fields: impl Into<Vec<Field>>) -> Self {
1444 self.nodes.push(NodeType {
1445 label: label.into(),
1446 fields: fields.into(),
1447 });
1448 self
1449 }
1450
1451 pub fn edge(
1452 mut self,
1453 label: impl Into<Label>,
1454 from: impl Into<Vec<Label>>,
1455 to: impl Into<Vec<Label>>,
1456 fields: impl Into<Vec<Field>>,
1457 ) -> Self {
1458 self.edges.push(EdgeType {
1459 label: label.into(),
1460 from: from.into(),
1461 to: to.into(),
1462 fields: fields.into(),
1463 directed: true,
1464 uniqueness: EdgeUniqueness::FromLabelTo,
1465 });
1466 self
1467 }
1468
1469 pub fn edge_type(mut self, edge_type: EdgeType) -> Self {
1470 self.edges.push(edge_type);
1471 self
1472 }
1473
1474 pub fn build(self) -> GraphSchema {
1475 GraphSchema {
1476 nodes: self.nodes,
1477 edges: self.edges,
1478 }
1479 }
1480}
1481
1482impl Field {
1483 pub fn required(name: impl Into<String>, ty: FieldType) -> Self {
1484 Self {
1485 name: name.into(),
1486 ty,
1487 required: true,
1488 }
1489 }
1490
1491 pub fn optional(name: impl Into<String>, ty: FieldType) -> Self {
1492 Self {
1493 name: name.into(),
1494 ty,
1495 required: false,
1496 }
1497 }
1498}
1499
1500fn validate_props(props: &Props, fields: &[Field], context: &str) -> Result<()> {
1501 for field in fields {
1502 match props.get(&field.name) {
1503 Some(value) => validate_field_value(value, &field.ty, context, &field.name)?,
1504 None if field.required => {
1505 return Err(GrustError::Schema(format!(
1506 "{context} missing required field '{}'",
1507 field.name
1508 )));
1509 }
1510 None => {}
1511 }
1512 }
1513 Ok(())
1514}
1515
1516fn validate_field_value(
1517 value: &Value,
1518 ty: &FieldType,
1519 context: &str,
1520 field_name: &str,
1521) -> Result<()> {
1522 let matches = match (value, ty) {
1523 (Value::String(_), FieldType::String)
1524 | (Value::Int(_), FieldType::Int)
1525 | (Value::Float(_), FieldType::Float)
1526 | (Value::Bool(_), FieldType::Bool)
1527 | (Value::DateTime(_), FieldType::DateTime)
1528 | (Value::StringArray(_), FieldType::StringArray)
1529 | (Value::IntArray(_), FieldType::IntArray)
1530 | (Value::FloatArray(_), FieldType::FloatArray)
1531 | (_, FieldType::Json) => true,
1532 (Value::String(value), FieldType::DateTime) => is_rfc3339_datetime(value),
1535 _ => false,
1536 };
1537 if matches {
1538 Ok(())
1539 } else {
1540 Err(GrustError::Schema(format!(
1541 "{context} field '{field_name}' expected {ty:?}, got {value:?}"
1542 )))
1543 }
1544}
1545
1546#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1547pub struct Traversal {
1548 pub start: Start,
1549 pub steps: Vec<Step>,
1550 pub limit: Option<u32>,
1551}
1552
1553impl Traversal {
1554 pub fn from_node(id: impl Into<NodeId>) -> Self {
1555 Self {
1556 start: Start::Node(id.into()),
1557 steps: Vec::new(),
1558 limit: None,
1559 }
1560 }
1561
1562 pub fn out(mut self, edge: impl Into<Label>) -> Self {
1563 self.steps.push(Step {
1564 direction: Direction::Out,
1565 edge: Some(edge.into()),
1566 node: None,
1567 });
1568 self
1569 }
1570
1571 pub fn in_(mut self, edge: impl Into<Label>) -> Self {
1572 self.steps.push(Step {
1573 direction: Direction::In,
1574 edge: Some(edge.into()),
1575 node: None,
1576 });
1577 self
1578 }
1579
1580 pub fn both(mut self, edge: impl Into<Label>) -> Self {
1581 self.steps.push(Step {
1582 direction: Direction::Both,
1583 edge: Some(edge.into()),
1584 node: None,
1585 });
1586 self
1587 }
1588
1589 pub fn to(mut self, node: impl Into<Label>) -> Self {
1596 let step = self
1597 .steps
1598 .last_mut()
1599 .expect("Traversal::to() must follow out(), in_(), or both()");
1600 step.node = Some(node.into());
1601 self
1602 }
1603
1604 pub fn limit(mut self, limit: u32) -> Self {
1605 self.limit = Some(limit);
1606 self
1607 }
1608}
1609
1610#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1611pub enum Start {
1612 Node(NodeId),
1613 NodesByLabel(Label),
1614 NodesByProperty {
1615 label: Label,
1616 key: String,
1617 value: Value,
1618 },
1619}
1620
1621#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1622pub struct Step {
1623 pub direction: Direction,
1624 pub edge: Option<Label>,
1625 pub node: Option<Label>,
1626}
1627
1628#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1629pub enum Direction {
1630 Out,
1631 In,
1632 Both,
1633}
1634
1635#[derive(Clone, Debug, Default, PartialEq)]
1636pub struct EdgeQuery {
1637 pub from: Option<NodeId>,
1638 pub to: Option<NodeId>,
1639 pub label: Option<Label>,
1640}
1641
1642#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
1644pub enum PutOutcome {
1645 Inserted,
1647 Updated,
1650 Upserted,
1653 Deduped,
1655}
1656
1657impl PutOutcome {
1658 pub fn written(self) -> bool {
1660 !matches!(self, Self::Deduped)
1661 }
1662}
1663
1664#[derive(Clone, Debug, Default, Eq, PartialEq)]
1667pub struct LoadReport {
1668 pub nodes: usize,
1669 pub edges: usize,
1670}
1671
1672#[async_trait]
1673pub trait GraphStore: Send + Sync {
1674 async fn apply_schema(&self, _schema: &GraphSchema) -> Result<()> {
1675 Ok(())
1676 }
1677
1678 async fn put_node(&self, node: &Node) -> Result<PutOutcome>;
1679 async fn put_edge(&self, edge: &Edge) -> Result<PutOutcome>;
1680
1681 async fn put_graph(&self, graph: &Graph) -> Result<LoadReport> {
1682 let mut report = LoadReport::default();
1683 for node in &graph.nodes {
1684 if self.put_node(node).await?.written() {
1685 report.nodes += 1;
1686 }
1687 }
1688 for edge in &graph.edges {
1689 if self.put_edge(edge).await?.written() {
1690 report.edges += 1;
1691 }
1692 }
1693 Ok(report)
1694 }
1695
1696 async fn put_typed_graph(&self, schema: &GraphSchema, graph: &Graph) -> Result<LoadReport> {
1697 schema.validate_graph(graph)?;
1698 self.apply_schema(schema).await?;
1699 self.put_graph(graph).await
1700 }
1701
1702 async fn get_node(&self, id: &NodeId) -> Result<Option<Node>>;
1703 async fn get_edges(&self, query: EdgeQuery) -> Result<Vec<Edge>>;
1704 async fn traverse(&self, traversal: Traversal) -> Result<Vec<Node>>;
1705}
1706
1707#[async_trait]
1708pub trait GraphAdminStore: GraphStore {
1709 async fn bootstrap(&self) -> Result<()> {
1710 Ok(())
1711 }
1712
1713 async fn clear(&self) -> Result<()>;
1714}
1715
1716#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1718pub enum GraphMutation {
1719 UpsertNode(Node),
1720 DeleteNode(NodeId),
1721 UpsertEdge(Edge),
1722 DeleteEdge {
1723 id: Option<EdgeId>,
1724 from: NodeId,
1725 label: Label,
1726 to: NodeId,
1727 },
1728}
1729
1730#[async_trait]
1735pub trait GraphMutationStore: GraphStore {
1736 async fn delete_node(&self, id: &NodeId) -> Result<()>;
1738
1739 async fn delete_edge(&self, from: &NodeId, label: &Label, to: &NodeId) -> Result<()>;
1741
1742 async fn apply_mutations(&self, mutations: &[GraphMutation]) -> Result<()> {
1744 for mutation in mutations {
1745 match mutation {
1746 GraphMutation::UpsertNode(node) => {
1747 self.put_node(node).await?;
1748 }
1749 GraphMutation::DeleteNode(id) => self.delete_node(id).await?,
1750 GraphMutation::UpsertEdge(edge) => {
1751 self.put_edge(edge).await?;
1752 }
1753 GraphMutation::DeleteEdge {
1754 from, label, to, ..
1755 } => self.delete_edge(from, label, to).await?,
1756 }
1757 }
1758 Ok(())
1759 }
1760}
1761
1762pub mod prelude {
1763 pub use crate::{
1764 Direction, Edge, EdgeId, EdgePolicy, EdgeQuery, EdgeType, EdgeUniqueness, Field, FieldType,
1765 Graph, GraphAdminStore, GraphBuilder, GraphMutation, GraphMutationStore, GraphSchema,
1766 GraphSchemaBuilder, GraphStore, GrustError, Label, LoadReport, Node, NodeId, NodeType,
1767 Props, PutOutcome, Result, Start, Step, Traversal, Value,
1768 };
1769
1770 #[cfg(feature = "typed-garde")]
1771 pub use crate::typed::{TypedEdge, TypedGraphBuilder, TypedNode, garde, props_from_serialize};
1772
1773 #[cfg(feature = "typed-zod-rs")]
1774 pub use crate::typed::{parse_typed_json, parse_typed_json_with, zod_rs};
1775}
1776
1777#[cfg(test)]
1778mod tests;