diff --git a/README.md b/README.md index dfb8937..242be55 100644 --- a/README.md +++ b/README.md @@ -7,40 +7,34 @@ [![Build Status](https://drone.xdrm.io/api/badges/go/aicra/status.svg)](https://drone.xdrm.io/go/aicra) -**Aicra** is a *configuration-driven* **web framework** written in Go that allows you to create a fully featured REST API. +Aicra is a *configuration-driven* REST API engine written in Go. -The whole management is done for you from a configuration file describing your API, you're left with implementing : +Most of the management is done for you using a configuration file describing your API. you're left with implementing : - handlers - optionnally middle-wares (_e.g. authentication, csrf_) - and optionnally your custom type checkers to check input parameters +> A example project is available [here](https://git.xdrm.io/go/articles-api) -The aicra server fulfills the `net/http` [Server interface](https://golang.org/pkg/net/http/#Server). - - - -> A example project is available [here](https://git.xdrm.io/go/tiny-url-ex) - - -### Table of contents +## Table of contents - [I/ Installation](#i-installation) -- [II/ Development](#ii-development) - * [1) Main executable](#1-main-executable) - * [2) API Configuration](#2-api-configuration) - - [Definition](#definition) - + [Input Arguments](#input-arguments) - - [1. Input types](#1-input-types) - - [2. Global Format](#2-global-format) +- [II/ Usage](#ii-usage) + * [1) Build a server](#1-build-a-server) + * [2) API Configuration](#2-api-configuration) + - [Definition](#definition) + + [Input Arguments](#input-arguments) + - [1. Input types](#1-input-types) + - [2. Global Format](#2-global-format) - [III/ Change Log](#iii-change-log) -### I/ Installation +## I/ Installation -You need a recent machine with `go` [installed](https://golang.org/doc/install). This package has not been tested under the version **1.10**. +You need a recent machine with `go` [installed](https://golang.org/doc/install). This package has not been tested under the version **1.14**. ```bash @@ -50,95 +44,112 @@ go get -u git.xdrm.io/go/aicra/cmd/aicra The library should now be available as `git.xdrm.io/go/aicra` in your imports. -### II/ Development +## II/ Usage -#### 1) Main executable +### 1) Build a server -Your main executable will declare and run the aicra server, it might look quite like the code below. +Here is some sample code that builds and sets up an aicra server using your api configuration file. ```go package main import ( - "log" - "net/http" + "log" + "net/http" + "os" - "git.xdrm.io/go/aicra" - "git.xdrm.io/go/aicra/datatype" - "git.xdrm.io/go/aicra/datatype/builtin" + "git.xdrm.io/go/aicra" + "git.xdrm.io/go/aicra/api" + "git.xdrm.io/go/aicra/datatype/builtin" ) func main() { - // 1. select your datatypes (builtin, custom) - var dtypes []datatype.T - dtypes = append(dtypes, builtin.AnyDataType{}) - dtypes = append(dtypes, builtin.BoolDataType{}) - dtypes = append(dtypes, builtin.UintDataType{}) - dtypes = append(dtypes, builtin.StringDataType{}) + builder := &aicra.Builder{} - // 2. create the server from the configuration file - server, err := aicra.New("path/to/your/api/definition.json", dtypes...) + // add datatypes your api uses + builder.AddType(builtin.BoolDataType{}) + builder.AddType(builtin.UintDataType{}) + builder.AddType(builtin.StringDataType{}) + + config, err := os.Open("./api.json") if err != nil { - log.Fatalf("cannot built aicra server: %s\n", err) + log.Fatalf("cannot open config: %s", err) } - // 3. bind your implementations - server.HandleFunc(http.MethodGet, "/path", func(req api.Request, res *api.Response){ - // ... process stuff ... - res.SetError(api.ErrorSuccess()); - }) - - // 4. extract to http server - httpServer, err := server.ToHTTPServer() + // pass your configuration + err = builder.Setup(config) + config.Close() if err != nil { - log.Fatalf("cannot get to http server: %s", err) + log.Fatalf("invalid config: %s", err) } - // 4. launch server - log.Fatal( http.ListenAndServe("localhost:8080", server) ) + // bind your handlers + builder.Bind(http.MethodGet, "/user/{id}", getUserById) + builder.Bind(http.MethodGet, "/user/{id}/username", getUsernameByID) + + // build the server and start listening + server, err := builder.Build() + if err != nil { + log.Fatalf("cannot build server: %s", err) + } + http.ListenAndServe("localhost:8080", server) } ``` +Here is an example handler +```go +type req struct{ + Param1 int + Param3 *string // optional are pointers +} +type res struct{ + Output1 string + Output2 bool +} -#### 2) API Configuration +func myHandler(r req) (*res, api.Error) { + err := doSomething() + if err != nil { + return nil, api.ErrorFailure + } + return &res{}, api.ErrorSuccess +} +``` -The whole project behavior is described inside a json file (_e.g. usually api.json_). For a better understanding of the format, take a look at this working [template](https://git.xdrm.io/go/tiny-url-ex/src/master/api.json). This file defines : + +### 2) API Configuration + +The whole api behavior is described inside a json file (_e.g. usually api.json_). For a better understanding of the format, take a look at this working [template](https://git.xdrm.io/go/articles-api/src/master/api.json). This file defines : - routes and their methods - every input for each method (called *argument*) - every output for each method - scope permissions (list of permissions needed by clients) - input policy : - - type of argument (_i.e. for data types_) + - type of argument (_c.f. data types_) - required/optional - variable renaming +#### Format - -###### Definition - -The root of the json file must be an array containing your requests definitions. - -For each, you will have to create fields described in the table above. +The root of the json file must be an array containing your requests definitions. For each, you will have to create fields described in the table above. | field path | description | example | | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | `info` | A short human-readable description of what the method does | `create a new user` | | `scope` | A 2-dimensional array of permissions. The first dimension can be translated to a **or** operator, the second dimension as a **and**. It allows you to combine permissions in complex ways. | `[["A", "B"], ["C", "D"]]` can be translated to : this method needs users to have permissions (A **and** B) **or** (C **and** D) | -| `in` | The list of arguments that the clients will have to provide. See [here](#input-arguments) for details. | | -| `out` | The list of output data that will be returned by your controllers. It has the same syntax as the `in` field but is only use for readability purpose and documentation. | | +| `in` | The list of arguments that the clients will have to provide. [Read more](#input-arguments). | | +| `out` | The list of output data that will be returned by your controllers. It has the same syntax as the `in` field but optional parameters are not allowed | -##### Input Arguments - -###### 1. Input types +### Input Arguments Input arguments defines what data from the HTTP request the method needs. Aicra is able to extract 3 types of data : -- **URI** - Curly Braces enclosed strings inside the request path. For instance, if your controller is bound to the `/user/{id}` URI, you can set the input argument `{id}` matching this uri part. +- **URI** - data from inside the request path. For instance, if your controller is bound to the `/user/{id}` URI, you can set the input argument `{id}` matching this uri part. - **Query** - data formatted at the end of the URL following the standard [HTTP Query](https://tools.ietf.org/html/rfc3986#section-3.4) syntax. - **URL encoded** - data send inside the body of the request but following the [HTTP Query](https://tools.ietf.org/html/rfc3986#section-3.4) syntax. - **Multipart** - data send inside the body of the request with a dedicated [format](https://tools.ietf.org/html/rfc2388#section-3). This format is not very lightweight but allows you to receive data as well as files. @@ -146,7 +157,7 @@ Input arguments defines what data from the HTTP request the method needs. Aicra -###### 2. Global Format +#### Format The `in` field in each method contains as list of arguments where the key is the argument name, and the value defines how to manage the variable. @@ -159,10 +170,6 @@ The `in` field in each method contains as list of arguments where the key is the In this example we want 3 arguments : -- the 1^st^ one is send at the end of the URI and is a number compliant with the `int` type checker. It is renamed `article_id`, this new name will be sent to the handler. -- the 2^nd^ one is send in the query (_e.g. [http://host/uri?get-var=value](http://host/uri?get-var=value)_). It must be a valid `string` or not given at all (the `?` at the beginning of the type tells that the argument is **optional**) ; it will be named `title`. -- the 3^rd^ can be send with a **JSON** body, in **multipart** or **URL encoded** it makes no difference and only give clients a choice over the technology to use. If not renamed, the variable will be given to the handler with the name `content`. - ```json [ { @@ -184,32 +191,6 @@ In this example we want 3 arguments : ] ``` - - -### III/ Change Log - -- [x] human-readable json configuration -- [x] nested routes (*i.e. `/user/:id:` and `/user/post/:id:`*) -- [x] nested URL arguments (*i.e. `/user/:id:` and `/user/:id:/post/​:id:​`*) -- [x] useful http methods: GET, POST, PUT, DELETE -- [x] manage URL, query and body arguments: - - [x] multipart/form-data (variables and file uploads) - - [x] application/x-www-form-urlencoded - - [x] application/json -- [x] required vs. optional parameters with a default value -- [x] parameter renaming -- [x] generic type check (*i.e. implement custom types alongside built-in ones*) -- [ ] built-in types - - [x] `any` - wildcard matching all values - - [x] `int` - see go types - - [x] `uint` - see go types - - [x] `float` - see go types - - [x] `string` - any text - - [x] `string(min, max)` - any string with a length between `min` and `max` - - [ ] `[a]` - array containing **only** elements matching `a` type - - [ ] `[a:b]` - map containing **only** keys of type `a` and values of type `b` (*a or b can be ommited*) -- [x] generic controllers implementation (shared objects) -- [x] response interface -- [x] log bound resources when building the aicra server -- [x] fail on check for unimplemented resources at server boot. -- [x] fail on check for unavailable types in api.json at server boot. +- the 1^st^ one is send at the end of the URI and is a number compliant with the `int` type checker. It is renamed `article_id`, this new name will be sent to the handler. +- the 2^nd^ one is send in the query (_e.g. [http://host/uri?get-var=value](http://host/uri?get-var=value)_). It must be a valid `string` or not given at all (the `?` at the beginning of the type tells that the argument is **optional**) ; it will be named `title`. +- the 3^rd^ can be send with a **JSON** body, in **multipart** or **URL encoded** it makes no difference and only give clients a choice over the technology to use. If not renamed, the variable will be given to the handler with the name `content`.