- JMSSerializer : Une alternative au sérialiseur natif de Symfony
- Automatiser la documentation avec NelmioApiDocBundle
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 ` |
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 ` |
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.
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 |
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.
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.
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.).