Skip to main content

frp_plexus/
types.rs

1use serde::{Deserialize, Serialize};
2
3// ---------------------------------------------------------------------------
4// TypeSig
5// ---------------------------------------------------------------------------
6
7/// A structural type signature used for port compatibility checking.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub enum TypeSig {
10    /// Matches any other `TypeSig`. Use as a wildcard input or output.
11    Any,
12    /// The null / absent type.
13    Null,
14    /// Boolean.
15    Bool,
16    /// 64-bit signed integer.
17    Int,
18    /// 64-bit floating-point number.
19    Float,
20    /// UTF-8 string.
21    String,
22    /// Raw byte sequence.
23    Bytes,
24    /// Homogeneous list of elements with the given element type.
25    List(Box<TypeSig>),
26    /// Map with string keys and values of the given type.
27    Map(Box<TypeSig>),
28    /// A user-defined named type (e.g. a domain struct name).
29    Named(std::string::String),
30}
31
32impl TypeSig {
33    /// Returns `true` if a value with type signature `self` can be accepted
34    /// where `other` is expected.
35    ///
36    /// Rules:
37    /// - `Any` is compatible with everything (in either position).
38    /// - `Named(a)` is compatible with `Named(b)` iff `a == b`.
39    /// - `List(a)` is compatible with `List(b)` iff `a` is compatible with `b`.
40    /// - `Map(a)` is compatible with `Map(b)` iff `a` is compatible with `b`.
41    /// - All other variants require exact equality.
42    pub fn is_compatible_with(&self, other: &TypeSig) -> bool {
43        match (self, other) {
44            (TypeSig::Any, _) | (_, TypeSig::Any) => true,
45            (TypeSig::List(a), TypeSig::List(b)) => a.is_compatible_with(b),
46            (TypeSig::Map(a), TypeSig::Map(b)) => a.is_compatible_with(b),
47            (TypeSig::Named(a), TypeSig::Named(b)) => a == b,
48            _ => self == other,
49        }
50    }
51}
52
53impl std::fmt::Display for TypeSig {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            TypeSig::Any => write!(f, "Any"),
57            TypeSig::Null => write!(f, "Null"),
58            TypeSig::Bool => write!(f, "Bool"),
59            TypeSig::Int => write!(f, "Int"),
60            TypeSig::Float => write!(f, "Float"),
61            TypeSig::String => write!(f, "String"),
62            TypeSig::Bytes => write!(f, "Bytes"),
63            TypeSig::List(inner) => write!(f, "List<{}>", inner),
64            TypeSig::Map(inner) => write!(f, "Map<{}>", inner),
65            TypeSig::Named(name) => write!(f, "{}", name),
66        }
67    }
68}
69
70// ---------------------------------------------------------------------------
71// LayerTag
72// ---------------------------------------------------------------------------
73
74/// Semantic layer tag for an atom or block, used for routing and filtering.
75#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
76pub enum LayerTag {
77    /// Core infrastructure layer.
78    Core,
79    /// Business domain layer.
80    Domain,
81    /// External integration layer.
82    Integration,
83    /// User-defined custom layer.
84    Custom(std::string::String),
85}
86
87impl std::fmt::Display for LayerTag {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        match self {
90            LayerTag::Core => write!(f, "Core"),
91            LayerTag::Domain => write!(f, "Domain"),
92            LayerTag::Integration => write!(f, "Integration"),
93            LayerTag::Custom(s) => write!(f, "Custom({})", s),
94        }
95    }
96}
97
98// ---------------------------------------------------------------------------
99// Tests
100// ---------------------------------------------------------------------------
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn any_is_compatible_with_everything() {
108        assert!(TypeSig::Any.is_compatible_with(&TypeSig::Int));
109        assert!(TypeSig::Int.is_compatible_with(&TypeSig::Any));
110        assert!(TypeSig::Any.is_compatible_with(&TypeSig::Any));
111    }
112
113    #[test]
114    fn exact_match_compatible() {
115        assert!(TypeSig::Int.is_compatible_with(&TypeSig::Int));
116        assert!(TypeSig::Bool.is_compatible_with(&TypeSig::Bool));
117    }
118
119    #[test]
120    fn mismatched_primitives_incompatible() {
121        assert!(!TypeSig::Int.is_compatible_with(&TypeSig::Float));
122        assert!(!TypeSig::Bool.is_compatible_with(&TypeSig::String));
123    }
124
125    #[test]
126    fn named_compatible_same() {
127        assert!(TypeSig::Named("Foo".into()).is_compatible_with(&TypeSig::Named("Foo".into())));
128    }
129
130    #[test]
131    fn named_incompatible_different() {
132        assert!(!TypeSig::Named("Foo".into()).is_compatible_with(&TypeSig::Named("Bar".into())));
133    }
134
135    #[test]
136    fn list_compatibility_recursive() {
137        let list_int = TypeSig::List(Box::new(TypeSig::Int));
138        let list_any = TypeSig::List(Box::new(TypeSig::Any));
139        assert!(list_int.is_compatible_with(&list_any));
140        assert!(!list_int.is_compatible_with(&TypeSig::List(Box::new(TypeSig::Bool))));
141    }
142}