Comprendre la spécification OpenAPI (Swagger) et apprendre à utiliser Swagger Editor
Par Hinault Romaric

Le , par Hinault Romaric, Responsable .NET
Dans mon précédent billet, j’ai présenté comment intégrer le Framework Swagger dans une application ASP.NET Core Web API.


Pour rappel, Swagger offre des outils permettant de générer la documentation pour son API Web. Il offre également une interface permettant d’explorer et tester les différentes méthodes offertes par le service.

Le framework Swagger pour ASP.NET Core (Swashbuckle.AspNetCore) est une implémentation open source qui repose sur la spécification OpenAPI. Le endpoint JSON retourné par Swashbuckle dans une Web API respecte cette spécification.

Origines du projet OpenAPI

L’open source est un moteur de l’innovation qui a changé le destin de nombreux projets, grâce à la contribution d’une communauté importante. Parmi ceux-ci, figure le projet Swagger.

Swagger est un projet open source lancé par une Startup en 2010. L’objectif est de mettre en place un Framework qui va permettre aux développeurs de documenter et de designer des API, tout en maintenant une synchronisation avec le code.

Parallèlement au développement du Framework, une spécification Swagger a été mise en place pour définir le standard à respecter pour designer et documenter son API. Le projet a attiré l’attention de nombreux développeurs, et est devenu la technologie la plus populaire pour designer et décrire les API RESTful.

L’intérêt de l’industrie pour Swagger a poussé des géants de l’IT à se joindre au développement du projet. Google, IBM ou encore Microsoft ont rejoint SmartBear Software, l’entreprise responsable du développement de la spécification Swagger et les outils associés, pour faire évoluer ce dernier.

En janvier 2016 le projet Swagger Specification a été renommé OpenAPI Specification (OAS) et est passé sous la gouvernance de la fondation Linux. L’OPEN API Initiative a été créé par la fondation pour offrir un cadre de travail aux entreprises participantes. Le projet a été également migré vers un nouveau repository sur GitHub. Plusieurs autres géants de l’IT ont rejoint l’initiative, dont Oracle, Adobe, SAP, SalesForce, etc. À ce jour, plus de 30 entreprises participent au développement de la spécification OpenAPI.

Selon le GitHub du projet, la spécification OpenAPI définie un standard, une interface de description de langage de programmation agnostique pour les API REST, qui permet à la fois aux humains et aux machines de découvrir et comprendre les capacités offertes par un service sans avoir besoin d’accéder à son code, consulter une documentation supplémentaire, ou analyser le trafic réseau.

Lorsqu’une API a été correctement définie avec OpenAPI, les développeurs peuvent comprendre et interagir avec le service avec beaucoup moins d’effort d’implémentation.

Description d’un document OpenAPI

Lorsque vous utilisez la spécification OpenAPI pour designer votre API, vous pouvez entre autres : générer une documentation interactive, générer le code de la documentation, client et serveur. Cela est possible à travers de nombreux projets open source développés autour de la spécification OpenAPI.

Les langages supportés pour designer une API sont JSON et YAML. La spécification définit plusieurs étiquettes qui vont permettre entre autres de :
  • définir les informations générales sur vos API : description, termes d’utilisation, licence, contact, etc. ;
  • fournir la liste des services qui seront offerts, avec pour chacun, comment les appeler et la structure de la réponse qui est retournée ;
  • définir le chemin pour consommer votre API ;
  • etc.



NB : vous pouvez consulter le site suivant pour explorer les différentes étiquettes de la spécification OpenAPI:
http://openapi-specification-visual-...pihandyman.io/

Une fois que vous maitrisez le langage JSON ou YAML et que vous comprenez la structure d’un document OpenAPI, avec un simple éditeur de texte, vous êtes en mesure de designer votre API.

Ci-dessous la structure d’un document YAML OpenAPI :

Code YAML : Sélectionner tout
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
swagger: '2.0' 
info: 
  version: Version de votre API 
  title: Nom 
  description: Description de l'API 
  termsOfService: Termes d'utilisation 
  contact: 
    name: Nom personne de contact 
    url: Site Web  
    email: Adesse mail 
  license: 
    name: Nom de la licence 
    url: Site Web ou tenir les informations sur la licence  
basePath: "/" 
paths: 
  "Chemin de la l'API": 
    get: 
      tags: 
      - Nom du Tag 
      summary: Resumé de la méthode qui est exposée 
      description: Description de la méthode exposée 
      operationId: Nom de la méthode exposée 
      consumes: [] 
      produces: 
      - application/json 
      responses: 
        '200': 
          description: Description de la réponse 
          schema: 
            "$ref": "#/definitions/ExempleDefinition" 
definitions: 
  ExempleDefinition: 
    type: sttring

Exemple de document pour une API Customers

Supposons que je souhaite mettre en place une API Customers. Cette API dispose d’une méthode Get permettant de retourner un Customer à partir de son ID. Ci-dessous la définition d’un Customer :

Code C# : Sélectionner tout
1
2
3
4
5
6
7
public class Customer 
    { 
        public int Id { get; set; } 
        public string FirstName { get; set; } 
        public string LastName { get; set; } 
        public string EMail { get; set; } 
    }

Le document OpenAPI pour notre API au format JSON est le suivant

Code json : Sélectionner tout
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
{ 
    "swagger": "2.0", 
    "info": { 
        "version": "v2", 
        "title": "SwaggerDemo API", 
        "description": "Customers API to demo Swagger", 
        "termsOfService": "None", 
        "contact": { 
            "name": "Hinault Romaric", 
            "url": "http://rdonfack.developpez.com/", 
            "email": "hinault@monsite.com" 
        }, 
        "license": { 
            "name": "Apache 2.0", 
            "url": "http://www.apache.org" 
        } 
    }, 
    "basePath": "/", 
    "paths": { 
        "/api/Customers/{id}": { 
            "get": { 
                "tags": [ 
                    "Customers" 
                ], 
                "summary": "Retourne un client spécifique à partir de son id", 
                "description": "Je manque d'imagination", 
                "operationId": "ApiCustomersByIdGet", 
                "consumes": [], 
                "produces": [ 
                    "application/json" 
                ], 
                "parameters": [ 
                    { 
                        "name": "id", 
                        "in": "path", 
                        "description": "id du client à retourner", 
                        "required": true, 
                        "type": "integer", 
                        "format": "int32" 
                    } 
                ], 
                "responses": { 
                    "200": { 
                        "description": "client sélectionné", 
                        "schema": { 
                            "$ref": "#/definitions/Customer" 
                        } 
                    } 
                } 
            } 
        } 
    }, 
    "definitions": { 
        "Customer": { 
            "type": "object", 
            "properties": { 
                "id": { 
                    "format": "int32", 
                    "type": "integer" 
                }, 
                "firstName": { 
                    "type": "string" 
                }, 
                "lastName": { 
                    "type": "string" 
                }, 
                "eMail": { 
                    "type": "string" 
                } 
            } 
        } 
    } 
}

Et le document YAML est le suivant :

Code YAML : Sélectionner tout
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
swagger: '2.0' 
info: 
  version: v2 
  title: SwaggerDemo API 
  description: Customers API to demo Swagger 
  termsOfService: None 
  contact: 
    name: Hinault Romaric 
    url: http://rdonfack.developpez.com/ 
    email: hinault@monsite.com 
  license: 
    name: Apache 2.0 
    url: http://www.apache.org 
basePath: "/" 
paths: 
  "/api/Customers/{id}": 
    get: 
      tags: 
      - Customers 
      summary: Retourne un client spécifique à partir de son id 
      description: Je manque d'imagination 
      operationId: ApiCustomersByIdGet 
      consumes: [] 
      produces: 
      - application/json 
      parameters: 
      - name: id 
        in: path 
        description: id du client à retourner 
        required: true 
        type: integer 
        format: int32 
      responses: 
        '200': 
          description: client sélectionné 
          schema: 
            "$ref": "#/definitions/Customer" 
definitions: 
  Customer: 
    type: object 
    properties: 
      id: 
        format: int32 
        type: integer 
      firstName: 
        type: string 
      lastName: 
        type: string 
      eMail: 
        type: string

Comme vous pouvez le constater, le format YAML est beaucoup plus facilement éditable pour un humain.

Éditeur

Écrire un document OpenAPI en utilisant un simple éditeur peut être assez fastidieux. Toutefois, il existe un éditeur Swagger en ligne gratuit. Ce dernier offre de nombreuses fonctionnalités intéressantes, dont l’autocompletion.


Swagger Editor dispose d’un éditeur à gauche permettant de designer votre API et d’une fenêtre d’aperçu à droite. Cette fenêtre affiche une documentation interactive sur l’API en cours de conception.


Génération du code de l’API à partir du document OpenAPI

Comme je l’ai souligné plus haut, une fois votre API désignée avec OpenAPI, vous pouvez directement générer la documentation interactive, le code client et serveur. Swagger Editor offre des fonctionnalités de génération du code client et serveur pour un large panel de plateformes et langages de programmation :


En utilisant la fonctionnalité de génération de code serveur pour ASP.NET Core, une application ASP.NET Core Web API est générée avec une solution Visual Studio :


Comme vous pouvez le constater, à partir du document OpenAPI de mon API, le code de l’entité Customer a été automatiquement généré avec les propriétés nécessaires :

Code C# : Sélectionner tout
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
   [DataContract] 
    public partial class Customer :  IEquatable<Customer> 
    { 
  
        /// <summary> 
        /// Initializes a new instance of the <see cref="Customer" /> class. 
        /// </summary> 
        /// <param name="Id">Id.</param> 
        /// <param name="FirstName">FirstName.</param> 
        /// <param name="LastName">LastName.</param> 
        /// <param name="EMail">EMail.</param> 
        public Customer(int? Id = default(int?), string FirstName = default(string), string LastName = default(string), string EMail = default(string)) 
        { 
            this.Id = Id; 
            this.FirstName = FirstName; 
            this.LastName = LastName; 
            this.EMail = EMail; 
  
        } 
  
        /// <summary> 
        /// Gets or Sets Id 
        /// </summary> 
        [DataMember(Name="id")] 
        public int? Id { get; set; } 
        /// <summary> 
        /// Gets or Sets FirstName 
        /// </summary> 
        [DataMember(Name="firstName")] 
        public string FirstName { get; set; } 
        /// <summary> 
        /// Gets or Sets LastName 
        /// </summary> 
        [DataMember(Name="lastName")] 
        public string LastName { get; set; } 
        /// <summary> 
        /// Gets or Sets EMail 
        /// </summary> 
        [DataMember(Name="eMail")] 
        public string EMail { get; set; } 
  
        /// <summary> 
        /// Returns the string presentation of the object 
        /// </summary> 
        /// <returns>String presentation of the object</returns> 
        public override string ToString() 
        { 
            var sb = new StringBuilder(); 
            sb.Append("class Customer {\n"); 
            sb.Append("  Id: ").Append(Id).Append("\n"); 
            sb.Append("  FirstName: ").Append(FirstName).Append("\n"); 
            sb.Append("  LastName: ").Append(LastName).Append("\n"); 
            sb.Append("  EMail: ").Append(EMail).Append("\n"); 
            sb.Append("}\n"); 
            return sb.ToString(); 
        } 
  
        /// <summary> 
        /// Returns the JSON string presentation of the object 
        /// </summary> 
        /// <returns>JSON string presentation of the object</returns> 
        public string ToJson() 
        { 
            return JsonConvert.SerializeObject(this, Formatting.Indented); 
        } 
  
        /// <summary> 
        /// Returns true if objects are equal 
        /// </summary> 
        /// <param name="obj">Object to be compared</param> 
        /// <returns>Boolean</returns> 
        public override bool Equals(object obj) 
        { 
            if (ReferenceEquals(null, obj)) return false; 
            if (ReferenceEquals(this, obj)) return true; 
            if (obj.GetType() != GetType()) return false; 
            return Equals((Customer)obj); 
        } 
  
        /// <summary> 
        /// Returns true if Customer instances are equal 
        /// </summary> 
        /// <param name="other">Instance of Customer to be compared</param> 
        /// <returns>Boolean</returns> 
        public bool Equals(Customer other) 
        { 
  
            if (ReferenceEquals(null, other)) return false; 
            if (ReferenceEquals(this, other)) return true; 
  
            return  
                ( 
                    this.Id == other.Id || 
                    this.Id != null && 
                    this.Id.Equals(other.Id) 
                ) &&  
                ( 
                    this.FirstName == other.FirstName || 
                    this.FirstName != null && 
                    this.FirstName.Equals(other.FirstName) 
                ) &&  
                ( 
                    this.LastName == other.LastName || 
                    this.LastName != null && 
                    this.LastName.Equals(other.LastName) 
                ) &&  
                ( 
                    this.EMail == other.EMail || 
                    this.EMail != null && 
                    this.EMail.Equals(other.EMail) 
                ); 
        } 
  
        /// <summary> 
        /// Gets the hash code 
        /// </summary> 
        /// <returns>Hash code</returns> 
        public override int GetHashCode() 
        { 
            // credit: http://stackoverflow.com/a/263416/677735 
            unchecked // Overflow is fine, just wrap 
            { 
                int hash = 41; 
                // Suitable nullity checks etc, of course :) 
                    if (this.Id != null) 
                    hash = hash * 59 + this.Id.GetHashCode(); 
                    if (this.FirstName != null) 
                    hash = hash * 59 + this.FirstName.GetHashCode(); 
                    if (this.LastName != null) 
                    hash = hash * 59 + this.LastName.GetHashCode(); 
                    if (this.EMail != null) 
                    hash = hash * 59 + this.EMail.GetHashCode(); 
                return hash; 
            } 
        } 
  
        #region Operators 
  
        public static bool operator ==(Customer left, Customer right) 
        { 
            return Equals(left, right); 
        } 
  
        public static bool operator !=(Customer left, Customer right) 
        { 
            return !Equals(left, right); 
        } 
  
        #endregion Operators 
  
    }

Le code du contrôleur a été généré avec la méthode d’action Get, qui prend en paramètre l’Id. Je n’ai plus qu’à modifier cette méthode d’action pour retourner le client à partir de ma banque de données.

Code c# : Sélectionner tout
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
namespace IO.Swagger.Controllers 
{  
    /// <summary> 
    ///  
    /// </summary> 
    public class CustomersApiController : Controller 
    {  
  
        /// <summary> 
        /// Retourne un client specifique à partir de son id 
        /// </summary> 
        /// <remarks>Je manque d'imagination</remarks> 
        /// <param name="id">id du client a retourné</param> 
        /// <response code="200">client selectionné</response> 
        [HttpGet] 
        [Route("//api/Customers/{id}")] 
        [SwaggerOperation("ApiCustomersByIdGet")] 
        [SwaggerResponse(200, type: typeof(Customer))] 
        public virtual IActionResult ApiCustomersByIdGet([FromRoute]int? id) 
        {  
            string exampleJson = null; 
  
            var example = exampleJson != null 
            ? JsonConvert.DeserializeObject<Customer>(exampleJson) 
            : default(Customer); 
            return new ObjectResult(example); 
        } 
    } 
}

Par ailleurs, la classe Startup a été configurée pour utiliser le générateur Swagger et SwaggerUI. Ce qui permet d’offrir un point d’accès à votre documentation à partir de votre application. Et si vous modifiez l’application, le générateur Swagger va générer le document OpenAPI correspondant :

Code c# : Sélectionner tout
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
// This method gets called by the runtime. Use this method to add services to the container. 
        public void ConfigureServices(IServiceCollection services) 
        { 
            // Add framework services. 
            services.AddMvc() 
                .AddJsonOptions( 
                    opts => { opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); }); 
  
            services.AddSwaggerGen(); 
  
            services.ConfigureSwaggerGen(options => 
            { 
                options.SingleApiVersion(new Info 
                { 
                    Version = "v1", 
                    Title = "IO.Swagger", 
                    Description = "IO.Swagger (ASP.NET Core 1.0)" 
                }); 
  
                options.DescribeAllEnumsAsStrings(); 
  
                var comments = new XPathDocument($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml"); 
                options.OperationFilter<XmlCommentsOperationFilter>(comments); 
                options.ModelFilter<XmlCommentsModelFilter>(comments); 
            }); 
  
        } 
  
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 
        { 
            loggerFactory.AddConsole(Configuration.GetSection("Logging")); 
            loggerFactory.AddDebug(); 
  
            app.UseMvc(); 
  
            app.UseDefaultFiles(); 
            app.UseStaticFiles(); 
  
            app.UseSwagger(); 
            app.UseSwaggerUi(); 
        }

Pour l’instant, la génération de code utilise ASP.NET Core 1.0.x.

Lorsque vous voulez créer des API REST, il est assez simple d’utiliser OpenAPI pour designer votre API, ensuite générer le code correspondant. Ce qui offrira un canevas aux développeurs pour les différents services qui seront offerts par l’API. Ces derniers n’auront plus qu’à implémenter la logique métier des services.

Si vous avez déjà créé votre Web API, comme je l’ai présenté dans le tutoriel précédent, vous pouvez simplement configurer le framework Swagger pour générer la documentation à partir du code.


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :
Contacter le responsable de la rubrique Accueil