Welcome to the new Golem Cloud Docs! 👋
Rib

Rib

Rib is the language used by the API 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.

rib_expr          ::= rib_expr_untyped (":" type_name)?

rib_expr_untyped  ::= simple_expr rib_expr_rest

simple_expr       ::= simple_expr_untyped (":" type_name)?

simple_expr_untyped ::=
      list_comprehension
    | list_reduce
    | pattern_match
    | let_binding
    | conditional
    | flag
    | record
    | code_block
    | tuple
    | sequence
    | boolean_literal
    | literal
    | not
    | option
    | result
    | identifier
    | call
    | signed_num


rib_expr_rest       ::= binary_rest | method_invocation | select_field_rest | select_index_res | number_rest  |  range_rest

select_field_rest   ::=  ("." select_field)*

binary_rest         ::=  (binary_op simple_expr)*

number_rest         ::=  "." fraction

method_invocation   ::= "." call

call                ::= function_name ("(" argument ("," argument)* ")")?

function_name       ::= variant | enum | worker_function_call

select_index_rest   ::= ("[" select_index "]")*

binary_op           ::=  ">=" | "<=" | "==" | "<" | ">" | "&&" | "||" | "+" | "-" | "*" | "/"

range_rest          ::= ".." ("="? pos_num | pos_num? )

fraction            ::= digits (('e' | 'E') ('+' | '-')? digits)?

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        ::= 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)* "]"

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 ";"
                     "}"

text                ::= letter (letter | digit | "_" | "-")*

pos_num             ::= digit+

digits              ::= [0-9]+

signed_num          ::= ("+" | "-")? pos_num

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          ::= any_text

function_name       ::= variant | enum | instance | text

instance            ::= "instance"

any_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 grammar is mainly to give a high level overview of the syntax of Rib.

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

1

You can annotate a type to Rib expression to make it specific, otherwise, it will get inferred as u64 for a positive integer, s64 for a signed integer, and f64 for a floating point number.

1: u64

Assign to a variable

let x = 1;

We can annotate the type of the variable as well.

let x: u64 = 1;

If you are passing this variable to a worker function, then you may not need to specify the type of the variable.

String

"foo"

String Interpolation (concatenation)

This is similar to languages like scala where you start with ${ and end with }

let x = "foo";
let y = "bar";
let z = "${x}-and-${y}";

Evaluating this Rib will result in "foo-and-bar". The type of z is a string.

Identifier

foo

Here foo is an identifier. Note that identifiers are not wrapped with quotes.

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 api-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 api-gateway part of golem the only valid global variable supported is request and nothing else. This would mean, it will fail compilation.

More on global inputs to follow.

Boolean

true
false

Sequence

# Sequence of numbers
[1, 2, 3]

You can annotate the type of the sequence as well.

let x: list<s32> = [1, 2, 3];
["foo", "bar", "baz"]
# 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

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

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

Note that, sometimes you will need to annotate the type of the number. It depends on the compilation context.

Note that keys are not considered as variables. Also note that keys in a WASM record don't have quotes. Example: {"foo" : "bar"} is wrong.

Tuple

(1, 20.1, "foo")

This is also equivalent to the following in Rib. You can be specific about the types just like in any rib expression.

let k: (u64, f64, string) = (1, 20.1, "foo");
k

Unlike sequence, the values in a tuple can be of different types.

Here is another example:

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

Flags

{ Foo, Bar, Baz }

This is of a flag type.

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.

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

Selection of Index

This is selecting an index from a sequence value.

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

You can also inline as given below

[1, 2, 3][0]

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.

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

Option

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.

 some(1)
 none

The syntax is inspired from wasm wave.

Comparison (Boolean)

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.

Arithmetic

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

+, -, / and * are supported.

Conditional Statement

 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

 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

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

  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

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

This will result in following error:

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:

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.

   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"}).

  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.

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

List Comprehension

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

List Aggregation

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

Ranges

Ranges can be right exclusive or right inclusive. The right exclusive range is denoted by .. and right inclusive range is denoted by ..=.

1..10;
let initial: u32 = 1;
let final: u32 = 10;
 
let x = initial..final;
 
for i in x {
  yield i
}

Similarly, you can use ..= to include the last number in the range.

1..=10;
let initial: u32 = 1;
let final: u32 = 10;
 
let x = initial..=final;
 
for i in x {
  yield i
}

Please note that, you may need to help Rib compiler with type annotations for the numbers involved in the range. This depends on the context. We will improve these aspects as we go.

You can also create infinite range, where you skip the right side of ..

1..;

However, note that as of now Rib interpreter (runtime) will spot any infinite loop and will throw an error. Example: The following will throw an error.

let x = 1:u8..;
 
for i in x {
  yield i
}

However, you can use infinite ranges to select a segment of the list without worrying about the end index.

let x: list<u32> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 
for i in x[1..] {
  yield i
}

Http Request Input and Field Selection

If you are using worker-gateway to write through http APIs, 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.

request

To select the body field in request,

request.body

To select a header from the request

request.headers

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

request.path.user

The request.path.* and request.headers.* will be inferred as string unless you specify it using type annotation.

Please note that Rib by itself don't support a keyword such as request, or path or body or headers. However, if you are using Rib through worker-gateway, then these values are available in the context of the Rib evaluator. Currently, in golem, worker-gateway is the only way to use Rib. But this will change soon. Example: We may be able to fire up a REPL and write Rib and invoke worker functions.

request.body will not be inferred properly, unless you pass it as it is to a worker function, as Rib knows the component metadata, and can infer the types automatically.

Type Inference

Rib is mainly used to write script to manipulate the input and call worker functions. In this case, for the most part, most of the types will be automatically inferred. Otherwise, there will be a compilation error, asking the developer to annotate the types and help the compiler.

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.

{
  "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.

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

Invoke worker functions

Rib is mainly used to write scripts to manipulate the input and call worker functions. Refer to the worker-gateway documentation for more on how you use Rib to invoke worker functions. This is useful to expose some http APIs on top of these worker functions running in golem platform, and you don't want to keep updating or change the components and expose additional APIs, as you can write a simple Rib script to do the necessary changes to the input and pass it to the worker functions. Invoking functions is similar to any other languages.

Durable worker function invocation

let my_worker = instance("my-worker");
let result = my_worker.get-cart-contents();
 
result

Rib is evaluated in the context of a particular component (this is taken care by worker-gateway that it evaluates Rib in the context of a wasm component).

In this script, first you create an instance (instance of a component) using instance function. instance function takes worker name as the argument. Once you created the instance, the functions in the component will be available to call.

Ephemeral Worker function invocation

The only difference here is that you don't need to pass an argument to the instance function.

let my_worker_instance = instance();
let result = my_worker.get-cart-contents();
result

You can avoid an unnecessary let-binding here too.

let my_worker_instance = instance();
my_worker.get-cart-contents();

In this case the return value of the script is the last expression in the script, and in this case, it is of the result type of get-cart-contents.

Worker function invocation with arguments

Let's say there exists a function add-to-cart which takes a product as the argument, where product is a wasm record

let my_worker_instance = instance("my-worker");
 
let product = {product-id: 1, quantity: 2, name: "mac"};
my_worker.add-to-cart(input);

Similarly you can pass multiple arguments to the worker function and they should be separated by ,.

Say you need to pass the username along with with the product details.

let my_worker_instance = instance("my-worker");
 
let product = {product-id: 1, quantity: 2, name: "mac"};
let username = "foo";
 
my_worker.add-to-cart(username, product);

You can inline the arguments as well.

let my_worker_instance = instance("my-worker");
 
let product = {product-id: 1, quantity: 2, name: "mac"};
my_worker.add-to-cart("foo", {product-id: 1, quantity: 2, name: "mac"});

Invoke functions in a Resource

Here is a relatively complex example where Rib is used to invoke functions in a resource cart Here the first step is to define the worker by calling instance function. Then you create a resource similar to a method invocation which is worker.cart. Here the only difference is cart is a resource rather than a function. Now you have the resource available to call methods on it such as add-item, remove-item etc.

Please note that, everything prior to a real function call is lazy. i.e, you are not reusing the same resource at runtime to call these functions.

  let worker = instance("my-worker");
  let cart = worker.cart("bar");
  cart.add-item({product-id: "mac", name: "apple", quantity: 1, price: 1});
  cart.remove-item(a);
  cart.update-item-quantity(a, 2);
  let result = cart.get-cart-contents();
  cart.drop();
  result

Handle conflicting function names using type parameters

Let's say a function name add-to-cart exists in multiple interfaces (say api1, api2) in the same component. In this case, you can specify the interface as a type parameter when invoking method in the instance.

Example:

let my_worker_instance = instance("my-worker");
 
let product = {product-id: 1, quantity: 2, name: "mac"};
 
let result = my_worker.add-to-cart[api1](product);
 
result

If you are not specifying type parameter that narrows down the context, then compiler will return an error similar to the below one:

error in the following rib found at line 3, column 30
`my_worker.add-to-cart(product)`
cause: invalid function call `qux`
function 'add-to-cart' exists in multiple interfaces. specify an interface name as type parameter from: api1, api2

Handle conflicting packages using type parameters

Let's say a function name add-to-cart exists in multiple packages. In this case, you can specify the package name too as a type parameter. Let's say you care only about the package amazon:shopping-cart.

let my_worker_instance = instance("my-worker");
 
let product = {product-id: 1, quantity: 2, name: "mac"};
 
let result = my_worker.add-to-cart[amazon:shopping-cart](product);
 
result

Handle conflicts at instance level

You can also include this type parameter at instance level such that every method invocation will be resolved to that package or interface.

let my_worker_instance = instance[amazon:shopping-cart]("my-worker");
 
let product = {product-id: 1, quantity: 2, name: "mac"};
 
let result = my_worker.add-to-cart(product);

Conflict resolution using fully qualified type parameter

If there exist a function add-to-cart in multiple interfaces within multiple packages, then you can specify fully qualified package name and interface as given below. As mentioned above, this can be either at instance level (which will get applied to all method calls on that instance) or at method level

let my_worker_instance = instance[amazon:shopping-cart/api]("my-worker");
 
let product = {product-id: 1, quantity: 2, name: "mac"};
 
let result = my_worker.add-to-cart(product);

Multiple invocations

Rib allows you to invoke a function multiple times, or invoke multiple functions across multiple workers in a single script. That said, it is important to note that Rib by itself is not durable and is stateless. The next time you invoke Rib (through worker-gateway for example), these functions will get executed against the worker again and doesn't cache any result in anyway.

Let's say we want to accumulate the contents of a shopping cart from user-id 1 to user-id 5.

  let worker = instance("my-worker");
  let cart = worker.cart[golem:it/api]("bar");
 
  let initial = 1: u64;
  let final = 5: u64;
  let range = initial..final;
 
  let result = for i in range {
     yield cart.get-cart-contents("user-id-${i}");
  };
 
  result
 

Currently Rib is not durable. Also it hasn't been tested with complex use-cases such as invoking multiple functions, or invoke functions against multiple workers. This is because, Rib's primary use-case in golem platform is for users to write reasonably simple scripts in the context of worker-gateway to manipulate the http input and call worker functions.

Limitations

We recommend the users of golem to not rely on Rib for complex business logic as of now. It's better to write it in standard languages that works with golem such as Rust, Python etc, making sure your logic or workflow is durable. We will be expanding the usability and reliability of Rib as we go.

Issues and Trouble Shooting

If you bump into compilation errors, annotating the types will help the compiler to infer the types properly. If you bump into internal runtime errors, please report to us and we will try to fix it as soon as possible.

We are making improvements in this space continuously.