allora_runtime/spec/
service_spec_yaml.rs

1//! YAML parser for ServiceActivatorSpec (v1).
2//! Expects structure defined in `schema/v1/service-activator.schema.yml`.
3//! Performs structural validation: required non-empty `ref-name`, `from`, `to`; optional non-empty `id`.
4//!
5//! # Accepted Shape (Informal)
6//! ```yaml
7//! version: 1
8//! service-activator:
9//!   id: hello_world          # optional, non-empty if present
10//!   ref-name: hello_world     # required, non-empty reference identifier (matches #[service(name=..)])
11//!   from: inbound.orders      # required, non-empty
12//!   to: vetted.orders         # required, non-empty
13//! ```
14//!
15//! # Validation Responsibilities
16//! * Validate top-level `version` via shared `validate_version` (must equal 1).
17//! * Ensure `service-activator` mapping exists and contains required keys.
18//! * Enforce non-empty strings for all present fields.
19//! * Defer uniqueness of `id` to future collection builder.
20//!
21//! # Non-Goals
22//! * Enforcing naming patterns or file path semantics for `ref-name` (it is a logical identifier).
23//! * Channel existence checks (handled by orchestration layer later).
24//!
25//! # Errors
26//! * Missing or wrong-typed fields -> `Error::Serialization` with descriptive messages.
27//! * Unsupported version -> `Error::Serialization`.
28
29use crate::error::{Error, Result};
30use crate::spec::version::validate_version;
31use crate::spec::ServiceActivatorSpec;
32use serde_yaml::Value as YamlValue;
33
34pub struct ServiceSpecYamlParser;
35
36impl ServiceSpecYamlParser {
37    pub fn parse_value(yaml: &YamlValue) -> Result<ServiceActivatorSpec> {
38        let _v = validate_version(yaml)?;
39        let service_root = yaml
40            .get("service-activator")
41            .ok_or_else(|| Error::serialization("missing 'service-activator'"))?;
42        if !service_root.is_mapping() {
43            return Err(Error::serialization(
44                "'service-activator' must be a mapping",
45            ));
46        }
47        let name_val = service_root
48            .get("ref-name")
49            .ok_or_else(|| Error::serialization("service-activator.ref-name required"))?;
50        let name_str = name_val
51            .as_str()
52            .ok_or_else(|| Error::serialization("service-activator.ref-name must be string"))?;
53        if name_str.is_empty() {
54            return Err(Error::serialization(
55                "service-activator.ref-name must not be empty",
56            ));
57        }
58        let from_val = service_root
59            .get("from")
60            .ok_or_else(|| Error::serialization("service-activator.from required"))?;
61        let from_str = from_val
62            .as_str()
63            .ok_or_else(|| Error::serialization("service-activator.from must be string"))?;
64        if from_str.is_empty() {
65            return Err(Error::serialization(
66                "service-activator.from must not be empty",
67            ));
68        }
69        let to_val = service_root
70            .get("to")
71            .ok_or_else(|| Error::serialization("service-activator.to required"))?;
72        let to_str = to_val
73            .as_str()
74            .ok_or_else(|| Error::serialization("service-activator.to must be string"))?;
75        if to_str.is_empty() {
76            return Err(Error::serialization(
77                "service-activator.to must not be empty",
78            ));
79        }
80        let id_opt = service_root
81            .get("id")
82            .and_then(|v| v.as_str())
83            .map(|s| s.to_string());
84        if let Some(ref idv) = id_opt {
85            if idv.is_empty() {
86                return Err(Error::serialization(
87                    "service-activator.id must not be empty",
88                ));
89            }
90        }
91        Ok(match id_opt {
92            Some(id) => ServiceActivatorSpec::with_id(id, name_str, from_str, to_str),
93            None => ServiceActivatorSpec::new(name_str, from_str, to_str),
94        })
95    }
96    pub fn parse_str(raw: &str) -> Result<ServiceActivatorSpec> {
97        let val: YamlValue = serde_yaml::from_str(raw)
98            .map_err(|e| Error::serialization(format!("yaml parse error: {e}")))?;
99        Self::parse_value(&val)
100    }
101}