Skip to main content

ratio_schema/
v1.rs

1//! # Canopy data schema V1
2//!
3//! ## License
4//!
5//! This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
6//! If a copy of the MPL was not distributed with this file,
7//! You can obtain one at <https://mozilla.org/MPL/2.0/>.
8//!
9//! **Code examples both in the docstrings and rendered documentation are free to use.**
10
11use 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    /// Failed to build graph object.
22    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    /// Vector of percentile [0.0, 1.0] and color strings. Colors are interpolated.
312    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/// A UUID key that knows what it's for.
486
487#[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    /// Create a new ID.
543    pub fn new() -> Self {
544        Default::default()
545    }
546    /// Imitate the ID of another type.
547    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}