La documentation avec OpenAPI (Swagger RESTFul API)

Que serait une API s’il était impossible de comprendre son mode de fonctionnement ?

Parler de documentation dans une API RESTful se rapproche beaucoup d’un oxymore. En effet, une API dite RESTFul devrait pouvoir être utilisée sans documentation.

Mais si vous vous souvenez bien, notre API n’implémente pas le niveau 3 du modèle de maturité de Richardson : HATEOAS qui permettrait de l’explorer automatiquement et d’interagir avec elle. Dès lors, pour faciliter son usage nous devons créer une documentation.

Elle permettra ainsi aux clients de notre API de comprendre son mode de fonctionnement et d’explorer rapidement les différentes fonctionnalités qu’elle expose.

Il existe un standard appelé OpenAPI, anciennement connu sous le nom de Swagger RESTful API, permettant d’avoir des spécifications simples pour une documentation exhaustive.

L’objectif de cette partie est d’avoir un aperçu de OpenAPI et de voir comment mettre en place une documentation en implémentant ces spécifications.

Qu'est-ce que OpenAPI ?

OpenAPI désigne un ensemble de spécifications permettant de décrire et de documenter une API REST.

Le terme décrire n’est pas utilisé par hasard car implémenter ces spécifications permet entre autres :

  • d’obtenir une documentation (Swagger UI) ;
  • et de générer des clients permettant d’interagir avec notre API (Swagger Codegen).

Les spécifications permettent de créer un fichier JSON qui décrit l’ensemble des éléments d’une API (URL des ressources, code de statut des réponses, verbes HTTP utilisés, etc.). Par convention, ce fichier est souvent nommé swagger.json.

Pour cette partie nous allons commencer par la pratique avant d’explorer la théorie autour de la documentation des API REST avec OpenAPI.

Rédaction de la documentation

Quel outil pouvons-nous utiliser pour créer la documentation ?

Bien que le résultat final du fichier OpenAPI soit en JSON, il peut être rédigé aussi bien en JSON qu’en YAML. Nous préférerons d’ailleurs le YAML par la suite.

Pour créer ce fichier swagger.json, il faut suivre les spécifications qui sont disponibles en ligne : Spécification OpenAPI (Swagger).

L’un des moyens les plus simples pour rédiger et tester les spécifications est d’utiliser le site Swagger Editor. Ce site propose une prévisualisation de la documentation qui sera générée et des exemples de configuration (en YAML) qui permettent de mieux appréhender les spécifications d’OpenAPI.

Structure de base du fichier swagger.json

Un fichier swagger.json a trois attributs obligatoires :

  • swagger : définit la version des spécifications utilisées ;
  • info : définit les métadonnées de notre API ;
  • et paths : définit les différentes URL et opérations disponibles dans l’API.

Le fichier de base ressemble donc a :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
swagger: '2.0' # obligatoire
info: # obligatoire
  title: Proposition de suggestions API    # obligatoire
  description: Proposer des idées de sortie à des utilisateurs en utilisant leurs préférences
  version: "1.0.0" # obligatoire

host: rest-api.local
schemes:
  - http
produces:
  - application/json
  - application/xml
consumes:
  - application/json
  - application/xml

paths: # obligatoire

`
Prévisualisation de la structure de base

Les attributs produces et consumes permettent de décrire les type MIME des réponses renvoyées et des requêtes acceptées par notre API. Il est possible d’utiliser du Markdown pour formater les différentes descriptions (attributs description) dans la documentation.

Tous les tests se feront en utilisant directement le site http://editor.swagger.io. Le fichier swagger.json définitif sera testé en local dans la dernière partie.

Déclarer une opération avec l’API

Documentation de la méthode de connexion

Pour commencer, nous allons essayer de rédiger la documentation de la méthode d’authentification à l’API. Pour déclarer une opération, nous devons utiliser l’attribut paths.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
swagger: '2.0' # obligatoire
info: # obligatoire
  title: Proposition de suggestions API    # obligatoire
  description: Proposer des idées de sortie à des utilisateurs en utilisant leurs préférences
  version: "1.0.0" # obligatoire

host: rest-api.local
schemes:
  - http
produces:
  - application/json
  - application/xml
consumes:
  - application/json
  - application/xml

paths: # obligatoire
  /auth-tokens:
    post:
      summary: Authentifie un utilisateur
      description: Crée un token permettant à l'utilisateur d'accéder aux contenus protégés
      responses: # obligatoire

`

Voici la base permettant de créer des opérations. Sous l’attribut paths, il faut définir l’URL de notre ressource et ensuite il faut déclarer les différents verbes HTTP qui sont utilisés sur celle-ci. Actuellement, nous avons la méthode POST permettant de créer un token. Nous devons maintenant définir :

  • le payload de la requête ;
  • la réponse en cas de succès ;
  • la réponse en cas d’erreur.

Toutes ces données sont déclarées en utilisant les spécifications de JSON Schema.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# ...
paths: # obligatoire
  /auth-tokens:
    post:
      summary: Authentifie un utilisateur
      description: Crée un token permettant à l'utilisateur d'accéder aux contenus protégés
      parameters:
        - name: credentials  # obligatoire
          in: body  # obligatoire
          required: true
          description: Login et mot de passe de l'utilisateur
          schema:
            type: object
            required: [login, password]
            properties:
              login: 
                type: string
              password:
                type: string


      responses:
        200:
          description: Token créé  # obligatoire
          schema:
            type: object
            properties:
              id:
                type: integer
              value:
                type: string
              created_at:
                type: string
                format: date-time
              user:
                type: object
                properties:
                  id:
                    type: integer
                  email:
                    type: string
                    format: email
                  firstname:
                    type: string
                  lastname:
                    type: string

        400:
          description: Donnée invalide  # obligatoire
          schema:
            type: object
            required: [message]
            properties:
              code:
                type: integer
              message:
                type: string
              errors:
                type: object
                properties:
                  children:
                    type: object
                    properties:
                      login:
                        type: object
                        properties:
                          errors:
                            type: array
                            items:
                              type: string
                      password:
                        type: object
                        properties:
                          errors:
                            type: array
                            items:
                              type: string  

`
Documentation de la méthode de création d’un token

Il est aussi possible de mieux organiser le fichier en rajoutant une entrée definitions qui permet de regrouper tous les schémas que nous avons déclarés. Ensuite, il suffira de faire référence à ces schémas en utilisant l’attribut $ref.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# ...
paths: # obligatoire
  /auth-tokens:
    post:
      summary: Authentifie un utilisateur
      description: Crée un token permettant à l'utilisateur d'accéder aux contenus protégés
      parameters:
        - name: credentials  # obligatoire
          in: body  # obligatoire
          required: true
          description: Login et mot de passe de l'utilisateur
          schema:
            $ref: "#/definitions/Credentials"

      responses:
        200:
          description: Token créé  # obligatoire
          schema:
            $ref: "#/definitions/AuthToken.auth-token"

        400:
          description: Donnée invalide  # obligatoire
          schema:
             $ref: "#/definitions/CredentialsTypeError" 

definitions:
  Credentials:
    type: object
    required: [login, password]
    properties:
      login: 
        type: string
      password:
        type: string

  AuthToken.auth-token:
    type: object
    required: [id, value, created_at, user]
    properties:
      id:
        type: integer
      value:
        type: string
        title: Token d'authentification
        description: Valeur à utiliser dans l'entête X-Auth-Token
      created_at:
        type: string
        format: date-time
      user:
        type: object
        properties:
          id:
            type: integer
          email:
            type: string
            format: email
          firstname:
            type: string
          lastname:
            type: string       

  CredentialsTypeError:
    type: object
    required: [message]
    properties:
      code:
        type: integer
      message:
        type: string
      errors:
        type: object
        properties:
          children:
            type: object
            properties:
              login:
                type: object
                properties:
                  errors:
                    type: array
                    items:
                      type: string
              password:
                type: object
                properties:
                  errors:
                    type: array
                    items:
                      type: string

Avec ces modifications, le résultat obtenu est exactement identique.

Documentation de la méthode de déconnexion

De la même façon pour documenter la suppression d’un token, nous devons rajouter une nouvelle URL. Mais cette fois-ci, elle doit être dynamique comme pour les routes Symfony.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ...

paths: # obligatoire
  /auth-tokens:
   # ...

  /auth-tokens/{id}:
    delete:
      summary: Déconnecte un utilisateur
      description: Supprime le token de l'utilisateur
      parameters:
        - $ref: "#/parameters/X-Auth-Token"
        - name: id  # obligatoire
          in: path  # obligatoire
          type: integer  # obligatoire si le paramètre dans in est différent de 'body'
          required: true
          description: Identifiant du token à supprimer

      responses:
        204:
          description: Token supprimé  # obligatoire

        400:
          description: Donnée invalide  # obligatoire
          schema:
             $ref: "#/definitions/GenericError" 


parameters:
  X-Auth-Token:
    name: X-Auth-Token  # obligatoire
    in: header  # obligatoire
    type: string  # obligatoire si le paramètre dans in est différent de 'body'
    required: true
    description: Valeur du token d'authentification


definitions:
  # ...                
  GenericError:
    type: object
    required: [code, message]
    properties:
      code: 
        type: string
      message:
        type: string

À l’instar de la méthode de connexion, nous utilisons aussi le paramètre in pour désigner l’identifiant du token. Cet attribut peut valoir :

  • path : le paramètre est extrait de l’URL de la ressource ;
  • query : le paramètre est un query string ;
  • header : le paramètre est une entête HTTP ;
  • body : le paramètre est dans le payload ;
  • form : le paramètre est dans le payload qui est encodé au format application/x-www-form-urlencoded ou multipart/form-data (c’est le format utilisé par un formulaire classique).

L’entête HTTP X-Auth-Token est utilisée par plusieurs requêtes de notre API. En le déclarant dans l’attribut parameters, cela nous permet de le réutiliser dans les appels API qui nous intéressent.

Il existe deux attributs securityDefinitions et security permettant de configurer la méthode d’authentification sans passer par l’attribut parameters. Mais pour les besoins de cet exemple, nous ne les utiliserons pas.

Documentation de la méthode de suppression d’un token

Toutes les informations utilisées pour créer ce fichier sont issues des spécifications officielles d’OpenAPI. Vous pourrez les consulter afin de voir l’ensemble des fonctionnalités qu’offrent OpenAPI.

Installer et utiliser Swagger UI

Swagger UI est un logiciel basé sur les technologies du web (HTML, Javascript, CSS) permettant de générer une documentation en utilisant les spécifications d’OpenAPI. Il fournit aussi un bac à sable permettant de tester les appels API directement depuis la documentation générée.

Installation de Swagger UI

Pour installer Swagger UI, il suffit de le télécharger depuis GitHub. Ensuite, nous allons le décompresser dans un dossier nommé swagger-ui dans le répertoire web. Nous utiliserons la version v2.1.4.

Si vous utilisez git, il suffit de se placer dans le dossier web et de lancer :

1
2
git clone https://github.com/swagger-api/swagger-ui.git
git checkout v2.1.4
Arborescence après l’installation de Swagger UI

Si l’installation s’est bien déroulée, en accédant à l’URL http://rest-api.local/swagger-ui/dist/index.html, la page d’accueil de Swagger UI s’affiche.

Page d’accueil de Swagger UI

Utiliser notre documentation

Depuis l’interface de Swagger Editor, il est possible d’exporter notre documentation au format JSON. Le fichier swagger.json ainsi obtenu ressemble à :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    "swagger": "2.0",
    "info": {
        "title": "Proposition de suggestions API",
        "description": "Proposer des idées de sortie à des utilisateurs en utilisant leurs préférences",
        "version": "1.0.0"
    },
    "host": "rest-api.local",
    "schemes": [
        "http"
    ],
    "produces": [
        "application/json",
        "application/xml"
    ],
    "consumes": [
        "application/json",
        "application/xml"
    ],
    "paths": {
        // ...
    },
    "parameters": {
         // ...
    },
    "definitions": {
         // ...
    }
}

Pour utiliser ce fichier swagger.json, il faut commencer par l’enregistrer dans le dossier web. Le fichier doit être disponible depuis un navigateur. Ensuite, il faut éditer le fichier web/swagger-ui/dist/indext.html et éditer les lignes 34 à 39.

1
2
3
4
5
6
7
 /*var url = window.location.search.match(/url=([^&]+)/);
  if (url && url.length > 1) {
    url = decodeURIComponent(url[1]);
  } else {
    url = "http://petstore.swagger.io/v2/swagger.json";
  }*/
var url ="/swagger.json";

En consultant l’URL, nous pouvons maintenant voir notre documentation et même tester les appels API depuis celui-ci.

Documentation de notre API avec Swagger UI

Après cette brève initiation à OpenAPI, connu aussi sous le nom de Swagger RESTFul API, vous avez pu remarquer que l’écosystème autour de cette technologie est assez riche.

Ces spécifications se basent sur un ensemble de standards reconnus comme JSON Schema qui facilitent grandement sa prise en main.

Le fichier swagger.json ainsi obtenu peut être exploité par beaucoup d’outils qui permettent d’augmenter notre productivité (Génération de code client, génération de code serveur, interface de documentation avec bac à sable, etc.).