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