In this page
Calling functions from the command line
greycat call runs an @expose function on a running server and prints its result.
The function runs as a task of that server, in its store, with the identity of the token that
calls it. Nothing is built and the store is not opened locally, so call works while
greycat serve is up, from the project directory or from any other machine.
It replaces the curl request you would write to try an endpoint, and it is the way to start
maintenance functions (an import, a migration, a recompute) on a server that must keep serving.
greycat run cannot do that: it opens the store itself, which a running server already holds.
A first call
Given this project:
@library("std", "0.0.0");
type Person {
name: String;
age: int;
}
@expose
fn add(a: int, b: int): int {
return a + b;
}
@expose
fn greet(name: String): String {
return "Hello, ${name}!";
}
@expose
fn people(): Array<Person> {
return [Person { name: "Ada", age: 36 }, Person { name: "Alan", age: 41 }];
}
Start the server in a terminal:
greycat serve
And call it from another one, in the same directory:
greycat call project::add 2 3
# 5
greycat call project::greet John
# Hello, John!
The function is named with its module, as in its HTTP route (/project::add). Only @expose
functions can be called. Any other name fails with
unresolved function: project::hidden (only exposed functions can be called).
Arguments
Arguments are positional, one per parameter. Before anything is sent, call downloads the ABI of
the server and checks each argument against the signature of the function:
- A
Stringorcharparameter takes its argument verbatim. No quotes are needed, and quotes are kept:greycat call project::greet '"John"'answersHello, "John"!. - Any other parameter reads its argument as JSON of the declared type:
42,2.5,true,null,[1, 2, 3],{"name":"Ada","age":36}. - Where the type is not precise (
any, an abstract type), a_typefield names the type of an object:'{"_type":"project::Person","name":"Ada","age":36}'. - Trailing nullable parameters can be left out. They receive
null.
@library("std", "0.0.0");
type Person {
name: String;
age: int;
}
@expose
fn describe(p: Person): String {
return "${p.name} is ${p.age}";
}
@expose
fn mean(values: Array<float>, scale: float?): float {
var sum = 0.0;
for (_, v in values) {
sum = sum + v;
}
return sum / values.size() * (scale ?? 1.0);
}
greycat call project::describe '{"name":"Ada","age":36}'
# Ada is 36
greycat call project::mean '[1, 2, 3]'
# 2.0
greycat call project::mean '[1, 2, 3]' 2
# 4.0
Quote JSON arguments for your shell, since it would otherwise split [1, 2, 3] into three words.
The check uses the full types of the ABI, generics included. A wrong argument fails before the call is made, and the message names the parameter:
greycat call project::add 2
# missing required argument 'b' of project::add(a: int, b: int): int
greycat call project::mean '[1]' 2 3
# too many arguments: 3 given to project::mean(values: Array<float>, scale: float?): float
greycat call project::describe '{"name":"Ada"}'
# json missing fields at line 1, column 1 got 'Map' while 'Person' expected for parameter 'p' of project::describe(p: Person): String
Output
The result is printed on stdout in GCL notation, indented:
greycat call project::people
Array<Person> {
Person {
name: "Ada",
age: 36
},
Person {
name: "Alan",
age: 41
}
}
--format=json prints JSON instead, which suits a script. --pretty=false prints the value on a
single line, in either format:
greycat call project::people --format=json --pretty=false
# [{"name":"Ada","age":36},{"name":"Alan","age":41}]
greycat call project::people --format=json | jq -r '.[].name'
# Ada
# Alan
On a terminal, the result is colored, and a progress line on stderr shows the status of the task
and its elapsed time while it runs. A function that declares its steps with
Task::expected_steps and Task::add_steps gets a progress bar with a percentage:
@library("std", "0.0.0");
@expose
fn recompute(n: int): int {
Task::expected_steps(n);
for (var i = 0; i < n; i++) {
// ... one unit of work
Task::add_steps(1);
}
return n;
}
project::recompute █████████ 30% running 2.4s
The progress line is only drawn when stderr is a terminal, so a redirected output only holds the result.
Errors and exit code
call exits with code 0 when the task ended and its result was printed. It exits with a
non-zero code when the function is unknown, an argument does not read, the token is refused, the
server cannot be reached, or the task failed.
When the function throws, the error and its stack are printed on stdout, and the status of the task on stderr:
@library("std", "0.0.0");
@expose
fn fail() {
throw "something went wrong";
}
greycat call project::fail
Error {
message: "something went wrong",
stack: Array<ErrorFrame> {
ErrorFrame {
module: "project",
function: "fail",
line: 5,
column: 34
}
}
}
project::fail: task 8 error
Ctrl-C cancels the task on the server, not only the command. The task stops with the error
cancelled, and call exits with a non-zero code.
Server and token
Run from the project directory, call needs no option. It finds the server and its root token
in gcdata/security/, which greycat serve fills when it starts:
- The server is
http://localhost:<port>, with the port the running server wrote togcdata/security/port, else8080.--portsets another port, and--urlanother server. - The call is authenticated with the root token in
gcdata/security/token.--tokensends another token, and the function then runs with the permissions of that user.
From another directory, or for a remote server, give both:
greycat call project::add 2 3 --url=https://twin.example.com --token=<token>
Each option has its environment variable, which is convenient in a script or a CI job:
export GREYCAT_URL=https://twin.example.com
export GREYCAT_TOKEN=<token>
greycat call project::add 2 3
greycat token --user=<name> prints a token for an existing user. Call with it to check what
that user is allowed to do: a function its role does not allow is refused with HTTP 403.
greycat user add alice user
greycat call project::add 2 3 --token=$(greycat token --user=alice)
# 5
A server with a certificate from a private authority needs --ca_path, the directory holding
the certificates to trust.
Task class and world
The task runs in the medium worker class by default. --task_class=small or
--task_class=large picks another one. When the server has no worker in that class, the task
runs in the nearest class that has some.
--world=<name> runs the function in a world of the server (see
Many Worlds). Without it, or with --world=main, the function runs in
main, whatever the default world of the user. A world the server does not have, or that the user
may not use, fails the call.
Reserved functions
The functions the server answers itself, such as runtime::Task::running or
runtime::Task::history, can be called as well. They return at once instead of running as a
task:
greycat call runtime::Task::running
# Array<Task> {}
runtime::Task::events (a stream of events) and runtime::Runtime::abi (a file) have no value
to print, so call refuses them.
Options
| Option | Environment variable | Description |
|---|---|---|
--url=<string> |
GREYCAT_URL |
Server to call, http://localhost:<port> when unset |
--port=<int> |
GREYCAT_PORT |
Port of the local server, read from gcdata/security/port when unset |
--token=<string> |
GREYCAT_TOKEN |
Token sent as Authorization, the root token of gcdata/security/token when unset |
--format=<string> |
GREYCAT_FORMAT |
Output format, gcl (default) or json |
--pretty=<bool> |
GREYCAT_PRETTY |
Indent the result, true by default |
--task_class=<string> |
GREYCAT_TASK_CLASS |
Worker class of the task: small, medium (default) or large |
--world=<string> |
GREYCAT_WORLD |
World the function runs in, main when unset |
--ca_path=<string> |
GREYCAT_CA_PATH |
Directory of extra CA certificates to trust for HTTPS |