rib/compiler/
mod.rs

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