# Rust

> Write custom APIs, hooks, schedulers and event listeners of API Maker in Rust. pub fn main(g: &mut G) -> Result<Value, Errors>, g.req, g.res, g.sys.db, am::fail, crates from crates.io, with many examples.

Source: https://docs.apimaker.dev/v4/docs/languages/rust.html

Code in Rust defines `pub fn main(g: &mut G) -> Result<Value, Errors>`. It is built once as a library the runner loads, on the official image of Rust (`rust:1-slim` unless the [settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html) name another version), with only the crates you add.

> **In short**
>
> - Write `pub fn main(g: &mut G) -> Result<Value, Errors>`.
> - It is built once, on `rust:1-slim` unless the settings name another version.
> - Refuse a request with `Err(am::fail(400, message))`.

**hello.rs**

```rust
use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let name = g.req.body()["name"].as_str().unwrap_or("world");
    Ok(json!({ "hello": name }))
}
```

| | |
|---|---|
| Runtime | stable Rust, 1.88 or later : a version of `rust:<version>-slim` (edition 2024) |
| `main` | `pub fn main(g: &mut G) -> Result<Value, Errors>` : `Ok(value)` answers, `Value::Null` lets the API go on |
| Calls of `g.sys` | typed : what a call takes is a type made key after key (`GetAll::new("mysql_8", "inventory", "customers").limit(10)`), what it answers has its type (`Vec<Value>`, `Option<Value>`, `i64`, `UpdateManyResult`) |
| Values | `serde_json::Value` (`am::Value`, `am::json!`) : `g.req.body()["id"]` ; a row as a struct of yours with `.to()` |
| `?` | works on every error : the calls of `g.sys`, parsing, `serde_json`, your own errors |
| Crates | crates.io : `serde[derive]` `1` is `serde` with the feature `derive` |
| Printing | `println!` and `eprintln!` go to the console of the account |

## The request and the answer

`g.req` and `g.res` have a method for each key they are known to have : `g.req.body()`, `g.req.query()`, `g.req.params()`, `g.req.headers()` read (`…_mut()` to change one where it is), `g.res.set_status_code(201)` sets. Each gives a `Value` : reading a key which is not there gives `Value::Null`; writing one makes it. The request and the answer are values of JSON themselves too : `g.req["added"] = json!(1)`.

A body which is a list (`[{…}, {…}]`, several rows to save) is that list : `g.req.body().as_array()`.

**Read the request, shape the answer**

```rust
use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let status = g.req.query()["status"].as_str().unwrap_or("open").to_owned();
    let signed = !g.req.headers()["x-am-authorization"].is_null();
    g.res.set_status_code(201);
    g.res.headers_mut()["X-Request-Kind"] = json!("orders");
    Ok(json!({ "customer": g.req.params()["id"], "status": status, "signed": signed }))
}
```

### Bytes as the answer

A file, a picture, a PDF : return `am::bytes(…)` of its bytes, or give that to `g.res.set_output`, and name their kind of content. A value of JSON has nothing which stands for bytes : `am::bytes` is the form they travel in. The answer is those bytes as they are, whatever the kind of content, with the headers of the code.

**A picture as the answer**

```rust
use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    // The bytes of a picture : read from a file, downloaded, or made by the code.
    let picture: &[u8] = b"GIF89a\x01\0\x01\0\x80\0\0\xff\xff\xff\xff\xff\xff!\xf9\x04\x01\n\0\x01\0,\0\0\0\0\x01\0\x01\0\0\x02\x02L\x01\0;\0";
    g.res.set_content_type("image/gif");
    g.res.headers_mut()["Content-Disposition"] = json!("inline; filename=\"pixel.gif\"");
    Ok(am::bytes(picture))
}
```

## Calling API Maker : `g.sys`

Each method is a call of the server, with its types : it waits for the answer and gives a `Result`. What it takes is a type of `am` made with what it is about, then key after key (`GetAll`, `GetById`, `Count`…) ; what it answers is typed too : rows are a `Vec<Value>`, a row an `Option<Value>`, a count an `i64`, what an API describes a struct (`UpdateManyResult`).

**Database calls**

```rust
use am::{Count, Errors, G, GetAll, GetById, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let rows = g.sys.db.get_all(GetAll::new("mysql_8", "inventory", "customers").limit(10).sort("-customer_id"))?;
    let two = g.sys.db.get_by_id(GetById::new("mysql_8", "inventory", "customers", 2))?;
    let count = g.sys.db.count(Count::new("mysql_8", "inventory", "customers").find(json!({ "country": "IN" })))?;
    Ok(json!({ "first": rows.first(), "name": two.map(|row| row["first_name"].clone()), "in_india": count }))
}
```

**Save, update, remove**

```rust
use am::{Errors, G, RemoveById, Save, UpdateById, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let saved = g.sys.db.save_single_or_multiple(Save::new("mysql_8", "inventory", "customers", json!({ "first_name": "Ada", "last_name": "Lovelace" })))?;
    g.sys.db.update_by_id(UpdateById::new("mysql_8", "inventory", "customers", 2).update_data(json!({ "first_name": "MALLORY" })))?;
    g.sys.db.remove_by_id(RemoveById::new("mysql_8", "inventory", "customers", 101))?;
    // What a save answers : `saved.row()`, `saved.rows()`, `saved.is_list()`.
    Ok(json!({ "saved": saved.row() }))
}
```

- A key the type has no method for is set as it is : `GetAll::new("mysql_8", "inventory", "customers").with("cursor", "abc")`. A type is made of JSON too : `GetAll::from(json!({ … }))`.
- A row as a struct of your own (`#[derive(serde::Deserialize)]`) : `let customers: Vec<Customer> = rows.to()?;`.
- The generated APIs are methods of `g.sys.db` too (`get_all_gen`…). The whole answer of a call (`success`, `status_code`, `data`, `total_count`, `errors`) : `g.sys.db.full.get_all(…)?`, an `ApiResponse<Vec<Value>>`.

Calls at the same time are threads, with `std::thread::scope` :

**Calls at the same time**

```rust
use am::{Count, Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let db = &g.sys.db;
    let (orders, products) = std::thread::scope(|scope| {
        let orders = scope.spawn(|| db.count(Count::new("mongodb", "shop", "orders")));
        let products = scope.spawn(|| db.count(Count::new("mongodb", "shop", "products")));
        (orders.join(), products.join())
    });
    let orders = orders.unwrap_or_else(|_| Ok(0))?;
    let products = products.unwrap_or_else(|_| Ok(0))?;
    Ok(json!({ "orders": orders, "products": products }))
}
```

### Rows as they arrive

**Sum a large table**

```rust
use am::{Errors, G, GetAll, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let mut total = 0.0;
    g.sys.db.get_all_by_stream(GetAll::new("mysql_8", "inventory", "orders"), |row| {
        total += row["amount"].as_f64().unwrap_or(0.0);
        Ok(())
    })?;
    Ok(json!({ "amount": total }))
}
```

The function stays on the thread of the request : it can borrow and change your variables.

### System APIs and cache

**Encrypt, hash, events and cache keys**

```rust
use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let sealed = g.sys.system.encrypt(json!({ "card": "4111" }))?;
    let digest = g.sys.system.hash("my text")?;
    g.sys.system.emit_event("order_created", json!({ "id": 7 }), &["notify_shop"])?;
    g.sys.cache.set_key("last_order", "7", Some(600))?;
    let last = g.sys.cache.get_key("last_order")?;
    Ok(json!({ "sealed": sealed, "digest": digest, "last": last }))
}
```

## Errors

**Refuse a request**

```rust
use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let Some(quantity) = g.req.body()["quantity"].as_u64() else {
        return Err(am::fail(400, "'quantity' is missing."));
    };
    Ok(json!({ "quantity": quantity }))
}
```

**Catch the errors of a call**

```rust
use am::{Errors, G, RemoveById, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    match g.sys.db.remove_by_id(RemoveById::new("mysql_8", "inventory", "customers", 404)) {
        Ok(_) => Ok(json!({ "removed": true })),
        Err(refused) => {
            g.logger.warn(format!("not removed : {}", refused.message()));
            Ok(json!({ "removed": false, "code": refused.code() }))
        }
    }
}
```

- `?` on any error type answers `500` with its message : `let n: i64 = text.parse()?;` gives `invalid digit found in string`.
- A panic answers `500` with `panic: …` and the lines of your code (`at main.rs:8 in helper`).

## Logs, printing and shared values

**Logs and shared values**

```rust
use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    g.logger.log(format!("order {} paid", 7));
    g.logger.error(json!({ "step": "payment" }));   // values as indented JSON
    println!("kept in the console of the account");
    // Shared by every piece of code of the sandbox : update changes a value while no other request does.
    let calls = g.shared.update("calls", |old| json!(old.and_then(|n| n.as_i64()).unwrap_or(0) + 1));
    Ok(calls)
}
```

## Hooks

**Pre hook : only my rows**

```rust
use am::{Errors, G, Value};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let owner = g.req.auth()["authAMDB"]["id"].clone();
    g.req.query_mut()["find"]["owner_id"] = owner;
    Ok(Value::Null)
}
```

**Post hook : hide a column**

```rust
use am::{Errors, G, Value};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    if let Some(rows) = g.res.output_mut().as_array_mut() {
        for row in rows {
            if let Some(row) = row.as_object_mut() {
                row.remove("password");
            }
        }
    }
    Ok(Value::Null)
}
```

## Utility classes

A [utility class](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.html) written in Rust is a module of its name in the crate of every piece of Rust code of the account.

**The utility class Tax**

```rust
use crate::Round::cents; // another utility class

pub const RATE: f64 = 0.2;

pub fn add(amount: f64) -> f64 {
    cents(amount * (1.0 + RATE))
}
```

**A custom API which uses it**

```rust
use am::{Errors, G, Value, json};

use crate::Tax;

pub fn main(g: &mut G) -> Result<Value, Errors> {
    Ok(json!({ "total": Tax::add(g.req.body()["amount"].as_f64().unwrap_or(0.0)) }))
}
```

- The name of the class is the name of the module : letters of a to z, digits and `_`, no word of Rust (`type`, `match`…) and none of `code`, `am`, `std`, `core`, `alloc`, `main`. Name it from the crate : `crate::Tax`.
- The modules are compiled into each piece of code which runs : what a class keeps in a `static` is kept per piece of code. Use `g.shared` for what all code shares.
- In the Git repository the class is `src/utility_classes/<folder>---<name>/<name>.rs`, declared `pub mod <name>;` in `am/rust/project.rs` : `cargo check` compiles the same paths.

## Test cases

A [test case](https://docs.apimaker.dev/v4/docs/test-cases/test-cases.html#other-languages) written in Rust names its tests in `am::tests!`, in the place of a `main`. A test is a `fn(g: &mut G) -> Result<(), Errors>` : the errors it returns, or what it panics with (`assert_eq!`), are its failure.

**A test case**

```rust
use am::{Errors, G, Mock, RunCustomApi, json};

fn the_cost_of_a_parcel(g: &mut G) -> Result<(), Errors> {
    g.sys.test.mock(Mock::new("db.getAll").when(json!({ "collection": "rates" })).returns(json!([{ "per_kg": 2.5 }])))?;
    let out = g.sys.test.run_custom_api(RunCustomApi::new("shippingCost").body(json!({ "weight": 4 })))?;
    assert_eq!(out["cost"].as_f64(), Some(10.0));
    Ok(())
}

am::tests!(the_cost_of_a_parcel);
```

- `g.sys.test` : `run_custom_api(RunCustomApi)`, `run_custom_api_full(RunCustomApi)` for the whole answer (`data`, `status_code`, `headers`, `logs`), `mock(Mock)`, `mocks(Vec<Mock>)`, `clear_mocks(Option<&str>)`, `calls(Option<&str>)`.
- The custom API under test can be written in any language.

## Crates

Add `uuid[v4]` `1` and `serde[derive]` `1` in [Sandbox Settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#packages), then :

**Crates of crates.io**

```rust
use am::{Errors, G, Value};
use serde::Serialize;

#[derive(Serialize)]
struct Ticket {
    id: String,
    seats: u32,
}

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let ticket = Ticket { id: uuid::Uuid::new_v4().to_string(), seats: 2 };
    Ok(serde_json::to_value(ticket)?)
}
```

- `serde_json` and `serde` (with `derive`) are always there; add any other crate with its version requirement (`1`, `^1.40`, `>=1.2, <2`) and features (`tokio[rt-multi-thread,macros]`).
- The crates are built with the runner when the image is built; while code runs nothing is downloaded.

## How it runs

- Each piece of code is built once (`cargo build --release`, about a second or two once the crates are built) as a library of its own, loaded into the runner. The runner and your code exchange JSON only, through functions of C : no type of one ever crosses into the other. Saving starts the build in the background; code which does not compile is reported in the console of the account (`main.rs:4:8: error[E0425]: cannot find value missing`).
- Every request runs on a thread of the runner; requests run at the same time, on every core. A thread takes the next request when its own is done : a `thread_local!` value of your code may be the one an earlier request left there.
- `static` values live as long as the runner; `g.shared` is shared with every other piece of code of the sandbox.

## In the code editor

The editor completes `g` as Rust writes it, the names of your instances, databases and tables, and the columns of a table on the rows a call answers. More in [the code editor in every language](https://docs.apimaker.dev/v4/docs/languages/index.html#editor).

**Where the editor helps**

```rust
use am::{Errors, G, GetAll, Value};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    // g.  →  req, res, sys, logger, shared      g.sys.db.  →  get_all, get_by_id, query, get_all_gen, full…
    g.logger.log(&g.req.body()["name"]);  // g.req.  →  body, query, headers, auth…      g.res.  →  set_status_code, headers_mut…
    let rows = g.sys.db.get_all(GetAll::new("mysql_8", "inventory", "customers").limit(10))?;
    //         GetAll::  →  new, default       GetAll::new("  →  your instances, then its databases, then their tables
    //         GetAll::new(…).  →  find, limit, sort, select…
    for row in &rows {
        g.logger.log(&row["first_name"]);  // row["  →  the columns of customers
    }
    Ok(rows[0]["first_name"].clone())
}
```

- After `GetAll::` the editor offers what makes the type (`new`, `default`), after `GetAll::new(…).` the methods which set its keys, each with what it is for. Inside `.find(json!({ "` it offers the columns of the table, inside `.select("` and `.sort("` too.
- What a call answers has its type : on the rows of a `get_all` the columns are offered inside the quotes of an index (`rows[0]["`, `row["`) and of `get` (`row.get("`), in a `for` loop, in a closure (`.map(|row| row["…"])`) and in the function given to `get_all_by_stream`. `changed.` (an `update_many`) offers `updated_rows_count`, `saved.` offers `row`, `rows` and `is_list`, the whole answer of `g.sys.db.full` offers `success`, `data`, `errors`…
- `am::` offers `fail` and what the crate has.
- `g.req.` and `g.res.` offer a method for each key of the request and of the answer ; `g.req.auth().` the opened tokens, written as an index (`g.req.auth()["authAMUser"]`).
- The columns follow a row the same way : `rows[0].` then `first_name` writes `rows[0]["first_name"]`.

## Reference

| `g.` | Rust |
|---|---|
| `req` | `body()`, `query()`, `params()`, `headers()`, `auth()`, `req_info()`, `event_data()` (each a `&Value`, `…_mut()` to change it, `set_…` to replace it), `is_api_request_from_user()` ; a `Value` itself |
| `res` | `set_status_code`, `set_content_type`, `headers_mut()`, `shared_mut()`, `output()`, `output_mut()`, `set_output`, `errors()`, `warnings()` ; a `Value` itself |
| `sys.db` | `get_all(GetAll)`, `get_by_id(GetById)`, `query(Query)`, `save_single_or_multiple(Save)`, `master_save(Save)`, `array_operations(ArrayOperations)`, `update_by_id(UpdateById)`, `update_many(UpdateMany)`, `replace_by_id(UpdateById)`, `remove_by_id(RemoveById)`, `remove_by_query(RemoveByQuery)`, `aggregate(Aggregate)`, `count(Count)`, `distinct(Distinct)`, `distinct_query(Distinct)`, `get_all_by_stream`, `query_by_stream`, and the generated APIs `get_all_gen`… |
| `sys.db.full`, `sys.system.full`, `sys.cache.full` | the same calls, each with its whole answer : `ApiResponse<T>` (`success`, `status_code`, `data`, `total_count`, `errors`, `warnings`) |
| `sys.system` | `encrypt`, `encrypt_with`, `decrypt`, `decrypt_with`, `hash`, `get_secret`, `get_secrets`, `call_external_api`, `call_external_apis`, `get_token`, `get_tokens`, `execute_query`, `get_table_meta`, `get_table_metas`, `create_indexes`, `drop_indexes`, `drop_indexes_of_many`, `get_indexes`, `get_indexes_of_many`, `emit_event`, `emit_event_ws`, `is_valid_data_for_table(s)`, `is_valid_data_for_custom_api(s)`, `multi_tenant_instance(s)_updated`, `is_valid_connection_string(s)`, `get_lock`, `release_lock` |
| `sys.cache` | `get_key`, `get_keys`, `set_key`, `set_keys`, `remove_key`, `remove_keys`, `reset_cache_db`, `reset_cache_dbs`, `reset_cache_custom_apis`, `reset_cache_custom_apis_of_tenant`, `reset_cache_system_apis`, `reset_cache_system_apis_of_tenant` |
| `sys.call("db.getAll", …)` | any API by its name in JavaScript, with its arguments as they are |
| `logger` | `log`, `debug`, `info`, `warn`, `error` : a text or a `Value` |
| `shared` | `get`, `set`, `update` |
| errors | `am::fail(code, message)`, `Errors { list }`, `.message()`, `.code()` |

## Debugging

- The debug runner of Rust builds your code without optimizations, with its symbols : lldb-dap attaches to it for the **Debug** tab of the API testing page.
- A panic stops the code when **Pause on exceptions** is on.
- Code on the native process of a Mac needs the developer mode of the Mac (`sudo DevToolsSecurity -enable`) : lldb-dap works through the debugserver of Xcode.
- Learn more [Debugging](https://docs.apimaker.dev/v4/docs/features/debugging.html).

## Related

- [Languages and runtimes](https://docs.apimaker.dev/v4/docs/languages/index.html) · [Sandbox settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html) · [The global object g](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html)
