From 610ab66ea82147e683dca92101d61c9a27c98a80 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 19 Jun 2021 00:17:37 +0200 Subject: [PATCH] readme: add logo, improve structure and explanations --- README.md | 295 +++++++++++++++++++++++------------------ readme.assets/logo.png | Bin 0 -> 19380 bytes 2 files changed, 169 insertions(+), 126 deletions(-) create mode 100644 readme.assets/logo.png diff --git a/README.md b/README.md index f2b2bab..424ee6b 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,79 @@ -# | aicra | +

+ + aicra logo + +

-[![Go version](https://img.shields.io/badge/go_version-1.10.3-blue.svg)](https://golang.org/doc/go1.10) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Go Report Card](https://goreportcard.com/badge/git.xdrm.io/go/aicra)](https://goreportcard.com/report/git.xdrm.io/go/aicra) -[![Go doc](https://godoc.org/git.xdrm.io/go/aicra?status.svg)](https://godoc.org/git.xdrm.io/go/aicra) -[![Build Status](https://drone.xdrm.io/api/badges/go/aicra/status.svg)](https://drone.xdrm.io/go/aicra) +

aicra

----- +

+ Fast, intuitive, and powerful configuration-driven engine for faster and easier REST development. +

-`aicra` is a lightweight and idiomatic API engine for building Go services. It's especially good at helping you write large REST API services that remain maintainable as your project grows. +[![Go version](https://img.shields.io/badge/go_version-1.16-blue.svg)](https://golang.org/doc/go1.16) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Go Report Card](https://goreportcard.com/badge/git.xdrm.io/go/aicra)](https://goreportcard.com/report/git.xdrm.io/go/aicra) [![Go doc](https://godoc.org/git.xdrm.io/go/aicra?status.svg)](https://godoc.org/git.xdrm.io/go/aicra) [![Build Status](https://drone.xdrm.io/api/badges/go/aicra/status.svg)](https://drone.xdrm.io/go/aicra) -The focus of the project is to allow you to build a fully featured REST API in an elegant, comfortable and inexpensive way. This is achieved by using a configuration file to drive the server. The configuration format describes the whole API: routes, input arguments, expected output, permissions, etc. +## Presentation -TL;DR: `aicra` is a fast configuration-driven REST API engine. +`aicra` is a lightweight and idiomatic configuration-driven engine for building REST services. It's especially good at helping you write large APIs that remain maintainable as your project grows. -Repetitive tasks is automatically processed by `aicra` from your configuration file, you just have to implement your handlers. +The focus of the project is to allow you to build a fully-featured REST API in an elegant, comfortable and inexpensive way. This is achieved by using a single configuration file to drive the server. This one file describes your entire API: methods, uris, input data, expected output, permissions, etc. -The engine automates : -- catching input data (_url, query, form-data, json, url-encoded_) -- handling missing input data (_required arguments_) -- handling input data validation -- checking for mandatory output parameters -- checking for missing method implementations -- checking for handler signature (input and output arguments) +Repetitive tasks are automatically processed by `aicra` based on your configuration, you're left with implementing your handlers (_usually business logic_). -> An example project is available [here](https://git.xdrm.io/go/articles-api) - -### Table of contents +## Table of contents - * [Installation](#installation) -- [Usage](#usage) - - [Create a server](#create-a-server) - - [Create a handler](#create-a-handler) -- [Configuration](#configuration) - - [Global format](#global-format) - * [Input section](#input-section) - + [Format](#format) - - [Example](#example) +- [Installation](#installation) +- [What's automated](#whats-automated) +- [Getting started](#getting-started) +- [Configuration file](#configuration-file) + * [Services](#services) + * [Input and output parameters](#input-and-output-parameters) + * [Example](#example) +- [Writing your code](#writing-your-code) - [Changelog](#changelog) ## Installation -You need a recent machine with `go` [installed](https://golang.org/doc/install). The package has not been tested under **go1.14**. - +To install the aicra package, you need to install Go and set your Go workspace first. +> not tested under Go 1.14 +1. you can use the below Go command to install aicra. ```bash -go get -u git.xdrm.io/go/aicra +$ go get -u git.xdrm.io/go/aicra +``` +2. Import it in your code: +```go +import "git.xdrm.io/go/aicra" ``` +## What's automated -# Usage +As the configuration file is here to make your life easier, let's take a quick look at what you do not have to do ; or in other words, what does `aicra` automates. +Http requests are only accepted when they have the permissions you have defined. If unauthorized, the request is rejected with an error response. -#### Create a server +Request data is automatically extracted and validated before it reaches your code. If a request has missing or invalid data an automatic error response is sent. -The code below sets up and creates an HTTP server from the `api.json` configuration. +When launching the server, it ensures everything is ok and won't start until fixed. You will get errors for: +- handler signature does not match the configuration +- a configuration service has no handler +- a handler does not match any service + +The same applies if your configuration is invalid: +- unknown HTTP method +- invalid uri +- uri collision between 2 services +- missing fields +- unknown data type +- input name collision + +## Getting started + +Here is the minimal code to launch your aicra server assuming your configuration file is `api.json`. ```go package main @@ -76,13 +91,13 @@ import ( func main() { builder := &aicra.Builder{} - // register available validators + // register data validators builder.AddType(builtin.BoolDataType{}) builder.AddType(builtin.UintDataType{}) builder.AddType(builtin.StringDataType{}) // load your configuration - config, err := os.Open("./api.json") + config, err := os.Open("api.json") if err != nil { log.Fatalf("cannot open config: %s", err) } @@ -92,11 +107,14 @@ func main() { log.Fatalf("invalid config: %s", err) } - // bind your handlers - builder.Bind(http.MethodGet, "/user/{id}", getUserById) - builder.Bind(http.MethodGet, "/user/{id}/username", getUsernameByID) + // bind handlers + err = builder.Bind(http.MethodGet, "/user/{id}", getUserById) + if err != nil { + log.Fatalf("cannog bind GET /user/{id}: %s", err) + } + // ... - // build the handler and start listening + // build your services handler, err := builder.Build() if err != nil { log.Fatalf("cannot build handler: %s", err) @@ -106,22 +124,123 @@ func main() { ``` If you want to use HTTPS, you can configure your own `http.Server`. - ```go func main() { server := &http.Server{ Addr: "localhost:8080", TLSConfig: tls.Config{}, - // aicra handler - Handler: handler, + // ... + Handler: AICRAHandler, } - server.ListenAndServe() } ``` +## Configuration file -#### Create a handler +First of all, the configuration uses `json`. + +> Quick note if you thought: "I have JSON, I would have preferred yaml, or even xml !" +> +> I've had a hard time deciding and testing different formats including yaml and xml. +> But as it describes our entire api and is crucial for our server to keep working over updates; xml would have been too verbose with growth and yaml on the other side would have been too difficult to read. Json sits in the right spot for this. + +Let's take a quick look at the configuration format ! + +> if you don't like boring explanations and prefer a working example, see [here](https://git.xdrm.io/go/articles-api/src/master/api.json) + +### Services + +To begin with, the configuration file defines a list of services. Each one is defined by: +- `method` an HTTP method +- `path` an uri pattern (can contain variables) +- `info` a short description of what it does +- `scope` a list of the required permissions +- `in` a list of input arguments +- `out` a list of output arguments +```json +[ + { + "method": "GET", + "path": "/article", + "scope": [["author", "reader"], ["admin"]], + "info": "returns all available articles", + "in": {}, + "out": {} + } +] +``` + +The `scope` is a 2-dimensional list of permissions. The first list means **or**, the second means **and**, it allows for complex permission combinations. The example above can be translated to: this method requires users to have permissions (author **and** reader) **or** (admin) + +### Input and output parameters + +Input and output parameters share the same format, featuring: +- `info` a short description of what it is +- `type` its data type (_c.f. validation_) +- `?` whether it is mandatory or optional +- `name` a custom name for easy access in code +```json +[ + { + "method": "PUT", + "path": "/article/{id}", + "scope": [["author"]], + "info": "updates an article", + "in": { + "{id}": { "info": "...", "type": "int", "name": "id" }, + "GET@title": { "info": "...", "type": "?string", "name": "title" }, + "content": { "info": "...", "type": "string" } + }, + "out": { + "title": { "info": "updated article title", "type": "string" }, + "content": { "info": "updated article content", "type": "string" } + } + } +] +``` + +If a parameter is optional you just have to prefix its type with a question mark, by default all parameters are mandatory. + +The format of the key of input arguments defines where it comes from: +1. `{param}` is an URI parameter that is extracted from the `"path"` +2. `GET@param` is an URL parameter that is extracted from the [HTTP Query](https://tools.ietf.org/html/rfc3986#section-3.4) syntax. +3. `param` is a body parameter that can be extracted from 3 formats independently: + - _url encoded_: data send in the body following the [HTTP Query](https://tools.ietf.org/html/rfc3986#section-3.4) syntax. + - _multipart_: data send in the body with a dedicated [format](https://tools.ietf.org/html/rfc2388#section-3). This format can be quite heavy but allows to transmit data as well as files. + - _JSON_: data sent in the body as a json object ; The _Content-Type_ header must be `application/json` for it to work. + +### Example +```json +[ + { + "method": "PUT", + "path": "/article/{id}", + "scope": [["author"]], + "info": "updates an article", + "in": { + "{id}": { "info": "...", "type": "int", "name": "id" }, + "GET@title": { "info": "...", "type": "?string", "name": "title" }, + "content": { "info": "...", "type": "string" } + }, + "out": { + "id": { "info": "updated article id", "type": "uint" }, + "title": { "info": "updated article title", "type": "string" }, + "content": { "info": "updated article content", "type": "string" } + } + } +] +``` + +1. `{id}` is extracted from the end of the URI and is a number compliant with the `int` type checker. It is renamed `ID`, this new name will be sent to the handler. +2. `GET@title` is extracted from 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`. +3. `content` can be extracted from json, multipart or url-encoded data; 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 its original name `content`. + + + +## Writing your code + +Besides your main package where you launch your server, you will need to create handlers matching services from the configuration. The code below implements a simple handler. ```go @@ -151,9 +270,9 @@ func myHandler(r req) (*res, api.Err) { } ``` -If your handler signature does not match the configuration exactly, the server will print out the error and will not start. +If your handler signature does not match the configuration exactly, the server will print out the error and won't start. -The `api.Err` type automatically maps to HTTP status codes and error descriptions that will be sent to the client as json; client will then always have to manage the same format. +The `api.Err` type automatically maps to HTTP status codes and error descriptions that will be sent to the client as json; clients have to manage the same format for every response. ```json { "error": { @@ -163,83 +282,7 @@ The `api.Err` type automatically maps to HTTP status codes and error description } ``` - -# 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 [configuration](https://git.xdrm.io/go/articles-api/src/master/api.json). - -The configuration file defines : -- routes and their methods -- every input argument for each method -- every output for each method -- scope permissions (list of permissions required by clients) -- input policy : - - type of argument (_c.f. data types_) - - required/optional - - variable renaming - -#### Global format - -The root of the json file must feature an array containing your requests definitions. For each, you will have to create fields described in the table above. - -- `info`: Short description of the method -- `in`: List of arguments that the clients will have to provide. [Read more](#input-arguments). -- `out`: List of output data that your controllers will output. It has the same syntax as the `in` field but optional parameters are not allowed. -- `scope`: A 2-dimensional array of permissions. The first level means **or**, the second means **and**. It allows to combine permissions in complex ways. - - Example: `[["A", "B"], ["C", "D"]]` translates to : this method requires users to have permissions (A **and** B) **or** (C **and** D) - - -##### Input section - -Input arguments defines what data from the HTTP request the method requires. `aicra` is able to extract 3 types of data : - -- **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 at the end of the URL following the standard [HTTP Query](https://tools.ietf.org/html/rfc3986#section-3.4) syntax. -- **Form** - data send from the body of the request ; it can be extracted in 3 ways: - - _URL encoded_: data send in the body following the [HTTP Query](https://tools.ietf.org/html/rfc3986#section-3.4) syntax. - - _Multipart_: data send in the body with a dedicated [format](https://tools.ietf.org/html/rfc2388#section-3). This format can be quite heavy but allows to transmit data as well as files. - - _JSON_: data send in the body as a json object ; each key being a variable name, each value its content. Note that the 'Content-Type' header must be set to `application/json` for the API to use it. - -> For Form data, the 3 methods can be used at once for different arguments; for instance if you need to send a file to an aicra server as well as other parameters, you can use JSON for parameters and Multipart for the file. - -###### Format - -The `in` field describes as list of arguments where the key is the argument name, and the value defines how to manage the variable. - - -Variable names from **URI** or **Query** must be named accordingly : -- an **URI** variable `{var}` from your request route must be named `{var}` in the `in` section -- a variable `var` in the **Query** has to be named `GET@var` in the `in` section - - -#### Example -```json -[ - { - "method": "PUT", - "path": "/article/{id}", - "scope": [["author"]], - "info": "updates an article", - "in": { - "{id}": { "info": "...", "type": "int", "name": "id" }, - "GET@title": { "info": "...", "type": "?string", "name": "title" }, - "content": { "info": "...", "type": "string" } - }, - "out": { - "id": { "info": "updated article id", "type": "uint" }, - "title": { "info": "updated article title", "type": "string" }, - "content": { "info": "updated article content", "type": "string" } - } - } -] -``` - -1. `{id}` is extracted from the end of the URI and is a number compliant with the `int` type checker. It is renamed `id`, this new name will be sent to the handler. -2. `GET@title` is extracted from 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`. -3. `content` can be extracted from json, multipart or url-encoded data; 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 its original name `content`. - - -# Changelog +## Changelog - [x] human-readable json configuration - [x] nested routes (*i.e. `/user/{id}` and `/user/post/{id}`*) diff --git a/readme.assets/logo.png b/readme.assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f164231751392ebf273cea2a496a60a86e0bb5e2 GIT binary patch literal 19380 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 mwJGnmbs8Po9{dxaB>oL_hnPgANc)Qb0000