Skip to main content

allora_runtime/spec/
http_outbound_adapter_spec_yaml.rs

1use crate::error::{Error, Result};
2use crate::spec::http_outbound_adapter_spec::{
3    HttpOutboundAdapterSpec, HttpOutboundAdapterSpecRootV1,
4};
5use serde_yaml::{self, Value as YamlValue};
6
7pub struct HttpOutboundAdapterSpecYamlParser;
8impl HttpOutboundAdapterSpecYamlParser {
9    pub fn parse_value(yaml: &YamlValue) -> Result<HttpOutboundAdapterSpec> {
10        let version_val = yaml
11            .get("version")
12            .and_then(|v| v.as_u64())
13            .ok_or_else(|| Error::serialization("missing or invalid version"))?
14            as u32;
15        if version_val != 1 {
16            return Err(Error::serialization(format!(
17                "unsupported version {} (expected 1)",
18                version_val
19            )));
20        }
21        let root = yaml
22            .get("http-outbound-adapter")
23            .ok_or_else(|| Error::serialization("missing 'http-outbound-adapter'"))?;
24        if !root.is_mapping() {
25            return Err(Error::serialization(
26                "'http-outbound-adapter' must be a mapping",
27            ));
28        }
29        let mut mapping = serde_yaml::Mapping::new();
30        mapping.insert(
31            YamlValue::String("version".into()),
32            YamlValue::Number(serde_yaml::Number::from(version_val)),
33        );
34        mapping.insert(
35            YamlValue::String("http-outbound-adapter".into()),
36            root.clone(),
37        );
38        let synthesized = YamlValue::Mapping(mapping);
39        let root: HttpOutboundAdapterSpecRootV1 = serde_yaml::from_value(synthesized)
40            .map_err(|e| Error::serialization(format!("yaml parse error: {e}")))?;
41        let blk = &root.http_outbound_adapter;
42        if blk.url.is_empty() {
43            return Err(Error::serialization(
44                "http-outbound-adapter.url must not be empty",
45            ));
46        }
47        // Validate URL eagerly so misconfiguration surfaces at config load,
48        // not at first dispatch. Also enforce that a scheme is present and
49        // is one of the schemes the underlying client supports.
50        let parsed = url::Url::parse(&blk.url)
51            .map_err(|e| Error::serialization(format!("http-outbound-adapter.url invalid: {e}")))?;
52        match parsed.scheme() {
53            "http" | "https" => {}
54            other => {
55                return Err(Error::serialization(format!(
56                    "http-outbound-adapter.url has unsupported scheme '{}' (expected http or https)",
57                    other
58                )));
59            }
60        }
61        if let Some(idv) = &blk.id {
62            if idv.is_empty() {
63                return Err(Error::serialization(
64                    "http-outbound-adapter.id must not be empty",
65                ));
66            }
67        }
68        Ok(HttpOutboundAdapterSpec::from_block(
69            root.http_outbound_adapter,
70        ))
71    }
72    pub fn parse_str(raw: &str) -> Result<HttpOutboundAdapterSpec> {
73        let yaml: YamlValue = serde_yaml::from_str(raw)
74            .map_err(|e| Error::serialization(format!("yaml parse error: {e}")))?;
75        Self::parse_value(&yaml)
76    }
77}