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