Tutoriel sur OpenAPI

imt

1. Objectif

Nous allons ici voir comment documenter une API en utilisant le standard OpenAPI. Nous allons spécifier l’API de notre (micro)service Movie.

Notons que pour des raisons pédagogiques nous avons d’abord écrit le code de notre (micro)service avant d’écrire la spécification de notre API. Une bonne pratique serait de procéder dans le sens inverse, même si des corrections peuvent être apportées à la spécification/documentation après implémentation.

Note
Une page introductive à OpenAPI https://swagger.io/docs/specification/about/
Note
La spécification complète de OpenAPI https://swagger.io/specification/
Note
Il existe d’autres standards comme RAML mais OpenAPI est très utilisé et utilise le format YAML
Note
Vous pouvez vous créer un compte gratuit sur https://app.swaggerhub.com/home et éditer votre documentation comme je le ferai en séance mais vous pouvez également écrire votre fichier yaml sur votre machine avec n’importe quel bon éditeur de fichiers texte ou de code (Sublime, Atom, VSCode, Emacs, etc.)

2. Informations et tags

Nous pouvons en premier indiquer la version du standard que nous utilisons. Nous allons définir les serveurs comme ne contenant rien.

openapi: 3.0.0
servers: []

Puis nous pouvons remplir l’objet d’information de la documentation

info:
  description: This is the API of the Movie service
  version: "1.0.0"
  title: Movie API
  contact:
    email: helene.coullon@imt-atlantique.fr
  license:
    name: GPL v3
    url: 'https://www.gnu.org/licenses/gpl-3.0.en.html'
Note
Pour plus de détails voir la spécification et l’objet info : https://swagger.io/specification/

Nous allons maintenant pouvoir ajouter des tags permettant d’identifier des catégories dans les chemins définis ensuite.

tags:
  - name: admins
    description: Secured Admin-only calls
  - name: developers
    description: Operations available to regular developers

Ici par exemple nous définissons 2 tags pour les utilisateurs de l’API, mais un tag peut représenter n’importe quelle classification utile.

3. Chemins et composants

3.1. Chemin simple

Les chemins décrivent les point d’accès de l’API définis par le décorateur @ dans Flask. Un premier exemple est donné ci-dessous.

paths:
  /:
    get:
      tags:
        - developers
      summary: home page of the service
      operationId: home
      description: |
        Nothing to do
      responses:
        '200':
          description: welcome message
          content:
            text/html:
              schema:
                type: string
                example: "<h1 style='color:blue'>Welcome to the Movie service!</h1>"
  1. Nous associons d’abord une requête de type GET à ce chemin. Notons que plusieurs types de requêtes peuvent être associés au même chemin, nous le verrons ensuite.

  2. Nous pouvons ensuite associer un tag à cette requête, ici le tag developers.

  3. Viennent ensuite des éléments associés à l’objet de requête comme une description, un id etc. (voir la spécification pour le détail).

  4. La réponse doit être spécifiée dans l’objet requête également, elle contient différents codes retour (ici uniquement 200) et décrit le contenu de la réponse ici une balise HTML.

Ce chemin correspond donc bien au code Python suivant :

@app.route("/", methods=['GET'])
def home():
    return make_response("<h1 style='color:blue'>Welcome to the Movie service!</h1>",200)

3.2. Chemin et JSON

Voyons une chemin pour lequel la requête GET retourne un format JSON maintenant.

/json:
    get:
      tags:
        - developers
      summary: get the full JSON database
      operationId: get_json
      description: |
        Nothing to do
      responses:
        '200':
          description: full JSON
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AllMovies'

Dans ce cas, nous pointons vers une référence à un autre objet YAML contenu dans components/schemas/AllMovies. Nous allons donc voir comment définir ce schéma JSON.

components:
  schemas:
    AllMovies:
      type: object
      required:
        - movies
      properties:
        movies:
          type: array
          items:
            type: object
            $ref: '#/components/schemas/MovieItem'

Ici le schéma AllMovies est décrit comme étant un objet constitué de movies uniquement, cet élément étant de type array et contenant lui même des objet dont le schéma est décrit dans components/schemas/MovieItem. Voici ce nouveau schéma.

    MovieItem:
      type: object
      required:
        - title
        - rating
        - director
        - id
      properties:
        title:
          type: string
          example: The Martian
        rating:
          type: integer
          example: 7
        director:
          type: string
          example: Paul McGuigan
        id:
          type: string
          example: 39ab85e5-5e8e-4dc5-afea-65dc368bd7ab

MovieItem est un objet constitué d’un titre de type string, d’une note de type integer, d’un directeur et d’un ID de types string.

3.3. Chemin et paramètres

Voici la spécification du chemin permettant de récupérer les informations d’un film à partir de son ID.

/movies/{movieid}:
    get:
      tags:
        - developers
      summary: get the movie by its id
      operationId: get_movie_byid
      description: |
        By passing in the appropriate options, you can get info of a Movie
      parameters:
        - name: movieid
          in: path
          required: true
          description: Movie ID.
          schema:
            type : string
            minimum: 1
            maximum: 1
      responses:
        '200':
          description: Movie description
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MovieItem'
        '400':
          description: bad input parameter

Les différences notables ici avec les exemples précédents sont :

  • un paramètre est déclaré et intégré dans le chemin,

  • plus d’un code retour est possible pour la réponse.

Les paramètres inclus dans le chemin sont identifiés par des {}, ouis le paramètre est spécifié en détail dans la liste des paramètres parameters. Le schéma du paramètre doit également être spécifié à l’image des schémas déclarés dans les composants.

3.4. Requête de type POST

Les requêtes de type POST sont spécifiées de façon similaire aux requête GET mais contiennent en plus un objet requestBody. Ici le corps de la requête est un format JSON qui est donné par le schéma components/schemas/MovieItem déjà étudié.

   post:
      tags:
        - admins
      summary: add a movie item
      operationId: create_movie
      description: Adds a movie to the system
      parameters:
        - name: movieid
          in: path
          required: true
          description: Movie ID.
          schema:
            type : string
            minimum: 1
            maximum: 1
      responses:
        '200':
          description: Movie created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MovieItem'
        '409':
          description: an existing item already exists
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MovieItem'
        description: Inventory item to add

3.5. Paramètre dans la requête

Voici un exemple de documentation avec un paramètre dans la requête. Rien de très différent hors mis la clé/valeur in: query.

/titles:
    get:
      tags:
        - developers
      summary: get the movie by its title
      operationId: get_movie_bytitle
      description: |
        By passing in the appropriate options, you can get Movie info
      parameters:
        - in: query
          name: title
          description: pass a title
          required: true
          schema:
            type: string
            minimum: 1
            maximum: 1
      responses:
        '200':
          description: Movie item
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MovieItem'
        '400':
          description: bad input parameter