# **No-code REST API server**
## Table of Contents
+ [**Using the binary**](#using-the-binary)
+ [**Config file**](#config-file)
+ [Specifying database tables](#specifying-database-tables)
+ [**Command Line Options**](#command-line-options)
+ [**API Format**](#api-format)
+ [GET Requests](#get-requests)
+ [POST Requests](#post-requests)
+ [DELETE Requests](#delete-requests)
+ [PATCH Requests](#patch-requests)
+ [**Using the library**](#using-the-library)
+ [**Miscellaneous**](#miscellaneous)
+ [**Flow of received HTTP requests in the app**](#flow-of-received-http-requests-in-the-app)
+ [**Adding middleware**](#adding-middleware)
+ [**Adding a new database implementation**](#adding-a-new-database-implementation)
+ [Interface : *Required*](#interface--required)
+ [Query](#query)
+ [Response Builder](#response-builder)
# **Using the binary**
An API can be setup using the server_config.toml
## **Building the binary**
### **Prerequisites**
- cargo (rust package manager)
This command will build the binary:
```
cargo build --release --bin="rust_rest_api" --features="build-binary"
```
## **Config file**
The config file uses the [TOML](https://toml.io/en/) format.
A typical config file looks like:
```toml
database="sqlite3"
database_path="database.db"
loglevel="debug"
host="127.0.0.1:3000"
# Example table
[table.people]
route = "/people"
name = "text"
age = "Integer"
```
*Currently only `database="sqlite3"` is supported*
```database_path```
Path where the database will be opened/saved to. This can be absolute or relative.
```loglevel```
This can be a value of:
- `error`
- `warn`
- `info`
- `debug`
- `trace`
- `off`
```host```
Specifies the IP and port that the server is run on in the format `ip:port`
### **Specifying database tables**
```[table.name]```
This creates a table with `name`.
```route = "/uri_to_table"```
This is a required attribute for each table. The table will be accessed at the URL `/uri_to_table` .
```field = "type"```
The remaining attributes specify the structure of the table.
`field` is the name of a field, or column.
`type` is the data type of the field. In sqlite3, the following types are supported:
- `null`
- `real`
- `integer`
- `text`
***A primary key `id` is automatically added for every table.***
## **Command Line Options**
`-c --config `
Sets the path to a custom config file
`-r --resetdb`
Reset the database before starting server
## **API Format**
The API uses [JSON](https://www.json.org/json-en.html) format to receive and send data.
### **GET Requests**
#### **Sending**
Sending a GET request to a table's route will retrieve all entries.
Query strings can be used to filter the results.
`/people?age=4` will translate to SQL `SELECT * FROM people WHERE age=4`
`+` in query strings are translated to a space.
#### **Returning**
A JSON string containing an array of returned results.
Each result is an ordered array containing each field. The first field is the unique id.
**Examples:**
```
curl 127.0.0.1:3000/people
=> [[1,"john",5],[2,"jess",19],[3,"mike",56],[4,"andrew",56]]
curl 127.0.0.1:3000/people?id=2
=> [[2,"jess",19]]
curl 127.0.0.1:3000/people?age=56
=> [[3,"mike",56],[4,"andrew",56]]
```
### **POST Requests**
Used to add new database entries.
#### **Sending**
A POST request must contain a JSON body. The format of the body is
```json
{
"columns": {
"name": "john",
"age": "8"
}
}
```
*All values in "columns" must be a string.*
*All fields for a table must be specified (except id).*
#### **Returning**
A JSON string containing an array of the most recently added values. This is in the same format as GET returns.
**Examples:**
```
curl -X POST -d @test.json 127.0.0.1:3000/people
=> [[1,"john",8]]
curl -X POST -d @test.json 127.0.0.1:3000/people
=> [[2,"john",8]]
```
### **DELETE Requests**
Used to delete database entries.
#### **Sending**
Sending a delete request to a table's route will delete the table's contents.
Query strings can be used to filter which entries are deleted.
`/people?age=4` will translate to SQL `DELETE FROM people WHERE age=4`
`+` in query strings are translated to a space.
#### **Returning**
Empty body.
If an error is encountered when deleting, HTTP error code 500/400 will be returned. It cannot be assumed the entry was deleted successfully.
Else, HTTP 200.
### **PATCH Requests**
Used to update database entries.
#### **Sending**
A PATCH request must contain a JSON body. The format of the body is
```json
{
"columns": {
"name": "jeff",
},
"filters": {
"age": "8"
}
}
```
*All values in "columns" and "filters" must be a string.*
This will update all columns' `name` to the value `"jeff"` for all entries that match `age=8`.
#### **Returning**
Empty body.
If an error is encountered when updating, HTTP error code 500/400 will be returned. It cannot be assumed any values were updated successfully.
Else, HTTP 200.
# **Using the library**
## A basic implementation (used for the binary) can be found [here](/src/bin.rs).
## **Miscellaneous**
**The config TOML file is parsed with:**
```rust
let (config, tables) = rest_api::config_parser::read_config(optional_path)
```
**Logging is enabled by calling:**
```rust
rest_api::enable_logging(&config)
```
*Call once only*
**An app is asynchronously run with:**
```rust
rest_api::api_http_server::http::run_app_server(addr, app).await
```
*This requires the parent function to be async*
## **Flow of received HTTP requests in the app**:
![Flow overview](/images/overview.png)
## **Adding middleware**
The easiest way to add functionality is to create your own [`App`](/src/lib/app.rs) with your own [`middleware`](/src/lib/api_http_server/middleware.rs).
This allows every request and response to be intercepted and processed.
To create a middleware, create a struct that implements the Middleware trait, as well as Sync and Send for thread safety.
Middlewares are registered when creating the `App` object. For reference, [bin.rs](/src/bin.rs#L59) shows how an app is created.
Example:
```rust
// create_auth_middleware() must return a struct that implements the Middleware + Send + Sync traits.
let auth_middleware = Box::new( create_auth_middleware() ) as Box;
let app = App {
routes,
middleware: vec![auth_middleware],
database_interface: Box::new(interface)
};
```
## **Adding a new database implementation**
To support a new database type, the following things must be implemented:
#### **Interface : *Required***
The [interface](/src/lib/database/interfaces.rs) acts as a 'bridge' between the incoming requests and the database.
It also handles other database functions such as deleting, creating and connecting.
An interface struct implements the DatabaseInterface trait.
Due to the `async` requirement for processing the request, the implementation of the trait must use `#[async_trait::async_trait]`, from the [async_trait](https://docs.rs/async-trait/latest/async_trait/) crate.
The supported data types are in the `SQLType` enum:
```rust
pub enum SQLType {
Null,
Integer,
Real,
Text,
}
```
#### **Query**
The [query](/src/lib/database/query.rs) is an optional trait that can help with converting requests to SQL. It is used in the SQLite3 interface implementation for parsing request data and safely executing SQL.
It has 2 generics ``.
`T` is a database connection type.
`A` is a cursor type, returned from executing a statement.
#### **Response Builder**
The [response builder](/src/lib/database/response.rs) is an optional trait that defines a function to convert a query result `Vec>` (where `T` is a database value) into a string for a response.
The outer `Vec` contains the rows, and the inner `Vec` contains the fields in a row.