Skip to main content

frp_weave/
validator.rs

1//! Stateless validators for connections and block/schema consistency.
2
3use frp_domain::{Block, BlockSchema, Port, PortDirection};
4
5use crate::error::WeaveError;
6
7/// Stateless structural validator.
8pub struct Validator;
9
10impl Validator {
11    /// Validate that `output` and `input` can be connected.
12    ///
13    /// Checks:
14    /// 1. `output` must have direction `Output`.
15    /// 2. `input` must have direction `Input`.
16    /// 3. The output type must be compatible with the input type.
17    pub fn validate_connection(output: &Port, input: &Port) -> Result<(), WeaveError> {
18        if output.direction != PortDirection::Output {
19            return Err(WeaveError::IncompatiblePorts {
20                from: output.name.clone(),
21                to: input.name.clone(),
22            });
23        }
24        if input.direction != PortDirection::Input {
25            return Err(WeaveError::IncompatiblePorts {
26                from: output.name.clone(),
27                to: input.name.clone(),
28            });
29        }
30        if !output.type_sig.is_compatible_with(&input.type_sig) {
31            return Err(WeaveError::IncompatiblePorts {
32                from: format!("{} ({:?})", output.name, output.type_sig),
33                to: format!("{} ({:?})", input.name, input.type_sig),
34            });
35        }
36        Ok(())
37    }
38
39    /// Validate that a block's atom list is non-empty and its schema is valid.
40    pub fn validate_block(block: &Block, schema: &BlockSchema) -> Result<(), WeaveError> {
41        if block.atoms.is_empty() {
42            return Err(WeaveError::ValidationFailed(
43                "block must contain at least one atom".to_string(),
44            ));
45        }
46        schema
47            .validate()
48            .map_err(|e| WeaveError::ValidationFailed(e.to_string()))?;
49        Ok(())
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use frp_domain::Port;
57    use frp_plexus::{PortId, TypeSig};
58
59    fn in_port(id: u64, name: &str, ty: TypeSig) -> Port {
60        Port::new_input(PortId::new(id), name.to_string(), ty)
61    }
62
63    fn out_port(id: u64, name: &str, ty: TypeSig) -> Port {
64        Port::new_output(PortId::new(id), name.to_string(), ty)
65    }
66
67    #[test]
68    fn compatible_any_types_pass() {
69        let out = out_port(1, "o", TypeSig::Any);
70        let inp = in_port(2, "i", TypeSig::Int);
71        Validator::validate_connection(&out, &inp).unwrap();
72    }
73
74    #[test]
75    fn matching_concrete_types_pass() {
76        let out = out_port(1, "o", TypeSig::Int);
77        let inp = in_port(2, "i", TypeSig::Int);
78        Validator::validate_connection(&out, &inp).unwrap();
79    }
80
81    #[test]
82    fn mismatched_types_fail() {
83        let out = out_port(1, "o", TypeSig::Int);
84        let inp = in_port(2, "i", TypeSig::Bool);
85        let err = Validator::validate_connection(&out, &inp).unwrap_err();
86        assert!(matches!(err, WeaveError::IncompatiblePorts { .. }));
87    }
88
89    #[test]
90    fn wrong_directions_fail() {
91        // Both inputs — output arg has wrong direction
92        let out = in_port(1, "o", TypeSig::Any);
93        let inp = in_port(2, "i", TypeSig::Any);
94        let err = Validator::validate_connection(&out, &inp).unwrap_err();
95        assert!(matches!(err, WeaveError::IncompatiblePorts { .. }));
96    }
97}