Skip to main content

rib/
function_name.rs

1use combine::stream::position::Stream;
2use combine::{eof, EasyParser, Parser};
3
4use serde::{Deserialize, Serialize};
5use std::fmt::Display;
6
7#[derive(PartialEq, Hash, Eq, Clone, Ord, PartialOrd)]
8pub struct SemVer(pub semver::Version);
9
10impl SemVer {
11    pub fn parse(version: &str) -> Result<Self, String> {
12        semver::Version::parse(version)
13            .map(SemVer)
14            .map_err(|e| format!("Invalid semver string: {e}"))
15    }
16}
17
18impl std::fmt::Debug for SemVer {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        write!(f, "{}", self.0)
21    }
22}
23
24#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
25pub enum ParsedFunctionSite {
26    Global,
27    Interface {
28        name: String,
29    },
30    PackagedInterface {
31        namespace: String,
32        package: String,
33        interface: String,
34        version: Option<SemVer>,
35    },
36}
37
38impl ParsedFunctionSite {
39    pub fn parse(name: impl AsRef<str>) -> Result<Self, String> {
40        ParsedFunctionName::parse(format!("{}.{{x}}", name.as_ref()))
41            .map(|ParsedFunctionName { site, .. }| site)
42    }
43
44    pub fn interface_name(&self) -> Option<String> {
45        match self {
46            Self::Global => None,
47            Self::Interface { name } => Some(name.clone()),
48            Self::PackagedInterface {
49                namespace,
50                package,
51                interface,
52                version: None,
53            } => Some(format!("{namespace}:{package}/{interface}")),
54            Self::PackagedInterface {
55                namespace,
56                package,
57                interface,
58                version: Some(version),
59            } => Some(format!("{namespace}:{package}/{interface}@{}", version.0)),
60        }
61    }
62
63    pub fn unversioned(&self) -> ParsedFunctionSite {
64        match self {
65            ParsedFunctionSite::Global => ParsedFunctionSite::Global,
66            ParsedFunctionSite::Interface { name } => {
67                ParsedFunctionSite::Interface { name: name.clone() }
68            }
69            ParsedFunctionSite::PackagedInterface {
70                namespace,
71                package,
72                interface,
73                version: _,
74            } => ParsedFunctionSite::PackagedInterface {
75                namespace: namespace.clone(),
76                package: package.clone(),
77                interface: interface.clone(),
78                version: None,
79            },
80        }
81    }
82}
83
84#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
85pub enum DynamicParsedFunctionReference {
86    Function { function: String },
87    RawResourceConstructor { resource: String },
88    RawResourceDrop { resource: String },
89    RawResourceMethod { resource: String, method: String },
90    RawResourceStaticMethod { resource: String, method: String },
91}
92
93impl DynamicParsedFunctionReference {
94    pub fn name_pretty(&self) -> String {
95        match self {
96            DynamicParsedFunctionReference::Function { function, .. } => function.clone(),
97            DynamicParsedFunctionReference::RawResourceConstructor { resource, .. } => {
98                resource.to_string()
99            }
100            DynamicParsedFunctionReference::RawResourceDrop { .. } => "drop".to_string(),
101            DynamicParsedFunctionReference::RawResourceMethod { method, .. } => method.to_string(),
102            DynamicParsedFunctionReference::RawResourceStaticMethod { method, .. } => {
103                method.to_string()
104            }
105        }
106    }
107
108    fn to_static(&self) -> ParsedFunctionReference {
109        match self {
110            Self::Function { function } => ParsedFunctionReference::Function {
111                function: function.clone(),
112            },
113            Self::RawResourceConstructor { resource } => {
114                ParsedFunctionReference::RawResourceConstructor {
115                    resource: resource.clone(),
116                }
117            }
118            Self::RawResourceDrop { resource } => ParsedFunctionReference::RawResourceDrop {
119                resource: resource.clone(),
120            },
121            Self::RawResourceMethod { resource, method } => {
122                ParsedFunctionReference::RawResourceMethod {
123                    resource: resource.clone(),
124                    method: method.clone(),
125                }
126            }
127            Self::RawResourceStaticMethod { resource, method } => {
128                ParsedFunctionReference::RawResourceStaticMethod {
129                    resource: resource.clone(),
130                    method: method.clone(),
131                }
132            }
133        }
134    }
135}
136
137#[derive(Debug, PartialEq, Eq, Clone, Hash)]
138pub enum ParsedFunctionReference {
139    Function { function: String },
140    RawResourceConstructor { resource: String },
141    RawResourceDrop { resource: String },
142    RawResourceMethod { resource: String, method: String },
143    RawResourceStaticMethod { resource: String, method: String },
144}
145
146impl Display for ParsedFunctionReference {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        let function_name = match self {
149            Self::Function { function } => function.clone(),
150            Self::RawResourceConstructor { resource } => format!("{resource}.new"),
151            Self::RawResourceMethod { resource, method } => format!("{resource}.{method}"),
152            Self::RawResourceStaticMethod { resource, method } => {
153                format!("[static]{resource}.{method}")
154            }
155            Self::RawResourceDrop { resource } => format!("{resource}.drop"),
156        };
157
158        write!(f, "{function_name}")
159    }
160}
161
162impl ParsedFunctionReference {
163    pub fn function_name(&self) -> String {
164        match self {
165            Self::Function { function, .. } => function.clone(),
166            Self::RawResourceConstructor { resource, .. } => format!("[constructor]{resource}"),
167            Self::RawResourceDrop { resource, .. } => format!("[drop]{resource}"),
168            Self::RawResourceMethod {
169                resource, method, ..
170            } => format!("[method]{resource}.{method}"),
171            Self::RawResourceStaticMethod {
172                resource, method, ..
173            } => format!("[static]{resource}.{method}"),
174        }
175    }
176
177    pub fn resource_method_name(&self) -> Option<String> {
178        match self {
179            Self::RawResourceMethod { method, .. }
180            | Self::RawResourceStaticMethod { method, .. } => Some(method.clone()),
181            _ => None,
182        }
183    }
184
185    pub fn method_as_static(&self) -> Option<ParsedFunctionReference> {
186        match self {
187            Self::RawResourceMethod { resource, method } => Some(Self::RawResourceStaticMethod {
188                resource: resource.clone(),
189                method: method.clone(),
190            }),
191
192            _ => None,
193        }
194    }
195
196    pub fn resource_name(&self) -> Option<&String> {
197        match self {
198            Self::RawResourceConstructor { resource }
199            | Self::RawResourceDrop { resource }
200            | Self::RawResourceMethod { resource, .. }
201            | Self::RawResourceStaticMethod { resource, .. } => Some(resource),
202            _ => None,
203        }
204    }
205}
206
207// DynamicParsedFunctionName is different from ParsedFunctionName.
208// In `DynamicParsedFunctionName` the resource parameters are `Expr` (Rib) while they are `String`
209// in `ParsedFunctionName`.
210// `Expr` implies the real values are yet to be computed, while `String`
211// in ParsedFunctionName is a textual representation of the evaluated values.
212// `Examples`:
213// `DynamicParsedFunctionName` : ns:name/interface.{resource1(identifier1, { field-a: some(identifier2) }).new}
214// `ParsedFunctionName` : ns:name/interface.{resource1("foo", { field-a: some("bar") }).new}
215#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
216pub struct DynamicParsedFunctionName {
217    pub site: ParsedFunctionSite,
218    pub function: DynamicParsedFunctionReference,
219}
220
221impl DynamicParsedFunctionName {
222    pub fn parse(name: impl AsRef<str>) -> Result<Self, String> {
223        let name = name.as_ref();
224
225        let mut parser = crate::parser::call::function_name();
226
227        let result = parser.easy_parse(Stream::new(name));
228
229        match result {
230            Ok((parsed, _)) => Ok(parsed),
231            Err(error) => {
232                let error_message = error.map_position(|p| p.to_string()).to_string();
233                Err(error_message)
234            }
235        }
236    }
237
238    pub fn function_name_with_prefix_identifiers(&self) -> String {
239        self.to_parsed_function_name().function.function_name()
240    }
241
242    // Usually resource name in the real metadata consist of prefixes such as [constructor]
243    // However, the one obtained through the dynamic-parsed-function-name is simple without these prefix
244    pub fn resource_name_simplified(&self) -> Option<String> {
245        self.to_parsed_function_name()
246            .function
247            .resource_name()
248            .cloned()
249    }
250
251    // Usually resource method in the real metadata consist of prefixes such as [method]
252    pub fn resource_method_name_simplified(&self) -> Option<String> {
253        self.to_parsed_function_name()
254            .function
255            .resource_method_name()
256    }
257
258    //
259    pub fn to_parsed_function_name(&self) -> ParsedFunctionName {
260        ParsedFunctionName {
261            site: self.site.clone(),
262            function: self.function.to_static(),
263        }
264    }
265}
266
267impl Display for DynamicParsedFunctionName {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        let function_name = self.to_parsed_function_name().to_string();
270        write!(f, "{function_name}")
271    }
272}
273
274#[derive(Debug, PartialEq, Eq, Clone, Hash)]
275pub struct ParsedFunctionName {
276    pub site: ParsedFunctionSite,
277    pub function: ParsedFunctionReference,
278}
279
280impl Serialize for ParsedFunctionName {
281    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
282        let function_name = self.to_string();
283        serializer.serialize_str(&function_name)
284    }
285}
286
287impl<'de> Deserialize<'de> for ParsedFunctionName {
288    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
289    where
290        D: serde::Deserializer<'de>,
291    {
292        let function_name = <String as Deserialize>::deserialize(deserializer)?;
293        ParsedFunctionName::parse(function_name).map_err(serde::de::Error::custom)
294    }
295}
296
297impl Display for ParsedFunctionName {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        let function_name = self
300            .site
301            .interface_name()
302            .map_or(self.function.function_name(), |interface| {
303                format!("{}.{{{}}}", interface, self.function)
304            });
305        write!(f, "{function_name}")
306    }
307}
308
309impl ParsedFunctionName {
310    pub fn new(site: ParsedFunctionSite, function: ParsedFunctionReference) -> Self {
311        Self { site, function }
312    }
313
314    pub fn global(name: String) -> Self {
315        Self {
316            site: ParsedFunctionSite::Global,
317            function: ParsedFunctionReference::Function { function: name },
318        }
319    }
320
321    pub fn on_interface(interface: String, function: String) -> Self {
322        Self {
323            site: ParsedFunctionSite::Interface { name: interface },
324            function: ParsedFunctionReference::Function { function },
325        }
326    }
327
328    pub fn parse(name: impl AsRef<str>) -> Result<Self, String> {
329        let name = name.as_ref();
330
331        let mut parser = crate::parser::call::function_name().skip(eof());
332
333        let result = parser.easy_parse(Stream::new(name));
334
335        match result {
336            Ok((parsed, _)) => Ok(parsed.to_parsed_function_name()),
337            Err(error) => {
338                let error_message = error.map_position(|p| p.to_string()).to_string();
339                Err(error_message)
340            }
341        }
342    }
343
344    pub fn site(&self) -> &ParsedFunctionSite {
345        &self.site
346    }
347
348    pub fn function(&self) -> &ParsedFunctionReference {
349        &self.function
350    }
351
352    pub fn method_as_static(&self) -> Option<Self> {
353        self.function.method_as_static().map(|function| Self {
354            site: self.site.clone(),
355            function,
356        })
357    }
358
359    pub fn is_constructor(&self) -> Option<&str> {
360        match &self.function {
361            ParsedFunctionReference::RawResourceConstructor { resource, .. } => Some(resource),
362            _ => None,
363        }
364    }
365
366    pub fn is_method(&self) -> Option<&str> {
367        match &self.function {
368            ParsedFunctionReference::RawResourceMethod { resource, .. }
369            | ParsedFunctionReference::RawResourceStaticMethod { resource, .. } => Some(resource),
370            _ => None,
371        }
372    }
373
374    pub fn is_static_method(&self) -> Option<&str> {
375        match &self.function {
376            ParsedFunctionReference::RawResourceStaticMethod { resource, .. } => Some(resource),
377            _ => None,
378        }
379    }
380
381    pub fn with_site(&self, site: ParsedFunctionSite) -> Self {
382        Self {
383            site,
384            function: self.function.clone(),
385        }
386    }
387
388    /// Segments for resolving a WebAssembly component export via nested instance names, then the
389    /// function export name.
390    ///
391    /// Packaged WIT paths like `component:pkg/iface.{fn}` identify the interface in metadata, but
392    /// component worlds usually export the interface **instance at the root** as `iface`, not as a
393    /// nested export named `component:pkg`. Runtimes should walk `Component::get_export_index` with
394    /// these segments (for example `["inventory", "lookup-sku"]`), not a path derived from splitting
395    /// the full [`ParsedFunctionSite::interface_name`] string.
396    pub fn wasm_component_export_path(&self) -> Vec<String> {
397        let mut segments: Vec<String> = match &self.site {
398            ParsedFunctionSite::Global => Vec::new(),
399            ParsedFunctionSite::Interface { name } => name
400                .split('/')
401                .filter(|s| !s.is_empty())
402                .map(str::to_string)
403                .collect(),
404            ParsedFunctionSite::PackagedInterface { interface, .. } => vec![interface.clone()],
405        };
406        let leaf = match &self.function {
407            ParsedFunctionReference::Function { function } => function.clone(),
408            _ => self.function.function_name(),
409        };
410        segments.push(leaf);
411        segments
412    }
413
414    /// Like [`wasm_component_export_path`](Self::wasm_component_export_path), but includes common
415    /// alternate spellings for the **last** segment (WIT kebab-case vs snake_case in lowered names).
416    pub fn wasm_component_export_path_candidates(&self) -> Vec<Vec<String>> {
417        let primary = self.wasm_component_export_path();
418        let mut out: Vec<Vec<String>> = Vec::new();
419        let mut push = |p: Vec<String>| {
420            if !out.iter().any(|e| e == &p) {
421                out.push(p);
422            }
423        };
424        push(primary.clone());
425        if let Some(last) = primary.last() {
426            let snake = last.replace('-', "_");
427            if snake != *last {
428                let mut alt = primary.clone();
429                alt.pop();
430                alt.push(snake);
431                push(alt);
432            }
433        }
434        out
435    }
436}
437
438#[cfg(test)]
439mod function_name_tests {
440    use super::{ParsedFunctionName, ParsedFunctionReference, ParsedFunctionSite, SemVer};
441    use test_r::test;
442
443    #[test]
444    fn parse_function_name_does_not_accept_partial_matches() {
445        let result = ParsedFunctionName::parse("x:y/z");
446        assert!(result.is_err());
447    }
448
449    #[test]
450    fn parse_function_name_global() {
451        let parsed = ParsedFunctionName::parse("run-example").expect("Parsing failed");
452        assert_eq!(parsed.site().interface_name(), None);
453        assert_eq!(parsed.function().function_name(), "run-example");
454        assert_eq!(
455            parsed,
456            ParsedFunctionName {
457                site: ParsedFunctionSite::Global,
458                function: ParsedFunctionReference::Function {
459                    function: "run-example".to_string()
460                },
461            }
462        );
463    }
464
465    #[test]
466    fn parse_function_name_in_exported_interface_no_package() {
467        let parsed = ParsedFunctionName::parse("interface.{fn1}").expect("Parsing failed");
468        assert_eq!(
469            parsed.site().interface_name(),
470            Some("interface".to_string())
471        );
472        assert_eq!(parsed.function().function_name(), "fn1".to_string());
473        assert_eq!(
474            parsed,
475            ParsedFunctionName {
476                site: ParsedFunctionSite::Interface {
477                    name: "interface".to_string()
478                },
479                function: ParsedFunctionReference::Function {
480                    function: "fn1".to_string()
481                },
482            }
483        );
484    }
485
486    #[test]
487    fn wasm_component_export_path_packaged_matches_world_root() {
488        let parsed =
489            ParsedFunctionName::parse("component:rib-smoke/inventory.{lookup-sku}").expect("parse");
490        assert_eq!(
491            parsed.wasm_component_export_path(),
492            vec!["inventory", "lookup-sku"]
493        );
494        let cands = parsed.wasm_component_export_path_candidates();
495        assert!(cands.iter().any(|p| p == &vec!["inventory", "lookup-sku"]));
496        assert!(cands.iter().any(|p| p == &vec!["inventory", "lookup_sku"]));
497    }
498
499    #[test]
500    fn parse_function_name_in_exported_interface() {
501        let parsed = ParsedFunctionName::parse("ns:name/interface.{fn1}").expect("Parsing failed");
502        assert_eq!(
503            parsed.site().interface_name(),
504            Some("ns:name/interface".to_string())
505        );
506        assert_eq!(parsed.function().function_name(), "fn1".to_string());
507        assert_eq!(
508            parsed,
509            ParsedFunctionName {
510                site: ParsedFunctionSite::PackagedInterface {
511                    namespace: "ns".to_string(),
512                    package: "name".to_string(),
513                    interface: "interface".to_string(),
514                    version: None,
515                },
516                function: ParsedFunctionReference::Function {
517                    function: "fn1".to_string()
518                },
519            }
520        );
521    }
522
523    #[test]
524    fn parse_function_name_in_versioned_exported_interface() {
525        let parsed = ParsedFunctionName::parse("wasi:cli/run@0.2.0.{run}").expect("Parsing failed");
526        assert_eq!(
527            parsed.site().interface_name(),
528            Some("wasi:cli/run@0.2.0".to_string())
529        );
530        assert_eq!(parsed.function().function_name(), "run".to_string());
531        assert_eq!(
532            parsed,
533            ParsedFunctionName {
534                site: ParsedFunctionSite::PackagedInterface {
535                    namespace: "wasi".to_string(),
536                    package: "cli".to_string(),
537                    interface: "run".to_string(),
538                    version: Some(SemVer(semver::Version::new(0, 2, 0))),
539                },
540                function: ParsedFunctionReference::Function {
541                    function: "run".to_string()
542                },
543            }
544        );
545    }
546
547    #[test]
548    fn parse_function_name_constructor_syntax_sugar() {
549        let parsed =
550            ParsedFunctionName::parse("ns:name/interface.{resource1.new}").expect("Parsing failed");
551        assert_eq!(
552            parsed.site().interface_name(),
553            Some("ns:name/interface".to_string())
554        );
555        assert_eq!(
556            parsed.function().function_name(),
557            "[constructor]resource1".to_string()
558        );
559        assert_eq!(
560            parsed,
561            ParsedFunctionName {
562                site: ParsedFunctionSite::PackagedInterface {
563                    namespace: "ns".to_string(),
564                    package: "name".to_string(),
565                    interface: "interface".to_string(),
566                    version: None,
567                },
568                function: ParsedFunctionReference::RawResourceConstructor {
569                    resource: "resource1".to_string()
570                },
571            }
572        );
573    }
574
575    #[test]
576    fn parse_function_name_constructor() {
577        let parsed = ParsedFunctionName::parse("ns:name/interface.{[constructor]resource1}")
578            .expect("Parsing failed");
579        assert_eq!(
580            parsed.site().interface_name(),
581            Some("ns:name/interface".to_string())
582        );
583        assert_eq!(
584            parsed.function().function_name(),
585            "[constructor]resource1".to_string()
586        );
587        assert_eq!(
588            parsed,
589            ParsedFunctionName {
590                site: ParsedFunctionSite::PackagedInterface {
591                    namespace: "ns".to_string(),
592                    package: "name".to_string(),
593                    interface: "interface".to_string(),
594                    version: None,
595                },
596                function: ParsedFunctionReference::RawResourceConstructor {
597                    resource: "resource1".to_string()
598                },
599            }
600        );
601    }
602
603    #[test]
604    fn parse_function_name_method_syntax_sugar() {
605        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.do-something}")
606            .expect("Parsing failed");
607        assert_eq!(
608            parsed.site().interface_name(),
609            Some("ns:name/interface".to_string())
610        );
611        assert_eq!(
612            parsed.function().function_name(),
613            "[method]resource1.do-something".to_string()
614        );
615        assert_eq!(
616            parsed,
617            ParsedFunctionName {
618                site: ParsedFunctionSite::PackagedInterface {
619                    namespace: "ns".to_string(),
620                    package: "name".to_string(),
621                    interface: "interface".to_string(),
622                    version: None,
623                },
624                function: ParsedFunctionReference::RawResourceMethod {
625                    resource: "resource1".to_string(),
626                    method: "do-something".to_string(),
627                },
628            }
629        );
630    }
631
632    #[test]
633    fn parse_function_name_method() {
634        let parsed =
635            ParsedFunctionName::parse("ns:name/interface.{[method]resource1.do-something}")
636                .expect("Parsing failed");
637        assert_eq!(
638            parsed.site().interface_name(),
639            Some("ns:name/interface".to_string())
640        );
641        assert_eq!(
642            parsed.function().function_name(),
643            "[method]resource1.do-something".to_string()
644        );
645        assert_eq!(
646            parsed,
647            ParsedFunctionName {
648                site: ParsedFunctionSite::PackagedInterface {
649                    namespace: "ns".to_string(),
650                    package: "name".to_string(),
651                    interface: "interface".to_string(),
652                    version: None,
653                },
654                function: ParsedFunctionReference::RawResourceMethod {
655                    resource: "resource1".to_string(),
656                    method: "do-something".to_string(),
657                },
658            }
659        );
660    }
661
662    #[test]
663    fn parse_function_name_static_method_syntax_sugar() {
664        // Note: the syntax sugared version cannot distinguish between method and static - so we need to check the actual existence of
665        // the function and fallback.
666        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.do-something-static}")
667            .expect("Parsing failed")
668            .method_as_static()
669            .unwrap();
670        assert_eq!(
671            parsed.site().interface_name(),
672            Some("ns:name/interface".to_string())
673        );
674        assert_eq!(
675            parsed.function().function_name(),
676            "[static]resource1.do-something-static".to_string()
677        );
678        assert_eq!(
679            parsed,
680            ParsedFunctionName {
681                site: ParsedFunctionSite::PackagedInterface {
682                    namespace: "ns".to_string(),
683                    package: "name".to_string(),
684                    interface: "interface".to_string(),
685                    version: None,
686                },
687                function: ParsedFunctionReference::RawResourceStaticMethod {
688                    resource: "resource1".to_string(),
689                    method: "do-something-static".to_string(),
690                },
691            }
692        );
693    }
694
695    #[test]
696    fn parse_function_name_static() {
697        let parsed =
698            ParsedFunctionName::parse("ns:name/interface.{[static]resource1.do-something-static}")
699                .expect("Parsing failed");
700        assert_eq!(
701            parsed.site().interface_name(),
702            Some("ns:name/interface".to_string())
703        );
704        assert_eq!(
705            parsed.function().function_name(),
706            "[static]resource1.do-something-static".to_string()
707        );
708        assert_eq!(
709            parsed,
710            ParsedFunctionName {
711                site: ParsedFunctionSite::PackagedInterface {
712                    namespace: "ns".to_string(),
713                    package: "name".to_string(),
714                    interface: "interface".to_string(),
715                    version: None,
716                },
717                function: ParsedFunctionReference::RawResourceStaticMethod {
718                    resource: "resource1".to_string(),
719                    method: "do-something-static".to_string(),
720                },
721            }
722        );
723    }
724
725    #[test]
726    fn parse_function_name_drop_syntax_sugar() {
727        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.drop}")
728            .expect("Parsing failed");
729        assert_eq!(
730            parsed.site().interface_name(),
731            Some("ns:name/interface".to_string())
732        );
733        assert_eq!(
734            parsed.function().function_name(),
735            "[drop]resource1".to_string()
736        );
737        assert_eq!(
738            parsed,
739            ParsedFunctionName {
740                site: ParsedFunctionSite::PackagedInterface {
741                    namespace: "ns".to_string(),
742                    package: "name".to_string(),
743                    interface: "interface".to_string(),
744                    version: None,
745                },
746                function: ParsedFunctionReference::RawResourceDrop {
747                    resource: "resource1".to_string()
748                },
749            }
750        );
751    }
752
753    #[test]
754    fn parse_function_name_drop() {
755        let parsed = ParsedFunctionName::parse("ns:name/interface.{[drop]resource1}")
756            .expect("Parsing failed");
757        assert_eq!(
758            parsed.site().interface_name(),
759            Some("ns:name/interface".to_string())
760        );
761        assert_eq!(
762            parsed.function().function_name(),
763            "[drop]resource1".to_string()
764        );
765        assert_eq!(
766            parsed,
767            ParsedFunctionName {
768                site: ParsedFunctionSite::PackagedInterface {
769                    namespace: "ns".to_string(),
770                    package: "name".to_string(),
771                    interface: "interface".to_string(),
772                    version: None,
773                },
774                function: ParsedFunctionReference::RawResourceDrop {
775                    resource: "resource1".to_string()
776                },
777            }
778        );
779    }
780
781    fn round_trip_function_name_parse(input: &str) {
782        let parsed = ParsedFunctionName::parse(input)
783            .unwrap_or_else(|_| panic!("Input Parsing failed for {input}"));
784        let parsed_written =
785            ParsedFunctionName::parse(parsed.to_string()).expect("Round-trip parsing failed");
786        assert_eq!(parsed, parsed_written);
787    }
788
789    #[test]
790    fn test_parsed_function_name_display() {
791        round_trip_function_name_parse("run-example");
792        round_trip_function_name_parse("interface.{fn1}");
793        round_trip_function_name_parse("wasi:cli/run@0.2.0.{run}");
794        round_trip_function_name_parse("ns:name/interface.{resource1.new}");
795        round_trip_function_name_parse("ns:name/interface.{[constructor]resource1}");
796        round_trip_function_name_parse("ns:name/interface.{resource1.do-something}");
797        round_trip_function_name_parse("ns:name/interface.{[static]resource1.do-something-static}");
798        round_trip_function_name_parse("ns:name/interface.{resource1.drop}");
799        round_trip_function_name_parse("ns:name/interface.{[drop]resource1}");
800    }
801}