Skip to main content

sails_idl_parser_v2/
lib.rs

1// Sails IDL v2 — parser using `pest-rs`
2#![no_std]
3
4extern crate alloc;
5
6#[cfg(feature = "std")]
7extern crate std;
8
9pub mod error;
10mod post_process;
11pub mod preprocess;
12pub mod visitor;
13#[cfg(feature = "ffi")]
14pub mod ffi {
15    pub mod ast;
16}
17
18use crate::error::{Error, Result};
19use alloc::{
20    boxed::Box,
21    format,
22    string::{String, ToString},
23    vec::Vec,
24};
25use core::str::FromStr;
26use pest::Parser;
27use pest::iterators::{Pair, Pairs};
28pub use preprocess::{IdlLoader, IdlSource};
29#[cfg(feature = "std")]
30pub use preprocess::{fs::FsLoader, git::GitLoader};
31use sails_idl_ast::*;
32// re-export
33pub use sails_idl_ast as ast;
34
35#[derive(pest_derive::Parser)]
36#[grammar = "idl.pest"]
37pub struct IdlParser;
38
39// ----------------------------- Public API ------------------------------------
40
41/// Parses the IDL source to tokens.
42pub fn parse_tokens(src: &str) -> Result<Pairs<'_, Rule>> {
43    let pairs = IdlParser::parse(Rule::Top, src)?;
44    Ok(pairs)
45}
46
47/// Parses the IDL source.
48pub fn parse_idl(src: &str) -> Result<IdlDoc> {
49    let mut pairs = IdlParser::parse(Rule::Top, src)?;
50    let mut doc = build_idl(
51        pairs
52            .next()
53            .ok_or(Error::Rule("expected Top".to_string()))?,
54    )?;
55
56    post_process::validate_and_post_process(&mut doc)?;
57    Ok(doc)
58}
59
60/// Parses the IDL source from the given path using a slice of loaders.
61///
62/// Loaders are tried in order — the first one that resolves the path is used.
63/// Use [`FsLoader`] for local files and [`GitLoader`] for `git://` URLs.
64pub fn parse_idl_with_loaders(path: &str, loaders: &[&dyn IdlLoader]) -> Result<IdlDoc> {
65    let src = preprocess::preprocess(path, loaders)?;
66    parse_idl(&src)
67}
68
69// ------------------------------- Builders ------------------------------------
70fn build_idl(top: Pair<Rule>) -> Result<IdlDoc> {
71    let mut globals = Vec::new();
72    let mut services = Vec::new();
73    let mut program = None;
74    for p in top.into_inner() {
75        match p.as_rule() {
76            Rule::GlobalAnn => {
77                globals.push(parse_annotation(p)?);
78            }
79            Rule::ServiceDecl => services.push(parse_service(p)?),
80            Rule::ProgramDecl if program.replace(parse_program(p)?).is_some() => {
81                return Err(Error::Validation(
82                    "expected at most one program per IDL document".to_string(),
83                ));
84            }
85            _ => {}
86        }
87    }
88    Ok(IdlDoc {
89        globals,
90        services,
91        program,
92    })
93}
94
95fn parse_ident(p: Pair<Rule>) -> Result<String> {
96    if p.as_rule() == Rule::Ident {
97        return Ok(p.as_str().to_string());
98    }
99    Err(Error::Rule("expected Ident".to_string()))
100}
101
102fn parse_service_ident(p: Pair<Rule>) -> Result<ServiceIdent> {
103    if p.as_rule() == Rule::ServiceIdent {
104        p.as_str().parse::<ServiceIdent>().map_err(Error::Parse)
105    } else {
106        Err(Error::Rule("expected ServiceIdent".to_string()))
107    }
108}
109
110fn parse_annotation(p: Pair<Rule>) -> Result<Annotation> {
111    let mut key = None;
112    let mut val = None;
113    for i in p.into_inner() {
114        match i.as_rule() {
115            Rule::Ident => key = Some(i.as_str().trim().to_string()),
116            Rule::StrToEol => val = Some(i.as_str().trim().to_string()),
117            _ => {}
118        }
119    }
120    let key = key.ok_or(Error::Rule("expected Ident".to_string()))?;
121    Ok((key, val))
122}
123
124fn parse_type_decl(p: Pair<Rule>) -> Result<TypeDecl> {
125    Ok(match p.as_rule() {
126        // TypeDecl is `silent` Rule, but this for futureproof
127        Rule::TypeDecl => parse_type_decl(
128            p.into_inner()
129                .next()
130                .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
131        )?,
132        Rule::Tuple => {
133            let mut types = Vec::new();
134            for el in p.into_inner() {
135                types.push(parse_type_decl(el)?);
136            }
137            if types.is_empty() {
138                TypeDecl::Primitive(PrimitiveType::Void)
139            } else {
140                TypeDecl::Tuple { types }
141            }
142        }
143        Rule::Slice => {
144            let mut it = p.into_inner();
145            let ty = expect_next(&mut it, parse_type_decl)?;
146            TypeDecl::Slice { item: Box::new(ty) }
147        }
148        Rule::Array => {
149            let mut it = p.into_inner();
150            let ty = expect_next(&mut it, parse_type_decl)?;
151            let len = expect_rule(&mut it, Rule::Number)?
152                .as_str()
153                .parse::<u32>()
154                .map_err(|e| Error::Parse(e.to_string()))?;
155            TypeDecl::Array {
156                item: Box::new(ty),
157                len,
158            }
159        }
160        Rule::Primitive => {
161            let primitive_type = PrimitiveType::from_str(p.as_str()).map_err(Error::Parse)?;
162            TypeDecl::Primitive(primitive_type)
163        }
164        Rule::Named => {
165            let mut name = String::new();
166            let mut generics: Vec<TypeDecl> = Vec::new();
167            for part in p.into_inner() {
168                match part.as_rule() {
169                    Rule::Ident => name = part.as_str().to_string(),
170                    Rule::Generics => {
171                        for t in part.into_inner() {
172                            generics.push(parse_type_decl(t)?);
173                        }
174                    }
175                    _ => {}
176                }
177            }
178            TypeDecl::Named { name, generics }
179        }
180        other => {
181            return Err(Error::Rule(format!(
182                "unexpected rule in TypeDecl: {other:?}"
183            )));
184        }
185    })
186}
187
188fn parse_param(p: Pair<'_, Rule>) -> Result<FuncParam> {
189    let mut it = p.into_inner();
190    let name = expect_next(&mut it, parse_ident)?;
191    let ty = expect_next(&mut it, parse_type_decl)?;
192    Ok(FuncParam {
193        name,
194        type_decl: ty,
195    })
196}
197
198fn parse_field(p: Pair<'_, Rule>) -> Result<StructField> {
199    let mut it = p.into_inner();
200    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
201    let part = it
202        .next()
203        .ok_or(Error::Rule("expected Ident | TypeDecl".to_string()))?;
204    let (name, type_decl) = match part.as_rule() {
205        Rule::Ident => {
206            let name = part.as_str().to_string();
207            let ty = expect_next(&mut it, parse_type_decl)?;
208            (Some(name), ty)
209        }
210        _ => (None, parse_type_decl(part)?),
211    };
212    Ok(StructField {
213        name,
214        type_decl,
215        annotations,
216        docs,
217    })
218}
219
220pub fn parse_type(p: Pair<Rule>) -> Result<Type> {
221    let mut ty = match p.as_rule() {
222        Rule::StructDecl => parse_struct_type(p),
223        Rule::EnumDecl => parse_enum_type(p),
224        Rule::AliasDecl => parse_alias_decl(p),
225        _ => Err(Error::Rule(
226            "expected StructDecl | EnumDecl | AliasDecl".to_string(),
227        )),
228    }?;
229    post_process::normalize_type_generics(&mut ty);
230    Ok(ty)
231}
232
233fn parse_alias_decl(p: Pair<Rule>) -> Result<Type> {
234    let mut it = p.into_inner();
235    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
236    let name = expect_next(&mut it, parse_ident)?;
237    let mut type_params = Vec::new();
238    let mut target = None;
239
240    for part in it {
241        match part.as_rule() {
242            Rule::TypeParams => {
243                for i in part.into_inner().filter(|x| x.as_rule() == Rule::Ident) {
244                    let name = i.as_str().to_string();
245                    type_params.push(TypeParameter { name, ty: None });
246                }
247            }
248            Rule::Tuple | Rule::Slice | Rule::Array | Rule::Primitive | Rule::Named => {
249                target = Some(parse_type_decl(part)?);
250            }
251            _ => {}
252        }
253    }
254
255    let target = target.ok_or_else(|| Error::Rule("expected TypeDecl".to_string()))?;
256
257    Ok(Type {
258        name,
259        type_params,
260        def: TypeDef::Alias(AliasDef { target }),
261        docs,
262        annotations,
263    })
264}
265
266fn parse_struct_type(p: Pair<Rule>) -> Result<Type> {
267    let mut it = p.into_inner();
268    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
269    let name = expect_next(&mut it, parse_ident)?;
270    let mut type_params = Vec::new();
271    let mut fields = Vec::new();
272    for part in it {
273        match part.as_rule() {
274            Rule::TypeParams => {
275                for i in part.into_inner().filter(|x| x.as_rule() == Rule::Ident) {
276                    let name = i.as_str().to_string();
277                    type_params.push(TypeParameter { name, ty: None });
278                }
279            }
280            Rule::Fields => {
281                for f in part.into_inner().filter(|x| x.as_rule() == Rule::Field) {
282                    fields.push(parse_field(f)?);
283                }
284            }
285            _ => {
286                return Err(Error::Rule(
287                    "expected StructDef | TupleDef | UnitDef".to_string(),
288                ));
289            }
290        };
291    }
292
293    Ok(Type {
294        name,
295        type_params,
296        def: TypeDef::Struct(StructDef { fields }),
297        docs,
298        annotations,
299    })
300}
301
302fn parse_enum_type(p: Pair<Rule>) -> Result<Type> {
303    let mut it = p.into_inner();
304    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
305    let name = expect_next(&mut it, parse_ident)?;
306    let mut type_params = Vec::new();
307    let mut variants = Vec::new();
308    for part in it {
309        match part.as_rule() {
310            Rule::TypeParams => {
311                for i in part.into_inner().filter(|x| x.as_rule() == Rule::Ident) {
312                    let name = i.as_str().to_string();
313                    type_params.push(TypeParameter { name, ty: None });
314                }
315            }
316            Rule::Variants => {
317                for v in part.into_inner().filter(|x| x.as_rule() == Rule::Variant) {
318                    variants.push(parse_enum_variant(v)?);
319                }
320            }
321            _ => {
322                return Err(Error::Rule("expected TypeParams | Variants".to_string()));
323            }
324        };
325    }
326
327    Ok(Type {
328        name,
329        type_params,
330        def: TypeDef::Enum(EnumDef { variants }),
331        docs,
332        annotations,
333    })
334}
335
336fn parse_enum_variant(p: Pair<Rule>) -> Result<EnumVariant> {
337    let mut it = p.into_inner();
338    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
339    let name = expect_next(&mut it, parse_ident)?;
340    let mut fields = Vec::new();
341    for part in it {
342        match part.as_rule() {
343            Rule::Fields => {
344                for f in part.into_inner().filter(|x| x.as_rule() == Rule::Field) {
345                    fields.push(parse_field(f)?);
346                }
347            }
348            _ => {
349                return Err(Error::Rule("expected Fields".to_string()));
350            }
351        };
352    }
353
354    Ok(EnumVariant {
355        name,
356        def: StructDef { fields },
357        entry_id: 0,
358        docs,
359        annotations,
360    })
361}
362
363fn parse_func(p: Pair<Rule>) -> Result<ServiceFunc> {
364    let mut it = p.into_inner();
365    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
366    let name = expect_next(&mut it, parse_ident)?;
367    let kind = if annotations.iter().any(|(k, _)| k == "query") {
368        FunctionKind::Query
369    } else {
370        FunctionKind::Command
371    };
372    let mut params = Vec::new();
373    let mut output = None;
374    let mut throws = None;
375    for part in it {
376        match part.as_rule() {
377            Rule::Params => {
378                for p in part.into_inner().filter(|x| x.as_rule() == Rule::Param) {
379                    params.push(parse_param(p)?);
380                }
381            }
382            Rule::Ret => {
383                output = Some(parse_type_decl(
384                    part.into_inner()
385                        .next()
386                        .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
387                )?)
388            }
389            Rule::Throws => {
390                throws = Some(parse_type_decl(
391                    part.into_inner()
392                        .next()
393                        .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
394                )?)
395            }
396            _ => {}
397        }
398    }
399    let output = output.unwrap_or(TypeDecl::Primitive(PrimitiveType::Void));
400    Ok(ServiceFunc {
401        name,
402        params,
403        output,
404        throws,
405        kind,
406        entry_id: 0,
407        docs,
408        annotations,
409    })
410}
411
412fn parse_service(p: Pair<Rule>) -> Result<ServiceUnit> {
413    let mut it = p.into_inner();
414    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
415    let name = expect_next(&mut it, parse_service_ident)?;
416
417    let mut extends = Vec::new();
418    let mut events = Vec::new();
419    let mut funcs = Vec::new();
420    let mut types = Vec::new();
421    for item in it {
422        match item.as_rule() {
423            Rule::ExtendsBlock => {
424                for i in item.into_inner() {
425                    let ident = parse_service_ident(i)?;
426                    extends.push(ident);
427                }
428            }
429            Rule::EventsBlock => {
430                for e in item.into_inner().filter(|x| x.as_rule() == Rule::Variant) {
431                    events.push(parse_enum_variant(e)?);
432                }
433            }
434            Rule::FunctionsBlock => {
435                for f in item.into_inner().filter(|x| x.as_rule() == Rule::FuncDecl) {
436                    funcs.push(parse_func(f)?);
437                }
438            }
439            Rule::TypesBlock => {
440                for t in item.into_inner() {
441                    types.push(parse_type(t)?);
442                }
443            }
444            _ => {}
445        }
446    }
447
448    let mut unit = ServiceUnit {
449        name,
450        extends,
451        events,
452        funcs,
453        types,
454        docs,
455        annotations,
456    };
457    unit.normalize();
458    Ok(unit)
459}
460
461fn parse_ctor_func(p: Pair<Rule>) -> Result<CtorFunc> {
462    let mut it = p.into_inner();
463    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
464    let name = expect_rule(&mut it, Rule::Ident)?.as_str().to_string();
465    let mut params = Vec::new();
466    let mut throws = None;
467    for part in it {
468        match part.as_rule() {
469            Rule::Params => {
470                for p in part.into_inner().filter(|x| x.as_rule() == Rule::Param) {
471                    params.push(parse_param(p)?);
472                }
473            }
474            Rule::Throws => {
475                throws = Some(parse_type_decl(
476                    part.into_inner()
477                        .next()
478                        .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
479                )?)
480            }
481            _ => {}
482        }
483    }
484    Ok(CtorFunc {
485        name,
486        params,
487        throws,
488        entry_id: 0,
489        docs,
490        annotations,
491    })
492}
493
494fn parse_program(p: Pair<Rule>) -> Result<ProgramUnit> {
495    let mut it = p.into_inner();
496    let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
497    let name = expect_next(&mut it, parse_ident)?;
498    let mut ctors = Vec::new();
499    let mut services = Vec::new();
500    let mut types = Vec::new();
501    for item in it {
502        match item.as_rule() {
503            Rule::ConstructorsBlock => {
504                for f in item.into_inner().filter(|x| x.as_rule() == Rule::CtorDecl) {
505                    ctors.push(parse_ctor_func(f)?);
506                }
507            }
508            Rule::ServicesBlock => {
509                for s in item
510                    .into_inner()
511                    .filter(|x| x.as_rule() == Rule::ServiceExpo)
512                {
513                    let len = services.len();
514                    if len >= u8::MAX as usize {
515                        return Err(Error::Validation(
516                            "Too many services in program. Max: 255".to_string(),
517                        ));
518                    }
519                    let mut sit = s.into_inner();
520                    let (docs, annotations) = parse_docs_and_annotations(&mut sit)?;
521                    let name = expect_next(&mut sit, parse_service_ident)?;
522                    let route = if let Some(p) = sit.next()
523                        && p.as_rule() == Rule::Ident
524                    {
525                        Some(p.as_str().to_string())
526                    } else {
527                        None
528                    };
529                    services.push(ServiceExpo {
530                        name,
531                        route,
532                        route_idx: (len as u8) + 1,
533                        docs,
534                        annotations,
535                    });
536                }
537            }
538            Rule::TypesBlock => {
539                for t in item.into_inner() {
540                    types.push(parse_type(t)?);
541                }
542            }
543            _ => {
544                return Err(Error::Rule(
545                    "expected ConstructorsBlock | ServicesBlock | TypesBlock".to_string(),
546                ));
547            }
548        }
549    }
550    let mut program = ProgramUnit {
551        name,
552        ctors,
553        services,
554        types,
555        docs,
556        annotations,
557    };
558    program.normalize();
559    Ok(program)
560}
561
562// ------------------------------ Helpers --------------------------------------
563
564fn parse_docs_and_annotations(pairs: &mut Pairs<Rule>) -> Result<(Vec<String>, Vec<Annotation>)> {
565    let mut docs = Vec::new();
566    let mut anns = Vec::new();
567    // iter over cloned
568    for p in pairs.clone() {
569        // peek Docs or Anns
570        match p.as_rule() {
571            Rule::DocLine => {
572                // pop pair
573                let _ = pairs.next();
574                for d in p.into_inner() {
575                    if d.as_rule() == Rule::StrToEol {
576                        docs.push(
577                            d.as_str()
578                                .strip_prefix(' ')
579                                .unwrap_or(d.as_str())
580                                .trim_end()
581                                .to_string(),
582                        );
583                    }
584                }
585            }
586            Rule::LocalAnn => {
587                // pop pair
588                let _ = pairs.next();
589                let ann = parse_annotation(p)?;
590                if ann.0 == "doc" {
591                    if let Some(val) = ann.1 {
592                        docs.push(val);
593                    }
594                } else {
595                    anns.push(ann);
596                }
597            }
598            _ => break,
599        }
600    }
601    Ok((docs, anns))
602}
603
604fn expect_next<'a, F: FnOnce(Pair<'a, Rule>) -> Result<T>, T>(
605    it: &mut impl Iterator<Item = Pair<'a, Rule>>,
606    f: F,
607) -> Result<T> {
608    if let Some(p) = it.next() {
609        return f(p);
610    }
611    Err(Error::Rule("expected next Rule".to_string()))
612}
613
614fn expect_rule<'a>(
615    it: &mut impl Iterator<Item = Pair<'a, Rule>>,
616    r: Rule,
617) -> Result<Pair<'a, Rule>> {
618    if let Some(p) = it.next()
619        && p.as_rule() == r
620    {
621        return Ok(p);
622    }
623    Err(Error::Rule(format!("expected {r:?}")))
624}
625
626// ------------------------------ Tests ----------------------------------------
627#[cfg(test)]
628mod tests {
629    use super::*;
630    use alloc::vec;
631
632    #[test]
633    fn parse_docs_and_annotations_lines() {
634        const SRC: &str = r#"
635            // Comment
636            /// Defines status of some point as colored by somebody or dead for some reason.
637            /// Dead point - won't be available for coloring anymore.
638            @indexed
639            "#;
640
641        let mut pairs = IdlParser::parse(Rule::DocsAndAnnotations, SRC).expect("parse idl");
642        let (docs, anns) = parse_docs_and_annotations(&mut pairs).expect("parse annotations");
643
644        assert_eq!(
645            docs,
646            vec![
647                "Defines status of some point as colored by somebody or dead for some reason.",
648                "Dead point - won't be available for coloring anymore."
649            ],
650        );
651        assert_eq!(anns, vec![("indexed".to_string(), None)])
652    }
653
654    #[test]
655    fn parse_docs_with_blank_doc_separator() {
656        const SRC: &str = r#"
657            /// Returns the number of roles assigned to the specified member.
658            ///
659            /// # Arguments
660            /// * `member_id` - The account identifier.
661            "#;
662
663        let mut pairs = IdlParser::parse(Rule::DocsAndAnnotations, SRC).expect("parse idl");
664        let (docs, anns) = parse_docs_and_annotations(&mut pairs).expect("parse annotations");
665
666        assert_eq!(
667            docs,
668            vec![
669                "Returns the number of roles assigned to the specified member.",
670                "",
671                "# Arguments",
672                "* `member_id` - The account identifier.",
673            ],
674        );
675        assert!(anns.is_empty());
676    }
677
678    #[test]
679    fn parse_global_annotations() {
680        const SRC: &str = r#"
681            // Global annotations
682            !@sails: 0.1.0
683            !@include: ownable.idl
684            !@include: git://github.com/some_repo/tippable.idl
685            "#;
686
687        let doc = parse_idl(SRC).expect("parse idl");
688
689        assert_eq!(3, doc.globals.len());
690    }
691
692    #[test]
693    fn parse_vector_of_tuples() {
694        use PrimitiveType::*;
695        use TypeDecl::*;
696
697        const SRC: &str = r#"[(Point<u32>, Option<PointStatus>, u32)]"#;
698        let mut pairs = IdlParser::parse(Rule::TypeDecl, SRC).expect("parse idl");
699        let ty = expect_next(&mut pairs, parse_type_decl).expect("parse TypeDecl");
700
701        assert_eq!(
702            ty,
703            Slice {
704                item: Box::new(Tuple {
705                    types: vec![
706                        Named {
707                            name: "Point".to_string(),
708                            generics: vec![Primitive(U32)]
709                        },
710                        TypeDecl::option(TypeDecl::named("PointStatus".to_string())),
711                        Primitive(U32)
712                    ]
713                })
714            }
715        );
716    }
717
718    #[test]
719    fn pars_service_func() {
720        use PrimitiveType::*;
721        use TypeDecl::*;
722
723        const SRC: &str = r#"
724            /// Sets color for the point.
725            /// app -> `fn color_point(&mut self, point: Point<u32>, color: Color) -> Result<(), ColorError>`
726            /// On `Ok` - auto-reply. On `Err` -> app will encode error bytes of `ColorError` (`gr_panic_bytes`).
727            ColorPoint(point: (u32, u32), color: Color) throws ColorError;"#;
728        let mut pairs = IdlParser::parse(Rule::FuncDecl, SRC).expect("parse idl");
729
730        let func = parse_func(pairs.next().expect("FuncDecl")).expect("parse func");
731
732        assert_eq!(
733            func,
734            ServiceFunc {
735                name: "ColorPoint".to_string(),
736                params: vec![
737                    FuncParam {
738                        name: "point".to_string(),
739                        type_decl: TypeDecl::tuple(vec![Primitive(U32), Primitive(U32)])
740                    },
741                    FuncParam {
742                        name: "color".to_string(),
743                        type_decl: TypeDecl::named("Color".to_string())
744                    }
745                ],
746                output: Primitive(Void),
747                throws: Some(Named {
748                    name: "ColorError".to_string(),
749                    generics: vec![]
750                }),
751                kind: FunctionKind::Command,
752                entry_id: 0,
753                annotations: vec![],
754                docs: vec![
755                    "Sets color for the point.".to_string(),
756                    "app -> `fn color_point(&mut self, point: Point<u32>, color: Color) -> Result<(), ColorError>`".to_string(),
757                    "On `Ok` - auto-reply. On `Err` -> app will encode error bytes of `ColorError` (`gr_panic_bytes`).".to_string(),
758                ],
759            }
760        );
761    }
762
763    #[test]
764    fn parse_minimal_service() {
765        const SRC: &str = r#"
766            /// Example
767            service X {
768                functions { Ping() -> bool; }
769                events { E }
770                types { struct T; }
771            }
772        "#;
773        let mut pairs = IdlParser::parse(Rule::ServiceDecl, SRC).expect("parse idl");
774        let svc = expect_next(&mut pairs, parse_service).expect("parse");
775        assert_eq!(svc.name.to_string(), "X");
776        assert!(svc.funcs.iter().any(|f| f.name == "Ping"));
777    }
778
779    #[test]
780    fn parse_test_idl() {
781        const SRC: &str = include_str!("../tests/idls/test.idl");
782
783        let doc = parse_idl(SRC).expect("parse idl");
784
785        assert_eq!(2, doc.globals.len());
786        assert_eq!(4, doc.services.len());
787    }
788
789    #[test]
790    fn parse_demo_idl() {
791        const SRC: &str = include_str!("../tests/idls/demo.idl");
792
793        let doc = parse_idl(SRC).expect("parse idl");
794
795        assert_eq!(3, doc.globals.len());
796    }
797
798    #[test]
799    fn parse_idl_rejects_multiple_programs() {
800        const SRC: &str = r#"
801            program First {}
802            program Second {}
803        "#;
804
805        let err = parse_idl(SRC).expect_err("multiple programs should fail");
806        assert!(matches!(err, Error::Validation(_)));
807        assert!(
808            err.to_string()
809                .contains("expected at most one program per IDL document")
810        );
811    }
812
813    #[test]
814    fn parse_alias_decl() {
815        const SRC: &str = r#"alias AliasName = u32;"#;
816
817        let mut pairs = IdlParser::parse(Rule::AliasDecl, SRC).expect("parse alias");
818        let ty = parse_type(pairs.next().expect("alias")).expect("alias should be supported");
819
820        assert_eq!(ty.name, "AliasName");
821        assert!(matches!(ty.def, TypeDef::Alias(_)));
822        if let TypeDef::Alias(alias) = ty.def {
823            assert!(matches!(
824                alias.target,
825                TypeDecl::Primitive(PrimitiveType::U32)
826            ));
827        }
828    }
829
830    #[test]
831    fn parse_type_normalizes_declared_generic_refs() {
832        const SRC: &str = r#"struct Wrapper<T> { value: T }"#;
833
834        let mut pairs = IdlParser::parse(Rule::StructDecl, SRC).expect("parse struct");
835        let ty = parse_type(pairs.next().expect("struct")).expect("parse type");
836
837        let TypeDef::Struct(def) = ty.def else {
838            panic!("expected struct");
839        };
840        assert_eq!(
841            def.fields[0].type_decl,
842            TypeDecl::Generic {
843                name: "T".to_string()
844            },
845        );
846    }
847
848    #[test]
849    fn parse_idl_rejects_self_extends() {
850        // `service A { extends { A } ... }` previously stack-overflowed in
851        // post_process::compute_service_id.
852        const SRC: &str = r#"
853            service A {
854                extends { A }
855                functions { Ping() -> bool; }
856            }
857        "#;
858
859        let err = parse_idl(SRC).expect_err("self-extends should fail");
860        assert!(matches!(err, Error::Validation(_)));
861        assert!(err.to_string().contains("cyclic"));
862    }
863
864    #[test]
865    fn parse_idl_rejects_extends_cycle() {
866        // A → B → A previously stack-overflowed.
867        const SRC: &str = r#"
868            service A {
869                extends { B }
870                functions { Ping() -> bool; }
871            }
872            service B {
873                extends { A }
874                functions { Pong() -> bool; }
875            }
876        "#;
877
878        let err = parse_idl(SRC).expect_err("cycle should fail");
879        assert!(matches!(err, Error::Validation(_)));
880        assert!(err.to_string().contains("cyclic"));
881    }
882
883    #[test]
884    fn parse_idl_rejects_duplicate_service_names() {
885        // Duplicate names previously trapped at runtime via a BTreeMap collision
886        // followed by an `expect` in update_service_id.
887        const SRC: &str = r#"
888            service S {
889                functions { A() -> bool; }
890            }
891            service S {
892                functions { B() -> bool; }
893            }
894        "#;
895
896        let err = parse_idl(SRC).expect_err("duplicate names should fail");
897        assert!(matches!(err, Error::Validation(_)));
898        assert!(err.to_string().contains("duplicate"));
899    }
900}