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#[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 pub fn resource_name_simplified(&self) -> Option<String> {
245 self.to_parsed_function_name()
246 .function
247 .resource_name()
248 .cloned()
249 }
250
251 pub fn resource_method_name_simplified(&self) -> Option<String> {
253 self.to_parsed_function_name()
254 .function
255 .resource_method_name()
256 }
257
258 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 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}