@@ -452,3 +452,233 @@ mod tests {
452452 ) ;
453453 }
454454}
455+
456+ /// Parse a WIT type syntax string into a `TypeWrapper`.
457+ ///
458+ /// Supports primitives (`bool`, `u8`..`u64`, `s8`..`s64`, `f32`, `f64`, `char`, `string`),
459+ /// `list<T>`, `option<T>`, `tuple<T1, T2, ...>`, and `result` variants.
460+ pub fn parse_wit_type ( s : & str ) -> Result < TypeWrapper , String > {
461+ let s = s. trim ( ) ;
462+ let ( ty, rest) = parse_type ( s) ?;
463+ let rest = rest. trim ( ) ;
464+ if !rest. is_empty ( ) {
465+ return Err ( format ! ( "unexpected trailing characters: '{rest}'" ) ) ;
466+ }
467+ Ok ( ty)
468+ }
469+
470+ fn parse_type ( s : & str ) -> Result < ( TypeWrapper , & str ) , String > {
471+ let s = s. trim ( ) ;
472+ if s. is_empty ( ) {
473+ return Err ( "unexpected end of input" . to_string ( ) ) ;
474+ }
475+
476+ let ident_end = s
477+ . find ( |c : char | !c. is_ascii_alphanumeric ( ) && c != '-' && c != '_' )
478+ . unwrap_or ( s. len ( ) ) ;
479+ let ident = & s[ ..ident_end] ;
480+ let rest = & s[ ident_end..] ;
481+
482+ match ident {
483+ "bool" => Ok ( ( TypeWrapper :: Bool , rest) ) ,
484+ "s8" => Ok ( ( TypeWrapper :: S8 , rest) ) ,
485+ "u8" => Ok ( ( TypeWrapper :: U8 , rest) ) ,
486+ "s16" => Ok ( ( TypeWrapper :: S16 , rest) ) ,
487+ "u16" => Ok ( ( TypeWrapper :: U16 , rest) ) ,
488+ "s32" => Ok ( ( TypeWrapper :: S32 , rest) ) ,
489+ "u32" => Ok ( ( TypeWrapper :: U32 , rest) ) ,
490+ "s64" => Ok ( ( TypeWrapper :: S64 , rest) ) ,
491+ "u64" => Ok ( ( TypeWrapper :: U64 , rest) ) ,
492+ "f32" => Ok ( ( TypeWrapper :: F32 , rest) ) ,
493+ "f64" => Ok ( ( TypeWrapper :: F64 , rest) ) ,
494+ "char" => Ok ( ( TypeWrapper :: Char , rest) ) ,
495+ "string" => Ok ( ( TypeWrapper :: String , rest) ) ,
496+
497+ "list" => {
498+ let rest = expect_char ( rest. trim_start ( ) , '<' ) ?;
499+ let ( inner, rest) = parse_type ( rest) ?;
500+ let rest = expect_char ( rest. trim_start ( ) , '>' ) ?;
501+ Ok ( ( TypeWrapper :: List ( Box :: new ( inner) ) , rest) )
502+ }
503+
504+ "option" => {
505+ let rest = expect_char ( rest. trim_start ( ) , '<' ) ?;
506+ let ( inner, rest) = parse_type ( rest) ?;
507+ let rest = expect_char ( rest. trim_start ( ) , '>' ) ?;
508+ Ok ( ( TypeWrapper :: Option ( Box :: new ( inner) ) , rest) )
509+ }
510+
511+ "tuple" => {
512+ let rest = expect_char ( rest. trim_start ( ) , '<' ) ?;
513+ let ( items, rest) = parse_comma_separated_types ( rest, '>' ) ?;
514+ Ok ( ( TypeWrapper :: Tuple ( items. into_boxed_slice ( ) ) , rest) )
515+ }
516+
517+ "result" => {
518+ let rest_trimmed = rest. trim_start ( ) ;
519+ if !rest_trimmed. starts_with ( '<' ) {
520+ return Ok ( (
521+ TypeWrapper :: Result {
522+ ok : None ,
523+ err : None ,
524+ } ,
525+ rest,
526+ ) ) ;
527+ }
528+ let rest = expect_char ( rest_trimmed, '<' ) ?;
529+ let rest_trimmed = rest. trim_start ( ) ;
530+
531+ let ( ok, rest) = if let Some ( stripped) = rest_trimmed. strip_prefix ( '_' ) {
532+ ( None , stripped)
533+ } else {
534+ let ( ty, rest) = parse_type ( rest) ?;
535+ ( Some ( Box :: new ( ty) ) , rest)
536+ } ;
537+
538+ let rest_trimmed = rest. trim_start ( ) ;
539+
540+ if let Some ( rest) = rest_trimmed. strip_prefix ( ',' ) {
541+ let ( err_ty, rest) = parse_type ( rest) ?;
542+ let rest = expect_char ( rest. trim_start ( ) , '>' ) ?;
543+ Ok ( (
544+ TypeWrapper :: Result {
545+ ok,
546+ err : Some ( Box :: new ( err_ty) ) ,
547+ } ,
548+ rest,
549+ ) )
550+ } else {
551+ let rest = expect_char ( rest_trimmed, '>' ) ?;
552+ Ok ( ( TypeWrapper :: Result { ok, err : None } , rest) )
553+ }
554+ }
555+
556+ _ => Err ( format ! ( "unknown type: '{ident}'" ) ) ,
557+ }
558+ }
559+
560+ fn expect_char ( s : & str , expected : char ) -> Result < & str , String > {
561+ let s = s. trim_start ( ) ;
562+ if s. starts_with ( expected) {
563+ Ok ( & s[ expected. len_utf8 ( ) ..] )
564+ } else {
565+ let found = s
566+ . chars ( )
567+ . next ( )
568+ . map_or ( "end of input" . to_string ( ) , |c| format ! ( "'{c}'" ) ) ;
569+ Err ( format ! ( "expected '{expected}', found {found}" ) )
570+ }
571+ }
572+
573+ fn parse_comma_separated_types ( s : & str , closing : char ) -> Result < ( Vec < TypeWrapper > , & str ) , String > {
574+ let mut items = Vec :: new ( ) ;
575+ let mut rest = s. trim_start ( ) ;
576+
577+ if rest. starts_with ( closing) {
578+ return Ok ( ( items, & rest[ closing. len_utf8 ( ) ..] ) ) ;
579+ }
580+
581+ loop {
582+ let ( ty, r) = parse_type ( rest) ?;
583+ items. push ( ty) ;
584+ rest = r. trim_start ( ) ;
585+
586+ if rest. starts_with ( ',' ) {
587+ rest = rest[ 1 ..] . trim_start ( ) ;
588+ } else if rest. starts_with ( closing) {
589+ rest = & rest[ closing. len_utf8 ( ) ..] ;
590+ break ;
591+ } else {
592+ let found = rest
593+ . chars ( )
594+ . next ( )
595+ . map_or ( "end of input" . to_string ( ) , |c| format ! ( "'{c}'" ) ) ;
596+ return Err ( format ! ( "expected ',' or '{closing}', found {found}" ) ) ;
597+ }
598+ }
599+
600+ Ok ( ( items, rest) )
601+ }
602+
603+ #[ cfg( test) ]
604+ mod tests_parse_wit_type {
605+ use super :: * ;
606+
607+ #[ test]
608+ fn primitives ( ) {
609+ assert_eq ! ( parse_wit_type( "bool" ) . unwrap( ) , TypeWrapper :: Bool ) ;
610+ assert_eq ! ( parse_wit_type( "u32" ) . unwrap( ) , TypeWrapper :: U32 ) ;
611+ assert_eq ! ( parse_wit_type( "string" ) . unwrap( ) , TypeWrapper :: String ) ;
612+ assert_eq ! ( parse_wit_type( "f64" ) . unwrap( ) , TypeWrapper :: F64 ) ;
613+ }
614+
615+ #[ test]
616+ fn list ( ) {
617+ assert_eq ! (
618+ parse_wit_type( "list<string>" ) . unwrap( ) ,
619+ TypeWrapper :: List ( Box :: new( TypeWrapper :: String ) )
620+ ) ;
621+ assert_eq ! (
622+ parse_wit_type( "list<list<u8>>" ) . unwrap( ) ,
623+ TypeWrapper :: List ( Box :: new( TypeWrapper :: List ( Box :: new( TypeWrapper :: U8 ) ) ) )
624+ ) ;
625+ }
626+
627+ #[ test]
628+ fn option ( ) {
629+ assert_eq ! (
630+ parse_wit_type( "option<u32>" ) . unwrap( ) ,
631+ TypeWrapper :: Option ( Box :: new( TypeWrapper :: U32 ) )
632+ ) ;
633+ }
634+
635+ #[ test]
636+ fn tuple ( ) {
637+ assert_eq ! (
638+ parse_wit_type( "tuple<u32, string>" ) . unwrap( ) ,
639+ TypeWrapper :: Tuple ( vec![ TypeWrapper :: U32 , TypeWrapper :: String ] . into_boxed_slice( ) )
640+ ) ;
641+ }
642+
643+ #[ test]
644+ fn result_variants ( ) {
645+ assert_eq ! (
646+ parse_wit_type( "result<string, string>" ) . unwrap( ) ,
647+ TypeWrapper :: Result {
648+ ok: Some ( Box :: new( TypeWrapper :: String ) ) ,
649+ err: Some ( Box :: new( TypeWrapper :: String ) ) ,
650+ }
651+ ) ;
652+ assert_eq ! (
653+ parse_wit_type( "result<string>" ) . unwrap( ) ,
654+ TypeWrapper :: Result {
655+ ok: Some ( Box :: new( TypeWrapper :: String ) ) ,
656+ err: None ,
657+ }
658+ ) ;
659+ assert_eq ! (
660+ parse_wit_type( "result" ) . unwrap( ) ,
661+ TypeWrapper :: Result {
662+ ok: None ,
663+ err: None ,
664+ }
665+ ) ;
666+ assert_eq ! (
667+ parse_wit_type( "result<_, string>" ) . unwrap( ) ,
668+ TypeWrapper :: Result {
669+ ok: None ,
670+ err: Some ( Box :: new( TypeWrapper :: String ) ) ,
671+ }
672+ ) ;
673+ }
674+
675+ #[ test]
676+ fn trailing_chars ( ) {
677+ assert ! ( parse_wit_type( "u32 extra" ) . is_err( ) ) ;
678+ }
679+
680+ #[ test]
681+ fn unknown_type ( ) {
682+ assert ! ( parse_wit_type( "foobar" ) . is_err( ) ) ;
683+ }
684+ }
0 commit comments