rib/registry/
function_dictionary.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::parser::{PackageName, TypeParameter};
16use crate::type_parameter::InterfaceName;
17use crate::{
18    CallType, DynamicParsedFunctionName, DynamicParsedFunctionReference, FunctionTypeRegistry,
19    InferredType, ParsedFunctionSite, RegistryKey, RegistryValue, SemVer,
20};
21use golem_wasm_ast::analysis::{AnalysedExport, AnalysedType, TypeEnum, TypeVariant};
22use std::collections::BTreeMap;
23use std::convert::TryFrom;
24use std::fmt::{Debug, Display, Formatter};
25
26// Global Function Dictionary is a user friendly projection of FunctionTypeRegistry for functions and arguments.
27// In fact, type inference phases make use of FunctionDictionary.
28// Unlike FunctionTypeRegistry, the function names in `FunctionDictionary` is closer to Rib grammar
29// of invoking functions. Example: A RegistryKey of `[constructor]cart` in FunctionTypeRegistry becomes
30// FunctionName::ResourceConstructor(cart) in FunctionDictionary
31#[derive(Debug, Hash, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
32pub struct FunctionDictionary {
33    pub name_and_types: Vec<(FunctionName, FunctionType)>,
34}
35
36impl FunctionDictionary {
37    pub fn get(&self, function_name: &FunctionName) -> Option<&FunctionType> {
38        self.name_and_types.iter().find_map(|(name, ftype)| {
39            if name == function_name {
40                Some(ftype)
41            } else {
42                None
43            }
44        })
45    }
46
47    pub fn get_all_variants(&self) -> Vec<TypeVariant> {
48        self.name_and_types
49            .iter()
50            .filter_map(|(_, ftype)| ftype.as_type_variant())
51            .collect()
52    }
53
54    pub fn get_all_enums(&self) -> Vec<TypeEnum> {
55        self.name_and_types
56            .iter()
57            .filter_map(|(_, ftype)| ftype.as_type_enum())
58            .collect()
59    }
60
61    pub fn get_enum_info(&self, identifier_name: &str) -> Option<TypeEnum> {
62        self.name_and_types.iter().find_map(|(f, ftype)| match f {
63            FunctionName::Enum(name) => {
64                if name == identifier_name {
65                    ftype.as_type_enum()
66                } else {
67                    None
68                }
69            }
70            _ => None,
71        })
72    }
73    pub fn get_variant_info(&self, identifier_name: &str) -> Vec<TypeVariant> {
74        self.name_and_types
75            .iter()
76            .filter_map(|(f, ftype)| match f {
77                FunctionName::Variant(name) => {
78                    if name == identifier_name {
79                        ftype.as_type_variant()
80                    } else {
81                        None
82                    }
83                }
84                _ => None,
85            })
86            .collect::<Vec<_>>()
87    }
88
89    pub fn function_names(&self) -> Vec<String> {
90        self.name_and_types
91            .iter()
92            .map(|(f, _)| f.name())
93            .collect::<Vec<_>>()
94    }
95}
96
97// A `ResourceMethodDictionary` is a typesafe subset or projection of resource methods in
98// `FunctionDictionary`.
99// The `InstanceType` holds resource method dictionary instead of a full function method dictionary,
100// if the instance is a resource creation.
101// Given the Dictionaries do become part of InferredType (InferredType::InstanceType::Dictionaries)
102// order of component loading into the rib context shouldn't change it's type.
103#[derive(Debug, Hash, Clone, Eq, PartialEq, PartialOrd, Ord)]
104pub struct ResourceMethodDictionary {
105    pub map: BTreeMap<FullyQualifiedResourceMethod, FunctionType>,
106}
107
108impl From<&ResourceMethodDictionary> for FunctionDictionary {
109    fn from(value: &ResourceMethodDictionary) -> Self {
110        FunctionDictionary {
111            name_and_types: value
112                .map
113                .iter()
114                .map(|(key, value)| (FunctionName::ResourceMethod(key.clone()), value.clone()))
115                .collect(),
116        }
117    }
118}
119
120impl FunctionDictionary {
121    pub fn from_exports(exports: &[AnalysedExport]) -> Result<FunctionDictionary, String> {
122        let registry = FunctionTypeRegistry::from_export_metadata(exports);
123        Self::from_function_type_registry(&registry)
124    }
125
126    pub fn from_function_type_registry(
127        registry: &FunctionTypeRegistry,
128    ) -> Result<FunctionDictionary, String> {
129        let mut map = vec![];
130
131        for (key, value) in registry.types.iter() {
132            match value {
133                RegistryValue::Function {
134                    parameter_types,
135                    return_type,
136                } => match key {
137                    RegistryKey::FunctionName(function_name) => {
138                        let function_name = resolve_function_name(None, None, function_name)?;
139
140                        map.push((
141                            function_name,
142                            FunctionType {
143                                parameter_types: parameter_types.iter().map(|x| x.into()).collect(),
144                                return_type: return_type.as_ref().map(|x| x.into()),
145                            },
146                        ));
147                    }
148
149                    RegistryKey::FunctionNameWithInterface {
150                        interface_name,
151                        function_name,
152                    } => {
153                        let type_parameter = TypeParameter::from_text(interface_name.as_str())?;
154
155                        let interface_name = type_parameter.get_interface_name();
156                        let package_name = type_parameter.get_package_name();
157
158                        let function_name =
159                            resolve_function_name(package_name, interface_name, function_name)?;
160
161                        map.push((
162                            function_name,
163                            FunctionType {
164                                parameter_types: parameter_types.iter().map(|x| x.into()).collect(),
165                                return_type: return_type.as_ref().map(|x| x.into()),
166                            },
167                        ));
168                    }
169                },
170
171                RegistryValue::Variant {
172                    parameter_types,
173                    variant_type,
174                } => match key {
175                    RegistryKey::FunctionName(name) => {
176                        let function_name = FunctionName::Variant(name.to_string());
177                        let cases = variant_type
178                            .cases
179                            .iter()
180                            .map(|x| (x.name.clone(), x.typ.as_ref().map(InferredType::from)))
181                            .collect::<Vec<_>>();
182
183                        map.push((
184                            function_name,
185                            FunctionType {
186                                parameter_types: parameter_types.iter().map(|x| x.into()).collect(),
187                                return_type: Some(InferredType::variant(cases)),
188                            },
189                        ));
190                    }
191                    RegistryKey::FunctionNameWithInterface { .. } => {}
192                },
193
194                RegistryValue::Value(value) => match value {
195                    AnalysedType::Enum(type_enum) => match key {
196                        RegistryKey::FunctionName(name) => {
197                            let function_name = FunctionName::Enum(name.to_string());
198
199                            map.push((
200                                function_name,
201                                FunctionType {
202                                    parameter_types: vec![],
203                                    return_type: Some(InferredType::enum_(type_enum.cases.clone())),
204                                },
205                            ));
206                        }
207                        RegistryKey::FunctionNameWithInterface { .. } => {}
208                    },
209                    AnalysedType::Variant(variant_type) => match key {
210                        RegistryKey::FunctionName(name) => {
211                            let function_name = FunctionName::Variant(name.to_string());
212
213                            let cases = variant_type
214                                .cases
215                                .iter()
216                                .map(|x| (x.name.clone(), x.typ.as_ref().map(InferredType::from)))
217                                .collect::<Vec<_>>();
218
219                            map.push((
220                                function_name,
221                                FunctionType {
222                                    parameter_types: vec![],
223                                    return_type: Some(InferredType::variant(cases)),
224                                },
225                            ));
226                        }
227                        RegistryKey::FunctionNameWithInterface { .. } => {}
228                    },
229
230                    _ => {}
231                },
232            };
233        }
234
235        Ok(FunctionDictionary {
236            name_and_types: map,
237        })
238    }
239}
240
241fn resolve_function_name(
242    package_name: Option<PackageName>,
243    interface_name: Option<InterfaceName>,
244    function_name: &str,
245) -> Result<FunctionName, String> {
246    match get_resource_name(function_name) {
247        Some(resource_name) => Ok(FunctionName::ResourceConstructor(
248            FullyQualifiedResourceConstructor {
249                package_name,
250                interface_name,
251                resource_name,
252            },
253        )),
254        None => match get_resource_method_name(function_name) {
255            Ok(Some((constructor, method))) => {
256                Ok(FunctionName::ResourceMethod(FullyQualifiedResourceMethod {
257                    package_name,
258                    interface_name,
259                    resource_name: constructor,
260                    method_name: method,
261                    static_function: false,
262                }))
263            }
264            Ok(None) => match get_resource_static_method_name(function_name) {
265                Ok(Some((constructor, method))) => {
266                    Ok(FunctionName::ResourceMethod(FullyQualifiedResourceMethod {
267                        package_name,
268                        interface_name,
269                        resource_name: constructor,
270                        method_name: method,
271                        static_function: true,
272                    }))
273                }
274                Ok(None) => Ok(FunctionName::Function(FullyQualifiedFunctionName {
275                    package_name,
276                    interface_name,
277                    function_name: function_name.to_string(),
278                })),
279
280                Err(e) => Err(format!("invalid resource static method call. {e}")),
281            },
282
283            Err(e) => Err(format!("invalid resource method call. {e}")),
284        },
285    }
286}
287
288fn get_resource_name(function_name: &str) -> Option<String> {
289    if function_name.trim().starts_with("[constructor]") {
290        Some(
291            function_name
292                .trim_start_matches("[constructor]")
293                .to_string(),
294        )
295    } else {
296        None
297    }
298}
299
300fn get_resource_static_method_name(
301    function_name: &str,
302) -> Result<Option<(String, String)>, String> {
303    if function_name.starts_with("[static]") {
304        let constructor_and_method = function_name.trim_start_matches("[static]").to_string();
305        let mut constructor_and_method = constructor_and_method.split('.');
306        let constructor = constructor_and_method.next();
307        let method = constructor_and_method.next();
308
309        match (constructor, method) {
310            (Some(constructor), Some(method)) => {
311                Ok(Some((constructor.to_string(), method.to_string())))
312            }
313            _ => Err(format!("Invalid resource method name: {function_name}")),
314        }
315    } else {
316        Ok(None)
317    }
318}
319
320fn get_resource_method_name(function_name: &str) -> Result<Option<(String, String)>, String> {
321    if function_name.starts_with("[method]") {
322        let constructor_and_method = function_name.trim_start_matches("[method]").to_string();
323        let mut constructor_and_method = constructor_and_method.split('.');
324        let constructor = constructor_and_method.next();
325        let method = constructor_and_method.next();
326
327        match (constructor, method) {
328            (Some(constructor), Some(method)) => {
329                Ok(Some((constructor.to_string(), method.to_string())))
330            }
331            _ => Err(format!("Invalid resource method name: {function_name}")),
332        }
333    } else if function_name.starts_with("[drop]") {
334        let constructor = function_name.trim_start_matches("[drop]").to_string();
335        Ok(Some((constructor, "drop".to_string())))
336    } else {
337        Ok(None)
338    }
339}
340
341#[derive(Debug, Hash, Clone, Eq, PartialEq, Ord, PartialOrd)]
342pub enum FunctionName {
343    Variant(String),
344    Enum(String),
345    Function(FullyQualifiedFunctionName),
346    ResourceConstructor(FullyQualifiedResourceConstructor),
347    ResourceMethod(FullyQualifiedResourceMethod),
348}
349
350impl Display for FunctionName {
351    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
352        write!(f, "{}", self.name())
353    }
354}
355
356impl FunctionName {
357    pub fn from_dynamic_parsed_function_name(
358        function_name: &DynamicParsedFunctionName,
359    ) -> FunctionName {
360        let site = &function_name.site;
361        let (package_name, interface_name) = match site {
362            ParsedFunctionSite::Global => (None, None),
363
364            ParsedFunctionSite::Interface { name } => (
365                None,
366                Some(InterfaceName {
367                    name: name.clone(),
368                    version: None,
369                }),
370            ),
371            ParsedFunctionSite::PackagedInterface {
372                namespace,
373                package,
374                interface,
375                version,
376            } => (
377                Some(PackageName {
378                    namespace: namespace.clone(),
379                    package_name: package.clone(),
380                    version: None,
381                }),
382                Some(InterfaceName {
383                    name: interface.clone(),
384                    version: version.as_ref().map(|v| v.0.to_string()),
385                }),
386            ),
387        };
388
389        match &function_name.function {
390            DynamicParsedFunctionReference::Function { function } => {
391                FunctionName::Function(FullyQualifiedFunctionName {
392                    package_name,
393                    interface_name,
394                    function_name: function.clone(),
395                })
396            }
397            DynamicParsedFunctionReference::RawResourceConstructor { resource } => {
398                FunctionName::ResourceConstructor(FullyQualifiedResourceConstructor {
399                    package_name,
400                    interface_name,
401                    resource_name: resource.clone(),
402                })
403            }
404            DynamicParsedFunctionReference::RawResourceDrop { resource } => {
405                FunctionName::ResourceMethod(FullyQualifiedResourceMethod {
406                    package_name,
407                    interface_name,
408                    resource_name: resource.clone(),
409                    method_name: "drop".to_string(),
410                    static_function: false,
411                })
412            }
413            DynamicParsedFunctionReference::RawResourceMethod { resource, method } => {
414                FunctionName::ResourceMethod(FullyQualifiedResourceMethod {
415                    package_name,
416                    interface_name,
417                    resource_name: resource.clone(),
418                    method_name: method.clone(),
419                    static_function: false,
420                })
421            }
422            DynamicParsedFunctionReference::RawResourceStaticMethod { resource, method } => {
423                FunctionName::ResourceMethod(FullyQualifiedResourceMethod {
424                    package_name,
425                    interface_name,
426                    resource_name: resource.clone(),
427                    method_name: method.clone(),
428                    static_function: true,
429                })
430            }
431        }
432    }
433
434    pub fn from_call_type(call_type: &CallType) -> Option<FunctionName> {
435        match call_type {
436            CallType::VariantConstructor(variant_name) => {
437                Some(FunctionName::Variant(variant_name.clone()))
438            }
439            CallType::EnumConstructor(enum_name) => Some(FunctionName::Enum(enum_name.clone())),
440            CallType::Function { function_name, .. } => {
441                Some(Self::from_dynamic_parsed_function_name(function_name))
442            }
443            CallType::InstanceCreation(_) => None,
444        }
445    }
446
447    pub fn interface_name(&self) -> Option<InterfaceName> {
448        match self {
449            FunctionName::Function(fqfn) => fqfn.interface_name.clone(),
450            FunctionName::ResourceConstructor(fqfn) => fqfn.interface_name.clone(),
451            FunctionName::ResourceMethod(resource_method) => resource_method.interface_name.clone(),
452            FunctionName::Variant(_) => None,
453            FunctionName::Enum(_) => None,
454        }
455    }
456
457    pub fn package_name(&self) -> Option<PackageName> {
458        match self {
459            FunctionName::Function(fqfn) => fqfn.package_name.clone(),
460            FunctionName::ResourceConstructor(fqfn) => fqfn.package_name.clone(),
461            FunctionName::ResourceMethod(fqfn) => fqfn.package_name.clone(),
462            FunctionName::Variant(_) => None,
463            FunctionName::Enum(_) => None,
464        }
465    }
466
467    pub fn name(&self) -> String {
468        match self {
469            FunctionName::Function(fqfn) => fqfn.function_name.to_string(),
470            FunctionName::ResourceConstructor(fqfn) => fqfn.resource_name.to_string(),
471            FunctionName::ResourceMethod(fqfn) => fqfn.method_name.to_string(),
472            FunctionName::Variant(name) => name.clone(),
473            FunctionName::Enum(name) => name.clone(),
474        }
475    }
476}
477
478#[derive(Debug, Hash, Clone, Eq, PartialEq, Ord, PartialOrd)]
479pub struct FullyQualifiedResourceConstructor {
480    pub package_name: Option<PackageName>,
481    pub interface_name: Option<InterfaceName>,
482    pub resource_name: String,
483}
484
485impl FullyQualifiedResourceConstructor {
486    pub fn parsed_function_site(&self) -> ParsedFunctionSite {
487        if let Some(package_name) = &self.package_name {
488            let interface_name = self.interface_name.clone().unwrap();
489
490            ParsedFunctionSite::PackagedInterface {
491                namespace: package_name.namespace.clone(),
492                package: package_name.package_name.clone(),
493                interface: self
494                    .interface_name
495                    .as_ref()
496                    .map_or_else(|| "".to_string(), |i| i.name.clone()),
497                version: interface_name
498                    .version
499                    .map(|x| SemVer(semver::Version::parse(&x).unwrap())),
500            }
501        } else if let Some(interface_name) = &self.interface_name {
502            ParsedFunctionSite::Interface {
503                name: interface_name.name.clone(),
504            }
505        } else {
506            ParsedFunctionSite::Global
507        }
508    }
509}
510
511#[derive(Debug, Hash, Clone, Eq, PartialEq, Ord, PartialOrd)]
512pub struct FullyQualifiedFunctionName {
513    pub package_name: Option<PackageName>,
514    pub interface_name: Option<InterfaceName>,
515    pub function_name: String,
516}
517
518#[derive(Debug, Hash, Clone, Eq, PartialEq, Ord, PartialOrd)]
519pub struct FullyQualifiedResourceMethod {
520    pub package_name: Option<PackageName>,
521    pub interface_name: Option<InterfaceName>,
522    pub resource_name: String,
523    pub method_name: String,
524    pub static_function: bool,
525}
526
527impl FullyQualifiedResourceMethod {
528    pub fn get_constructor(&self) -> FullyQualifiedResourceConstructor {
529        FullyQualifiedResourceConstructor {
530            package_name: self.package_name.clone(),
531            interface_name: self.interface_name.clone(),
532            resource_name: self.resource_name.clone(),
533        }
534    }
535
536    // TODO; Remove this conversion inside Rib.
537    // FunctionName (the structure used by rib) can be used in all places of usage of DynamicParsedFunctionName
538    pub fn dynamic_parsed_function_name(&self) -> Result<DynamicParsedFunctionName, String> {
539        let mut dynamic_parsed_str = String::new();
540
541        // Construct the package/interface prefix
542        if let Some(package) = &self.package_name {
543            dynamic_parsed_str.push_str(&package.to_string());
544            dynamic_parsed_str.push('/');
545        }
546
547        if let Some(interface) = &self.interface_name {
548            dynamic_parsed_str.push_str(&interface.to_string());
549            dynamic_parsed_str.push('.');
550        }
551
552        dynamic_parsed_str.push('{');
553        if self.static_function {
554            dynamic_parsed_str.push_str("[static]");
555        } else {
556            dynamic_parsed_str.push_str("[method]");
557        }
558
559        dynamic_parsed_str.push_str(&self.resource_name);
560        dynamic_parsed_str.push('.');
561        dynamic_parsed_str.push_str(&self.method_name);
562        dynamic_parsed_str.push('}');
563
564        DynamicParsedFunctionName::parse(dynamic_parsed_str)
565    }
566
567    pub fn method_name(&self) -> &String {
568        &self.method_name
569    }
570}
571
572impl Display for FullyQualifiedFunctionName {
573    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574        if let Some(package_name) = &self.package_name {
575            write!(f, "{package_name}")?
576        }
577
578        if let Some(interface_name) = &self.interface_name {
579            write!(f, "/{interface_name}.")?;
580            write!(f, "{{{}}}", self.function_name)
581        } else {
582            write!(f, "{}", self.function_name)
583        }
584    }
585}
586
587#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
588pub struct FunctionType {
589    pub parameter_types: Vec<InferredType>,
590    pub return_type: Option<InferredType>,
591}
592
593impl FunctionType {
594    pub fn as_type_variant(&self) -> Option<TypeVariant> {
595        let analysed_type = AnalysedType::try_from(&self.return_type.clone()?).ok()?;
596
597        match analysed_type {
598            AnalysedType::Variant(type_variant) => Some(type_variant),
599            _ => None,
600        }
601    }
602
603    pub fn as_type_enum(&self) -> Option<TypeEnum> {
604        let analysed_type = AnalysedType::try_from(&self.return_type.clone()?).ok()?;
605        match analysed_type {
606            AnalysedType::Enum(type_enum) => Some(type_enum),
607            _ => None,
608        }
609    }
610
611    pub fn parameter_types(&self) -> Vec<AnalysedType> {
612        self.parameter_types
613            .iter()
614            .map(|x| AnalysedType::try_from(x).unwrap())
615            .collect()
616    }
617
618    pub fn return_type(&self) -> Option<AnalysedType> {
619        self.return_type
620            .clone()
621            .map(|x| AnalysedType::try_from(&x).unwrap())
622    }
623}
624
625#[cfg(feature = "protobuf")]
626mod protobuf {
627    use crate::FunctionName;
628
629    impl TryFrom<golem_api_grpc::proto::golem::rib::function_name_type::FunctionName> for FunctionName {
630        type Error = String;
631
632        fn try_from(
633            value: golem_api_grpc::proto::golem::rib::function_name_type::FunctionName,
634        ) -> Result<Self, Self::Error> {
635            match value {
636                golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::VariantName(name) => {
637                    Ok(FunctionName::Variant(name))
638                }
639                golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::EnumName(name) => {
640                    Ok(FunctionName::Enum(name))
641                }
642                golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::Function(fqfn) => {
643                    Ok(FunctionName::Function(fqfn.try_into()?))
644                }
645                golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::ResourceConstructor(fqrc) => {
646                    Ok(FunctionName::ResourceConstructor(fqrc.try_into()?))
647                }
648                golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::ResourceMethod(fqrm) => {
649                    Ok(FunctionName::ResourceMethod(fqrm.try_into()?))
650                }
651            }
652        }
653    }
654
655    impl From<FunctionName> for golem_api_grpc::proto::golem::rib::function_name_type::FunctionName {
656        fn from(value: FunctionName) -> Self {
657            match value {
658                FunctionName::Variant(name) => {
659                    golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::VariantName(name)
660                }
661                FunctionName::Enum(name) => {
662                    golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::EnumName(name)
663                }
664                FunctionName::Function(fqfn) => {
665                    golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::Function(fqfn.into())
666                }
667                FunctionName::ResourceConstructor(fqrc) => {
668                    golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::ResourceConstructor(fqrc.into())
669                }
670                FunctionName::ResourceMethod(fqrm) => {
671                    golem_api_grpc::proto::golem::rib::function_name_type::FunctionName::ResourceMethod(fqrm.into())
672                }
673            }
674        }
675    }
676}