Skip to main content

activecube_rs/cube/
definition.rs

1#[derive(Debug, Clone, PartialEq, Eq)]
2pub enum DimType {
3    String,
4    Int,
5    Float,
6    DateTime,
7    Bool,
8}
9
10#[derive(Debug, Clone)]
11pub struct Dimension {
12    pub graphql_name: String,
13    pub column: String,
14    pub dim_type: DimType,
15}
16
17#[derive(Debug, Clone)]
18pub enum DimensionNode {
19    Leaf(Dimension),
20    Group {
21        graphql_name: String,
22        children: Vec<DimensionNode>,
23    },
24}
25
26/// A named selector defines a filterable field on a Cube.
27/// Each selector maps a GraphQL argument name to a column + type,
28/// enabling `eq`, `gt`, `in`, `any` etc.
29#[derive(Debug, Clone)]
30pub struct SelectorDef {
31    pub graphql_name: String,
32    pub column: String,
33    pub dim_type: DimType,
34}
35
36pub fn selector(graphql_name: &str, column: &str, dim_type: DimType) -> SelectorDef {
37    SelectorDef {
38        graphql_name: graphql_name.to_string(),
39        column: column.to_string(),
40        dim_type,
41    }
42}
43
44#[derive(Debug, Clone)]
45pub struct CubeDefinition {
46    pub name: String,
47    pub schema: String,
48    /// Table name pattern. Use `{chain}` as placeholder for chain-prefixed tables
49    /// (e.g. `{chain}_trades` → `sol_trades`). For tables without chain prefix
50    /// (e.g. `dex_pool_liquidities`), use the literal table name and set
51    /// `chain_column` instead.
52    pub table_pattern: String,
53    /// If set, the table doesn't use a `{chain}` prefix in its name. Instead,
54    /// the chain is filtered via `WHERE <chain_column> = ?`. Example:
55    /// `dex_pool_liquidities` has a `chain` column rather than `sol_dex_pool_liquidities`.
56    pub chain_column: Option<String>,
57    pub dimensions: Vec<DimensionNode>,
58    pub metrics: Vec<String>,
59    pub selectors: Vec<SelectorDef>,
60    pub default_filters: Vec<(String, String)>,
61    pub default_limit: u32,
62    pub max_limit: u32,
63}
64
65impl CubeDefinition {
66    pub fn table_for_chain(&self, chain: &str) -> String {
67        self.table_pattern.replace("{chain}", chain)
68    }
69
70    pub fn qualified_table(&self, chain: &str) -> String {
71        format!("{}.{}", self.schema, self.table_for_chain(chain))
72    }
73
74    pub fn flat_dimensions(&self) -> Vec<(String, Dimension)> {
75        let mut out = Vec::new();
76        for node in &self.dimensions {
77            collect_leaves(node, "", &mut out);
78        }
79        out
80    }
81}
82
83fn collect_leaves(node: &DimensionNode, prefix: &str, out: &mut Vec<(String, Dimension)>) {
84    match node {
85        DimensionNode::Leaf(dim) => {
86            let path = if prefix.is_empty() {
87                dim.graphql_name.clone()
88            } else {
89                format!("{}_{}", prefix, dim.graphql_name)
90            };
91            out.push((path, dim.clone()));
92        }
93        DimensionNode::Group { graphql_name, children } => {
94            let new_prefix = if prefix.is_empty() {
95                graphql_name.clone()
96            } else {
97                format!("{prefix}_{graphql_name}")
98            };
99            for child in children {
100                collect_leaves(child, &new_prefix, out);
101            }
102        }
103    }
104}
105
106// ---------------------------------------------------------------------------
107// CubeBuilder — ergonomic builder pattern for CubeDefinition
108// ---------------------------------------------------------------------------
109
110pub struct CubeBuilder {
111    def: CubeDefinition,
112}
113
114impl CubeBuilder {
115    pub fn new(name: &str) -> Self {
116        Self {
117            def: CubeDefinition {
118                name: name.to_string(),
119                schema: String::new(),
120                table_pattern: String::new(),
121                chain_column: None,
122                dimensions: Vec::new(),
123                metrics: Vec::new(),
124                selectors: Vec::new(),
125                default_filters: Vec::new(),
126                default_limit: 25,
127                max_limit: 10000,
128            },
129        }
130    }
131
132    pub fn schema(mut self, schema: &str) -> Self {
133        self.def.schema = schema.to_string();
134        self
135    }
136
137    pub fn table(mut self, pattern: &str) -> Self {
138        self.def.table_pattern = pattern.to_string();
139        self
140    }
141
142    pub fn chain_column(mut self, column: &str) -> Self {
143        self.def.chain_column = Some(column.to_string());
144        self
145    }
146
147    pub fn dimension(mut self, node: DimensionNode) -> Self {
148        self.def.dimensions.push(node);
149        self
150    }
151
152    pub fn metric(mut self, name: &str) -> Self {
153        self.def.metrics.push(name.to_string());
154        self
155    }
156
157    pub fn metrics(mut self, names: &[&str]) -> Self {
158        self.def.metrics.extend(names.iter().map(|s| s.to_string()));
159        self
160    }
161
162    pub fn selector(mut self, sel: SelectorDef) -> Self {
163        self.def.selectors.push(sel);
164        self
165    }
166
167    pub fn default_filter(mut self, column: &str, value: &str) -> Self {
168        self.def.default_filters.push((column.to_string(), value.to_string()));
169        self
170    }
171
172    pub fn default_limit(mut self, limit: u32) -> Self {
173        self.def.default_limit = limit;
174        self
175    }
176
177    pub fn max_limit(mut self, limit: u32) -> Self {
178        self.def.max_limit = limit;
179        self
180    }
181
182    pub fn build(self) -> CubeDefinition {
183        self.def
184    }
185}
186
187// ---------------------------------------------------------------------------
188// Helper functions for concise dimension/selector construction
189// ---------------------------------------------------------------------------
190
191pub fn dim(graphql_name: &str, column: &str, dim_type: DimType) -> DimensionNode {
192    DimensionNode::Leaf(Dimension {
193        graphql_name: graphql_name.to_string(),
194        column: column.to_string(),
195        dim_type,
196    })
197}
198
199pub fn dim_group(graphql_name: &str, children: Vec<DimensionNode>) -> DimensionNode {
200    DimensionNode::Group {
201        graphql_name: graphql_name.to_string(),
202        children,
203    }
204}