Skip to main content

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