Skip to main content

nodedb_array/schema/validation/
attrs.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Attribute-list validation.
4//!
5//! Rules:
6//! - At least one attribute.
7//! - Attribute names are unique.
8//! - Attribute names do not collide with dimension names — but that
9//!   cross-check happens at the schema level (the builder calls
10//!   [`super::dims::check`] first, then this).
11
12use std::collections::HashSet;
13
14use crate::error::{ArrayError, ArrayResult};
15use crate::schema::attr_spec::AttrSpec;
16
17pub fn check(array: &str, attrs: &[AttrSpec]) -> ArrayResult<()> {
18    if attrs.is_empty() {
19        return Err(ArrayError::InvalidSchema {
20            array: array.to_string(),
21            detail: "at least one attribute is required".to_string(),
22        });
23    }
24    let mut seen = HashSet::with_capacity(attrs.len());
25    for a in attrs {
26        if !seen.insert(a.name.as_str()) {
27            return Err(ArrayError::InvalidAttr {
28                array: array.to_string(),
29                attr: a.name.clone(),
30                detail: "duplicate attribute name".to_string(),
31            });
32        }
33    }
34    Ok(())
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use crate::schema::attr_spec::AttrType;
41
42    #[test]
43    fn rejects_empty_attr_list() {
44        assert!(check("a", &[]).is_err());
45    }
46
47    #[test]
48    fn rejects_duplicate_attr_names() {
49        let attrs = vec![
50            AttrSpec::new("v", AttrType::Int64, false),
51            AttrSpec::new("v", AttrType::Int64, false),
52        ];
53        assert!(check("a", &attrs).is_err());
54    }
55
56    #[test]
57    fn accepts_well_formed_attrs() {
58        let attrs = vec![
59            AttrSpec::new("variant", AttrType::String, false),
60            AttrSpec::new("qual", AttrType::Float64, true),
61        ];
62        assert!(check("a", &attrs).is_ok());
63    }
64}