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    /// Append FINAL to FROM clause for ReplacingMergeTree tables in ClickHouse.
64    pub use_final: bool,
65    /// Human-readable description of the cube's purpose, exposed via _cubeMetadata.
66    pub description: String,
67}
68
69impl CubeDefinition {
70    pub fn table_for_chain(&self, chain: &str) -> String {
71        self.table_pattern.replace("{chain}", chain)
72    }
73
74    pub fn qualified_table(&self, chain: &str) -> String {
75        format!("{}.{}", self.schema, self.table_for_chain(chain))
76    }
77
78    pub fn flat_dimensions(&self) -> Vec<(String, Dimension)> {
79        let mut out = Vec::new();
80        for node in &self.dimensions {
81            collect_leaves(node, "", &mut out);
82        }
83        out
84    }
85}
86
87fn collect_leaves(node: &DimensionNode, prefix: &str, out: &mut Vec<(String, Dimension)>) {
88    match node {
89        DimensionNode::Leaf(dim) => {
90            let path = if prefix.is_empty() {
91                dim.graphql_name.clone()
92            } else {
93                format!("{}_{}", prefix, dim.graphql_name)
94            };
95            out.push((path, dim.clone()));
96        }
97        DimensionNode::Group { graphql_name, children } => {
98            let new_prefix = if prefix.is_empty() {
99                graphql_name.clone()
100            } else {
101                format!("{prefix}_{graphql_name}")
102            };
103            for child in children {
104                collect_leaves(child, &new_prefix, out);
105            }
106        }
107    }
108}
109
110// ---------------------------------------------------------------------------
111// CubeBuilder — ergonomic builder pattern for CubeDefinition
112// ---------------------------------------------------------------------------
113
114pub struct CubeBuilder {
115    def: CubeDefinition,
116}
117
118impl CubeBuilder {
119    pub fn new(name: &str) -> Self {
120        Self {
121            def: CubeDefinition {
122                name: name.to_string(),
123                schema: String::new(),
124                table_pattern: String::new(),
125                chain_column: None,
126                dimensions: Vec::new(),
127                metrics: Vec::new(),
128                selectors: Vec::new(),
129                default_filters: Vec::new(),
130                default_limit: 25,
131                max_limit: 10000,
132                use_final: false,
133                description: String::new(),
134            },
135        }
136    }
137
138    pub fn schema(mut self, schema: &str) -> Self {
139        self.def.schema = schema.to_string();
140        self
141    }
142
143    pub fn table(mut self, pattern: &str) -> Self {
144        self.def.table_pattern = pattern.to_string();
145        self
146    }
147
148    pub fn chain_column(mut self, column: &str) -> Self {
149        self.def.chain_column = Some(column.to_string());
150        self
151    }
152
153    pub fn dimension(mut self, node: DimensionNode) -> Self {
154        self.def.dimensions.push(node);
155        self
156    }
157
158    pub fn metric(mut self, name: &str) -> Self {
159        self.def.metrics.push(name.to_string());
160        self
161    }
162
163    pub fn metrics(mut self, names: &[&str]) -> Self {
164        self.def.metrics.extend(names.iter().map(|s| s.to_string()));
165        self
166    }
167
168    pub fn selector(mut self, sel: SelectorDef) -> Self {
169        self.def.selectors.push(sel);
170        self
171    }
172
173    pub fn default_filter(mut self, column: &str, value: &str) -> Self {
174        self.def.default_filters.push((column.to_string(), value.to_string()));
175        self
176    }
177
178    pub fn default_limit(mut self, limit: u32) -> Self {
179        self.def.default_limit = limit;
180        self
181    }
182
183    pub fn max_limit(mut self, limit: u32) -> Self {
184        self.def.max_limit = limit;
185        self
186    }
187
188    pub fn use_final(mut self, val: bool) -> Self {
189        self.def.use_final = val;
190        self
191    }
192
193    pub fn description(mut self, desc: &str) -> Self {
194        self.def.description = desc.to_string();
195        self
196    }
197
198    pub fn build(self) -> CubeDefinition {
199        self.def
200    }
201}
202
203// ---------------------------------------------------------------------------
204// Helper functions for concise dimension/selector construction
205// ---------------------------------------------------------------------------
206
207pub fn dim(graphql_name: &str, column: &str, dim_type: DimType) -> DimensionNode {
208    DimensionNode::Leaf(Dimension {
209        graphql_name: graphql_name.to_string(),
210        column: column.to_string(),
211        dim_type,
212    })
213}
214
215pub fn dim_group(graphql_name: &str, children: Vec<DimensionNode>) -> DimensionNode {
216    DimensionNode::Group {
217        graphql_name: graphql_name.to_string(),
218        children,
219    }
220}