rib/
function_name.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 bincode::{BorrowDecode, Decode, Encode};
16use combine::stream::position::Stream;
17use combine::{eof, EasyParser, Parser};
18use semver::{BuildMetadata, Prerelease};
19use serde::{Deserialize, Serialize};
20use std::borrow::Cow;
21use std::fmt::Display;
22
23#[derive(PartialEq, Hash, Eq, Clone, Ord, PartialOrd)]
24pub struct SemVer(pub semver::Version);
25
26impl SemVer {
27    pub fn parse(version: &str) -> Result<Self, String> {
28        semver::Version::parse(version)
29            .map(SemVer)
30            .map_err(|e| format!("Invalid semver string: {e}"))
31    }
32}
33
34impl std::fmt::Debug for SemVer {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}", self.0)
37    }
38}
39
40impl Encode for SemVer {
41    fn encode<E: bincode::enc::Encoder>(
42        &self,
43        encoder: &mut E,
44    ) -> Result<(), bincode::error::EncodeError> {
45        self.0.major.encode(encoder)?;
46        self.0.minor.encode(encoder)?;
47        self.0.patch.encode(encoder)?;
48        self.0.pre.as_str().encode(encoder)?;
49        self.0.build.as_str().encode(encoder)?;
50        Ok(())
51    }
52}
53
54impl<Context> Decode<Context> for SemVer {
55    fn decode<D: bincode::de::Decoder<Context = Context>>(
56        decoder: &mut D,
57    ) -> Result<Self, bincode::error::DecodeError> {
58        let major = u64::decode(decoder)?;
59        let minor = u64::decode(decoder)?;
60        let patch = u64::decode(decoder)?;
61        let pre_str = String::decode(decoder)?;
62        let build_str = String::decode(decoder)?;
63        let pre = Prerelease::new(&pre_str)
64            .map_err(|_| bincode::error::DecodeError::OtherString("Invalid prerelease".into()))?;
65        let build = BuildMetadata::new(&build_str).map_err(|_| {
66            bincode::error::DecodeError::OtherString("Invalid build metadata".into())
67        })?;
68
69        Ok(SemVer(semver::Version {
70            major,
71            minor,
72            patch,
73            pre,
74            build,
75        }))
76    }
77}
78
79impl<'de, Context> BorrowDecode<'de, Context> for SemVer {
80    fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = Context>>(
81        decoder: &mut D,
82    ) -> Result<Self, bincode::error::DecodeError> {
83        let major = u64::borrow_decode(decoder)?;
84        let minor = u64::borrow_decode(decoder)?;
85        let patch = u64::borrow_decode(decoder)?;
86        let pre_str = <Cow<'de, str> as BorrowDecode<Context>>::borrow_decode(decoder)?;
87        let build_str = <Cow<'de, str> as BorrowDecode<Context>>::borrow_decode(decoder)?;
88        let pre = Prerelease::new(&pre_str)
89            .map_err(|_| bincode::error::DecodeError::OtherString("Invalid prerelease".into()))?;
90        let build = BuildMetadata::new(&build_str).map_err(|_| {
91            bincode::error::DecodeError::OtherString("Invalid build metadata".into())
92        })?;
93        Ok(SemVer(semver::Version {
94            major,
95            minor,
96            patch,
97            pre,
98            build,
99        }))
100    }
101}
102
103#[derive(Debug, Hash, PartialEq, Eq, Clone, Encode, Decode, Ord, PartialOrd)]
104pub enum ParsedFunctionSite {
105    Global,
106    Interface {
107        name: String,
108    },
109    PackagedInterface {
110        namespace: String,
111        package: String,
112        interface: String,
113        version: Option<SemVer>,
114    },
115}
116
117impl ParsedFunctionSite {
118    pub fn parse(name: impl AsRef<str>) -> Result<Self, String> {
119        ParsedFunctionName::parse(format!("{}.{{x}}", name.as_ref()))
120            .map(|ParsedFunctionName { site, .. }| site)
121    }
122
123    pub fn interface_name(&self) -> Option<String> {
124        match self {
125            Self::Global => None,
126            Self::Interface { name } => Some(name.clone()),
127            Self::PackagedInterface {
128                namespace,
129                package,
130                interface,
131                version: None,
132            } => Some(format!("{namespace}:{package}/{interface}")),
133            Self::PackagedInterface {
134                namespace,
135                package,
136                interface,
137                version: Some(version),
138            } => Some(format!("{namespace}:{package}/{interface}@{}", version.0)),
139        }
140    }
141
142    pub fn unversioned(&self) -> ParsedFunctionSite {
143        match self {
144            ParsedFunctionSite::Global => ParsedFunctionSite::Global,
145            ParsedFunctionSite::Interface { name } => {
146                ParsedFunctionSite::Interface { name: name.clone() }
147            }
148            ParsedFunctionSite::PackagedInterface {
149                namespace,
150                package,
151                interface,
152                version: _,
153            } => ParsedFunctionSite::PackagedInterface {
154                namespace: namespace.clone(),
155                package: package.clone(),
156                interface: interface.clone(),
157                version: None,
158            },
159        }
160    }
161}
162
163#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
164pub enum DynamicParsedFunctionReference {
165    Function { function: String },
166    RawResourceConstructor { resource: String },
167    RawResourceDrop { resource: String },
168    RawResourceMethod { resource: String, method: String },
169    RawResourceStaticMethod { resource: String, method: String },
170}
171
172impl DynamicParsedFunctionReference {
173    pub fn name_pretty(&self) -> String {
174        match self {
175            DynamicParsedFunctionReference::Function { function, .. } => function.clone(),
176            DynamicParsedFunctionReference::RawResourceConstructor { resource, .. } => {
177                resource.to_string()
178            }
179            DynamicParsedFunctionReference::RawResourceDrop { .. } => "drop".to_string(),
180            DynamicParsedFunctionReference::RawResourceMethod { method, .. } => method.to_string(),
181            DynamicParsedFunctionReference::RawResourceStaticMethod { method, .. } => {
182                method.to_string()
183            }
184        }
185    }
186
187    fn to_static(&self) -> ParsedFunctionReference {
188        match self {
189            Self::Function { function } => ParsedFunctionReference::Function {
190                function: function.clone(),
191            },
192            Self::RawResourceConstructor { resource } => {
193                ParsedFunctionReference::RawResourceConstructor {
194                    resource: resource.clone(),
195                }
196            }
197            Self::RawResourceDrop { resource } => ParsedFunctionReference::RawResourceDrop {
198                resource: resource.clone(),
199            },
200            Self::RawResourceMethod { resource, method } => {
201                ParsedFunctionReference::RawResourceMethod {
202                    resource: resource.clone(),
203                    method: method.clone(),
204                }
205            }
206            Self::RawResourceStaticMethod { resource, method } => {
207                ParsedFunctionReference::RawResourceStaticMethod {
208                    resource: resource.clone(),
209                    method: method.clone(),
210                }
211            }
212        }
213    }
214}
215
216#[derive(Debug, PartialEq, Eq, Clone, Hash, Encode, Decode)]
217pub enum ParsedFunctionReference {
218    Function { function: String },
219    RawResourceConstructor { resource: String },
220    RawResourceDrop { resource: String },
221    RawResourceMethod { resource: String, method: String },
222    RawResourceStaticMethod { resource: String, method: String },
223}
224
225impl Display for ParsedFunctionReference {
226    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227        let function_name = match self {
228            Self::Function { function } => function.clone(),
229            Self::RawResourceConstructor { resource } => format!("{resource}.new"),
230            Self::RawResourceMethod { resource, method } => format!("{resource}.{method}"),
231            Self::RawResourceStaticMethod { resource, method } => {
232                format!("[static]{resource}.{method}")
233            }
234            Self::RawResourceDrop { resource } => format!("{resource}.drop"),
235        };
236
237        write!(f, "{function_name}")
238    }
239}
240
241impl ParsedFunctionReference {
242    pub fn function_name(&self) -> String {
243        match self {
244            Self::Function { function, .. } => function.clone(),
245            Self::RawResourceConstructor { resource, .. } => format!("[constructor]{resource}"),
246            Self::RawResourceDrop { resource, .. } => format!("[drop]{resource}"),
247            Self::RawResourceMethod {
248                resource, method, ..
249            } => format!("[method]{resource}.{method}"),
250            Self::RawResourceStaticMethod {
251                resource, method, ..
252            } => format!("[static]{resource}.{method}"),
253        }
254    }
255
256    pub fn resource_method_name(&self) -> Option<String> {
257        match self {
258            Self::RawResourceMethod { method, .. }
259            | Self::RawResourceStaticMethod { method, .. } => Some(method.clone()),
260            _ => None,
261        }
262    }
263
264    pub fn method_as_static(&self) -> Option<ParsedFunctionReference> {
265        match self {
266            Self::RawResourceMethod { resource, method } => Some(Self::RawResourceStaticMethod {
267                resource: resource.clone(),
268                method: method.clone(),
269            }),
270
271            _ => None,
272        }
273    }
274
275    pub fn resource_name(&self) -> Option<&String> {
276        match self {
277            Self::RawResourceConstructor { resource }
278            | Self::RawResourceDrop { resource }
279            | Self::RawResourceMethod { resource, .. }
280            | Self::RawResourceStaticMethod { resource, .. } => Some(resource),
281            _ => None,
282        }
283    }
284}
285
286// DynamicParsedFunctionName is different from ParsedFunctionName.
287// In `DynamicParsedFunctionName` the resource parameters are `Expr` (Rib) while they are `String`
288// in `ParsedFunctionName`.
289// `Expr` implies the real values are yet to be computed, while `String`
290// in ParsedFunctionName is a textual representation of the evaluated values.
291// `Examples`:
292// `DynamicParsedFunctionName` : ns:name/interface.{resource1(identifier1, { field-a: some(identifier2) }).new}
293// `ParsedFunctionName` : ns:name/interface.{resource1("foo", { field-a: some("bar") }).new}
294#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
295pub struct DynamicParsedFunctionName {
296    pub site: ParsedFunctionSite,
297    pub function: DynamicParsedFunctionReference,
298}
299
300impl DynamicParsedFunctionName {
301    pub fn parse(name: impl AsRef<str>) -> Result<Self, String> {
302        let name = name.as_ref();
303
304        let mut parser = crate::parser::call::function_name();
305
306        let result = parser.easy_parse(Stream::new(name));
307
308        match result {
309            Ok((parsed, _)) => Ok(parsed),
310            Err(error) => {
311                let error_message = error.map_position(|p| p.to_string()).to_string();
312                Err(error_message)
313            }
314        }
315    }
316
317    pub fn function_name_with_prefix_identifiers(&self) -> String {
318        self.to_parsed_function_name().function.function_name()
319    }
320
321    // Usually resource name in the real metadata consist of prefixes such as [constructor]
322    // However, the one obtained through the dynamic-parsed-function-name is simple without these prefix
323    pub fn resource_name_simplified(&self) -> Option<String> {
324        self.to_parsed_function_name()
325            .function
326            .resource_name()
327            .cloned()
328    }
329
330    // Usually resource method in the real metadata consist of prefixes such as [method]
331    pub fn resource_method_name_simplified(&self) -> Option<String> {
332        self.to_parsed_function_name()
333            .function
334            .resource_method_name()
335    }
336
337    //
338    pub fn to_parsed_function_name(&self) -> ParsedFunctionName {
339        ParsedFunctionName {
340            site: self.site.clone(),
341            function: self.function.to_static(),
342        }
343    }
344}
345
346impl Display for DynamicParsedFunctionName {
347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348        let function_name = self.to_parsed_function_name().to_string();
349        write!(f, "{function_name}")
350    }
351}
352
353#[derive(Debug, PartialEq, Eq, Clone, Hash, Encode, Decode)]
354pub struct ParsedFunctionName {
355    pub site: ParsedFunctionSite,
356    pub function: ParsedFunctionReference,
357}
358
359impl Serialize for ParsedFunctionName {
360    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
361        let function_name = self.to_string();
362        serializer.serialize_str(&function_name)
363    }
364}
365
366impl<'de> Deserialize<'de> for ParsedFunctionName {
367    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
368    where
369        D: serde::Deserializer<'de>,
370    {
371        let function_name = String::deserialize(deserializer)?;
372        ParsedFunctionName::parse(function_name).map_err(serde::de::Error::custom)
373    }
374}
375
376impl Display for ParsedFunctionName {
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        let function_name = self
379            .site
380            .interface_name()
381            .map_or(self.function.function_name(), |interface| {
382                format!("{}.{{{}}}", interface, self.function)
383            });
384        write!(f, "{function_name}")
385    }
386}
387
388impl ParsedFunctionName {
389    pub fn new(site: ParsedFunctionSite, function: ParsedFunctionReference) -> Self {
390        Self { site, function }
391    }
392
393    pub fn global(name: String) -> Self {
394        Self {
395            site: ParsedFunctionSite::Global,
396            function: ParsedFunctionReference::Function { function: name },
397        }
398    }
399
400    pub fn on_interface(interface: String, function: String) -> Self {
401        Self {
402            site: ParsedFunctionSite::Interface { name: interface },
403            function: ParsedFunctionReference::Function { function },
404        }
405    }
406
407    pub fn parse(name: impl AsRef<str>) -> Result<Self, String> {
408        let name = name.as_ref();
409
410        let mut parser = crate::parser::call::function_name().skip(eof());
411
412        let result = parser.easy_parse(Stream::new(name));
413
414        match result {
415            Ok((parsed, _)) => Ok(parsed.to_parsed_function_name()),
416            Err(error) => {
417                let error_message = error.map_position(|p| p.to_string()).to_string();
418                Err(error_message)
419            }
420        }
421    }
422
423    pub fn site(&self) -> &ParsedFunctionSite {
424        &self.site
425    }
426
427    pub fn function(&self) -> &ParsedFunctionReference {
428        &self.function
429    }
430
431    pub fn method_as_static(&self) -> Option<Self> {
432        self.function.method_as_static().map(|function| Self {
433            site: self.site.clone(),
434            function,
435        })
436    }
437
438    pub fn is_constructor(&self) -> Option<&str> {
439        match &self.function {
440            ParsedFunctionReference::RawResourceConstructor { resource, .. } => Some(resource),
441            _ => None,
442        }
443    }
444
445    pub fn is_method(&self) -> Option<&str> {
446        match &self.function {
447            ParsedFunctionReference::RawResourceMethod { resource, .. }
448            | ParsedFunctionReference::RawResourceStaticMethod { resource, .. } => Some(resource),
449            _ => None,
450        }
451    }
452
453    pub fn is_static_method(&self) -> Option<&str> {
454        match &self.function {
455            ParsedFunctionReference::RawResourceStaticMethod { resource, .. } => Some(resource),
456            _ => None,
457        }
458    }
459
460    pub fn with_site(&self, site: ParsedFunctionSite) -> Self {
461        Self {
462            site,
463            function: self.function.clone(),
464        }
465    }
466}
467
468#[cfg(feature = "protobuf")]
469mod protobuf {
470    use crate::{
471        DynamicParsedFunctionName, DynamicParsedFunctionReference, ParsedFunctionName,
472        ParsedFunctionReference, ParsedFunctionSite, SemVer,
473    };
474    use golem_api_grpc::proto::golem::rib::dynamic_parsed_function_reference::FunctionReference as ProtoDynamicFunctionReference;
475    use semver::{BuildMetadata, Prerelease};
476
477    impl TryFrom<golem_api_grpc::proto::golem::rib::SemVersion> for SemVer {
478        type Error = String;
479
480        fn try_from(
481            value: golem_api_grpc::proto::golem::rib::SemVersion,
482        ) -> Result<Self, Self::Error> {
483            Ok(SemVer(semver::Version {
484                major: value.major,
485                minor: value.minor,
486                patch: value.patch,
487                pre: Prerelease::new(&value.pre).map_err(|_| "Invalid prerelease".to_string())?,
488                build: BuildMetadata::new(&value.build)
489                    .map_err(|_| "Invalid build metadata".to_string())?,
490            }))
491        }
492    }
493
494    impl From<SemVer> for golem_api_grpc::proto::golem::rib::SemVersion {
495        fn from(value: SemVer) -> Self {
496            golem_api_grpc::proto::golem::rib::SemVersion {
497                major: value.0.major,
498                minor: value.0.minor,
499                patch: value.0.patch,
500                pre: value.0.pre.to_string(),
501                build: value.0.build.to_string(),
502            }
503        }
504    }
505
506    impl TryFrom<golem_api_grpc::proto::golem::rib::ParsedFunctionSite> for ParsedFunctionSite {
507        type Error = String;
508
509        fn try_from(
510            value: golem_api_grpc::proto::golem::rib::ParsedFunctionSite,
511        ) -> Result<Self, Self::Error> {
512            let site = value.site.ok_or("Missing site".to_string())?;
513            match site {
514                golem_api_grpc::proto::golem::rib::parsed_function_site::Site::Global(_) => {
515                    Ok(Self::Global)
516                }
517                golem_api_grpc::proto::golem::rib::parsed_function_site::Site::Interface(
518                    golem_api_grpc::proto::golem::rib::InterfaceFunctionSite { name },
519                ) => Ok(Self::Interface { name }),
520                golem_api_grpc::proto::golem::rib::parsed_function_site::Site::PackageInterface(
521                    golem_api_grpc::proto::golem::rib::PackageInterfaceFunctionSite {
522                        namespace,
523                        package,
524                        interface,
525                        version,
526                    },
527                ) => {
528                    let version = match version {
529                        Some(version) => Some(version.try_into()?),
530                        None => None,
531                    };
532
533                    Ok(Self::PackagedInterface {
534                        namespace,
535                        package,
536                        interface,
537                        version,
538                    })
539                }
540            }
541        }
542    }
543
544    impl From<ParsedFunctionSite> for golem_api_grpc::proto::golem::rib::ParsedFunctionSite {
545        fn from(value: ParsedFunctionSite) -> Self {
546            let site = match value {
547                ParsedFunctionSite::Global => {
548                    golem_api_grpc::proto::golem::rib::parsed_function_site::Site::Global(
549                        golem_api_grpc::proto::golem::rib::GlobalFunctionSite {},
550                    )
551                }
552                ParsedFunctionSite::Interface { name } => {
553                    golem_api_grpc::proto::golem::rib::parsed_function_site::Site::Interface(
554                        golem_api_grpc::proto::golem::rib::InterfaceFunctionSite { name },
555                    )
556                }
557                ParsedFunctionSite::PackagedInterface {
558                    namespace,
559                    package,
560                    interface,
561                    version,
562                } => {
563                    golem_api_grpc::proto::golem::rib::parsed_function_site::Site::PackageInterface(
564                        golem_api_grpc::proto::golem::rib::PackageInterfaceFunctionSite {
565                            namespace,
566                            package,
567                            interface,
568                            version: version.map(|v| v.into()),
569                        },
570                    )
571                }
572            };
573            golem_api_grpc::proto::golem::rib::ParsedFunctionSite { site: Some(site) }
574        }
575    }
576
577    impl From<DynamicParsedFunctionReference>
578        for golem_api_grpc::proto::golem::rib::DynamicParsedFunctionReference
579    {
580        fn from(value: DynamicParsedFunctionReference) -> Self {
581            let function = match value {
582                DynamicParsedFunctionReference::Function { function } => ProtoDynamicFunctionReference::Function(
583                    golem_api_grpc::proto::golem::rib::FunctionFunctionReference { function },
584                ),
585                DynamicParsedFunctionReference::RawResourceConstructor { resource } => ProtoDynamicFunctionReference::RawResourceConstructor(
586                    golem_api_grpc::proto::golem::rib::RawResourceConstructorFunctionReference { resource },
587                ),
588                DynamicParsedFunctionReference::RawResourceMethod { resource, method } => ProtoDynamicFunctionReference::RawResourceMethod(
589                    golem_api_grpc::proto::golem::rib::RawResourceMethodFunctionReference { resource, method },
590                ),
591                DynamicParsedFunctionReference::RawResourceStaticMethod { resource, method } => ProtoDynamicFunctionReference::RawResourceStaticMethod(
592                    golem_api_grpc::proto::golem::rib::RawResourceStaticMethodFunctionReference { resource, method },
593                ),
594                DynamicParsedFunctionReference::RawResourceDrop { resource } => ProtoDynamicFunctionReference::RawResourceDrop(
595                    golem_api_grpc::proto::golem::rib::RawResourceDropFunctionReference { resource },
596                ),
597            };
598
599            golem_api_grpc::proto::golem::rib::DynamicParsedFunctionReference {
600                function_reference: Some(function),
601            }
602        }
603    }
604
605    impl TryFrom<golem_api_grpc::proto::golem::rib::DynamicParsedFunctionReference>
606        for DynamicParsedFunctionReference
607    {
608        type Error = String;
609
610        fn try_from(
611            value: golem_api_grpc::proto::golem::rib::DynamicParsedFunctionReference,
612        ) -> Result<Self, Self::Error> {
613            let function = value
614                .function_reference
615                .ok_or("Missing function reference".to_string())?;
616
617            match function {
618                ProtoDynamicFunctionReference::Function(
619                    golem_api_grpc::proto::golem::rib::FunctionFunctionReference { function },
620                ) => Ok(Self::Function { function }),
621                ProtoDynamicFunctionReference::RawResourceConstructor(
622                    golem_api_grpc::proto::golem::rib::RawResourceConstructorFunctionReference {
623                        resource,
624                    },
625                ) => Ok(Self::RawResourceConstructor { resource }),
626                ProtoDynamicFunctionReference::RawResourceMethod(
627                    golem_api_grpc::proto::golem::rib::RawResourceMethodFunctionReference {
628                        resource,
629                        method,
630                    },
631                ) => Ok(Self::RawResourceMethod { resource, method }),
632                ProtoDynamicFunctionReference::RawResourceStaticMethod(
633                    golem_api_grpc::proto::golem::rib::RawResourceStaticMethodFunctionReference {
634                        resource,
635                        method,
636                    },
637                ) => Ok(Self::RawResourceStaticMethod { resource, method }),
638                ProtoDynamicFunctionReference::RawResourceDrop(
639                    golem_api_grpc::proto::golem::rib::RawResourceDropFunctionReference {
640                        resource,
641                    },
642                ) => Ok(Self::RawResourceDrop { resource }),
643            }
644        }
645    }
646
647    impl TryFrom<golem_api_grpc::proto::golem::rib::ParsedFunctionReference>
648        for ParsedFunctionReference
649    {
650        type Error = String;
651
652        fn try_from(
653            value: golem_api_grpc::proto::golem::rib::ParsedFunctionReference,
654        ) -> Result<Self, Self::Error> {
655            let function = value
656                .function_reference
657                .ok_or("Missing function".to_string())?;
658            match function {
659                golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::Function(golem_api_grpc::proto::golem::rib::FunctionFunctionReference {
660                                                                                                              function
661                                                                                                          }) => {
662                    Ok(Self::Function { function })
663                }
664                golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceConstructor(golem_api_grpc::proto::golem::rib::RawResourceConstructorFunctionReference {
665                                                                                                                            resource
666                                                                                                                        }) => {
667                    Ok(Self::RawResourceConstructor { resource })
668                }
669                golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceMethod(golem_api_grpc::proto::golem::rib::RawResourceMethodFunctionReference {
670                                                                                                                       resource,
671                                                                                                                       method
672                                                                                                                   }) => {
673                    Ok(Self::RawResourceMethod { resource, method })
674                }
675                golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceStaticMethod(golem_api_grpc::proto::golem::rib::RawResourceStaticMethodFunctionReference {
676                                                                                                                             resource,
677                                                                                                                             method
678                                                                                                                         }) => {
679                    Ok(Self::RawResourceStaticMethod { resource, method })
680                }
681                golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceDrop(golem_api_grpc::proto::golem::rib::RawResourceDropFunctionReference {
682                                                                                                                     resource
683                                                                                                                 }) => {
684                    Ok(Self::RawResourceDrop { resource })
685                }
686            }
687        }
688    }
689
690    impl From<ParsedFunctionReference> for golem_api_grpc::proto::golem::rib::ParsedFunctionReference {
691        fn from(value: ParsedFunctionReference) -> Self {
692            let function = match value {
693                ParsedFunctionReference::Function { function } => golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::Function(
694                    golem_api_grpc::proto::golem::rib::FunctionFunctionReference { function },
695                ),
696                ParsedFunctionReference::RawResourceConstructor { resource } => {
697                    golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceConstructor(
698                        golem_api_grpc::proto::golem::rib::RawResourceConstructorFunctionReference {
699                            resource,
700                        },
701                    )
702                }
703                ParsedFunctionReference::RawResourceMethod { resource, method } => {
704                    golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceMethod(
705                        golem_api_grpc::proto::golem::rib::RawResourceMethodFunctionReference {
706                            resource,
707                            method,
708                        },
709                    )
710                }
711                ParsedFunctionReference::RawResourceStaticMethod { resource, method } => {
712                    golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceStaticMethod(
713                        golem_api_grpc::proto::golem::rib::RawResourceStaticMethodFunctionReference {
714                            resource,
715                            method,
716                        },
717                    )
718                }
719                ParsedFunctionReference::RawResourceDrop { resource } => golem_api_grpc::proto::golem::rib::parsed_function_reference::FunctionReference::RawResourceDrop(
720                    golem_api_grpc::proto::golem::rib::RawResourceDropFunctionReference { resource },
721                ),
722            };
723            golem_api_grpc::proto::golem::rib::ParsedFunctionReference {
724                function_reference: Some(function),
725            }
726        }
727    }
728
729    impl TryFrom<golem_api_grpc::proto::golem::rib::DynamicParsedFunctionName>
730        for DynamicParsedFunctionName
731    {
732        type Error = String;
733
734        fn try_from(
735            value: golem_api_grpc::proto::golem::rib::DynamicParsedFunctionName,
736        ) -> Result<Self, Self::Error> {
737            let site = ParsedFunctionSite::try_from(value.site.ok_or("Missing site".to_string())?)?;
738            let function = DynamicParsedFunctionReference::try_from(
739                value.function.ok_or("Missing function".to_string())?,
740            )?;
741            Ok(Self { site, function })
742        }
743    }
744
745    impl TryFrom<golem_api_grpc::proto::golem::rib::ParsedFunctionName> for ParsedFunctionName {
746        type Error = String;
747
748        fn try_from(
749            value: golem_api_grpc::proto::golem::rib::ParsedFunctionName,
750        ) -> Result<Self, Self::Error> {
751            let site = ParsedFunctionSite::try_from(value.site.ok_or("Missing site".to_string())?)?;
752            let function = ParsedFunctionReference::try_from(
753                value.function.ok_or("Missing function".to_string())?,
754            )?;
755            Ok(Self { site, function })
756        }
757    }
758
759    impl From<ParsedFunctionName> for golem_api_grpc::proto::golem::rib::ParsedFunctionName {
760        fn from(value: ParsedFunctionName) -> Self {
761            golem_api_grpc::proto::golem::rib::ParsedFunctionName {
762                site: Some(value.site.into()),
763                function: Some(value.function.into()),
764            }
765        }
766    }
767
768    impl From<DynamicParsedFunctionName>
769        for golem_api_grpc::proto::golem::rib::DynamicParsedFunctionName
770    {
771        fn from(value: DynamicParsedFunctionName) -> Self {
772            golem_api_grpc::proto::golem::rib::DynamicParsedFunctionName {
773                site: Some(value.site.into()),
774                function: Some(value.function.into()),
775            }
776        }
777    }
778}
779
780#[cfg(test)]
781mod function_name_tests {
782    use super::{ParsedFunctionName, ParsedFunctionReference, ParsedFunctionSite, SemVer};
783    use test_r::test;
784
785    #[test]
786    fn parse_function_name_does_not_accept_partial_matches() {
787        let result = ParsedFunctionName::parse("x:y/z");
788        assert!(result.is_err());
789    }
790
791    #[test]
792    fn parse_function_name_global() {
793        let parsed = ParsedFunctionName::parse("run-example").expect("Parsing failed");
794        assert_eq!(parsed.site().interface_name(), None);
795        assert_eq!(parsed.function().function_name(), "run-example");
796        assert_eq!(
797            parsed,
798            ParsedFunctionName {
799                site: ParsedFunctionSite::Global,
800                function: ParsedFunctionReference::Function {
801                    function: "run-example".to_string()
802                },
803            }
804        );
805    }
806
807    #[test]
808    fn parse_function_name_in_exported_interface_no_package() {
809        let parsed = ParsedFunctionName::parse("interface.{fn1}").expect("Parsing failed");
810        assert_eq!(
811            parsed.site().interface_name(),
812            Some("interface".to_string())
813        );
814        assert_eq!(parsed.function().function_name(), "fn1".to_string());
815        assert_eq!(
816            parsed,
817            ParsedFunctionName {
818                site: ParsedFunctionSite::Interface {
819                    name: "interface".to_string()
820                },
821                function: ParsedFunctionReference::Function {
822                    function: "fn1".to_string()
823                },
824            }
825        );
826    }
827
828    #[test]
829    fn parse_function_name_in_exported_interface() {
830        let parsed = ParsedFunctionName::parse("ns:name/interface.{fn1}").expect("Parsing failed");
831        assert_eq!(
832            parsed.site().interface_name(),
833            Some("ns:name/interface".to_string())
834        );
835        assert_eq!(parsed.function().function_name(), "fn1".to_string());
836        assert_eq!(
837            parsed,
838            ParsedFunctionName {
839                site: ParsedFunctionSite::PackagedInterface {
840                    namespace: "ns".to_string(),
841                    package: "name".to_string(),
842                    interface: "interface".to_string(),
843                    version: None,
844                },
845                function: ParsedFunctionReference::Function {
846                    function: "fn1".to_string()
847                },
848            }
849        );
850    }
851
852    #[test]
853    fn parse_function_name_in_versioned_exported_interface() {
854        let parsed = ParsedFunctionName::parse("wasi:cli/run@0.2.0.{run}").expect("Parsing failed");
855        assert_eq!(
856            parsed.site().interface_name(),
857            Some("wasi:cli/run@0.2.0".to_string())
858        );
859        assert_eq!(parsed.function().function_name(), "run".to_string());
860        assert_eq!(
861            parsed,
862            ParsedFunctionName {
863                site: ParsedFunctionSite::PackagedInterface {
864                    namespace: "wasi".to_string(),
865                    package: "cli".to_string(),
866                    interface: "run".to_string(),
867                    version: Some(SemVer(semver::Version::new(0, 2, 0))),
868                },
869                function: ParsedFunctionReference::Function {
870                    function: "run".to_string()
871                },
872            }
873        );
874    }
875
876    #[test]
877    fn parse_function_name_constructor_syntax_sugar() {
878        let parsed =
879            ParsedFunctionName::parse("ns:name/interface.{resource1.new}").expect("Parsing failed");
880        assert_eq!(
881            parsed.site().interface_name(),
882            Some("ns:name/interface".to_string())
883        );
884        assert_eq!(
885            parsed.function().function_name(),
886            "[constructor]resource1".to_string()
887        );
888        assert_eq!(
889            parsed,
890            ParsedFunctionName {
891                site: ParsedFunctionSite::PackagedInterface {
892                    namespace: "ns".to_string(),
893                    package: "name".to_string(),
894                    interface: "interface".to_string(),
895                    version: None,
896                },
897                function: ParsedFunctionReference::RawResourceConstructor {
898                    resource: "resource1".to_string()
899                },
900            }
901        );
902    }
903
904    #[test]
905    fn parse_function_name_constructor() {
906        let parsed = ParsedFunctionName::parse("ns:name/interface.{[constructor]resource1}")
907            .expect("Parsing failed");
908        assert_eq!(
909            parsed.site().interface_name(),
910            Some("ns:name/interface".to_string())
911        );
912        assert_eq!(
913            parsed.function().function_name(),
914            "[constructor]resource1".to_string()
915        );
916        assert_eq!(
917            parsed,
918            ParsedFunctionName {
919                site: ParsedFunctionSite::PackagedInterface {
920                    namespace: "ns".to_string(),
921                    package: "name".to_string(),
922                    interface: "interface".to_string(),
923                    version: None,
924                },
925                function: ParsedFunctionReference::RawResourceConstructor {
926                    resource: "resource1".to_string()
927                },
928            }
929        );
930    }
931
932    #[test]
933    fn parse_function_name_method_syntax_sugar() {
934        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.do-something}")
935            .expect("Parsing failed");
936        assert_eq!(
937            parsed.site().interface_name(),
938            Some("ns:name/interface".to_string())
939        );
940        assert_eq!(
941            parsed.function().function_name(),
942            "[method]resource1.do-something".to_string()
943        );
944        assert_eq!(
945            parsed,
946            ParsedFunctionName {
947                site: ParsedFunctionSite::PackagedInterface {
948                    namespace: "ns".to_string(),
949                    package: "name".to_string(),
950                    interface: "interface".to_string(),
951                    version: None,
952                },
953                function: ParsedFunctionReference::RawResourceMethod {
954                    resource: "resource1".to_string(),
955                    method: "do-something".to_string(),
956                },
957            }
958        );
959    }
960
961    #[test]
962    fn parse_function_name_method() {
963        let parsed =
964            ParsedFunctionName::parse("ns:name/interface.{[method]resource1.do-something}")
965                .expect("Parsing failed");
966        assert_eq!(
967            parsed.site().interface_name(),
968            Some("ns:name/interface".to_string())
969        );
970        assert_eq!(
971            parsed.function().function_name(),
972            "[method]resource1.do-something".to_string()
973        );
974        assert_eq!(
975            parsed,
976            ParsedFunctionName {
977                site: ParsedFunctionSite::PackagedInterface {
978                    namespace: "ns".to_string(),
979                    package: "name".to_string(),
980                    interface: "interface".to_string(),
981                    version: None,
982                },
983                function: ParsedFunctionReference::RawResourceMethod {
984                    resource: "resource1".to_string(),
985                    method: "do-something".to_string(),
986                },
987            }
988        );
989    }
990
991    #[test]
992    fn parse_function_name_static_method_syntax_sugar() {
993        // Note: the syntax sugared version cannot distinguish between method and static - so we need to check the actual existence of
994        // the function and fallback.
995        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.do-something-static}")
996            .expect("Parsing failed")
997            .method_as_static()
998            .unwrap();
999        assert_eq!(
1000            parsed.site().interface_name(),
1001            Some("ns:name/interface".to_string())
1002        );
1003        assert_eq!(
1004            parsed.function().function_name(),
1005            "[static]resource1.do-something-static".to_string()
1006        );
1007        assert_eq!(
1008            parsed,
1009            ParsedFunctionName {
1010                site: ParsedFunctionSite::PackagedInterface {
1011                    namespace: "ns".to_string(),
1012                    package: "name".to_string(),
1013                    interface: "interface".to_string(),
1014                    version: None,
1015                },
1016                function: ParsedFunctionReference::RawResourceStaticMethod {
1017                    resource: "resource1".to_string(),
1018                    method: "do-something-static".to_string(),
1019                },
1020            }
1021        );
1022    }
1023
1024    #[test]
1025    fn parse_function_name_static() {
1026        let parsed =
1027            ParsedFunctionName::parse("ns:name/interface.{[static]resource1.do-something-static}")
1028                .expect("Parsing failed");
1029        assert_eq!(
1030            parsed.site().interface_name(),
1031            Some("ns:name/interface".to_string())
1032        );
1033        assert_eq!(
1034            parsed.function().function_name(),
1035            "[static]resource1.do-something-static".to_string()
1036        );
1037        assert_eq!(
1038            parsed,
1039            ParsedFunctionName {
1040                site: ParsedFunctionSite::PackagedInterface {
1041                    namespace: "ns".to_string(),
1042                    package: "name".to_string(),
1043                    interface: "interface".to_string(),
1044                    version: None,
1045                },
1046                function: ParsedFunctionReference::RawResourceStaticMethod {
1047                    resource: "resource1".to_string(),
1048                    method: "do-something-static".to_string(),
1049                },
1050            }
1051        );
1052    }
1053
1054    #[test]
1055    fn parse_function_name_drop_syntax_sugar() {
1056        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.drop}")
1057            .expect("Parsing failed");
1058        assert_eq!(
1059            parsed.site().interface_name(),
1060            Some("ns:name/interface".to_string())
1061        );
1062        assert_eq!(
1063            parsed.function().function_name(),
1064            "[drop]resource1".to_string()
1065        );
1066        assert_eq!(
1067            parsed,
1068            ParsedFunctionName {
1069                site: ParsedFunctionSite::PackagedInterface {
1070                    namespace: "ns".to_string(),
1071                    package: "name".to_string(),
1072                    interface: "interface".to_string(),
1073                    version: None,
1074                },
1075                function: ParsedFunctionReference::RawResourceDrop {
1076                    resource: "resource1".to_string()
1077                },
1078            }
1079        );
1080    }
1081
1082    #[test]
1083    fn parse_function_name_drop() {
1084        let parsed = ParsedFunctionName::parse("ns:name/interface.{[drop]resource1}")
1085            .expect("Parsing failed");
1086        assert_eq!(
1087            parsed.site().interface_name(),
1088            Some("ns:name/interface".to_string())
1089        );
1090        assert_eq!(
1091            parsed.function().function_name(),
1092            "[drop]resource1".to_string()
1093        );
1094        assert_eq!(
1095            parsed,
1096            ParsedFunctionName {
1097                site: ParsedFunctionSite::PackagedInterface {
1098                    namespace: "ns".to_string(),
1099                    package: "name".to_string(),
1100                    interface: "interface".to_string(),
1101                    version: None,
1102                },
1103                function: ParsedFunctionReference::RawResourceDrop {
1104                    resource: "resource1".to_string()
1105                },
1106            }
1107        );
1108    }
1109
1110    fn round_trip_function_name_parse(input: &str) {
1111        let parsed = ParsedFunctionName::parse(input)
1112            .unwrap_or_else(|_| panic!("Input Parsing failed for {input}"));
1113        let parsed_written =
1114            ParsedFunctionName::parse(parsed.to_string()).expect("Round-trip parsing failed");
1115        assert_eq!(parsed, parsed_written);
1116    }
1117
1118    #[test]
1119    fn test_parsed_function_name_display() {
1120        round_trip_function_name_parse("run-example");
1121        round_trip_function_name_parse("interface.{fn1}");
1122        round_trip_function_name_parse("wasi:cli/run@0.2.0.{run}");
1123        round_trip_function_name_parse("ns:name/interface.{resource1.new}");
1124        round_trip_function_name_parse("ns:name/interface.{[constructor]resource1}");
1125        round_trip_function_name_parse("ns:name/interface.{resource1.do-something}");
1126        round_trip_function_name_parse("ns:name/interface.{[static]resource1.do-something-static}");
1127        round_trip_function_name_parse("ns:name/interface.{resource1.drop}");
1128        round_trip_function_name_parse("ns:name/interface.{[drop]resource1}");
1129    }
1130}