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