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           ::= request
                     | let_binding
                     | select_field
                     | select_index
                     | sequence
                     | record
                     | tuple
                     | literal
                     | number
                     | flags
                     | variable
                     | boolean
                     | concat
                     | multiple
                     | not
                     | greater_than
                     | greater_than_or_equal_to
                     | less_than_or_equal_to
                     | equal_to
                     | less_than
                     | conditional
                     | pattern_match
                     | option
                     | result
                     | function-call
 
let_binding        ::= "let" variable (':' type_name)? '=' rib-expr ';'
select_field       ::= request_field | worker_field | (record | variable) '.' variable_sequence
worker_field       ::= "worker.response" | "worker.response." variable_sequence
request_field      ::= request_body | request_path | request_header
request_body       ::= "request.body" | "request.body." variable_sequence
request_path       ::= "request.path" | "request.path." variable_sequence
request_header     ::= "request.header." variable_sequence
variable_sequence  ::= variable ('.' variable)*
select_index       ::= (sequence | variable) ('[' rib-expr ']')+
sequence           ::= '[' rib-expr (',' rib-expr)* ']'
record             ::= '{' field (',' field)* '}'
tuple              ::= '(' rib-expr (',' rib-expr)* ')'
literal            ::= '"' [a-zA-Z0-9]* '"'
variable           ::= [a-zA-Z0-9_]+
number             ::= DIGITS(number_type_suffix)?
flags              ::= '{' STRING (',' STRING)* '}'
boolean            ::= "boolean" '(' BOOLEAN ')'
concat             ::= concat-elem*
concat-elem        ::= text | '${' variable '}'
text               ::= '[^${}]*'
multiple           ::= "multiple" '(' rib-expr* ')'
not                ::= "not" '(' rib-expr ')'
gt                 ::= rib-expr '>' rib-expr
gt_or_eq_to        ::= rib-expr ">=" rib-expr
lt                 ::= rib-expr '<' rib-expr
lt_or_eq_to        ::= rib-expr "<=" rib-expr
eq_to              ::= rib-expr "==" rib-expr
conditional        ::= "if" rib-expr "then" rib-expr "else" rib-expr
pattern_match      ::= "match" rib-expr '{' match_arms '}'
match_arms         ::= match_arm (',' match_arm)*
match_arm          ::= pattern '=>' rib-expr
pattern            ::= variable | ok | some | none | err | '_'
option             ::= "some" '(' rib-expr ')' | "none"
result             ::= "ok" '(' rib-expr ')' | "err" '(' rib-expr ')'
field_list         ::= field (',' field)*
field              ::= variable ':' rib-expr
pattern            ::= variable
                      | literal
                      | '_' // wildcard pattern
type_name          ::= "bool"
                     | "s8" | "u8"
                     | "s16" | "u16"
                     | "s32" | "u32"
                     | "s64" | "u64"
                     | "f32" | "f64"
                     | "chr" | "str"
                     | "list" '<' type_name '>'
                     | "tuple" '<' type_name (',' type_name)* '>'
                     | "option" '<' type_name '>'
 
number_type_suffix ::= "s8"|"s16"|"s32"|"s64"|"u8"|"u16"|"u32"|"u64"|"f32"|"f64"
 

The following sections show some examples of each construct.

Examples

Note that Rib expressions in the below examples need to be wrapped in code block using ${ }. This is to differentiate between raw text and an actual Rib program. As we progress, we may try to avoid the need of it, and being able to write Rib program in IDE without code-blocks. Example: ${1} is evaluated as a Number while 1 is evaluated as text.

Rib scripts can be multiline, and it should be separated by ;. The last expression in the Rib script is the return value of the script.

Numbers

Terminal
 
1

However, 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.

Example:

Terminal
1f64

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.

String

Terminal
 
"Hello"

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

Boolean

Terminal
true

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

Variables

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. Usually variables are used to refer to the values in the context of evaluation. The context can mostly be let binding or pattern matching or request or worker or response etc.

More explanations on variables can be found in other examples.

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: 30u32 }
{ city: "New York", population: 8000000u64 }
{ country: "France", capital: "Paris" }
{ fruits: ["apple"", "banana"", "orange""], count: 3u32 }
 

This is parsed as a WASM Record. The syntax is inspired from WASM-WAVE. The minor difference here is strings are wrapped with single quotes instead of double quotes. Note that keys are not considered as variables. They are considered as literals (even in the absence of single quotes) if they are part of Record.

Tuple

(1u8, 100u32, 20.1f32)

This is also equivalent to the following in Rib.

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
{ name: "John", age: 30u16 }.name

Selection of Index

This is selecting an index from a sequence value.

Terminal
[1, 2, 3][0]

For example foo[10] is valid given foo is an Rib variable that gets evaluated to a sequence.

Request and selection of Fields

If using worker-bridge of golem OSS, you can use the variable request as this is available in the runtime context of Rib in worker-bridge that points to the input request.

Terminal
request

To select the body field in request,

Terminal
 
request.body

Note that Rib infers types of the values only if the value is being passed to a worker function, where in it looks up the component metadata. However, in situations where you have a standalone script such as request.body, it cannot infer the types and fail to compile. The best way to fix this and ensure there are no compile time errors is to annotate it the type.

let x: str = 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: str = request.body.user;
x

Invoke Worker Functions using Rib

Terminal
let worker_result = worker_function_name(request.body);
worker_result

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

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

Then we can use Rib language to select the field user, as it considers the value of worker.response WASM Record type, and therefore a valid selection.

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

This is again, using the same syntax followed in wasm-wave. A worker's response can be at times a Result type, where it can either ok(x) or err(y) and if we use Rib language to peek/unwrap this result.

This can exist anywhere. If needed, user can embed a result in the request body and worker-bridge consider them to be a Result within the Record. More on these details can be found under worker-bridge's documentation

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
5 > 3
 

The left side rib expression is evaluated as a Number 5, and the right side rib expression is evaluated as a Number 3. The comparison operator > is used to compare the two numbers, resulting in true.

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.

Trying to compare complex values will result in evaluation error. Example : [1, 2, 3] > [4, 5, 6] will result in error. We will add support for these in Expr language.

Conditional Statement

Terminal
 
 let id: u32 = request.user.id;
 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"
 

If branch and then branch are not type checked as of now. Meaning similar to other dynamic languages like python, they may get evaluated to different types of WASM values.

Pattern Matching

Terminal
 
match <expr> {
    <pattern> => <expr>,
    <pattern> => <expr>
}
 

Here are few examples that gives you an idea of what can be done with the current support of pattern matching. 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 worker.response.

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

You can bring them in multiple lines as follows:

Terminal
  match worker_result {
    ok(x) => "foo",
    err(msg) => "error"
  }
Terminal
  match worker.response {
    some(x) => "found",
    none => "not found"
  }

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.

Let's say we have a variant of type as given below responded from the worker. The worker-bridge is aware of the type of response from the worker.

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

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]
  }
 

More on let binding below

Wild card patterns

In some of the above examples, the binding variables are unused. In this case, 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 simple example of let binding.

Terminal
 
 let a = { age: worker.response.age, city: "New York", name: request.body.name };
 
 match worker.response.admin {
   Admin(_) => "admin",
   User(x) => a
 }
 

Or even as simple as:

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

The return value of the script is the last expression in the script, and 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.

Following are some known issues and how to fix them if you encounter them

  • Stack overflow when inlining record or nested complex expressions. The solution is to use let binding expressions and use variables instead of inlining records.
let d: str = request.path.id;
let x = { a: 1u32, b: 2u32, c: d };
call(x)
  • 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.