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
678pub fn relationship_type(value: &str) -> String {
683 let relationship = value
684 .chars()
685 .map(|ch| {
686 if ch.is_ascii_alphanumeric() {
687 ch.to_ascii_uppercase()
688 } else {
689 '_'
690 }
691 })
692 .collect::<String>();
693 if relationship.is_empty() {
694 "RELATED_TO".to_string()
695 } else {
696 relationship
697 }
698}
699
700pub fn schema_identifier(value: &str) -> Result<String> {
705 let identifier = value
706 .chars()
707 .map(|ch| {
708 if ch.is_ascii_alphanumeric() {
709 ch.to_ascii_lowercase()
710 } else {
711 '_'
712 }
713 })
714 .collect::<String>();
715 if identifier.is_empty()
716 || identifier
717 .chars()
718 .next()
719 .is_some_and(|ch| ch.is_ascii_digit())
720 {
721 return Err(GrustError::Schema(format!(
722 "invalid schema identifier '{value}'"
723 )));
724 }
725 Ok(identifier)
726}
727
728pub fn edge_key(edge: &Edge) -> String {
734 edge.id
735 .as_ref()
736 .map(EdgeId::as_str)
737 .map(ToString::to_string)
738 .unwrap_or_else(|| {
739 format!(
740 "{}\u{1f}{}\u{1f}{}",
741 edge.from.as_str(),
742 edge.label.as_str(),
743 edge.to.as_str()
744 )
745 })
746}
747
748#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
749pub struct Graph {
750 pub nodes: Vec<Node>,
751 pub edges: Vec<Edge>,
752}
753
754impl Graph {
755 pub fn new(nodes: Vec<Node>, edges: Vec<Edge>) -> Self {
756 Self { nodes, edges }
757 }
758
759 pub fn from_yaml(yaml: &str) -> Result<Self> {
760 yaml::graph_from_yaml(yaml)
761 }
762
763 pub fn to_yaml(&self) -> Result<String> {
764 yaml::graph_to_yaml(self)
765 }
766
767 pub fn from_json(json: &str) -> Result<Self> {
768 json::graph_from_json(json)
769 }
770
771 pub fn to_json(&self) -> Result<String> {
772 json::graph_to_json(self)
773 }
774
775 pub fn from_xml(xml: &str) -> Result<Self> {
776 xml::graph_from_xml(xml)
777 }
778
779 pub fn to_xml(&self) -> Result<String> {
780 xml::graph_to_xml(self)
781 }
782
783 pub fn builder() -> GraphBuilder {
784 GraphBuilder::new()
785 }
786}
787
788mod graph_doc {
789 use std::collections::{BTreeMap, BTreeSet};
790
791 use serde::{Deserialize, Serialize};
792
793 use crate::{Edge, EdgeId, Graph, GrustError, Label, Node, NodeId, Props, Value};
794
795 #[derive(Debug, Serialize, Deserialize)]
796 pub(super) struct GraphDoc {
797 #[serde(default)]
798 pub(super) nodes: Vec<NodeDoc>,
799 #[serde(default)]
800 pub(super) edges: Vec<EdgeDoc>,
801 }
802
803 #[derive(Debug, Serialize, Deserialize)]
804 pub(super) struct NodeDoc {
805 pub(super) id: NodeId,
806 pub(super) label: Label,
807 #[serde(default, deserialize_with = "deserialize_props")]
808 pub(super) props: Props,
809 }
810
811 #[derive(Debug, Serialize, Deserialize)]
812 pub(super) struct NodeDocOut {
813 pub(super) id: NodeId,
814 pub(super) label: Label,
815 #[serde(default)]
816 pub(super) props: Props,
817 }
818
819 #[derive(Debug, Serialize, Deserialize)]
820 pub(super) struct EdgeDoc {
821 #[serde(default)]
822 pub(super) id: Option<EdgeId>,
823 pub(super) label: Label,
824 pub(super) from: NodeId,
825 pub(super) to: NodeId,
826 #[serde(default, deserialize_with = "deserialize_props")]
827 pub(super) props: Props,
828 }
829
830 #[derive(Debug, Serialize, Deserialize)]
831 pub(super) struct EdgeDocOut {
832 #[serde(default)]
833 pub(super) id: Option<EdgeId>,
834 pub(super) label: Label,
835 pub(super) from: NodeId,
836 pub(super) to: NodeId,
837 #[serde(default)]
838 pub(super) props: Props,
839 }
840
841 pub(super) fn graph_from_doc(doc: GraphDoc) -> super::Result<Graph> {
842 let mut ids = BTreeSet::new();
843 for node in &doc.nodes {
844 if !ids.insert(node.id.clone()) {
845 return Err(GrustError::Schema(format!(
846 "duplicate node id '{}'",
847 node.id
848 )));
849 }
850 }
851
852 let mut edges = Vec::with_capacity(doc.edges.len());
853 for edge in doc.edges {
854 if !ids.contains(&edge.from) {
855 return Err(GrustError::Schema(format!(
856 "edge '{}' references unknown from node '{}'",
857 edge.label, edge.from
858 )));
859 }
860 if !ids.contains(&edge.to) {
861 return Err(GrustError::Schema(format!(
862 "edge '{}' references unknown to node '{}'",
863 edge.label, edge.to
864 )));
865 }
866
867 let mut graph_edge = Edge::new(edge.label, edge.from, edge.to, edge.props);
868 graph_edge.id = edge.id;
869 edges.push(graph_edge);
870 }
871
872 let nodes = doc
873 .nodes
874 .into_iter()
875 .map(|node| Node::new(node.label, node.id, node.props))
876 .collect();
877
878 Ok(Graph::new(nodes, edges))
879 }
880
881 pub(super) fn graph_to_doc(graph: &Graph) -> GraphDocOut {
882 GraphDocOut {
883 nodes: graph
884 .nodes
885 .iter()
886 .map(|node| NodeDocOut {
887 id: node.id.clone(),
888 label: node.label.clone(),
889 props: without_generated_id(&node.props, &node.id),
890 })
891 .collect(),
892 edges: graph
893 .edges
894 .iter()
895 .map(|edge| EdgeDocOut {
896 id: edge.id.clone(),
897 label: edge.label.clone(),
898 from: edge.from.clone(),
899 to: edge.to.clone(),
900 props: edge.props.clone(),
901 })
902 .collect(),
903 }
904 }
905
906 fn without_generated_id(props: &Props, id: &NodeId) -> Props {
907 let mut props = props.clone();
908 if props.get("id") == Some(&Value::from(id.as_str())) {
909 props.remove("id");
910 }
911 props
912 }
913
914 fn deserialize_props<'de, D>(deserializer: D) -> std::result::Result<Props, D::Error>
915 where
916 D: serde::Deserializer<'de>,
917 {
918 let raw = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
919 raw.into_iter()
920 .map(|(key, value)| {
921 value_from_json(value)
922 .map(|value| (key, value))
923 .map_err(serde::de::Error::custom)
924 })
925 .collect()
926 }
927
928 fn value_from_json(value: serde_json::Value) -> std::result::Result<Value, String> {
929 if let serde_json::Value::Object(mapping) = &value
930 && mapping.contains_key("type")
931 && mapping.contains_key("value")
932 {
933 return serde_json::from_value(value)
934 .map_err(|err| format!("invalid tagged Grust value: {err}"));
935 }
936
937 Ok(Value::from_json(value))
938 }
939
940 #[derive(Debug, Serialize, Deserialize)]
941 pub(super) struct GraphDocOut {
942 pub(super) nodes: Vec<NodeDocOut>,
943 pub(super) edges: Vec<EdgeDocOut>,
944 }
945}
946
947mod yaml {
948 use crate::{Graph, GrustError};
949
950 pub(super) fn graph_from_yaml(yaml: &str) -> super::Result<Graph> {
951 let doc: super::graph_doc::GraphDoc = serde_yaml::from_str(yaml)
952 .map_err(|err| GrustError::Serialization(format!("YAML parse error: {err}")))?;
953 super::graph_doc::graph_from_doc(doc)
954 }
955
956 pub(super) fn graph_to_yaml(graph: &Graph) -> super::Result<String> {
957 serde_yaml::to_string(&super::graph_doc::graph_to_doc(graph))
958 .map_err(|err| GrustError::Serialization(format!("YAML serialization error: {err}")))
959 }
960}
961
962mod json {
963 use crate::{Graph, GrustError};
964
965 pub(super) fn graph_from_json(json: &str) -> super::Result<Graph> {
966 let doc: super::graph_doc::GraphDoc = serde_json::from_str(json)
967 .map_err(|err| GrustError::Serialization(format!("JSON parse error: {err}")))?;
968 super::graph_doc::graph_from_doc(doc)
969 }
970
971 pub(super) fn graph_to_json(graph: &Graph) -> super::Result<String> {
972 serde_json::to_string_pretty(&super::graph_doc::graph_to_doc(graph))
973 .map_err(|err| GrustError::Serialization(format!("JSON serialization error: {err}")))
974 }
975}
976
977mod xml {
978 use serde::{Deserialize, Serialize};
979
980 use crate::{Edge, EdgeId, Graph, GrustError, Label, Node, NodeId, Props, Value};
981
982 #[derive(Debug, Serialize, Deserialize)]
983 #[serde(rename = "graph")]
984 struct GraphXml {
985 #[serde(default)]
986 nodes: NodesXml,
987 #[serde(default)]
988 edges: EdgesXml,
989 }
990
991 #[derive(Debug, Default, Serialize, Deserialize)]
992 struct NodesXml {
993 #[serde(rename = "node", default)]
994 items: Vec<NodeXml>,
995 }
996
997 #[derive(Debug, Default, Serialize, Deserialize)]
998 struct EdgesXml {
999 #[serde(rename = "edge", default)]
1000 items: Vec<EdgeXml>,
1001 }
1002
1003 #[derive(Debug, Serialize, Deserialize)]
1004 struct NodeXml {
1005 id: NodeId,
1006 label: Label,
1007 #[serde(default)]
1008 props: PropsXml,
1009 }
1010
1011 #[derive(Debug, Serialize, Deserialize)]
1012 struct EdgeXml {
1013 #[serde(default, skip_serializing_if = "Option::is_none")]
1014 id: Option<EdgeId>,
1015 label: Label,
1016 from: NodeId,
1017 to: NodeId,
1018 #[serde(default)]
1019 props: PropsXml,
1020 }
1021
1022 #[derive(Debug, Default, Serialize, Deserialize)]
1023 struct PropsXml {
1024 #[serde(rename = "prop", default)]
1025 items: Vec<PropXml>,
1026 }
1027
1028 #[derive(Debug, Serialize, Deserialize)]
1029 struct PropXml {
1030 key: String,
1031 value: Value,
1032 }
1033
1034 pub(super) fn graph_from_xml(xml: &str) -> super::Result<Graph> {
1035 let doc: GraphXml = quick_xml::de::from_str(xml)
1036 .map_err(|err| GrustError::Serialization(format!("XML parse error: {err}")))?;
1037 super::graph_doc::graph_from_doc(doc.into())
1038 }
1039
1040 pub(super) fn graph_to_xml(graph: &Graph) -> super::Result<String> {
1041 quick_xml::se::to_string(&GraphXml::from(graph))
1042 .map_err(|err| GrustError::Serialization(format!("XML serialization error: {err}")))
1043 }
1044
1045 impl From<GraphXml> for super::graph_doc::GraphDoc {
1046 fn from(value: GraphXml) -> Self {
1047 Self {
1048 nodes: value.nodes.items.into_iter().map(Into::into).collect(),
1049 edges: value.edges.items.into_iter().map(Into::into).collect(),
1050 }
1051 }
1052 }
1053
1054 impl From<NodeXml> for super::graph_doc::NodeDoc {
1055 fn from(value: NodeXml) -> Self {
1056 Self {
1057 id: value.id,
1058 label: value.label,
1059 props: value.props.into(),
1060 }
1061 }
1062 }
1063
1064 impl From<EdgeXml> for super::graph_doc::EdgeDoc {
1065 fn from(value: EdgeXml) -> Self {
1066 Self {
1067 id: value.id,
1068 label: value.label,
1069 from: value.from,
1070 to: value.to,
1071 props: value.props.into(),
1072 }
1073 }
1074 }
1075
1076 impl From<PropsXml> for Props {
1077 fn from(value: PropsXml) -> Self {
1078 value
1079 .items
1080 .into_iter()
1081 .map(|prop| (prop.key, prop.value))
1082 .collect()
1083 }
1084 }
1085
1086 impl From<&Graph> for GraphXml {
1087 fn from(graph: &Graph) -> Self {
1088 Self {
1089 nodes: NodesXml {
1090 items: graph.nodes.iter().map(NodeXml::from).collect(),
1091 },
1092 edges: EdgesXml {
1093 items: graph.edges.iter().map(EdgeXml::from).collect(),
1094 },
1095 }
1096 }
1097 }
1098
1099 impl From<&Node> for NodeXml {
1100 fn from(node: &Node) -> Self {
1101 let props = super::graph_doc::graph_to_doc(&Graph::new(vec![node.clone()], Vec::new()))
1102 .nodes
1103 .into_iter()
1104 .next()
1105 .expect("node exists")
1106 .props;
1107 Self {
1108 id: node.id.clone(),
1109 label: node.label.clone(),
1110 props: props.into(),
1111 }
1112 }
1113 }
1114
1115 impl From<&Edge> for EdgeXml {
1116 fn from(edge: &Edge) -> Self {
1117 Self {
1118 id: edge.id.clone(),
1119 label: edge.label.clone(),
1120 from: edge.from.clone(),
1121 to: edge.to.clone(),
1122 props: edge.props.clone().into(),
1123 }
1124 }
1125 }
1126
1127 impl From<Props> for PropsXml {
1128 fn from(value: Props) -> Self {
1129 Self {
1130 items: value
1131 .into_iter()
1132 .map(|(key, value)| PropXml { key, value })
1133 .collect(),
1134 }
1135 }
1136 }
1137}
1138
1139#[derive(Clone, Debug, Default, Eq, PartialEq)]
1140pub enum EdgePolicy {
1141 AllowDuplicates,
1142 #[default]
1143 DedupeByFromLabelTo,
1144}
1145
1146#[derive(Clone, Debug, Default)]
1147pub struct GraphBuilder {
1148 nodes: BTreeMap<NodeId, Node>,
1149 edges: Vec<Edge>,
1150 edge_keys: BTreeSet<(NodeId, Label, NodeId)>,
1151 edge_policy: EdgePolicy,
1152}
1153
1154impl GraphBuilder {
1155 pub fn new() -> Self {
1156 Self::default()
1157 }
1158
1159 pub fn edge_policy(mut self, edge_policy: EdgePolicy) -> Self {
1160 self.edge_policy = edge_policy;
1161 self
1162 }
1163
1164 pub fn node<'a>(
1165 &'a mut self,
1166 label: impl Into<Label>,
1167 id: impl Into<NodeId>,
1168 ) -> NodeBuilder<'a> {
1169 NodeBuilder {
1170 builder: self,
1171 label: label.into(),
1172 id: id.into(),
1173 props: Props::new(),
1174 }
1175 }
1176
1177 pub fn edge<'a>(
1178 &'a mut self,
1179 label: impl Into<Label>,
1180 from: impl Into<NodeId>,
1181 to: impl Into<NodeId>,
1182 ) -> EdgeBuilder<'a> {
1183 EdgeBuilder {
1184 builder: self,
1185 id: None,
1186 label: label.into(),
1187 from: from.into(),
1188 to: to.into(),
1189 props: Props::new(),
1190 }
1191 }
1192
1193 pub fn add_node(&mut self, node: Node) -> NodeId {
1200 let id = node.id.clone();
1201 self.nodes
1202 .entry(id.clone())
1203 .and_modify(|existing| {
1204 if existing.label == node.label {
1205 existing.props.extend(node.props.clone());
1206 } else {
1207 *existing = node.clone();
1208 }
1209 })
1210 .or_insert(node);
1211 id
1212 }
1213
1214 pub fn add_edge(&mut self, edge: Edge) -> PutOutcome {
1217 match self.edge_policy {
1218 EdgePolicy::AllowDuplicates => {
1219 self.edges.push(edge);
1220 PutOutcome::Inserted
1221 }
1222 EdgePolicy::DedupeByFromLabelTo => {
1223 let key = (edge.from.clone(), edge.label.clone(), edge.to.clone());
1224 if self.edge_keys.insert(key) {
1225 self.edges.push(edge);
1226 PutOutcome::Inserted
1227 } else {
1228 PutOutcome::Deduped
1229 }
1230 }
1231 }
1232 }
1233
1234 pub fn build(self) -> Graph {
1235 Graph {
1236 nodes: self.nodes.into_values().collect(),
1237 edges: self.edges,
1238 }
1239 }
1240}
1241
1242pub struct NodeBuilder<'a> {
1243 builder: &'a mut GraphBuilder,
1244 label: Label,
1245 id: NodeId,
1246 props: Props,
1247}
1248
1249impl<'a> NodeBuilder<'a> {
1250 pub fn prop(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1251 self.props.insert(key.into(), value.into());
1252 self
1253 }
1254
1255 pub fn props(mut self, props: Props) -> Self {
1256 self.props.extend(props);
1257 self
1258 }
1259
1260 pub fn finish(self) -> NodeId {
1261 let node = Node::new(self.label, self.id, self.props);
1262 self.builder.add_node(node)
1263 }
1264}
1265
1266pub struct EdgeBuilder<'a> {
1267 builder: &'a mut GraphBuilder,
1268 id: Option<EdgeId>,
1269 label: Label,
1270 from: NodeId,
1271 to: NodeId,
1272 props: Props,
1273}
1274
1275impl<'a> EdgeBuilder<'a> {
1276 pub fn id(mut self, id: impl Into<EdgeId>) -> Self {
1277 self.id = Some(id.into());
1278 self
1279 }
1280
1281 pub fn prop(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1282 self.props.insert(key.into(), value.into());
1283 self
1284 }
1285
1286 pub fn props(mut self, props: Props) -> Self {
1287 self.props.extend(props);
1288 self
1289 }
1290
1291 pub fn finish(self) -> PutOutcome {
1292 let mut edge = Edge::new(self.label, self.from, self.to, self.props);
1293 edge.id = self.id;
1294 self.builder.add_edge(edge)
1295 }
1296}
1297
1298#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1299pub struct GraphSchema {
1300 pub nodes: Vec<NodeType>,
1301 pub edges: Vec<EdgeType>,
1302}
1303
1304impl GraphSchema {
1305 pub fn builder() -> GraphSchemaBuilder {
1306 GraphSchemaBuilder::default()
1307 }
1308
1309 pub fn node_type(&self, label: &Label) -> Option<&NodeType> {
1310 self.nodes
1311 .iter()
1312 .find(|node_type| &node_type.label == label)
1313 }
1314
1315 pub fn edge_type(&self, label: &Label) -> Option<&EdgeType> {
1316 self.edges
1317 .iter()
1318 .find(|edge_type| &edge_type.label == label)
1319 }
1320
1321 pub fn validate_graph(&self, graph: &Graph) -> Result<()> {
1322 for node in &graph.nodes {
1323 self.validate_node(node)?;
1324 }
1325 let labels: BTreeMap<&NodeId, &Label> = graph
1326 .nodes
1327 .iter()
1328 .map(|node| (&node.id, &node.label))
1329 .collect();
1330 for edge in &graph.edges {
1331 self.validate_edge_with(edge, |id| labels.get(id).copied())?;
1332 }
1333 self.validate_edge_uniqueness(graph)
1334 }
1335
1336 fn validate_edge_uniqueness(&self, graph: &Graph) -> Result<()> {
1339 let mut seen = BTreeSet::new();
1340 for edge in &graph.edges {
1341 let Some(edge_type) = self.edge_type(&edge.label) else {
1342 continue;
1343 };
1344 if edge_type.uniqueness == EdgeUniqueness::None {
1345 continue;
1346 }
1347 let (a, b) = if edge_type.directed || edge.from <= edge.to {
1348 (&edge.from, &edge.to)
1349 } else {
1350 (&edge.to, &edge.from)
1351 };
1352 if !seen.insert((edge.label.clone(), a.clone(), b.clone())) {
1353 return Err(GrustError::Schema(format!(
1354 "duplicate edge '{}' between '{}' and '{}' violates {:?} uniqueness",
1355 edge.label.as_str(),
1356 a.as_str(),
1357 b.as_str(),
1358 edge_type.uniqueness
1359 )));
1360 }
1361 }
1362 Ok(())
1363 }
1364
1365 pub fn validate_node(&self, node: &Node) -> Result<()> {
1366 let node_type = self.node_type(&node.label).ok_or_else(|| {
1367 GrustError::Schema(format!("schema has no node type '{}'", node.label.as_str()))
1368 })?;
1369 validate_props(
1370 &node.props,
1371 &node_type.fields,
1372 &format!("node '{}'", node.id.as_str()),
1373 )
1374 }
1375
1376 pub fn validate_edge(&self, edge: &Edge, graph: &Graph) -> Result<()> {
1377 self.validate_edge_with(edge, |id| {
1378 graph
1379 .nodes
1380 .iter()
1381 .find(|node| &node.id == id)
1382 .map(|node| &node.label)
1383 })
1384 }
1385
1386 pub fn validate_edge_with<'a>(
1389 &self,
1390 edge: &Edge,
1391 lookup: impl Fn(&NodeId) -> Option<&'a Label>,
1392 ) -> Result<()> {
1393 let edge_type = self.edge_type(&edge.label).ok_or_else(|| {
1394 GrustError::Schema(format!("schema has no edge type '{}'", edge.label.as_str()))
1395 })?;
1396
1397 let from_label = lookup(&edge.from).ok_or_else(|| {
1398 GrustError::Schema(format!(
1399 "edge '{}' references unknown from node '{}'",
1400 edge.label.as_str(),
1401 edge.from.as_str()
1402 ))
1403 })?;
1404 let to_label = lookup(&edge.to).ok_or_else(|| {
1405 GrustError::Schema(format!(
1406 "edge '{}' references unknown to node '{}'",
1407 edge.label.as_str(),
1408 edge.to.as_str()
1409 ))
1410 })?;
1411
1412 let from_matches =
1413 |label: &Label| edge_type.from.is_empty() || edge_type.from.contains(label);
1414 let to_matches = |label: &Label| edge_type.to.is_empty() || edge_type.to.contains(label);
1415 let endpoints_ok = (from_matches(from_label) && to_matches(to_label))
1418 || (!edge_type.directed && from_matches(to_label) && to_matches(from_label));
1419 if !endpoints_ok {
1420 if !from_matches(from_label) {
1421 return Err(GrustError::Schema(format!(
1422 "edge '{}' cannot start from node label '{}'",
1423 edge.label.as_str(),
1424 from_label.as_str()
1425 )));
1426 }
1427 return Err(GrustError::Schema(format!(
1428 "edge '{}' cannot end at node label '{}'",
1429 edge.label.as_str(),
1430 to_label.as_str()
1431 )));
1432 }
1433
1434 validate_props(
1435 &edge.props,
1436 &edge_type.fields,
1437 &format!("edge '{}'", edge.label.as_str()),
1438 )
1439 }
1440
1441 pub fn validate_edge_props(&self, edge: &Edge) -> Result<()> {
1445 let edge_type = self.edge_type(&edge.label).ok_or_else(|| {
1446 GrustError::Schema(format!("schema has no edge type '{}'", edge.label.as_str()))
1447 })?;
1448 validate_props(
1449 &edge.props,
1450 &edge_type.fields,
1451 &format!("edge '{}'", edge.label.as_str()),
1452 )
1453 }
1454}
1455
1456#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1457pub struct NodeType {
1458 pub label: Label,
1459 pub fields: Vec<Field>,
1460}
1461
1462#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1463pub struct EdgeType {
1464 pub label: Label,
1465 pub from: Vec<Label>,
1466 pub to: Vec<Label>,
1467 pub fields: Vec<Field>,
1468 pub directed: bool,
1469 pub uniqueness: EdgeUniqueness,
1470}
1471
1472#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1473pub struct Field {
1474 pub name: String,
1475 pub ty: FieldType,
1476 pub required: bool,
1477}
1478
1479#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1480pub enum FieldType {
1481 String,
1482 Int,
1483 Float,
1484 Bool,
1485 DateTime,
1486 StringArray,
1487 IntArray,
1488 FloatArray,
1489 Json,
1490}
1491
1492#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1500pub enum EdgeUniqueness {
1501 None,
1502 FromTo,
1503 FromLabelTo,
1504}
1505
1506#[derive(Clone, Debug, Default)]
1507pub struct GraphSchemaBuilder {
1508 nodes: Vec<NodeType>,
1509 edges: Vec<EdgeType>,
1510}
1511
1512impl GraphSchemaBuilder {
1513 pub fn node(mut self, label: impl Into<Label>, fields: impl Into<Vec<Field>>) -> Self {
1514 self.nodes.push(NodeType {
1515 label: label.into(),
1516 fields: fields.into(),
1517 });
1518 self
1519 }
1520
1521 pub fn edge(
1522 mut self,
1523 label: impl Into<Label>,
1524 from: impl Into<Vec<Label>>,
1525 to: impl Into<Vec<Label>>,
1526 fields: impl Into<Vec<Field>>,
1527 ) -> Self {
1528 self.edges.push(EdgeType {
1529 label: label.into(),
1530 from: from.into(),
1531 to: to.into(),
1532 fields: fields.into(),
1533 directed: true,
1534 uniqueness: EdgeUniqueness::FromLabelTo,
1535 });
1536 self
1537 }
1538
1539 pub fn edge_type(mut self, edge_type: EdgeType) -> Self {
1540 self.edges.push(edge_type);
1541 self
1542 }
1543
1544 pub fn build(self) -> GraphSchema {
1545 GraphSchema {
1546 nodes: self.nodes,
1547 edges: self.edges,
1548 }
1549 }
1550}
1551
1552impl Field {
1553 pub fn required(name: impl Into<String>, ty: FieldType) -> Self {
1554 Self {
1555 name: name.into(),
1556 ty,
1557 required: true,
1558 }
1559 }
1560
1561 pub fn optional(name: impl Into<String>, ty: FieldType) -> Self {
1562 Self {
1563 name: name.into(),
1564 ty,
1565 required: false,
1566 }
1567 }
1568}
1569
1570fn validate_props(props: &Props, fields: &[Field], context: &str) -> Result<()> {
1571 for field in fields {
1572 match props.get(&field.name) {
1573 Some(value) => validate_field_value(value, &field.ty, context, &field.name)?,
1574 None if field.required => {
1575 return Err(GrustError::Schema(format!(
1576 "{context} missing required field '{}'",
1577 field.name
1578 )));
1579 }
1580 None => {}
1581 }
1582 }
1583 Ok(())
1584}
1585
1586fn validate_field_value(
1587 value: &Value,
1588 ty: &FieldType,
1589 context: &str,
1590 field_name: &str,
1591) -> Result<()> {
1592 let matches = match (value, ty) {
1593 (Value::String(_), FieldType::String)
1594 | (Value::Int(_), FieldType::Int)
1595 | (Value::Float(_), FieldType::Float)
1596 | (Value::Bool(_), FieldType::Bool)
1597 | (Value::DateTime(_), FieldType::DateTime)
1598 | (Value::StringArray(_), FieldType::StringArray)
1599 | (Value::IntArray(_), FieldType::IntArray)
1600 | (Value::FloatArray(_), FieldType::FloatArray)
1601 | (_, FieldType::Json) => true,
1602 (Value::String(value), FieldType::DateTime) => is_rfc3339_datetime(value),
1605 _ => false,
1606 };
1607 if matches {
1608 Ok(())
1609 } else {
1610 Err(GrustError::Schema(format!(
1611 "{context} field '{field_name}' expected {ty:?}, got {value:?}"
1612 )))
1613 }
1614}
1615
1616#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1617pub struct Traversal {
1618 pub start: Start,
1619 pub steps: Vec<Step>,
1620 pub limit: Option<u32>,
1621}
1622
1623impl Traversal {
1624 pub fn from_node(id: impl Into<NodeId>) -> Self {
1625 Self {
1626 start: Start::Node(id.into()),
1627 steps: Vec::new(),
1628 limit: None,
1629 }
1630 }
1631
1632 pub fn out(mut self, edge: impl Into<Label>) -> Self {
1633 self.steps.push(Step {
1634 direction: Direction::Out,
1635 edge: Some(edge.into()),
1636 node: None,
1637 });
1638 self
1639 }
1640
1641 pub fn in_(mut self, edge: impl Into<Label>) -> Self {
1642 self.steps.push(Step {
1643 direction: Direction::In,
1644 edge: Some(edge.into()),
1645 node: None,
1646 });
1647 self
1648 }
1649
1650 pub fn both(mut self, edge: impl Into<Label>) -> Self {
1651 self.steps.push(Step {
1652 direction: Direction::Both,
1653 edge: Some(edge.into()),
1654 node: None,
1655 });
1656 self
1657 }
1658
1659 pub fn to(mut self, node: impl Into<Label>) -> Self {
1666 let step = self
1667 .steps
1668 .last_mut()
1669 .expect("Traversal::to() must follow out(), in_(), or both()");
1670 step.node = Some(node.into());
1671 self
1672 }
1673
1674 pub fn limit(mut self, limit: u32) -> Self {
1675 self.limit = Some(limit);
1676 self
1677 }
1678}
1679
1680#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1681pub enum Start {
1682 Node(NodeId),
1683 NodesByLabel(Label),
1684 NodesByProperty {
1685 label: Label,
1686 key: String,
1687 value: Value,
1688 },
1689}
1690
1691#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1692pub struct Step {
1693 pub direction: Direction,
1694 pub edge: Option<Label>,
1695 pub node: Option<Label>,
1696}
1697
1698#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1699pub enum Direction {
1700 Out,
1701 In,
1702 Both,
1703}
1704
1705#[derive(Clone, Debug, Default, PartialEq)]
1706pub struct EdgeQuery {
1707 pub from: Option<NodeId>,
1708 pub to: Option<NodeId>,
1709 pub label: Option<Label>,
1710}
1711
1712#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
1714pub enum PutOutcome {
1715 Inserted,
1717 Updated,
1720 Upserted,
1723 Deduped,
1725}
1726
1727impl PutOutcome {
1728 pub fn written(self) -> bool {
1730 !matches!(self, Self::Deduped)
1731 }
1732}
1733
1734#[derive(Clone, Debug, Default, Eq, PartialEq)]
1737pub struct LoadReport {
1738 pub nodes: usize,
1739 pub edges: usize,
1740}
1741
1742#[async_trait]
1743pub trait GraphStore: Send + Sync {
1744 async fn apply_schema(&self, _schema: &GraphSchema) -> Result<()> {
1745 Ok(())
1746 }
1747
1748 async fn put_node(&self, node: &Node) -> Result<PutOutcome>;
1749 async fn put_edge(&self, edge: &Edge) -> Result<PutOutcome>;
1750
1751 async fn put_graph(&self, graph: &Graph) -> Result<LoadReport> {
1752 let mut report = LoadReport::default();
1753 for node in &graph.nodes {
1754 if self.put_node(node).await?.written() {
1755 report.nodes += 1;
1756 }
1757 }
1758 for edge in &graph.edges {
1759 if self.put_edge(edge).await?.written() {
1760 report.edges += 1;
1761 }
1762 }
1763 Ok(report)
1764 }
1765
1766 async fn put_typed_graph(&self, schema: &GraphSchema, graph: &Graph) -> Result<LoadReport> {
1767 schema.validate_graph(graph)?;
1768 self.apply_schema(schema).await?;
1769 self.put_graph(graph).await
1770 }
1771
1772 async fn get_node(&self, id: &NodeId) -> Result<Option<Node>>;
1773
1774 async fn get_nodes(&self, ids: &[NodeId]) -> Result<Vec<Node>> {
1781 let mut nodes = Vec::new();
1782 for id in ids {
1783 if let Some(node) = self.get_node(id).await? {
1784 nodes.push(node);
1785 }
1786 }
1787 Ok(nodes)
1788 }
1789
1790 async fn get_edges(&self, query: EdgeQuery) -> Result<Vec<Edge>>;
1791 async fn traverse(&self, traversal: Traversal) -> Result<Vec<Node>>;
1792}
1793
1794#[async_trait]
1795pub trait GraphAdminStore: GraphStore {
1796 async fn bootstrap(&self) -> Result<()> {
1797 Ok(())
1798 }
1799
1800 async fn clear(&self) -> Result<()>;
1801}
1802
1803#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1805pub enum GraphMutation {
1806 UpsertNode(Node),
1807 DeleteNode(NodeId),
1808 UpsertEdge(Edge),
1809 DeleteEdge {
1810 id: Option<EdgeId>,
1811 from: NodeId,
1812 label: Label,
1813 to: NodeId,
1814 },
1815}
1816
1817#[async_trait]
1822pub trait GraphMutationStore: GraphStore {
1823 async fn delete_node(&self, id: &NodeId) -> Result<()>;
1825
1826 async fn delete_edge(&self, from: &NodeId, label: &Label, to: &NodeId) -> Result<()>;
1828
1829 async fn apply_mutations(&self, mutations: &[GraphMutation]) -> Result<()> {
1836 for mutation in mutations {
1837 match mutation {
1838 GraphMutation::UpsertNode(node) => {
1839 self.put_node(node).await?;
1840 }
1841 GraphMutation::DeleteNode(id) => self.delete_node(id).await?,
1842 GraphMutation::UpsertEdge(edge) => {
1843 self.put_edge(edge).await?;
1844 }
1845 GraphMutation::DeleteEdge {
1846 from, label, to, ..
1847 } => self.delete_edge(from, label, to).await?,
1848 }
1849 }
1850 Ok(())
1851 }
1852}
1853
1854pub mod prelude {
1855 pub use crate::{
1856 Direction, Edge, EdgeId, EdgePolicy, EdgeQuery, EdgeType, EdgeUniqueness, Field, FieldType,
1857 Graph, GraphAdminStore, GraphBuilder, GraphMutation, GraphMutationStore, GraphSchema,
1858 GraphSchemaBuilder, GraphStore, GrustError, Label, LoadReport, Node, NodeId, NodeType,
1859 Props, PutOutcome, Result, Start, Step, Traversal, Value, edge_key, relationship_type,
1860 schema_identifier,
1861 };
1862
1863 #[cfg(feature = "typed-garde")]
1864 pub use crate::typed::{TypedEdge, TypedGraphBuilder, TypedNode, garde, props_from_serialize};
1865
1866 #[cfg(feature = "typed-zod-rs")]
1867 pub use crate::typed::{parse_typed_json, parse_typed_json_with, zod_rs};
1868}
1869
1870#[cfg(test)]
1871mod tests;