Welcome to the new Golem Cloud Docs! 👋
Documentation
Rib

Rib

Rib is the language used by the Worker Gateway that enables users to write programs capable of manipulating worker responses, which are WebAssembly (WASM) values.

Rib uses the WAVE (WebAssembly Value Encoding) (opens in a new tab) syntax for encoding values.

Rib Grammar

rib-expr below defines the grammar of Rib.

Terminal
rib_expr         ::= simple_expr rib_expr_rest
 
rib_expr_rest    ::= (binary_op simple_expr)*
 
simple_expr ::=
      list_comprehension
    | list_reduce
    | pattern_match
    | let_binding
    | conditional
    | selection_expr
    | flag
    | record
    | code_block
    | tuple
    | sequence
    | boolean_literal
    | literal
    | not
    | option
    | result
    | identifier
    | call
    | number
 
let_binding         ::= "let" let_variable (":" type_name)? "=" rib_expr
 
conditional         ::= "if" rib_expr "then" rib_expr "else" rib_expr
 
selection_expr      ::= select_field | select_index
 
select_field        ::= selection_base_expr "." identifier
 
select_index        ::= selection_base_expr "[" pos_num "]"
 
nested_indices      ::= "[" pos_num "]" ("," "[" pos_num "]")*
 
flag                ::= "{" flag_names "}"
 
flag_names          ::= flag_name ("," flag_name)*
 
flag_name           ::= (letter | "_" | digit | "-")+
 
record              ::= "{" record_fields "}"
 
record_fields       ::= field ("," field)*
 
field               ::= field_key ":" rib_expr
 
field_key           ::= (letter | "_" | digit | "-")+
 
code_block          ::= rib_expr (";" rib_expr)*
 
code_block_unit     ::= code_block ";"
 
selection_base_expr ::= select_index | select_field | identifier | sequence | tuple
 
tuple               ::= "("  ib_expr ("," rib_expr)* ")"
 
sequence            ::= "["  rib_expr ("," rib_expr)* "]"
 
call                ::= function_name ("(" argument ("," argument)* ")")?
 
function_name       ::= variant | enum | worker_function_call
 
enum                ::= identifier
 
variant             ::= identifier ( "(" rib_expr ")" )   ?
 
argument            ::= rib_expr
 
list_comprehension  ::= "for" identifier_text "in" expr "{"
                           code_block_unit?
                           "yield" expr ";"
                         "}"
 
list_reduce         ::= "reduce" identifier_text"," identifier_text "in" expr "from" expr "{"
                       code_block_unit?
                       "yield" expr ";"
                     "}"
 
binary_op           ::=  ">=" | "<=" | "==" | "<" | ">" | "&&" | "||" | "+" | "-" | "*" | "/"
 
text                ::= letter (letter | digit | "_" | "-")*
 
pos_num             ::= digit+
 
number              ::= pos_num (":" type_name)?
 
literal             ::= "\"" (static_term | dynamic_term)* "\""
 
static_term         ::= (letter | space | digit | "_" | "-" | "." | "/" | ":" | "@")+
 
dynamic_term        ::= "${" rib_expr "}"
 
boolean_literal     ::= "true" | "false"
 
not                 ::= "!" rib_expr
 
option              ::= "some" "(" rib_expr ")" | "none"
 
result              ::= "ok" "(" rib_expr ")" | "error" "(" rib_expr ")"
 
identifier          ::= identifier_text
 
identifier_text     ::= letter (letter | digit | "_" | "-")*
 
type_name           := basic_type | list_type | tuple_type | option_type | result_type
 
basic_type          ::= "bool" | "s8" | "u8" | "s16" | "u16" | "s32" | "u32" | "s64" | "u64" | "f32" | "f64" | "char" | "string"
 
list_type           ::= "list" "<" (basic_type | list_type | tuple_type | option_type | result_type) ">"
 
tuple_type          ::= "tuple" "<" (basic_type | list_type | tuple_type | option_type | result_type) ("," (basic_type | list_type | tuple_type | option_type | result_type))* ">"
 
option_type         ::= "option" "<" (basic_type | list_type | tuple_type | option_type | result_type) ">"
 
result_type         ::= "result" "<" (basic_type | list_type | tuple_type | option_type | result_type) "," (basic_type | list_type | tuple_type | option_type | result_type) ">"
 

The following sections show some examples of each construct.

Examples

Rib scripts can be multiline, and it should be separated by ;.

Return type of a Rib script

The last expression in the Rib script is the return value of the script. The last line in Rib script shouldn't end with a ;, as this is a syntax error.

Numbers

Terminal
1u16

Most of the time you need to specify the type of the number as it can be u8, u16, f32 or any of the WASM types representing numbers.

Another way to annotate type is

let x: f64 = 1;

This is a parsed as a number of type u64. Similarly -1 is parsed as a number of type i64 and 1.1 is parsed as a number of type f64.

Sometimes, you may not need to explicitly specify the type if you are passing the variable to a function that expects a specific number type. In this case, Rib can infer the types. Otherwise, you need to explicitly mention it.

String

Terminal
 
"Hello"

This is parsed as a string literal. If the strings are not wrapped with quotes, then it is considered as a identifier.

Boolean

Terminal
true

This is parsed as a boolean true. Similarly false is parsed as a boolean literal.

Variable identifier

Terminal
foo

This is parsed as a Rib variable expression and evaluating such an expression can fail if the value of this variable is not available in the context of evaluation. This implies, if you are running Rib specifying a wasm component through worker-gateway in golem, then Rib has the access to look at component metadata. If Rib finds this variable foo in the component metadata,then it tries to infer its types.

If such a variable is not present in the wasm component (i.e, you can cross check this in WIT file of the component), then technically there are two possibilities. Rib will choose to fail the compilation or consider it as a global variable input. A global input is nothing but Rib expects foo to be passed in to the evaluator of Rib script (somehow). Since you are using Rib from the worker-gateway part of golem the only valid global variable supported is request and nothing else. This would mean, it will fail compilation.

Sequence

Terminal
# Sequence of numbers
[1, 2, 3]
Terminal
# Sequence of strings
["foo", "bar", "baz"]
Terminal
# Sequence of record
[{a: "foo"}, {b : "bar"}]

This is parsed as a sequence of values. While the values can be of any type, similar to any dynamic language, evaluation may fail with error if we mix different types in a sequence. In other words, Rib evaluator do expect the values in a sequence to be of the same type.

Record

Terminal
 
{ name: "John", age: 30u16 }
 
{ city: "New York", population: 8000000u64 }
 
{ name: "John", age: 30u16, { country: "France", capital: "Paris" } }

This is parsed as a WASM Record. The syntax is inspired from WASM-WAVE.

Note that keys are not considered as variables. They are considered as literal strings (representing key name of a record element), even in the absence of quotes.

Why age is 30u16 instead of 30 ? If Rib infers the overall type of record (from the context), then you may not need to explicitly specify the type of 30 in the above example. However most of the times, you will need to specify the number types. We will try to be improve this part of Rib by providing some flexibility.

Tuple

Terminal
(1u8, 100u32, 20.1f32)

This is also equivalent to the following in Rib.

Terminal
let k: (u8, u32, f32) = (1, 100, 20.1);
k

This is parsed as a tuple of values. Unlike sequence, the values in a tuple can be of different types.

Terminal
("foo", 1, {a: "bar"})

Flags

Terminal
{ Foo, Bar, Baz }

This is parsed as Flag type in WASM. The values are separated by comma.

Selection of Field

A field can be selected from an Rib expression if it is a Record type. For example, foo.user is a valid selection given foo is a variable that gets evaluated to a record value.

Terminal
let foo = { name: "John", age: 30u16 };
foo.name

You can also inline as given below:

Terminal
{ name: "John", age: 30u16 }.name

Selection of Index

This is selecting an index from a sequence value.

Terminal
let x = [1, 2, 3];
x[0]

You can also inline as given below

Terminal
[1, 2, 3][0]

Request and selection of Fields

If you are using worker-gatway to upload API definitions, you can use the variable request in your Rib script, and that will be considered as a global input. worker-gateway will ensure to pass the value of request to the rib evaluator internally.

Please refer to worker-gateway documents for more details.

Terminal
request

To select the body field in request,

Terminal
 
request.body

To select a header from the request

Terminal
 
request.headers
 

To select the value of a path or query variable in your http request.

Terminal
request.path.user
 

Please note that Rib by itself don't support a keyword such as request, or path or body or headers. However these values are fed by the worker gateway. Please refer to worker-gateway documentations for more details.

Type Inference with Request Variables

Note that Rib infers types of the values only if the value is being passed to a worker function directly or indirectly. This is because Rib knows the argument types of a worker function and it can infer the values based on these types.

However, in situations where you have a standalone script such as request.body, it cannot infer the types and it will fail to compile. The best way to fix the compil time errors is to annotate it the type.

let x: string = request.body;
x

Say the request body is a record in Json, as given below. Rib sees it as a WASM Record type.

Terminal
{
  "user": "Alice",
  "age": 30u32
}

Then we can use Rib language to select the field user, as it considers this request's body as a WASM Record type.

Terminal
let x: string = request.body.user;
x

Invoke Worker Functions using Rib

Terminal
let worker_result = worker_function_name(request.body);
{ body: worker_result.user, headers: { "Content-Type": "application/json" } }

If the worker response is a record, then we can select the field in the response as follows

Terminal
 {
  "user": "Alice",
  "age": 30u32
}
 

Result

Resultin Rib is WASM Result, which can take the shape of ok or err. This is similar to Result type in std Rust.

A Result can be Ok of a value, or an Err of a value.

Terminal
 ok(1)
Terminal
 err("error")
Terminal
 
{
  "user": "ok(Alice)",
  "age": 30u32
}

Option Expr

Option in corresponding to WASM , which can take the shape of Some or None. This is similar to Option type in std Rust.

An Option can be Some of a value, or None.

Terminal
 some(1)
Terminal
 none

The syntax is inspired from wasm wave.

Comparison Operators

Terminal
let x: u8 = 1;
let y: u8 = 2;
x + y
 

Math operations

Terminal
let x: u8 = 1;
let y: u8 = 2;
x + y

Similarly, we can use other comparison operators like >=, <=, ==, < etc. Both operands should be a valid Rib code that points/evaluated to a number or string.

Conditional Statement

Terminal
 
 let id: u32 = 10;
 if id > 3 then "higher" else "lower"
 

The structure of the conditional statement is if <condition> then <then-rib-expr> else <else-rib-expr>, where condition-expr is an expr that should get evaluated to boolean. The then-rib-expr or else-rib-expr can be an any valid rib code, which could be another if else itself

Terminal
 
 let id: u32 = request.user.id;
 if id > 3 then "higher" else if id == 3 then "equal" else "lower"
 

You must ensure that the branches (then branch and else branch) resolve to the same type. Otherwise, Rib will fail to compile.

Pattern Matching

Terminal
 
let res: result<str, str> = ok("foo");
 
match res {
    ok(v) => v,
    err(msg) => msg
}
 

This would probably be your go-to construct when you are dealing with complex data structures like result or option or other custom variant (WASM Variant) that comes out as the response of a worker function

Terminal
  let worker_result = my_worker_function_name(1, "jon");
  match worker_result {
     ok(x) => "foo",
     err(msg) => "error"
  }

Exhaustive pattern matching

If the pattern match is not exhaustive, then it will throw compilation errors

Example: The following pattern matching is invalid

Terminal
  match worker_result {
    some(x) => "found",
  }

This will result in following error:

Terminal
Error: Non-exhaustive pattern match. The following patterns are not covered: `none`.
To ensure a complete match, add these patterns or cover them with a wildcard (`_`) or an identifier.

Now, let's say your worker responded with a variant. Note on variant: A variant statement defines a new type where instances of the type match exactly one of the variants listed for the type. This is similar to a "sum" type in algebraic datatype (or an enum in Rust if you're familiar with it). Variants can be thought of as tagged unions as well.

Pattern Match on Variants

Given you are using Rib through worker bridge, which knows about component metadata, then, let's say we have a variant of type as given below responded from the worker, when invoking a function called foo:

Terminal
variant my_variant {
    bar( {a: u32,b: string }),
    foo(list<string>),
}

then, the following will work:

let x = foo("abc");

match x {
  bar({a: _, b: _}) => "success",
  foo(some_list) => some_list[0]
}

Variables in the context of pattern matching

In all of the above, there exist a variable in the context of pattern. Example x in ok(x) or msg in err(msg) or x in some(x) or x in bar(x) or x in foo(x).

These variables are bound to the value that is being matched.

Example, given the worker response is ok(1), the following match expression will result in 2.

Terminal
   let result = my-worker-function("foo");
 
   match result {
    ok(x) => x + 1,
    err(msg) => 0
  }

The following match expression will result in "c", if the worker response was variant value foo(["a", "b", "c"]), and will result in "a" if the worker.response was variant value bar({a: 1, b: "a"}).

Terminal
  let result = my-worker-function();
 
  match result {
    bar(x) => x.b,
    foo(x) => x[1]
  }
 

Wild card patterns

In some of the above examples, the binding variables are unused. We can use _ as a wildcard pattern to indicate that we don't care about the value.

Terminal
  match worker.response {
    bar(_) => "bar",
    foo(_) => "foo"
  }

Let Binding

We have already seen let binding in the above examples. Here is another set of examples.

Terminal
 
 let worker_response = foo("bar");
 let a = { age: 10u8, city: "New York", name: "foo" };
 
 match worker_response {
   Admin(_) => {age: "**",  city: "***", name: "***" },
   User(x) => a
 }
 

Or even as simple as:

Terminal
 
  let x = { a : 1u32 };
  let y = { b : 2u32 };
  let z = x.a > y.b;
  z
 

List Comprehension

Terminal
 let x = ["foo", "bar"];
 
 for p in x {
   yield p;
 }

List Aggregation

Terminal
 let ages: list<u16> = [1, 2, 3];
 
 reduce z, a in ages from 0 {
   yield z + a;
 }

As mentioned above, return value of the script is the last expression in the script, and in this case, it is of the type boolean.

Known issues and Limitations

Rib has become a typed script as part of 1.0 release, and is probably the newest entry into the whole ecosystem of Golem. We are making great improvements in this space and there will be constant release especially in the space of Rib.

  • Incomplete syntax errors. We are working on this. For the most part a syntax (parser errors) comes from the following:

    • Missing semicolon at the end of the every Rib statement.
    • Adding a semicolon at the end of the last line in your Rib script. Rib will fail to compile in this case.
    • Missing closing curly braces.
    • Missing closing square brackets
    • Missing comma in sequence elements.
    • Invalid function names.
  • Complex compiler error messages. We are working on this

  • Compiler errors occur when you don't specify the type of numbers. Example 1 will fail whil 1u8 will not. This is because Rib is typed and is trying to infer a proper WASM type. We will see how to improve this.