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
389#[cfg(test)]
390mod function_name_tests {
391    use super::{ParsedFunctionName, ParsedFunctionReference, ParsedFunctionSite, SemVer};
392    use test_r::test;
393
394    #[test]
395    fn parse_function_name_does_not_accept_partial_matches() {
396        let result = ParsedFunctionName::parse("x:y/z");
397        assert!(result.is_err());
398    }
399
400    #[test]
401    fn parse_function_name_global() {
402        let parsed = ParsedFunctionName::parse("run-example").expect("Parsing failed");
403        assert_eq!(parsed.site().interface_name(), None);
404        assert_eq!(parsed.function().function_name(), "run-example");
405        assert_eq!(
406            parsed,
407            ParsedFunctionName {
408                site: ParsedFunctionSite::Global,
409                function: ParsedFunctionReference::Function {
410                    function: "run-example".to_string()
411                },
412            }
413        );
414    }
415
416    #[test]
417    fn parse_function_name_in_exported_interface_no_package() {
418        let parsed = ParsedFunctionName::parse("interface.{fn1}").expect("Parsing failed");
419        assert_eq!(
420            parsed.site().interface_name(),
421            Some("interface".to_string())
422        );
423        assert_eq!(parsed.function().function_name(), "fn1".to_string());
424        assert_eq!(
425            parsed,
426            ParsedFunctionName {
427                site: ParsedFunctionSite::Interface {
428                    name: "interface".to_string()
429                },
430                function: ParsedFunctionReference::Function {
431                    function: "fn1".to_string()
432                },
433            }
434        );
435    }
436
437    #[test]
438    fn parse_function_name_in_exported_interface() {
439        let parsed = ParsedFunctionName::parse("ns:name/interface.{fn1}").expect("Parsing failed");
440        assert_eq!(
441            parsed.site().interface_name(),
442            Some("ns:name/interface".to_string())
443        );
444        assert_eq!(parsed.function().function_name(), "fn1".to_string());
445        assert_eq!(
446            parsed,
447            ParsedFunctionName {
448                site: ParsedFunctionSite::PackagedInterface {
449                    namespace: "ns".to_string(),
450                    package: "name".to_string(),
451                    interface: "interface".to_string(),
452                    version: None,
453                },
454                function: ParsedFunctionReference::Function {
455                    function: "fn1".to_string()
456                },
457            }
458        );
459    }
460
461    #[test]
462    fn parse_function_name_in_versioned_exported_interface() {
463        let parsed = ParsedFunctionName::parse("wasi:cli/run@0.2.0.{run}").expect("Parsing failed");
464        assert_eq!(
465            parsed.site().interface_name(),
466            Some("wasi:cli/run@0.2.0".to_string())
467        );
468        assert_eq!(parsed.function().function_name(), "run".to_string());
469        assert_eq!(
470            parsed,
471            ParsedFunctionName {
472                site: ParsedFunctionSite::PackagedInterface {
473                    namespace: "wasi".to_string(),
474                    package: "cli".to_string(),
475                    interface: "run".to_string(),
476                    version: Some(SemVer(semver::Version::new(0, 2, 0))),
477                },
478                function: ParsedFunctionReference::Function {
479                    function: "run".to_string()
480                },
481            }
482        );
483    }
484
485    #[test]
486    fn parse_function_name_constructor_syntax_sugar() {
487        let parsed =
488            ParsedFunctionName::parse("ns:name/interface.{resource1.new}").expect("Parsing failed");
489        assert_eq!(
490            parsed.site().interface_name(),
491            Some("ns:name/interface".to_string())
492        );
493        assert_eq!(
494            parsed.function().function_name(),
495            "[constructor]resource1".to_string()
496        );
497        assert_eq!(
498            parsed,
499            ParsedFunctionName {
500                site: ParsedFunctionSite::PackagedInterface {
501                    namespace: "ns".to_string(),
502                    package: "name".to_string(),
503                    interface: "interface".to_string(),
504                    version: None,
505                },
506                function: ParsedFunctionReference::RawResourceConstructor {
507                    resource: "resource1".to_string()
508                },
509            }
510        );
511    }
512
513    #[test]
514    fn parse_function_name_constructor() {
515        let parsed = ParsedFunctionName::parse("ns:name/interface.{[constructor]resource1}")
516            .expect("Parsing failed");
517        assert_eq!(
518            parsed.site().interface_name(),
519            Some("ns:name/interface".to_string())
520        );
521        assert_eq!(
522            parsed.function().function_name(),
523            "[constructor]resource1".to_string()
524        );
525        assert_eq!(
526            parsed,
527            ParsedFunctionName {
528                site: ParsedFunctionSite::PackagedInterface {
529                    namespace: "ns".to_string(),
530                    package: "name".to_string(),
531                    interface: "interface".to_string(),
532                    version: None,
533                },
534                function: ParsedFunctionReference::RawResourceConstructor {
535                    resource: "resource1".to_string()
536                },
537            }
538        );
539    }
540
541    #[test]
542    fn parse_function_name_method_syntax_sugar() {
543        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.do-something}")
544            .expect("Parsing failed");
545        assert_eq!(
546            parsed.site().interface_name(),
547            Some("ns:name/interface".to_string())
548        );
549        assert_eq!(
550            parsed.function().function_name(),
551            "[method]resource1.do-something".to_string()
552        );
553        assert_eq!(
554            parsed,
555            ParsedFunctionName {
556                site: ParsedFunctionSite::PackagedInterface {
557                    namespace: "ns".to_string(),
558                    package: "name".to_string(),
559                    interface: "interface".to_string(),
560                    version: None,
561                },
562                function: ParsedFunctionReference::RawResourceMethod {
563                    resource: "resource1".to_string(),
564                    method: "do-something".to_string(),
565                },
566            }
567        );
568    }
569
570    #[test]
571    fn parse_function_name_method() {
572        let parsed =
573            ParsedFunctionName::parse("ns:name/interface.{[method]resource1.do-something}")
574                .expect("Parsing failed");
575        assert_eq!(
576            parsed.site().interface_name(),
577            Some("ns:name/interface".to_string())
578        );
579        assert_eq!(
580            parsed.function().function_name(),
581            "[method]resource1.do-something".to_string()
582        );
583        assert_eq!(
584            parsed,
585            ParsedFunctionName {
586                site: ParsedFunctionSite::PackagedInterface {
587                    namespace: "ns".to_string(),
588                    package: "name".to_string(),
589                    interface: "interface".to_string(),
590                    version: None,
591                },
592                function: ParsedFunctionReference::RawResourceMethod {
593                    resource: "resource1".to_string(),
594                    method: "do-something".to_string(),
595                },
596            }
597        );
598    }
599
600    #[test]
601    fn parse_function_name_static_method_syntax_sugar() {
602        // Note: the syntax sugared version cannot distinguish between method and static - so we need to check the actual existence of
603        // the function and fallback.
604        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.do-something-static}")
605            .expect("Parsing failed")
606            .method_as_static()
607            .unwrap();
608        assert_eq!(
609            parsed.site().interface_name(),
610            Some("ns:name/interface".to_string())
611        );
612        assert_eq!(
613            parsed.function().function_name(),
614            "[static]resource1.do-something-static".to_string()
615        );
616        assert_eq!(
617            parsed,
618            ParsedFunctionName {
619                site: ParsedFunctionSite::PackagedInterface {
620                    namespace: "ns".to_string(),
621                    package: "name".to_string(),
622                    interface: "interface".to_string(),
623                    version: None,
624                },
625                function: ParsedFunctionReference::RawResourceStaticMethod {
626                    resource: "resource1".to_string(),
627                    method: "do-something-static".to_string(),
628                },
629            }
630        );
631    }
632
633    #[test]
634    fn parse_function_name_static() {
635        let parsed =
636            ParsedFunctionName::parse("ns:name/interface.{[static]resource1.do-something-static}")
637                .expect("Parsing failed");
638        assert_eq!(
639            parsed.site().interface_name(),
640            Some("ns:name/interface".to_string())
641        );
642        assert_eq!(
643            parsed.function().function_name(),
644            "[static]resource1.do-something-static".to_string()
645        );
646        assert_eq!(
647            parsed,
648            ParsedFunctionName {
649                site: ParsedFunctionSite::PackagedInterface {
650                    namespace: "ns".to_string(),
651                    package: "name".to_string(),
652                    interface: "interface".to_string(),
653                    version: None,
654                },
655                function: ParsedFunctionReference::RawResourceStaticMethod {
656                    resource: "resource1".to_string(),
657                    method: "do-something-static".to_string(),
658                },
659            }
660        );
661    }
662
663    #[test]
664    fn parse_function_name_drop_syntax_sugar() {
665        let parsed = ParsedFunctionName::parse("ns:name/interface.{resource1.drop}")
666            .expect("Parsing failed");
667        assert_eq!(
668            parsed.site().interface_name(),
669            Some("ns:name/interface".to_string())
670        );
671        assert_eq!(
672            parsed.function().function_name(),
673            "[drop]resource1".to_string()
674        );
675        assert_eq!(
676            parsed,
677            ParsedFunctionName {
678                site: ParsedFunctionSite::PackagedInterface {
679                    namespace: "ns".to_string(),
680                    package: "name".to_string(),
681                    interface: "interface".to_string(),
682                    version: None,
683                },
684                function: ParsedFunctionReference::RawResourceDrop {
685                    resource: "resource1".to_string()
686                },
687            }
688        );
689    }
690
691    #[test]
692    fn parse_function_name_drop() {
693        let parsed = ParsedFunctionName::parse("ns:name/interface.{[drop]resource1}")
694            .expect("Parsing failed");
695        assert_eq!(
696            parsed.site().interface_name(),
697            Some("ns:name/interface".to_string())
698        );
699        assert_eq!(
700            parsed.function().function_name(),
701            "[drop]resource1".to_string()
702        );
703        assert_eq!(
704            parsed,
705            ParsedFunctionName {
706                site: ParsedFunctionSite::PackagedInterface {
707                    namespace: "ns".to_string(),
708                    package: "name".to_string(),
709                    interface: "interface".to_string(),
710                    version: None,
711                },
712                function: ParsedFunctionReference::RawResourceDrop {
713                    resource: "resource1".to_string()
714                },
715            }
716        );
717    }
718
719    fn round_trip_function_name_parse(input: &str) {
720        let parsed = ParsedFunctionName::parse(input)
721            .unwrap_or_else(|_| panic!("Input Parsing failed for {input}"));
722        let parsed_written =
723            ParsedFunctionName::parse(parsed.to_string()).expect("Round-trip parsing failed");
724        assert_eq!(parsed, parsed_written);
725    }
726
727    #[test]
728    fn test_parsed_function_name_display() {
729        round_trip_function_name_parse("run-example");
730        round_trip_function_name_parse("interface.{fn1}");
731        round_trip_function_name_parse("wasi:cli/run@0.2.0.{run}");
732        round_trip_function_name_parse("ns:name/interface.{resource1.new}");
733        round_trip_function_name_parse("ns:name/interface.{[constructor]resource1}");
734        round_trip_function_name_parse("ns:name/interface.{resource1.do-something}");
735        round_trip_function_name_parse("ns:name/interface.{[static]resource1.do-something-static}");
736        round_trip_function_name_parse("ns:name/interface.{resource1.drop}");
737        round_trip_function_name_parse("ns:name/interface.{[drop]resource1}");
738    }
739}