Skip to main content

rib/compiler/
mod.rs

1pub use byte_code::*;
2pub use compiler_output::*;
3pub use ir::*;
4pub use type_with_unit::*;
5pub use worker_functions_in_rib::*;
6
7use crate::rib_type_error::RibTypeError;
8use crate::wit_type::{TypeEnum, TypeVariant};
9use crate::{
10    ComponentDependency, CustomInstanceSpec, Expr, GlobalVariableTypeSpec, InferredExpr,
11    RibInputTypeInfo, RibOutputTypeInfo,
12};
13use std::error::Error;
14use std::fmt::Display;
15
16mod byte_code;
17mod compiler_output;
18mod desugar;
19mod ir;
20mod type_with_unit;
21mod worker_functions_in_rib;
22
23#[derive(Default)]
24pub struct RibCompiler {
25    component: ComponentDependency,
26    global_variable_type_spec: Vec<GlobalVariableTypeSpec>,
27    custom_instance_spec: Vec<CustomInstanceSpec>,
28}
29
30impl RibCompiler {
31    pub fn new(config: RibCompilerConfig) -> RibCompiler {
32        let global_variable_type_spec = config.input_spec;
33
34        RibCompiler {
35            component: config.component,
36            global_variable_type_spec,
37            custom_instance_spec: config.custom_instance_spec,
38        }
39    }
40
41    pub fn infer_types(&self, expr: Expr) -> Result<InferredExpr, RibCompilationError> {
42        let _infer_profile = crate::profile::InferOnlyProfileGuard::new();
43
44        let expr_for_infer = { expr.clone() };
45
46        let result = {
47            let _p = crate::profile::Scope::new(
48                "compile: RibCompiler.infer_types InferredExpr::from_expr",
49            );
50            InferredExpr::from_expr(
51                expr_for_infer,
52                &self.component,
53                &self.global_variable_type_spec,
54                &self.custom_instance_spec,
55            )
56        };
57        result.map_err(|err| {
58            let rib_type_error = RibTypeError::from_rib_type_error_internal(err, expr);
59            RibCompilationError::RibTypeError(Box::new(rib_type_error))
60        })
61    }
62
63    pub fn get_custom_instance_names(&self) -> Vec<String> {
64        self.custom_instance_spec
65            .iter()
66            .map(|spec| spec.instance_name.clone())
67            .collect::<Vec<_>>()
68    }
69
70    pub fn get_component_dependency(&self) -> ComponentDependency {
71        self.component.clone()
72    }
73
74    pub fn compile(&self, expr: Expr) -> Result<CompilerOutput, RibCompilationError> {
75        let _compile_profile = crate::profile::CompileProfileGuard::enter();
76        let inferred_expr = {
77            let _p = crate::profile::Scope::new("compile: RibCompiler.compile infer_types (total)");
78            self.infer_types(expr)?
79        };
80
81        let function_calls_identified = {
82            let _p = crate::profile::Scope::new("compile: identity function call");
83            WorkerFunctionsInRib::from_inferred_expr(&inferred_expr, &self.component)?
84        };
85
86        let global_input_type_info = {
87            let _p = crate::profile::Scope::new("compile: identify input types of rib");
88            RibInputTypeInfo::from_expr(&inferred_expr)?
89        };
90        let output_type_info = {
91            let _p = crate::profile::Scope::new("compile: identify output types of rib");
92            RibOutputTypeInfo::from_expr(&inferred_expr)?
93        };
94
95        // allowed_global_variables
96        let allowed_global_variables: Vec<String> = self
97            .global_variable_type_spec
98            .iter()
99            .map(|x| x.variable())
100            .collect::<Vec<_>>();
101
102        let mut unidentified_global_inputs = vec![];
103
104        if !allowed_global_variables.is_empty() {
105            for (name, _) in global_input_type_info.types.iter() {
106                if !allowed_global_variables.contains(name) {
107                    unidentified_global_inputs.push(name.clone());
108                }
109            }
110        }
111
112        if !unidentified_global_inputs.is_empty() {
113            return Err(RibCompilationError::UnsupportedGlobalInput {
114                invalid_global_inputs: unidentified_global_inputs,
115                valid_global_inputs: allowed_global_variables,
116            });
117        }
118
119        let byte_code = {
120            let _p = crate::profile::Scope::new("compile: byte code generation");
121            RibByteCode::from_expr(&inferred_expr)?
122        };
123
124        Ok(CompilerOutput {
125            worker_invoke_calls: function_calls_identified,
126            byte_code,
127            rib_input_type_info: global_input_type_info,
128            rib_output_type_info: Some(output_type_info),
129        })
130    }
131
132    pub fn get_variants(&self) -> Vec<TypeVariant> {
133        self.component.get_variants()
134    }
135
136    pub fn get_enums(&self) -> Vec<TypeEnum> {
137        self.component.get_enums()
138    }
139}
140
141/// Compiler configuration options for Rib.
142///
143/// # Fields
144/// - `component_metadata`: Component metadata that describes the worker functions available.
145/// - `global_input_spec`: Defines constraints and types for global input variables.
146///   By default, Rib allows any identifier (e.g., `foo`) to be treated as a global variable.
147///   A global variable is a variable that is not defined in the Rib script but is expected to be provided
148///   by the environment in which the Rib script is executed (e.g., `request`, `env`). Hence it is called `global_input`.
149///   This field can restrict global variables to a predefined set. If the field is empty, any identifier
150///   can be used as a global variable.
151///
152///   You can also associate specific types with known global variables using
153///   `GlobalVariableTypeSpec`. For example, the path `request.path.*` can be enforced to always
154///   be of type `string`. Note that not all global variables require a type specification.
155#[derive(Default)]
156pub struct RibCompilerConfig {
157    pub component: ComponentDependency,
158    input_spec: Vec<GlobalVariableTypeSpec>,
159    custom_instance_spec: Vec<CustomInstanceSpec>,
160}
161
162impl RibCompilerConfig {
163    pub fn new(
164        component: ComponentDependency,
165        input_spec: Vec<GlobalVariableTypeSpec>,
166        custom_instance_spec: Vec<CustomInstanceSpec>,
167    ) -> RibCompilerConfig {
168        RibCompilerConfig {
169            component,
170            input_spec,
171            custom_instance_spec,
172        }
173    }
174}
175
176pub trait GenerateInstanceName {
177    fn generate_instance_name(&self) -> String;
178}
179
180pub struct DefaultWorkerNameGenerator;
181
182impl GenerateInstanceName for DefaultWorkerNameGenerator {
183    fn generate_instance_name(&self) -> String {
184        let uuid = uuid::Uuid::new_v4();
185        format!("instance-{uuid}")
186    }
187}
188
189#[derive(Debug, Clone, PartialEq)]
190pub enum RibCompilationError {
191    // Bytecode generation errors should ideally never occur.
192    // They are considered programming errors that indicate some part of type checking
193    // or inference needs to be fixed.
194    ByteCodeGenerationFail(Box<RibByteCodeGenerationError>),
195
196    // RibTypeError is a type error that occurs during type inference.
197    // This is a typical compilation error, such as: expected u32, found str.
198    RibTypeError(Box<RibTypeError>),
199
200    // This captures only the syntax parse errors in a Rib script.
201    InvalidSyntax(String),
202
203    // This occurs when the Rib script includes global inputs that cannot be
204    // fulfilled. For example, if Rib is used from a REPL, the only valid global input will be `env`.
205    // If it is used from the Golem API gateway, it is  `request`.
206    // If the user specifies a global input such as `foo`
207    // (e.g., the compiler will treat `foo` as a global input in a Rib script like `my-worker-function(foo)`),
208    // it will fail compilation with this error.
209    // Note: the type inference phase will still be happy with this Rib script;
210    // we perform this validation as an extra step at the end to allow clients of `golem-rib`
211    // to decide what global inputs are valid.
212    UnsupportedGlobalInput {
213        invalid_global_inputs: Vec<String>,
214        valid_global_inputs: Vec<String>,
215    },
216
217    // A typical use of static analysis in Rib is to identify all the valid worker functions.
218    // If this analysis phase fails, it typically indicates a bug in the Rib compiler.
219    RibStaticAnalysisError(String),
220}
221
222impl From<RibByteCodeGenerationError> for RibCompilationError {
223    fn from(err: RibByteCodeGenerationError) -> Self {
224        RibCompilationError::ByteCodeGenerationFail(Box::new(err))
225    }
226}
227
228impl From<RibTypeError> for RibCompilationError {
229    fn from(err: RibTypeError) -> Self {
230        RibCompilationError::RibTypeError(Box::new(err))
231    }
232}
233
234impl Display for RibCompilationError {
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        match self {
237            RibCompilationError::RibStaticAnalysisError(msg) => {
238                write!(f, "rib static analysis error: {msg}")
239            }
240            RibCompilationError::RibTypeError(err) => write!(f, "{err}"),
241            RibCompilationError::InvalidSyntax(msg) => write!(f, "invalid rib syntax: {msg}"),
242            RibCompilationError::UnsupportedGlobalInput {
243                invalid_global_inputs,
244                valid_global_inputs,
245            } => {
246                write!(
247                    f,
248                    "unsupported global input variables: {}. expected: {}",
249                    invalid_global_inputs.join(", "),
250                    valid_global_inputs.join(", ")
251                )
252            }
253            RibCompilationError::ByteCodeGenerationFail(e) => {
254                write!(f, "{e}")
255            }
256        }
257    }
258}
259
260impl Error for RibCompilationError {}
261
262#[cfg(test)]
263mod compiler_error_tests {
264    mod type_mismatch_errors {
265        use test_r::test;
266
267        use crate::compiler::compiler_error_tests::test_utils;
268        use crate::compiler::compiler_error_tests::test_utils::strip_spaces;
269        use crate::{Expr, RibCompiler, RibCompilerConfig};
270
271        #[test]
272        async fn test_invalid_pattern_match0() {
273            let expr = r#"
274          match 1 {
275            1 =>  {  foo : "bar"  },
276            2 =>  {  foo : 1  }
277          }
278
279        "#;
280
281            let expr = Expr::from_text(expr).unwrap();
282
283            let metadata = test_utils::get_metadata();
284
285            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
286            let error_msg = compiler.compile(expr).unwrap_err().to_string();
287
288            let expected = r#"
289            error in the following rib found at line 3, column 28
290            `"bar"`
291            cause: type mismatch. expected s32, found string
292            the expression `"bar"` is inferred as `string` by default
293            "#;
294
295            assert_eq!(error_msg, test_utils::strip_spaces(expected));
296        }
297
298        #[test]
299        async fn test_invalid_pattern_match1() {
300            let expr = r#"
301          let x = 1;
302          match some(x) {
303            some(_) => {foo: x},
304            none => {foo: "bar"}
305          }
306        "#;
307
308            let expr = Expr::from_text(expr).unwrap();
309
310            let metadata = test_utils::get_metadata();
311
312            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
313            let error_msg = compiler.compile(expr).unwrap_err().to_string();
314
315            let expected = r#"
316            error in the following rib found at line 2, column 19
317            `1`
318            cause: type mismatch. expected string, found s32
319            the expression `1` is inferred as `s32` by default
320            "#;
321
322            assert_eq!(error_msg, test_utils::strip_spaces(expected));
323        }
324
325        #[test]
326        async fn test_invalid_pattern_match2() {
327            let expr = r#"
328          let x: option<u64> = some(1);
329          match x {
330            some(x) => ok(x),
331            none    => ok("none")
332          }
333        "#;
334
335            let expr = Expr::from_text(expr).unwrap();
336
337            let metadata = test_utils::get_metadata();
338
339            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
340            let error_msg = compiler.compile(expr).unwrap_err().to_string();
341
342            let expected = r#"
343            error in the following rib found at line 5, column 27
344            `"none"`
345            cause: type mismatch. expected u64, found string
346            expected type u64 based on expression `x` found at line 4 column 27
347            the expression `"none"` is inferred as `string` by default
348            "#;
349
350            assert_eq!(error_msg, test_utils::strip_spaces(expected));
351        }
352
353        #[test]
354        async fn test_invalid_pattern_match3() {
355            let expr = r#"
356          let x: option<u64> = some(1);
357          match x {
358            some(x) => ok("none"),
359            none    => ok(1)
360          }
361        "#;
362
363            let expr = Expr::from_text(expr).unwrap();
364
365            let metadata = test_utils::get_metadata();
366
367            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
368            let error_msg = compiler.compile(expr).unwrap_err().to_string();
369
370            let expected = r#"
371            error in the following rib found at line 4, column 27
372            `"none"`
373            cause: type mismatch. expected s32, found string
374            expected type s32 based on expression `1` found at line 5 column 27
375            the expression `1` is inferred as `s32` by default
376            the expression `"none"` is inferred as `string` by default
377            "#;
378
379            assert_eq!(error_msg, test_utils::strip_spaces(expected));
380        }
381
382        #[test]
383        async fn test_invalid_pattern_match4() {
384            let expr = r#"
385          let x: s32 = 1;
386          let y: u64 = 2;
387
388          match some(1) {
389            some(_) => ok(x),
390            none    => ok(y)
391          }
392        "#;
393
394            let expr = Expr::from_text(expr).unwrap();
395
396            let metadata = test_utils::get_metadata();
397
398            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
399            let error_msg = compiler.compile(expr).unwrap_err().to_string();
400
401            let expected = r#"
402            error in the following rib found at line 7, column 27
403            `y`
404            cause: type mismatch. expected s32, found u64
405            expected type s32 based on expression `x` found at line 6 column 27
406            the type of `x` is declared as `s32` at line 2 column 11
407            the type of `y` is declared as `u64` at line 3 column 11
408            "#;
409
410            assert_eq!(error_msg, test_utils::strip_spaces(expected));
411        }
412
413        #[test]
414        fn test_invalid_function_call0() {
415            let expr = r#"
416          let result = foo(1);
417          result
418        "#;
419
420            let expr = Expr::from_text(expr).unwrap();
421
422            let metadata = test_utils::get_metadata();
423
424            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
425            let error_msg = compiler.compile(expr).unwrap_err().to_string();
426
427            let expected = r#"
428            error in the following rib found at line 2, column 28
429            `1`
430            cause: type mismatch. expected record { a: record { aa: s32, ab: s32, ac: list<s32>, ad: record { ada: s32 }, ae: tuple<s32, string> }, b: u64, c: list<s32>, d: record { da: s32 } }, found s32
431            invalid argument to the function `foo`
432            "#;
433
434            assert_eq!(error_msg, test_utils::strip_spaces(expected));
435        }
436
437        #[test]
438        fn test_invalid_function_call1asdasd() {
439            let expr = r#"
440          let worker = instance("my-worker");
441          let result = worker.foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: "foo", c: [1, 2, 3], d: {da: 4}});
442          result
443        "#;
444
445            let expr = Expr::from_text(expr).unwrap();
446
447            let metadata = test_utils::get_metadata();
448
449            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
450            let error_msg = compiler.compile(expr).unwrap_err().to_string();
451
452            let expected = r#"
453            error in the following rib found at line 3, column 100
454            `"foo"`
455            cause: type mismatch. expected u64, found string
456            the expression `"foo"` is inferred as `string` by default
457            "#;
458
459            assert_eq!(error_msg, test_utils::strip_spaces(expected));
460        }
461
462        #[test]
463        fn test_invalid_function_call2() {
464            let expr = r#"
465          let worker = instance("my-worker");
466          let result = worker.foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: ["foo", "bar"], d: {da: 4}});
467          result
468        "#;
469
470            let expr = Expr::from_text(expr).unwrap();
471
472            let metadata = test_utils::get_metadata();
473
474            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
475            let error_msg = compiler.compile(expr).unwrap_err().to_string();
476
477            let expected = r#"
478            error in the following rib found at line 3, column 107
479            `"foo"`
480            cause: type mismatch. expected s32, found string
481            the expression `"foo"` is inferred as `string` by default
482            "#;
483
484            assert_eq!(error_msg, test_utils::strip_spaces(expected));
485        }
486
487        #[test]
488        fn test_invalid_function_call3() {
489            let expr = r#"
490          let worker = instance();
491          let result = worker.foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: "foo"}});
492          result
493        "#;
494
495            let expr = Expr::from_text(expr).unwrap();
496
497            let metadata = test_utils::get_metadata();
498
499            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
500            let error_msg = compiler.compile(expr).unwrap_err().to_string();
501
502            let expected = r#"
503            error in the following rib found at line 3, column 122
504            `"foo"`
505            cause: type mismatch. expected s32, found string
506            the expression `"foo"` is inferred as `string` by default
507            "#;
508
509            assert_eq!(error_msg, test_utils::strip_spaces(expected));
510        }
511
512        // Here the difference is, the shape itself is different losing the preciseness of the error.
513        // The best precise error
514        // is type-mismatch, however, here we get an ambiguity error. This can be improved,
515        // by not allowing accumulation of conflicting types into Exprs that are part of a function call
516        #[test]
517        fn test_invalid_function_call4() {
518            let expr = r#"
519          let result = foo({a: {aa: 1, ab: 2, ac: (1, 2), ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: 1}});
520          result
521        "#;
522
523            let expr = Expr::from_text(expr).unwrap();
524
525            let metadata = test_utils::get_metadata();
526
527            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
528            let error_msg = compiler.compile(expr).unwrap_err().to_string();
529
530            let expected = r#"
531            error in the following rib found at line 2, column 51
532            `(1, 2)`
533            cause: ambiguous types: `list<number>`, `tuple<number, number>`
534            "#;
535
536            assert_eq!(error_msg, test_utils::strip_spaces(expected));
537        }
538
539        #[test]
540        fn test_invalid_function_call5() {
541            let expr = r#"
542            let x = {a: "foo"};
543          let result = foo({a: {aa: 1, ab: 2, ac: x, ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: 1}});
544          result
545        "#;
546
547            let expr = Expr::from_text(expr).unwrap();
548
549            let metadata = test_utils::get_metadata();
550
551            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
552            let error_msg = compiler.compile(expr).unwrap_err().to_string();
553
554            let expected = r#"
555            error in the following rib found at line 2, column 21
556            `{a: "foo"}`
557            cause: ambiguous types: `list<number>`, `record{a: string}`
558            "#;
559
560            assert_eq!(error_msg, test_utils::strip_spaces(expected));
561        }
562
563        #[test]
564        fn test_invalid_function_call6() {
565            let expr = r#"
566          let worker = instance("my-worker");
567          let result = worker.foo({a: {aa: "foo", ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}});
568          result
569        "#;
570
571            let expr = Expr::from_text(expr).unwrap();
572
573            let metadata = test_utils::get_metadata();
574
575            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
576            let error_msg = compiler.compile(expr).unwrap_err().to_string();
577
578            let expected = r#"
579            error in the following rib found at line 3, column 44
580            `"foo"`
581            cause: type mismatch. expected s32, found string
582            the expression `"foo"` is inferred as `string` by default
583            "#;
584
585            assert_eq!(error_msg, test_utils::strip_spaces(expected));
586        }
587
588        #[test]
589        fn test_invalid_function_call7() {
590            let expr = r#"
591          let worker = instance();
592          let result = worker.foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}});
593          result
594        "#;
595
596            let expr = Expr::from_text(expr).unwrap();
597
598            let metadata = test_utils::get_metadata();
599
600            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
601            let error_msg = compiler.compile(expr).unwrap_err().to_string();
602
603            let expected = r#"
604            error in the following rib found at line 3, column 76
605            `"1"`
606            cause: type mismatch. expected s32, found string
607            the expression `"1"` is inferred as `string` by default
608            "#;
609
610            assert_eq!(error_msg, test_utils::strip_spaces(expected));
611        }
612
613        #[test]
614        fn test_invalid_function_call8() {
615            let expr = r#"
616            let worker = instance("my-worker");
617            let bar = {a: {ac: 1}};
618            worker.foo(bar)
619        "#;
620
621            let expr = Expr::from_text(expr).unwrap();
622
623            let metadata = test_utils::get_metadata();
624
625            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
626            let error_msg = compiler.compile(expr).unwrap_err().to_string();
627
628            let expected = r#"
629            error in the following rib found at line 3, column 32
630            `1`
631            cause: type mismatch. expected list<s32>, found s32
632            the expression `1` is inferred as `s32` by default
633            "#;
634
635            assert_eq!(error_msg, test_utils::strip_spaces(expected));
636        }
637
638        #[test]
639        fn test_invalid_function_call9() {
640            let expr = r#"
641          let worker = instance("my-worker");
642          let result = worker.foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}});
643          result
644        "#;
645
646            let expr = Expr::from_text(expr).unwrap();
647
648            let metadata = test_utils::get_metadata();
649
650            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
651            let error_msg = compiler.compile(expr).unwrap_err().to_string();
652
653            let expected = r#"
654            error in the following rib found at line 3, column 88
655            `2`
656            cause: type mismatch. expected string, found s32
657            the expression `2` is inferred as `s32` by default
658            "#;
659
660            assert_eq!(error_msg, test_utils::strip_spaces(expected));
661        }
662
663        #[test]
664        fn test_invalid_function_call10() {
665            let expr = r#"
666          let result = foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3]});
667          result
668        "#;
669
670            let expr = Expr::from_text(expr).unwrap();
671
672            let metadata = test_utils::get_metadata();
673
674            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
675            let error_msg = compiler.compile(expr).unwrap_err().to_string();
676
677            let expected = r#"
678            error in the following rib found at line 2, column 28
679            `{a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3]}`
680            cause: invalid argument to the function `foo`.  missing field(s) in record: `d`
681            "#;
682
683            assert_eq!(error_msg, test_utils::strip_spaces(expected));
684        }
685
686        #[test]
687        fn test_invalid_function_call11() {
688            let expr = r#"
689          let result = foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ad: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}});
690          result
691        "#;
692
693            let expr = Expr::from_text(expr).unwrap();
694
695            let metadata = test_utils::get_metadata();
696
697            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
698            let error_msg = compiler.compile(expr).unwrap_err().to_string();
699
700            let expected = r#"
701            error in the following rib found at line 2, column 28
702            `{a: {aa: 1, ab: 2, ac: [1, 2], ad: {ad: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}}`
703            cause: invalid argument to the function `foo`.  missing field(s) in record: `a.ad.ada`
704            "#;
705
706            assert_eq!(error_msg, test_utils::strip_spaces(expected));
707        }
708
709        #[test]
710        fn test_invalid_function_call12() {
711            let expr = r#"
712          let result = foo({aa: {aa: 1, ab: 2, ac: [1, 2], ad: {ad: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}});
713          result
714        "#;
715
716            let expr = Expr::from_text(expr).unwrap();
717
718            let metadata = test_utils::get_metadata();
719
720            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
721            let error_msg = compiler.compile(expr).unwrap_err().to_string();
722
723            let expected = r#"
724            error in the following rib found at line 2, column 28
725            `{aa: {aa: 1, ab: 2, ac: [1, 2], ad: {ad: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}}`
726            cause: invalid argument to the function `foo`.  missing field(s) in record: `a`
727            "#;
728
729            assert_eq!(error_msg, test_utils::strip_spaces(expected));
730        }
731
732        #[test]
733        fn test_invalid_function_call13() {
734            let expr = r#"
735            let aa = 1;
736          let result = foo({aa: 1});
737          result
738        "#;
739
740            let expr = Expr::from_text(expr).unwrap();
741
742            let metadata = test_utils::get_metadata();
743
744            let compiler = RibCompiler::new(RibCompilerConfig::new(metadata, vec![], vec![]));
745            let error_msg = compiler.compile(expr).unwrap_err().to_string();
746
747            let expected = r#"
748            error in the following rib found at line 3, column 28
749            `{aa: 1}`
750            cause: invalid argument to the function `foo`.  missing field(s) in record: `a, b, c, d`
751            "#;
752
753            assert_eq!(error_msg, test_utils::strip_spaces(expected));
754        }
755
756        #[test]
757        async fn test_invalid_resource_constructor_call0() {
758            let expr = r#"
759          let worker = instance("my-worker");
760          let x = worker.cart()
761        "#;
762            let expr = Expr::from_text(expr).unwrap();
763            let component_metadata = test_utils::get_metadata();
764
765            let compiler_config = RibCompilerConfig::new(component_metadata, vec![], vec![]);
766            let compiler = RibCompiler::new(compiler_config);
767            let error_message = compiler.compile(expr).unwrap_err().to_string();
768
769            let expected = r#"
770            error in the following rib found at line 3, column 19
771            `worker.cart()`
772            cause: invalid argument size for function `cart`. expected 1 arguments, found 0
773            "#;
774
775            assert_eq!(error_message, strip_spaces(expected));
776        }
777
778        #[test]
779        async fn test_invalid_resource_constructor_call1() {
780            let expr = r#"
781          let worker = instance("my-worker");
782          let x = worker.cart(1)
783        "#;
784            let expr = Expr::from_text(expr).unwrap();
785            let component_metadata = test_utils::get_metadata();
786
787            let compiler_config = RibCompilerConfig::new(component_metadata, vec![], vec![]);
788            let compiler = RibCompiler::new(compiler_config);
789            let error_message = compiler.compile(expr).unwrap_err().to_string();
790
791            let expected = r#"
792            error in the following rib found at line 3, column 31
793            `1`
794            cause: type mismatch. expected string, found s32
795            invalid argument to the function `cart`
796            "#;
797
798            assert_eq!(error_message, strip_spaces(expected));
799        }
800
801        #[test]
802        async fn test_invalid_resource_method_call0() {
803            let expr = r#"
804          let worker = instance("my-worker");
805          let x = worker.cart("foo");
806          x.add-item(1)
807        "#;
808            let expr = Expr::from_text(expr).unwrap();
809            let component_metadata = test_utils::get_metadata();
810
811            let compiler_config = RibCompilerConfig::new(component_metadata, vec![], vec![]);
812            let compiler = RibCompiler::new(compiler_config);
813            let error_message = compiler.compile(expr).unwrap_err().to_string();
814
815            let expected = r#"
816            error in the following rib found at line 4, column 22
817            `1`
818            cause: type mismatch. expected record { product-id: string, name: string, price: f32, quantity: u32 }, found s32
819            invalid argument to the function `add-item`
820            "#;
821
822            assert_eq!(error_message, strip_spaces(expected));
823        }
824    }
825
826    mod test_utils {
827        use crate::wit_type::{
828            case, f32, field, handle, list, record, s32, str, tuple, u32, u64, variant,
829        };
830        use crate::wit_type::{
831            AnalysedResourceId, AnalysedResourceMode, NameTypePair, WitExport, WitFunction,
832            WitFunctionParameter, WitFunctionResult, WitInterface,
833        };
834        use crate::{ComponentDependency, ComponentDependencyKey};
835        use uuid::Uuid;
836
837        pub(crate) fn strip_spaces(input: &str) -> String {
838            let lines = input.lines();
839
840            let first_line = lines
841                .clone()
842                .find(|line| !line.trim().is_empty())
843                .unwrap_or("");
844            let margin_width = first_line.chars().take_while(|c| c.is_whitespace()).count();
845
846            let result = lines
847                .map(|line| {
848                    if line.trim().is_empty() {
849                        String::new()
850                    } else {
851                        line[margin_width..].to_string()
852                    }
853                })
854                .collect::<Vec<String>>()
855                .join("\n");
856
857            result.strip_prefix("\n").unwrap_or(&result).to_string()
858        }
859
860        pub(crate) fn get_metadata() -> ComponentDependency {
861            let function_export = WitExport::Function(WitFunction {
862                name: "foo".to_string(),
863                parameters: vec![WitFunctionParameter {
864                    name: "arg1".to_string(),
865                    typ: record(vec![
866                        NameTypePair {
867                            name: "a".to_string(),
868                            typ: record(vec![
869                                NameTypePair {
870                                    name: "aa".to_string(),
871                                    typ: s32(),
872                                },
873                                NameTypePair {
874                                    name: "ab".to_string(),
875                                    typ: s32(),
876                                },
877                                NameTypePair {
878                                    name: "ac".to_string(),
879                                    typ: list(s32()),
880                                },
881                                NameTypePair {
882                                    name: "ad".to_string(),
883                                    typ: record(vec![NameTypePair {
884                                        name: "ada".to_string(),
885                                        typ: s32(),
886                                    }]),
887                                },
888                                NameTypePair {
889                                    name: "ae".to_string(),
890                                    typ: tuple(vec![s32(), str()]),
891                                },
892                            ]),
893                        },
894                        NameTypePair {
895                            name: "b".to_string(),
896                            typ: u64(),
897                        },
898                        NameTypePair {
899                            name: "c".to_string(),
900                            typ: list(s32()),
901                        },
902                        NameTypePair {
903                            name: "d".to_string(),
904                            typ: record(vec![NameTypePair {
905                                name: "da".to_string(),
906                                typ: s32(),
907                            }]),
908                        },
909                    ]),
910                }],
911                result: Some(WitFunctionResult { typ: str() }),
912            });
913
914            let resource_export = WitExport::Interface(WitInterface {
915                name: "golem:it/api".to_string(),
916                functions: vec![
917                    WitFunction {
918                        name: "[constructor]cart".to_string(),
919                        parameters: vec![WitFunctionParameter {
920                            name: "cons".to_string(),
921                            typ: str(),
922                        }],
923                        result: Some(WitFunctionResult {
924                            typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Owned),
925                        }),
926                    },
927                    WitFunction {
928                        name: "[method]cart.add-item".to_string(),
929                        parameters: vec![
930                            WitFunctionParameter {
931                                name: "self".to_string(),
932                                typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
933                            },
934                            WitFunctionParameter {
935                                name: "item".to_string(),
936                                typ: record(vec![
937                                    field("product-id", str()),
938                                    field("name", str()),
939                                    field("price", f32()),
940                                    field("quantity", u32()),
941                                ]),
942                            },
943                        ],
944                        result: None,
945                    },
946                    WitFunction {
947                        name: "[method]cart.remove-item".to_string(),
948                        parameters: vec![
949                            WitFunctionParameter {
950                                name: "self".to_string(),
951                                typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
952                            },
953                            WitFunctionParameter {
954                                name: "product-id".to_string(),
955                                typ: str(),
956                            },
957                        ],
958                        result: None,
959                    },
960                    WitFunction {
961                        name: "[method]cart.update-item-quantity".to_string(),
962                        parameters: vec![
963                            WitFunctionParameter {
964                                name: "self".to_string(),
965                                typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
966                            },
967                            WitFunctionParameter {
968                                name: "product-id".to_string(),
969                                typ: str(),
970                            },
971                            WitFunctionParameter {
972                                name: "quantity".to_string(),
973                                typ: u32(),
974                            },
975                        ],
976                        result: None,
977                    },
978                    WitFunction {
979                        name: "[method]cart.checkout".to_string(),
980                        parameters: vec![WitFunctionParameter {
981                            name: "self".to_string(),
982                            typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
983                        }],
984                        result: Some(WitFunctionResult {
985                            typ: variant(vec![
986                                case("error", str()),
987                                case("success", record(vec![field("order-id", str())])),
988                            ]),
989                        }),
990                    },
991                    WitFunction {
992                        name: "[method]cart.get-cart-contents".to_string(),
993                        parameters: vec![WitFunctionParameter {
994                            name: "self".to_string(),
995                            typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
996                        }],
997                        result: Some(WitFunctionResult {
998                            typ: list(record(vec![
999                                field("product-id", str()),
1000                                field("name", str()),
1001                                field("price", f32()),
1002                                field("quantity", u32()),
1003                            ])),
1004                        }),
1005                    },
1006                    WitFunction {
1007                        name: "[method]cart.merge-with".to_string(),
1008                        parameters: vec![
1009                            WitFunctionParameter {
1010                                name: "self".to_string(),
1011                                typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
1012                            },
1013                            WitFunctionParameter {
1014                                name: "other-cart".to_string(),
1015                                typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
1016                            },
1017                        ],
1018                        result: None,
1019                    },
1020                    WitFunction {
1021                        name: "[drop]cart".to_string(),
1022                        parameters: vec![WitFunctionParameter {
1023                            name: "self".to_string(),
1024                            typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Owned),
1025                        }],
1026                        result: None,
1027                    },
1028                ],
1029            });
1030
1031            let key = ComponentDependencyKey {
1032                component_name: "some_name".to_string(),
1033                component_id: Uuid::new_v4(),
1034                component_revision: 0,
1035                root_package_name: None,
1036                root_package_version: None,
1037            };
1038
1039            let exports = vec![function_export, resource_export];
1040            ComponentDependency::from_wit_metadata(key, &exports).unwrap()
1041        }
1042    }
1043}