rib/registry/
component_dependencies.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Golem Source License v1.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://license.golem.cloud/LICENSE
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{
16    ComponentDependencyKey, Expr, FullyQualifiedInterfaceName, FunctionDictionary, FunctionName,
17    FunctionType, FunctionTypeRegistry, InstanceCreationType, InterfaceName, PackageName,
18    TypeParameter,
19};
20use golem_wasm_ast::analysis::TypeEnum;
21use golem_wasm_ast::analysis::{AnalysedExport, TypeVariant};
22use std::collections::BTreeMap;
23
24#[derive(Debug, Default, Hash, Clone, Eq, PartialEq, PartialOrd, Ord)]
25pub struct ComponentDependencies {
26    pub dependencies: BTreeMap<ComponentDependencyKey, FunctionDictionary>,
27}
28
29impl ComponentDependencies {
30    pub fn get_variants(&self) -> Vec<TypeVariant> {
31        let mut variants = vec![];
32
33        for function_dict in self.dependencies.values() {
34            variants.extend(function_dict.get_all_variants());
35        }
36
37        variants
38    }
39
40    pub fn get_enums(&self) -> Vec<TypeEnum> {
41        let mut enums = vec![];
42
43        for function_dict in self.dependencies.values() {
44            enums.extend(function_dict.get_all_enums());
45        }
46
47        enums
48    }
49
50    pub fn get_function_type(
51        &self,
52        component_info: &Option<ComponentDependencyKey>,
53        function_name: &FunctionName,
54    ) -> Result<(ComponentDependencyKey, FunctionType), String> {
55        // If function name is unique across all components, we are not in need of a component_info per se
56        // and we can return the exact component dependency
57        match component_info {
58            None => {
59                let mut function_types_in_component = vec![];
60
61                for (component_dependency_key, function_dict) in &self.dependencies {
62                    let types = function_dict
63                        .name_and_types
64                        .iter()
65                        .filter_map(|(f_name, function_type)| {
66                            if f_name == function_name {
67                                Some(function_type)
68                            } else {
69                                None
70                            }
71                        })
72                        .collect::<Vec<_>>();
73
74                    function_types_in_component.push((component_dependency_key.clone(), types));
75                }
76
77                if function_types_in_component.is_empty() {
78                    Err("unknown function".to_string())
79                } else if function_types_in_component.len() > 1 {
80                    Err(format!(
81                        "function `{function_name}` is ambiguous across components"
82                    ))
83                } else {
84                    let (key, types) = function_types_in_component.pop().unwrap();
85
86                    if types.is_empty() {
87                        Err("unknown function".to_string())
88                    } else {
89                        Ok((key, types[0].clone()))
90                    }
91                }
92            }
93            Some(component_dep_key) => {
94                let function_dictionary = self
95                    .dependencies
96                    .get(component_dep_key)
97                    .cloned()
98                    .ok_or_else(|| {
99                        format!(
100                            "component dependency for `{}` not found",
101                            component_dep_key.component_name
102                        )
103                    })?;
104
105                let function_type = function_dictionary.name_and_types.iter().find_map(
106                    |(f_name, function_type)| {
107                        if f_name == function_name {
108                            Some(function_type.clone())
109                        } else {
110                            None
111                        }
112                    },
113                );
114
115                if let Some(function_type) = function_type {
116                    Ok((component_dep_key.clone(), function_type))
117                } else {
118                    Err(format!(
119                        "function `{}` not found in component `{}`",
120                        function_name, component_dep_key.component_name
121                    ))
122                }
123            }
124        }
125    }
126
127    pub fn narrow_to_component(&mut self, component_dependency_key: &ComponentDependencyKey) {
128        // If the component dependency key is not found, we do nothing
129        if let Some(function_dict) = self.dependencies.remove(component_dependency_key) {
130            self.dependencies.clear();
131            self.dependencies
132                .insert(component_dependency_key.clone(), function_dict);
133        }
134    }
135
136    pub fn function_dictionary(&self) -> Vec<&FunctionDictionary> {
137        self.dependencies.values().collect::<Vec<_>>()
138    }
139
140    pub fn filter_by_interface(
141        &self,
142        interface_name: &InterfaceName,
143    ) -> Result<crate::ComponentDependencies, String> {
144        let mut tree = BTreeMap::new();
145
146        for (component_info, function_dict) in self.dependencies.iter() {
147            let name_and_types: Vec<&(FunctionName, FunctionType)> = function_dict
148                .name_and_types
149                .iter()
150                .filter(|(f, _)| f.interface_name().as_ref() == Some(interface_name))
151                .collect::<Vec<_>>();
152
153            if !name_and_types.is_empty() {
154                tree.insert(
155                    component_info.clone(),
156                    FunctionDictionary {
157                        name_and_types: name_and_types.into_iter().cloned().collect(),
158                    },
159                );
160            }
161        }
162
163        if tree.is_empty() {
164            return Err(format!("interface `{interface_name}` not found"));
165        }
166
167        Ok(ComponentDependencies { dependencies: tree })
168    }
169
170    pub fn filter_by_package_name(
171        &self,
172        package_name: &PackageName,
173    ) -> Result<crate::ComponentDependencies, String> {
174        // If the package name corresponds to the root package name we pick that up
175        let mut tree = BTreeMap::new();
176
177        for (component_info, function_dict) in self.dependencies.iter() {
178            if let Some(root_package_name) = &component_info.root_package_name {
179                if root_package_name == &package_name.to_string() {
180                    tree.insert(component_info.clone(), function_dict.clone());
181                }
182            } else {
183                // If this package doesn't correspond to a root, but happens to be part of the component then
184
185                let name_and_types = function_dict
186                    .name_and_types
187                    .iter()
188                    .filter(|(f, _)| f.package_name() == Some(package_name.clone()))
189                    .collect::<Vec<_>>();
190
191                if !name_and_types.is_empty() {
192                    tree.insert(
193                        component_info.clone(),
194                        FunctionDictionary {
195                            name_and_types: name_and_types.into_iter().cloned().collect(),
196                        },
197                    );
198                }
199            }
200        }
201
202        if tree.is_empty() {
203            return Err(format!("package `{package_name}` not found"));
204        }
205
206        Ok(crate::ComponentDependencies { dependencies: tree })
207    }
208
209    pub fn filter_by_fully_qualified_interface(
210        &self,
211        fqi: &FullyQualifiedInterfaceName,
212    ) -> Result<Self, String> {
213        let mut tree = BTreeMap::new();
214
215        for (component_info, function_dict) in self.dependencies.iter() {
216            if let Some(root_package_name) = &component_info.root_package_name {
217                if root_package_name == &fqi.package_name.to_string() {
218                    tree.insert(component_info.clone(), function_dict.clone());
219                }
220            } else {
221                // If this package doesn't correspond to a root, but happens to be part of the component then
222
223                let name_and_types = function_dict
224                    .name_and_types
225                    .iter()
226                    .filter(|(f, _)| {
227                        f.package_name() == Some(fqi.package_name.clone())
228                            && f.interface_name() == Some(fqi.interface_name.clone())
229                    })
230                    .collect::<Vec<_>>();
231
232                if !name_and_types.is_empty() {
233                    tree.insert(
234                        component_info.clone(),
235                        FunctionDictionary {
236                            name_and_types: name_and_types.into_iter().cloned().collect(),
237                        },
238                    );
239                }
240            }
241        }
242
243        if tree.is_empty() {
244            return Err(format!("`{fqi}` not found"));
245        }
246
247        Ok(ComponentDependencies { dependencies: tree })
248    }
249
250    // type-parameter can be None.
251    // If present, it may represent the root package name of the component
252    // or it could represent the package or interface within a component
253    pub fn get_worker_instance_type(
254        &self,
255        type_parameter: Option<TypeParameter>,
256        worker_name: Option<Expr>,
257    ) -> Result<InstanceCreationType, String> {
258        match type_parameter {
259            None => Ok(InstanceCreationType::WitWorker {
260                component_info: None,
261                worker_name: worker_name.map(Box::new),
262            }),
263
264            Some(TypeParameter::PackageName(package_name)) => {
265                // If the user has specified the root package name, annotate the InstanceCreationType with the component already
266                let result = self
267                    .dependencies
268                    .iter()
269                    .find(|(x, _)| match &x.root_package_name {
270                        Some(name) => {
271                            let pkg = match &x.root_package_version {
272                                None => name.to_string(),
273                                Some(version) => format!("{name}@{version}"),
274                            };
275
276                            pkg == package_name.to_string()
277                        }
278
279                        None => false,
280                    });
281
282                if let Some(result) = result {
283                    Ok(InstanceCreationType::WitWorker {
284                        component_info: Some(result.0.clone()),
285                        worker_name: worker_name.map(Box::new),
286                    })
287                } else {
288                    Ok(InstanceCreationType::WitWorker {
289                        component_info: None,
290                        worker_name: worker_name.map(Box::new),
291                    })
292                }
293            }
294
295            Some(TypeParameter::Interface(interface_name)) => {
296                let filtered_by_interface = self.filter_by_interface(&interface_name)?;
297
298                let dependency_key = if filtered_by_interface.dependencies.len() == 1 {
299                    filtered_by_interface
300                        .dependencies
301                        .iter()
302                        .next()
303                        .map(|(k, _)| k.clone())
304                        .unwrap()
305                } else {
306                    return Err(format!(
307                        "interface `{interface_name}` is ambiguous across components"
308                    ));
309                };
310
311                Ok(InstanceCreationType::WitWorker {
312                    component_info: Some(dependency_key.clone()),
313                    worker_name: worker_name.map(Box::new),
314                })
315            }
316
317            _ => Ok(InstanceCreationType::WitWorker {
318                component_info: None,
319                worker_name: worker_name.map(Box::new),
320            }),
321        }
322    }
323
324    pub fn from_raw(
325        component_and_exports: Vec<(ComponentDependencyKey, &Vec<AnalysedExport>)>,
326    ) -> Result<Self, String> {
327        let mut dependencies = BTreeMap::new();
328
329        for (component_info, exports) in component_and_exports {
330            let function_type_registry = FunctionTypeRegistry::from_export_metadata(exports);
331            let function_dictionary =
332                FunctionDictionary::from_function_type_registry(&function_type_registry)?;
333            dependencies.insert(component_info, function_dictionary);
334        }
335
336        Ok(ComponentDependencies { dependencies })
337    }
338}