Skip to main content

allora_runtime/spec/
aggregators_spec_yaml.rs

1//! YAML parser for [`crate::spec::AggregatorsSpec`] (collection v1).
2//!
3//! Delegates per-entry parsing to [`crate::spec::AggregatorSpecYamlParser`]
4//! and focuses on sequence + version validation. Mirrors
5//! [`crate::spec::FiltersSpecYamlParser`].
6//!
7//! # Accepted Shape (Informal)
8//! ```yaml
9//! version: 1
10//! aggregators:
11//!   - id: finality_quorum
12//!     correlation_header: block_hash
13//!     completion: chain.validator_quorum
14//!     strategy: allora.emit_signal
15//!     store: chain.persistent_finality
16//!   - correlation_header: oracle_submission_id
17//!     completion: chain.oracle_data_quorum
18//! ```
19//!
20//! Uniqueness + auto-id generation are deferred to
21//! [`crate::dsl::build_aggregators_from_spec`].
22
23use crate::error::{Error, Result};
24use crate::spec::aggregators_spec::AggregatorsSpec;
25use crate::spec::version::validate_version;
26use crate::spec::AggregatorSpecYamlParser;
27use serde_yaml::Value as YamlValue;
28
29pub struct AggregatorsSpecYamlParser;
30
31impl AggregatorsSpecYamlParser {
32    pub fn parse_value(yaml: &YamlValue) -> Result<AggregatorsSpec> {
33        let v = validate_version(yaml)?;
34        let aggs_val = yaml
35            .get("aggregators")
36            .ok_or_else(|| Error::serialization("missing 'aggregators'"))?;
37        if !aggs_val.is_sequence() {
38            return Err(Error::serialization("'aggregators' must be a sequence"));
39        }
40        let seq = aggs_val.as_sequence().unwrap();
41        if seq.is_empty() {
42            return Err(Error::serialization(
43                "'aggregators' sequence must not be empty (minItems=1)",
44            ));
45        }
46        let mut spec = AggregatorsSpec::new(v);
47        for item in seq {
48            if !item.is_mapping() {
49                return Err(Error::serialization("aggregator entry must be a mapping"));
50            }
51            // Synthesize a single-aggregator doc so the existing parser is reused.
52            let mut obj = serde_yaml::Mapping::new();
53            obj.insert(
54                YamlValue::String("version".into()),
55                YamlValue::Number(serde_yaml::Number::from(v)),
56            );
57            obj.insert(YamlValue::String("aggregator".into()), item.clone());
58            let synthesized = YamlValue::Mapping(obj);
59            spec.push(AggregatorSpecYamlParser::parse_value(&synthesized)?);
60        }
61        Ok(spec)
62    }
63
64    pub fn parse_str(raw: &str) -> Result<AggregatorsSpec> {
65        let val: YamlValue = serde_yaml::from_str(raw)
66            .map_err(|e| Error::serialization(format!("yaml parse error: {e}")))?;
67        Self::parse_value(&val)
68    }
69}