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
1138#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1139pub struct NodeType {
1140 pub label: Label,
1141 pub fields: Vec<Field>,
1142}
1143
1144#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1145pub struct EdgeType {
1146 pub label: Label,
1147 pub from: Vec<Label>,
1148 pub to: Vec<Label>,
1149 pub fields: Vec<Field>,
1150 pub directed: bool,
1151 pub uniqueness: EdgeUniqueness,
1152}
1153
1154#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1155pub struct Field {
1156 pub name: String,
1157 pub ty: FieldType,
1158 pub required: bool,
1159}
1160
1161#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1162pub enum FieldType {
1163 String,
1164 Int,
1165 Float,
1166 Bool,
1167 DateTime,
1168 StringArray,
1169 Json,
1170}
1171
1172#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1173pub enum EdgeUniqueness {
1174 None,
1175 FromTo,
1176 FromLabelTo,
1177}
1178
1179#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1180pub struct Traversal {
1181 pub start: Start,
1182 pub steps: Vec<Step>,
1183 pub limit: Option<u32>,
1184}
1185
1186impl Traversal {
1187 pub fn from_node(id: impl Into<NodeId>) -> Self {
1188 Self {
1189 start: Start::Node(id.into()),
1190 steps: Vec::new(),
1191 limit: None,
1192 }
1193 }
1194
1195 pub fn out(mut self, edge: impl Into<Label>) -> Self {
1196 self.steps.push(Step {
1197 direction: Direction::Out,
1198 edge: Some(edge.into()),
1199 node: None,
1200 });
1201 self
1202 }
1203
1204 pub fn in_(mut self, edge: impl Into<Label>) -> Self {
1205 self.steps.push(Step {
1206 direction: Direction::In,
1207 edge: Some(edge.into()),
1208 node: None,
1209 });
1210 self
1211 }
1212
1213 pub fn both(mut self, edge: impl Into<Label>) -> Self {
1214 self.steps.push(Step {
1215 direction: Direction::Both,
1216 edge: Some(edge.into()),
1217 node: None,
1218 });
1219 self
1220 }
1221
1222 pub fn to(mut self, node: impl Into<Label>) -> Self {
1223 if let Some(step) = self.steps.last_mut() {
1224 step.node = Some(node.into());
1225 }
1226 self
1227 }
1228
1229 pub fn limit(mut self, limit: u32) -> Self {
1230 self.limit = Some(limit);
1231 self
1232 }
1233}
1234
1235#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1236pub enum Start {
1237 Node(NodeId),
1238 NodesByLabel(Label),
1239 NodesByProperty {
1240 label: Label,
1241 key: String,
1242 value: Value,
1243 },
1244}
1245
1246#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1247pub struct Step {
1248 pub direction: Direction,
1249 pub edge: Option<Label>,
1250 pub node: Option<Label>,
1251}
1252
1253#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1254pub enum Direction {
1255 Out,
1256 In,
1257 Both,
1258}
1259
1260#[derive(Clone, Debug, Default, PartialEq)]
1261pub struct EdgeQuery {
1262 pub from: Option<NodeId>,
1263 pub to: Option<NodeId>,
1264 pub label: Option<Label>,
1265}
1266
1267#[derive(Clone, Debug, Default, Eq, PartialEq)]
1268pub struct LoadReport {
1269 pub nodes: usize,
1270 pub edges: usize,
1271}
1272
1273#[async_trait]
1274pub trait GraphStore: Send + Sync {
1275 async fn apply_schema(&self, _schema: &GraphSchema) -> Result<()> {
1276 Ok(())
1277 }
1278
1279 async fn put_node(&self, node: &Node) -> Result<NodeId>;
1280 async fn put_edge(&self, edge: &Edge) -> Result<Option<EdgeId>>;
1281
1282 async fn put_graph(&self, graph: &Graph) -> Result<LoadReport> {
1283 let mut report = LoadReport::default();
1284 for node in &graph.nodes {
1285 self.put_node(node).await?;
1286 report.nodes += 1;
1287 }
1288 for edge in &graph.edges {
1289 self.put_edge(edge).await?;
1290 report.edges += 1;
1291 }
1292 Ok(report)
1293 }
1294
1295 async fn get_node(&self, id: &NodeId) -> Result<Option<Node>>;
1296 async fn get_edges(&self, query: EdgeQuery) -> Result<Vec<Edge>>;
1297 async fn traverse(&self, traversal: Traversal) -> Result<Vec<Node>>;
1298}
1299
1300#[async_trait]
1301pub trait GraphAdminStore: GraphStore {
1302 async fn bootstrap(&self) -> Result<()> {
1303 Ok(())
1304 }
1305
1306 async fn clear(&self) -> Result<()>;
1307}
1308
1309pub mod prelude {
1310 pub use crate::{
1311 Direction, Edge, EdgeId, EdgePolicy, EdgeQuery, EdgeType, Field, FieldType, Graph,
1312 GraphAdminStore, GraphBuilder, GraphSchema, GraphStore, GrustError, Label, LoadReport,
1313 Node, NodeId, NodeType, Props, Result, Start, Step, Traversal, Value,
1314 };
1315
1316 #[cfg(feature = "typed-garde")]
1317 pub use crate::typed::{TypedEdge, TypedGraphBuilder, TypedNode, garde, props_from_serialize};
1318
1319 #[cfg(feature = "typed-zod-rs")]
1320 pub use crate::typed::{parse_typed_json, parse_typed_json_with, zod_rs};
1321}
1322
1323#[cfg(test)]
1324mod tests;