Skip to main content

nodedb_array/schema/
builder.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Schema construction with full validation.
4//!
5//! Every `ArraySchema` in the system passes through `ArraySchemaBuilder::build()`
6//! so the per-domain validation rules (dim arity, tile-extent arity,
7//! non-empty attrs, unique names, well-formed bounds) live in one
8//! place.
9
10use super::array_schema::ArraySchema;
11use super::attr_spec::AttrSpec;
12use super::cell_order::{CellOrder, TileOrder};
13use super::dim_spec::DimSpec;
14use super::validation;
15use crate::error::ArrayResult;
16
17/// Builder for [`ArraySchema`]. Build is fallible.
18#[derive(Debug, Clone)]
19pub struct ArraySchemaBuilder {
20    name: String,
21    dims: Vec<DimSpec>,
22    attrs: Vec<AttrSpec>,
23    tile_extents: Vec<u64>,
24    cell_order: CellOrder,
25    tile_order: TileOrder,
26}
27
28impl ArraySchemaBuilder {
29    pub fn new(name: impl Into<String>) -> Self {
30        Self {
31            name: name.into(),
32            dims: Vec::new(),
33            attrs: Vec::new(),
34            tile_extents: Vec::new(),
35            cell_order: CellOrder::default(),
36            tile_order: TileOrder::default(),
37        }
38    }
39
40    pub fn dim(mut self, dim: DimSpec) -> Self {
41        self.dims.push(dim);
42        self
43    }
44
45    pub fn attr(mut self, attr: AttrSpec) -> Self {
46        self.attrs.push(attr);
47        self
48    }
49
50    pub fn tile_extents(mut self, extents: Vec<u64>) -> Self {
51        self.tile_extents = extents;
52        self
53    }
54
55    pub fn cell_order(mut self, order: CellOrder) -> Self {
56        self.cell_order = order;
57        self
58    }
59
60    pub fn tile_order(mut self, order: TileOrder) -> Self {
61        self.tile_order = order;
62        self
63    }
64
65    pub fn build(self) -> ArrayResult<ArraySchema> {
66        validation::dims::check(&self.name, &self.dims)?;
67        validation::attrs::check(&self.name, &self.attrs)?;
68        validation::tiles::check(&self.name, &self.dims, &self.tile_extents)?;
69        Ok(ArraySchema {
70            name: self.name,
71            dims: self.dims,
72            attrs: self.attrs,
73            tile_extents: self.tile_extents,
74            cell_order: self.cell_order,
75            tile_order: self.tile_order,
76        })
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::super::attr_spec::AttrType;
83    use super::super::dim_spec::DimType;
84    use super::*;
85    use crate::types::domain::{Domain, DomainBound};
86
87    fn int64_dim(name: &str, hi: i64) -> DimSpec {
88        DimSpec::new(
89            name,
90            DimType::Int64,
91            Domain::new(DomainBound::Int64(0), DomainBound::Int64(hi)),
92        )
93    }
94
95    #[test]
96    fn build_succeeds_for_well_formed_schema() {
97        let s = ArraySchemaBuilder::new("g")
98            .dim(int64_dim("chrom", 24))
99            .dim(int64_dim("pos", 300_000_000))
100            .attr(AttrSpec::new("variant", AttrType::String, false))
101            .tile_extents(vec![1, 1_000_000])
102            .build()
103            .unwrap();
104        assert_eq!(s.arity(), 2);
105        assert_eq!(s.cell_order, CellOrder::Hilbert);
106    }
107
108    #[test]
109    fn build_rejects_no_dims() {
110        let r = ArraySchemaBuilder::new("g")
111            .attr(AttrSpec::new("v", AttrType::Int64, false))
112            .tile_extents(vec![])
113            .build();
114        assert!(r.is_err());
115    }
116
117    #[test]
118    fn build_rejects_no_attrs() {
119        let r = ArraySchemaBuilder::new("g")
120            .dim(int64_dim("d", 10))
121            .tile_extents(vec![1])
122            .build();
123        assert!(r.is_err());
124    }
125
126    #[test]
127    fn build_rejects_extent_arity_mismatch() {
128        let r = ArraySchemaBuilder::new("g")
129            .dim(int64_dim("d", 10))
130            .attr(AttrSpec::new("v", AttrType::Int64, false))
131            .tile_extents(vec![1, 2])
132            .build();
133        assert!(r.is_err());
134    }
135}