zerodds_ccm/
lightweight.rs1use alloc::vec::Vec;
17use core::fmt;
18
19use zerodds_idl::ast::{Export, InterfaceDef, ScopedName};
20
21use crate::transform::ComponentEquivalent;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum LightweightFilterError {
26 EmptyAfterFilter,
31}
32
33impl fmt::Display for LightweightFilterError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::EmptyAfterFilter => f.write_str(
37 "component would have no operations after applying lightweight CCM filter",
38 ),
39 }
40 }
41}
42
43#[cfg(feature = "std")]
44impl std::error::Error for LightweightFilterError {}
45
46pub fn filter_to_lightweight(
70 eq: ComponentEquivalent,
71) -> Result<ComponentEquivalent, LightweightFilterError> {
72 let kept_exports: Vec<Export> = eq
73 .equivalent_interface
74 .exports
75 .into_iter()
76 .filter(|e| !is_filtered_export(e))
77 .collect();
78 if kept_exports.is_empty() && !eq.event_consumer_interfaces.is_empty() {
79 return Err(LightweightFilterError::EmptyAfterFilter);
83 }
84 let filtered_iface = InterfaceDef {
85 exports: kept_exports,
86 bases: eq
87 .equivalent_interface
88 .bases
89 .into_iter()
90 .filter(|b| !is_filtered_base(b))
91 .collect(),
92 ..eq.equivalent_interface
93 };
94 Ok(ComponentEquivalent {
95 equivalent_interface: filtered_iface,
96 event_consumer_interfaces: eq.event_consumer_interfaces,
97 })
98}
99
100fn is_filtered_export(e: &Export) -> bool {
101 if let Export::Op(o) = e {
102 let n = &o.name.text;
103 matches!(
105 n.as_str(),
106 "configure" | "set_configuration" | "configuration_complete"
107 )
108 } else {
109 false
110 }
111}
112
113fn is_filtered_base(b: &ScopedName) -> bool {
114 let _ = b;
117 false
118}
119
120#[cfg(test)]
121#[allow(clippy::expect_used)]
122mod tests {
123 use super::*;
124 use crate::transform::transform_component;
125 use zerodds_idl::ast::{
126 ComponentDef, ComponentExport, Identifier, OpDecl, ParamAttribute, ParamDecl,
127 PrimitiveType, ScopedName, TypeSpec,
128 };
129 use zerodds_idl::errors::Span;
130
131 fn span() -> Span {
132 Span::SYNTHETIC
133 }
134
135 fn ident(s: &str) -> Identifier {
136 Identifier::new(s, span())
137 }
138
139 fn sn(parts: &[&str]) -> ScopedName {
140 ScopedName {
141 absolute: false,
142 parts: parts.iter().map(|p| ident(p)).collect(),
143 span: span(),
144 }
145 }
146
147 #[test]
148 fn lightweight_filter_drops_configurator_operations() {
149 let c = ComponentDef {
152 name: ident("C"),
153 base: None,
154 supports: Vec::new(),
155 body: alloc::vec![ComponentExport::Provides {
156 type_spec: sn(&["I"]),
157 name: ident("foo"),
158 span: span(),
159 }],
160 annotations: Vec::new(),
161 span: span(),
162 };
163 let mut eq = transform_component(&c);
164 eq.equivalent_interface.exports.push(Export::Op(OpDecl {
166 name: ident("configure"),
167 oneway: false,
168 return_type: None,
169 params: alloc::vec![ParamDecl {
170 attribute: ParamAttribute::In,
171 type_spec: TypeSpec::Primitive(PrimitiveType::Boolean),
172 name: ident("comp"),
173 annotations: Vec::new(),
174 span: span(),
175 }],
176 raises: Vec::new(),
177 annotations: Vec::new(),
178 span: span(),
179 }));
180 let filtered = filter_to_lightweight(eq).expect("filter ok");
181 let names: Vec<String> = filtered
182 .equivalent_interface
183 .exports
184 .iter()
185 .filter_map(|e| match e {
186 Export::Op(o) => Some(o.name.text.clone()),
187 _ => None,
188 })
189 .collect();
190 assert!(names.contains(&String::from("provide_foo")));
191 assert!(!names.contains(&String::from("configure")));
192 }
193
194 #[test]
195 fn lightweight_filter_keeps_typespecific_ops() {
196 let c = ComponentDef {
199 name: ident("C"),
200 base: None,
201 supports: Vec::new(),
202 body: alloc::vec![
203 ComponentExport::Provides {
204 type_spec: sn(&["I"]),
205 name: ident("foo"),
206 span: span(),
207 },
208 ComponentExport::Uses {
209 type_spec: sn(&["J"]),
210 name: ident("bar"),
211 multiple: false,
212 span: span(),
213 },
214 ],
215 annotations: Vec::new(),
216 span: span(),
217 };
218 let eq = transform_component(&c);
219 let filtered = filter_to_lightweight(eq).expect("filter ok");
220 let names: Vec<String> = filtered
221 .equivalent_interface
222 .exports
223 .iter()
224 .filter_map(|e| match e {
225 Export::Op(o) => Some(o.name.text.clone()),
226 _ => None,
227 })
228 .collect();
229 for expected in [
230 "provide_foo",
231 "connect_bar",
232 "disconnect_bar",
233 "get_connection_bar",
234 ] {
235 assert!(
236 names.contains(&String::from(expected)),
237 "missing {expected}"
238 );
239 }
240 }
241
242 #[test]
243 fn display_error_describes_empty_after_filter() {
244 let s = alloc::format!("{}", LightweightFilterError::EmptyAfterFilter);
245 assert!(s.contains("lightweight"));
246 }
247}