```yaml module: api version: 1.2 ``` For developers ==== [**I.** Overview](#i-overview) - [**1** Introduction & features](#1-introduction--features) - [**2** Basic knowledge](#2-basic-knowledge) [**II.** Usage](#ii-usage) - [**1** REST API](#1-rest-api) - [**2** Internal use (PHP)](#2-internal-use) [**III.** Configuration](#iii-configuration) - [**1** Configuration format](#1---configuration-format) - [**2** Method definition](#2---methoddefinition) - [**2.1** Method description](#21---methoddescription) - [**2.2** Method permissions](#22---methodpermissions) - [**2.3** Method parameters](#23---methodparameters) - [parameter name](#parametername) - [parameter description](#parameterdescription) - [parameter type](#parameterchecker_type) - [parameter optional vs. required](#parameteris_optional) - [parameter rename](#parameterrename) - [parameter default value](#parameterdefault_value) - [**2.4** Method output format](#24---methodoutput_format) - [**2.5** Method options](#25---methodoptions) [**IV.** Implementation](#iv-implementation) - [**1** Permissions : AuthSystem](#1---permissions--authsystem) - [**2** Core implementation](#2---core-implementation) - [Classes](#classes) - [Methods](#methods) - [Method arguments](#method-arguments) - [Return statement](#return-statement) - [Before and After scripts](#before-and-after-scripts) - [Example](#example) [**V.** Type Checker](#v-type-checker) - [**1** Default Types](#1---default-types) - [**2** Complex Types](#2---complex-types) [**VI.** Documentation](#vi-documentation) - [**1** API accessible documentation](#1---api-accessible-documentation) For clients ==== [**I.** Simple request](#i-simple-request) - [**1** URL](#1---URL) - [**2** variables](#2---variables) [**II.** Usage](#ii-usage) # **I.** Overview ## **1** Introduction & features The `api` package (v1.2) allows you to easily create and manage a REST API for your applications. The aim of this package is to make your life easier working with APIs and internal delegation. The only things you have to do is to implement your controllers, middle-wares and write 2 configuration files, the package will do the rest. Things you **have** to do : - write the project configuration file (cf. [project configuration](#iii-project-configuration)) - write the API definition file (cf. [api definition](#iv-api-definition)) - implement your middle-wares to manage authentication, csrf, etc (cf. [AuthSystem](#1---middle-wares)) - implement your controllers (cf. ???) - implement additional project-specific type checkers (cf. ???) Things you **don't have** to do : - check the input variables (cf. [Checker](#v-type-checker)) - multiple permission management - optional or required input - Form data type : x-www-urlencoded, multipart, or file upload - URL variables (slash separated at the end of the route, like standard uri) - and a lot more ... ## **2** Basic knowledge The API uses the routes defined in the _api.json_ configuration to export your code (in compiled go or other language). Middle-wares are executed when receiving a new request, then the controllers will be loaded according to the URI. Example: * the module `article` contains methods: * `GET` to get article data * `POST` to post a new article * `PUT` to edit an existing article * `DELETE` to delete an exisint article *Note that these are all the available methods handled by aicra* # **III.** Project Configuration The documentation consists of directions for how to compile and run the project controllers, middle-wares and type checkers. ## **1** - configuration format ```json { "root": "", "driver": "", "types": { "default": true, "folder": "" }, "controllers": { "folder": " # **IV.** Implementation ## **1** - middle-wares Each middle-ware must implement the [driver.Middleware](https://godoc.org/git.xdrm.io/go/aicra/driver#Middleware) interface. The `Inspect(http.Request, *[]string)` method gives you the actual http request. And you must edit the string list to add scope elements. After all middle-wares are executed, the final scope is used to check the **method.permission** field to decide whether the controller can be accessed. ### example For instance here, we check if a token is sent inside the **Authorization** HTTP header. The token if valid defines scope permissions. ```go package main import ( "git.xdrm.io/aicra/driver" "net/http" ) // for the API code to export our middle-ware func Export() driver.Middleware { return new(TokenManager) } // mockup type to implement the driver.Middleware interface type TokenManager interface{} func (tm TokenManager) Inspect(req http.Request, scope *[]string) { token := req.Header.Get("Authorization") if !isTokenValid(token) { return } scope = append(scope, getTokenPermissions(token)...) } ``` *Note*: Functions `isTokenValid()` and `getTokenPermissions()` do not exist, it was in order for all to understand the example. ## **2** - controller Each controller must implement the [driver.Controller](https://godoc.org/git.xdrm.io/go/aicra/driver#Controller) interface. The `Get(response.Arguments) response.Response` method defines the controller implementation for the `GET` method. Other methods are `Post`, `Put`, and `Delete`. Each controller will be given the arguments according to the api definition. ### example For instance here, we implement a simple **user** controller. ```go package main import ( "git.xdrm.io/aicra/driver" "git.xdrm.io/aicra/response" "git.xdrm.io/aicra/err" ) // for the API code to export our middle-ware func Export() driver.Controller { return new(UserController) } // mockup type to implement the driver.Controller interface type UserController interface{} func (ctl UserController) Get(args response.Arguments) response.Response { res := response.New() // extract user ID argument user_id, ok := args["user_id"].(float64) if !ok { res.Err = e.Failure return *res } // fetch user data res.Set("user", getUserData(user_id)) return res } func (ctl UserController) Post(args response.Arguments) response.Response { res := response.New() // extract user ID argument username, ok := args["username"].(string) password, ok1 := args["password"].(string) if !ok || !ok1 { res.Err = e.Failure return *res } // store user data res.Set("created", storeUser(username, password)) return res } ``` *Note*: Functions `getUserData(int) map[string]string` and `storeUser(string,string) bool` do not exist, it was in order for all to understand the example. # **V.** Type Checker Each type checker checks http request extracted values according to the type given in the api definition. The default types below are available in the `$GOPATH/src/git.xdrm.io/go/aicra/internal/checker/default`, they are loaded by default in any project. You can choose not to load them by settings `types`.`default` to `false` in the project configuration. To add custom types you must implement the [driver.Checker](https://godoc.org/git.xdrm.io/go/aicra/driver#Checker) interface and export it in order for aicra to dynamically load it. ## **1** - Default types **Warning :** All received values are extracted using JSON format if possible, else values are considered raw strings. - `2`, `-3.2` are extracted as floats - `true` and `false` are stored as booleans - `null` is stored as null - `[x, y, z]` is stored as an array containing **x**, **y**, and **z** that can be themselves JSON-decoded if possible. - `{ "a": x }` is stored as a map containing **x** at the key "a", **x** can be itself JSON-decoded if possible. - `"some string"` is stored as a string - `some string` here, the decoding will fail (it is no valid JSON) so it will be stored as a string |Type|Example|Description| |---|---|---| |`any`|`[9,"a"]`, `"a"`|Any data (can be simple or complex)| |`id`|`10`, `23`|Positive integer number| |`int`|`-10`, `23`|Any integer number| |`float`|`-10.2`, `23.5`|Any float| |`string`|`"Hello!"`|String that can be of any length (even empty)| |`digest(L)`|`"4612473aa81f93a878..."`|String with a length of `L`, containing only hexadecimal lowercase characters.| |`array`|`[]`, `[1, "a"]`|Any array| |`bool`|`true`, `false`|Boolean| |`varchar(a,b)`|`"Hello!"`|String with a length between `a` and `b` (included)| ## **2** - Complex types |Type|Sub-Type|Description| |---|---|---| |`array`|`a`|Array containing only entries matching the type `a`| |`FILE`|_a raw file send in `multipart/form-data`|A raw file sent by `multipart/form-data`| > **Note:** It is possible to chain `array` type as many as needed. **Ex.:** `array>` - Will only match an array containing arrays that only contains `id` entries. # **VI.** Documentation With the *all-in-config* method, we can generate a consistent documentation or other documents from the `/config/modules.json` file. ## **1** - API accessible documentation You can request the API for information about the current URI by using the `OPTIONS` HTTP method. ==== # **I.** Simple request ## **1** - URL ### format The `uri` format is as defined: `{base}/{path}/{GET_parameters}`, where - `{base}` is the server's *API* base uri (ex: `https://example.com/api/v1` or `https://api.exampl.com`) - `{path}` is the effective path you want to access (ex: `article/author`) - `{GET_parameters}` is a set of slash-separated values (ex: `val0/val1/val2//val4`) *Note:* GET parameters are not used as usual (`?var1=val1&var2=val2...`), instead the position in the URL gives them an implicit name which is `URL#`, where `#` is the index in the uri (beginning with 0). ### example 1 If you want to edit an article with the server's REST API, it could be defined as following: ```yaml http: PUT path: article/{id_article} input: body: new content of the article output: updated: the updated article data ``` Let's take the example where you want to update the article which id is **23** and set its body to "**blabla new content**" `HTTP REQUEST` ``` PUT article/23 HTTP/1.0 body=blabla+new+content ``` `HTTP RESPONSE` ``` HTTP/1.0 200 OK Content-Type: application/json { "error": 0, "ErrorDescription": "all right", "updated": { "id_article": 23, "title": "article 23", "body": "blabla new content" } } ``` ### example 2 If you want to get a specific article line, the request could be defined as following ```yaml http: GET path: article/line/{id_article}/{no_line} input: - output: articles: the list of matching lines ``` Let's take the example where you want to get **all articles** because `id_article` is set to optional, but you only want the first line of each so you have to give only the second parameter set to `1` (first line). *Solution:* The position in the `uri` where `id_article` must be, have to be left empty: it will result of 2 slashes (`//`). `HTTP REQUEST` ``` GET article/line//1 HTTP/1.0 ``` `HTTP RESPONSE` ``` HTTP/1.0 200 OK Content-Type: application/json { "error": 0, "ErrorDescription": "all right", "articles": [ { "id_article": 23, "line": [ 0: "blabla new content" },{ "id_article": 25, "line": [ 0: "some article" } ] } ```