1use std::collections::{BTreeMap, BTreeSet};
12use std::marker::PhantomData;
13
14use ratio_graph::{EdgeStore, NodeStore};
15use serde_json::Value;
16use snafu::prelude::*;
17use uuid::Uuid;
18
19#[derive(Clone, Debug, Snafu)]
20pub enum Error {
21 FaultyGraph { source: ratio_graph::Error },
23}
24
25#[cfg(feature = "serde")]
26fn is_default<T: Default + PartialEq>(t: &T) -> bool {
27 t == &T::default()
28}
29
30#[cfg(feature = "serde")]
31fn is_empty_vec<T>(t: &[T]) -> bool {
32 t.is_empty()
33}
34
35#[cfg(feature = "serde")]
36fn is_empty_set<T>(t: &BTreeSet<T>) -> bool {
37 t.is_empty()
38}
39
40#[cfg(feature = "serde")]
41fn is_empty_map<K, V>(t: &BTreeMap<K, V>) -> bool {
42 t.is_empty()
43}
44
45#[derive(Clone, Debug, PartialEq)]
46#[cfg_attr(
47 feature = "serde",
48 derive(serde::Serialize, serde::Deserialize),
49 serde(rename_all = "camelCase")
50)]
51#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
52#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
53pub enum V1 {
54 Graph(Box<Graph>),
55 Session(Box<Session>),
56 Workspace(Box<Workspace>),
57}
58impl From<Graph> for V1 {
59 fn from(val: Graph) -> Self {
60 V1::Graph(Box::new(val))
61 }
62}
63impl From<Session> for V1 {
64 fn from(val: Session) -> Self {
65 V1::Session(Box::new(val))
66 }
67}
68impl From<Workspace> for V1 {
69 fn from(val: Workspace) -> Self {
70 V1::Workspace(Box::new(val))
71 }
72}
73
74#[derive(Clone, Debug, PartialEq)]
75#[cfg_attr(
76 feature = "serde",
77 derive(serde::Serialize, serde::Deserialize),
78 serde(default, rename_all = "camelCase")
79)]
80#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
81pub struct Metadata<T> {
82 #[cfg_attr(feature = "serde", serde(flatten))]
83 pub id: Id<T>,
84 pub name: String,
85 pub kind: String,
86 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_set"))]
87 pub labels: BTreeSet<String>,
88 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_map"))]
89 pub weights: BTreeMap<String, f64>,
90 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_map"))]
91 pub annotations: BTreeMap<String, Value>,
92}
93impl<T> Default for Metadata<T> {
94 fn default() -> Self {
95 Self {
96 id: Id::default(),
97 name: "default".to_string(),
98 kind: "default".to_string(),
99 labels: BTreeSet::<String>::default(),
100 weights: BTreeMap::<String, f64>::default(),
101 annotations: BTreeMap::<String, Value>::default(),
102 }
103 }
104}
105impl<T> From<Metadata<T>> for ratio_graph::Metadata {
106 fn from(val: Metadata<T>) -> Self {
107 ratio_graph::Metadata::new(
108 Some(val.id.id),
109 Some(val.name),
110 Some(val.kind),
111 Some(val.labels),
112 Some(val.weights),
113 Some(val.annotations),
114 )
115 }
116}
117impl<T> From<ratio_graph::Metadata> for Metadata<T> {
118 fn from(value: ratio_graph::Metadata) -> Self {
119 Self {
120 id: Id {
121 id: *value.id(),
122 ..Default::default()
123 },
124 name: value.name,
125 kind: value.kind,
126 labels: value.labels,
127 weights: value.weights,
128 annotations: value.annotations,
129 }
130 }
131}
132
133#[derive(Clone, Debug, Default, PartialEq)]
134#[cfg_attr(
135 feature = "serde",
136 derive(serde::Serialize, serde::Deserialize),
137 serde(default, rename_all = "camelCase")
138)]
139#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
140#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
141pub struct Node {
142 #[cfg_attr(feature = "serde", serde(flatten))]
143 pub metadata: Metadata<Node>,
144 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
145 pub parent: Option<Id<Node>>,
146 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
147 pub children: Vec<Id<Node>>,
148 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
149 pub is_bus: bool,
150}
151impl From<Node> for ratio_graph::Node {
152 fn from(val: Node) -> Self {
153 let Node {
154 metadata,
155 parent,
156 children,
157 is_bus,
158 } = val;
159 ratio_graph::Node {
160 metadata: metadata.into(),
161 parent: parent.map(|p| p.id),
162 children: children.into_iter().map(|id| id.id).collect(),
163 is_bus,
164 }
165 }
166}
167impl From<ratio_graph::Node> for Node {
168 fn from(value: ratio_graph::Node) -> Self {
169 Self {
170 metadata: value.metadata.into(),
171 parent: value.parent.map(Id::from),
172 children: value.children.into_iter().map(Id::from).collect(),
173 is_bus: value.is_bus,
174 }
175 }
176}
177
178#[derive(Clone, Debug, Default, PartialEq)]
179#[cfg_attr(
180 feature = "serde",
181 derive(serde::Serialize, serde::Deserialize),
182 serde(default, rename_all = "camelCase")
183)]
184#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
185#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
186pub struct Edge {
187 #[cfg_attr(feature = "serde", serde(flatten))]
188 pub metadata: Metadata<Edge>,
189 pub source: Id<Node>,
190 pub target: Id<Node>,
191}
192impl From<Edge> for ratio_graph::Edge {
193 fn from(val: Edge) -> Self {
194 ratio_graph::Edge {
195 metadata: val.metadata.into(),
196 source: val.source.into(),
197 target: val.target.into(),
198 }
199 }
200}
201impl From<ratio_graph::Edge> for Edge {
202 fn from(value: ratio_graph::Edge) -> Self {
203 Self {
204 metadata: value.metadata.into(),
205 source: value.source.into(),
206 target: value.target.into(),
207 }
208 }
209}
210
211#[derive(Clone, Debug, Default, PartialEq)]
212#[cfg_attr(
213 feature = "serde",
214 derive(serde::Serialize, serde::Deserialize),
215 serde(default, rename_all = "camelCase")
216)]
217#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
218#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
219pub struct Graph {
220 #[cfg_attr(feature = "serde", serde(flatten))]
221 pub metadata: Metadata<Graph>,
222 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
223 pub nodes: Vec<Node>,
224 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
225 pub edges: Vec<Edge>,
226}
227impl TryInto<ratio_graph::Graph> for Graph {
228 type Error = Error;
229 fn try_into(self) -> Result<ratio_graph::Graph, Error> {
230 let metadata = self.metadata.into();
231 let nodes: Vec<ratio_graph::Node> = self.nodes.into_iter().map(Node::into).collect();
232 let edges: Vec<ratio_graph::Edge> = self.edges.into_iter().map(Edge::into).collect();
233 ratio_graph::Graph::new(Some(metadata), nodes, edges).context(FaultyGraphSnafu)
234 }
235}
236impl From<ratio_graph::Graph> for Graph {
237 fn from(value: ratio_graph::Graph) -> Self {
238 Self {
239 metadata: value.metadata.to_owned().into(),
240 nodes: value.all_nodes().map(|n| n.to_owned().into()).collect(),
241 edges: value.all_edges().map(|e| e.to_owned().into()).collect(),
242 }
243 }
244}
245
246#[derive(Clone, Debug, Default, PartialEq)]
247#[cfg_attr(
248 feature = "serde",
249 derive(serde::Serialize, serde::Deserialize),
250 serde(default, rename_all = "camelCase")
251)]
252#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
253#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
254pub struct NumericalBound {
255 pub value: f64,
256 pub inclusive: bool,
257}
258
259#[derive(Clone, Debug, Default, PartialEq)]
260#[cfg_attr(
261 feature = "serde",
262 derive(serde::Serialize, serde::Deserialize),
263 serde(default, rename_all = "camelCase")
264)]
265#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
266#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
267pub struct NumericalDomain {
268 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
269 pub lower: Option<NumericalBound>,
270 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
271 pub upper: Option<NumericalBound>,
272}
273
274#[derive(Clone, Debug, Default, PartialEq)]
275#[cfg_attr(
276 feature = "serde",
277 derive(serde::Serialize, serde::Deserialize),
278 serde(default, rename_all = "camelCase")
279)]
280#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
281#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
282pub struct CategoricalPalette {
283 pub colors: Vec<String>,
284 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_map"))]
285 pub by_name: BTreeMap<String, usize>,
286}
287
288#[derive(Clone, Debug, Default, PartialEq)]
289#[cfg_attr(
290 feature = "serde",
291 derive(serde::Serialize, serde::Deserialize),
292 serde(rename_all = "camelCase")
293)]
294#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
295#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
296pub enum Interpolation {
297 #[default]
298 Linear,
299 Logarithmic,
300}
301
302#[derive(Clone, Debug, Default, PartialEq)]
303#[cfg_attr(
304 feature = "serde",
305 derive(serde::Serialize, serde::Deserialize),
306 serde(default, rename_all = "camelCase")
307)]
308#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
309#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
310pub struct NumericalColorScale {
311 pub colors: Vec<(f64, String)>,
313}
314
315#[derive(Clone, Debug, Default, PartialEq)]
316#[cfg_attr(
317 feature = "serde",
318 derive(serde::Serialize, serde::Deserialize),
319 serde(default, rename_all = "camelCase")
320)]
321#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
322#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
323pub struct NumericalPalette {
324 pub scales: Vec<NumericalColorScale>,
325 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_map"))]
326 pub by_name: BTreeMap<String, usize>,
327}
328
329#[derive(Clone, Debug, Default, PartialEq)]
330#[cfg_attr(
331 feature = "serde",
332 derive(serde::Serialize, serde::Deserialize),
333 serde(rename_all = "camelCase")
334)]
335#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
336#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
337pub enum NodeSelectionMode {
338 Dependent,
339 #[default]
340 Independent,
341}
342
343#[derive(Clone, Debug, Default, PartialEq)]
344#[cfg_attr(
345 feature = "serde",
346 derive(serde::Serialize, serde::Deserialize),
347 serde(rename_all = "camelCase")
348)]
349#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
350#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
351pub enum EdgeDivisionMode {
352 Absolute,
353 #[default]
354 Equal,
355 Relative,
356}
357
358#[derive(Clone, Debug, Default, PartialEq)]
359#[cfg_attr(
360 feature = "serde",
361 derive(serde::Serialize, serde::Deserialize),
362 serde(rename_all = "camelCase")
363)]
364#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
365#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
366pub enum EdgeDisplayMode {
367 #[default]
368 Binary,
369 Kinds,
370 Labels,
371 Weights,
372}
373
374#[derive(Clone, Debug, Default, PartialEq)]
375#[cfg_attr(
376 feature = "serde",
377 derive(serde::Serialize, serde::Deserialize),
378 serde(default, rename_all = "camelCase")
379)]
380#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
381#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
382pub struct DisplayOptions {
383 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
384 pub edge_display_mode: EdgeDisplayMode,
385 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
386 pub edge_division_mode: EdgeDivisionMode,
387 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
388 pub edge_kinds: Vec<String>,
389 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
390 pub edge_labels: Vec<String>,
391 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
392 pub edge_weights: Vec<String>,
393}
394
395#[derive(Clone, Debug, Default, PartialEq)]
396#[cfg_attr(
397 feature = "serde",
398 derive(serde::Serialize, serde::Deserialize),
399 serde(default, rename_all = "camelCase")
400)]
401#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
402#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
403pub struct FilterOptions {
404 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
405 pub node_selection: NodeSelectionMode,
406 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
407 pub node_kinds: Vec<String>,
408 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
409 pub node_labels: Vec<String>,
410 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
411 pub edge_kinds: Vec<String>,
412 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
413 pub edge_labels: Vec<String>,
414}
415
416#[derive(Clone, Debug, Default, PartialEq)]
417#[cfg_attr(
418 feature = "serde",
419 derive(serde::Serialize, serde::Deserialize),
420 serde(rename_all = "camelCase")
421)]
422#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
423#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
424pub enum AnalysisOptions {
425 #[default]
426 None,
427}
428
429#[derive(Clone, Debug, Default, PartialEq)]
430#[cfg_attr(
431 feature = "serde",
432 derive(serde::Serialize, serde::Deserialize),
433 serde(default, rename_all = "camelCase")
434)]
435#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
436#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
437pub struct Tab {
438 #[cfg_attr(feature = "serde", serde(flatten))]
439 pub metadata: Metadata<Tab>,
440 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
441 pub graph: Option<Graph>,
442 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
443 pub display_options: DisplayOptions,
444 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
445 pub filter_options: FilterOptions,
446}
447
448#[derive(Clone, Debug, Default, PartialEq)]
449#[cfg_attr(
450 feature = "serde",
451 derive(serde::Serialize, serde::Deserialize),
452 serde(default, rename_all = "camelCase")
453)]
454#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
455#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
456pub struct Session {
457 #[cfg_attr(feature = "serde", serde(flatten))]
458 pub metadata: Metadata<Session>,
459 pub graph: Graph,
460 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
461 pub tabs: Vec<Tab>,
462 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_default"))]
463 pub active_tab: usize,
464 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
465 pub categorical_palette: Option<CategoricalPalette>,
466 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
467 pub numerical_palettes: Option<BTreeMap<String, NumericalPalette>>,
468}
469
470#[derive(Clone, Debug, Default, PartialEq)]
471#[cfg_attr(
472 feature = "serde",
473 derive(serde::Serialize, serde::Deserialize),
474 serde(default, rename_all = "camelCase")
475)]
476#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
477#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
478pub struct Workspace {
479 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty_vec"))]
480 pub sessions: Vec<Session>,
481 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
482 pub active_session: Option<Id<Session>>,
483}
484
485#[derive(Clone, Debug)]
488#[cfg_attr(
489 feature = "serde",
490 derive(serde::Serialize, serde::Deserialize),
491 serde(default, rename_all = "camelCase")
492)]
493#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
494pub struct Id<T> {
495 pub id: Uuid,
496 #[cfg_attr(feature = "serde", serde(skip))]
497 _phantom: PhantomData<T>,
498}
499impl<T> Id<T> {
500 pub fn nil() -> Self {
501 Self {
502 id: Uuid::nil(),
503 _phantom: PhantomData,
504 }
505 }
506}
507impl<T> Default for Id<T> {
508 fn default() -> Self {
509 Self {
510 id: Uuid::new_v4(),
511 _phantom: PhantomData,
512 }
513 }
514}
515impl<T> From<Id<T>> for Uuid {
516 fn from(val: Id<T>) -> Self {
517 val.id
518 }
519}
520impl<T> std::hash::Hash for Id<T> {
521 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
522 self.id.hash(state)
523 }
524}
525impl<T> PartialEq for Id<T> {
526 fn eq(&self, other: &Id<T>) -> bool {
527 self.id == other.id
528 }
529}
530impl<T> PartialOrd for Id<T> {
531 fn partial_cmp(&self, other: &Id<T>) -> Option<std::cmp::Ordering> {
532 Some(self.cmp(other))
533 }
534}
535impl<T> Eq for Id<T> {}
536impl<T> Ord for Id<T> {
537 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
538 self.id.cmp(&other.id)
539 }
540}
541impl<T> Id<T> {
542 pub fn new() -> Self {
544 Default::default()
545 }
546 pub fn imitate<T2>(&self) -> Id<T2> {
548 Id::<T2> {
549 id: self.id,
550 _phantom: PhantomData,
551 }
552 }
553}
554impl<T, U: std::borrow::Borrow<Uuid>> From<U> for Id<T> {
555 fn from(value: U) -> Self {
556 Self {
557 id: value.borrow().to_owned(),
558 _phantom: PhantomData,
559 }
560 }
561}
562impl<T> std::fmt::Display for Id<T> {
563 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
564 write!(f, "{}", self.id)
565 }
566}