Article
· Avr 2 22m de lecture

L'API REST avec Swagger dans InterSystems IRIS

L'API REST avec Swagger dans InterSystems IRIS

Salut

Le protocole HTTP permet de récupérer des ressources, telles que des documents HTML. Il est à la base de tout échange de données sur le Web et constitue un protocole client-serveur, ce qui signifie que les requêtes sont initiées par le destinataire, généralement un navigateur Web.

Les API REST tirent parti de ce protocole pour échanger des messages entre le client et le serveur. Cela rend les API REST rapides, légères et flexibles. Les API REST utilisent les verbes HTTP GET, POST, PUT, DELETE et d'autres pour indiquer les actions qu'elles veulent effectuer.

Lorsque nous appelons une API REST, il s'agit en réalité d'un appel HTTP. L'API reçoit cet appel et, conformément au verbe et au chemin demandés, elle effectue l'action souhaitée. Dans le cas de l'implémentation d'Iris, nous pouvons le voir clairement dans la zone de définition de l'URLMap:

XData UrlMap
{
<Routes>
        <Route Url="/cliente" Method="POST" Call="Incluir"  Cors="true"/>
        <Route Url="/cliente/:chave" Method="PUT" Call="Alterar"  Cors="true"/>
        <Route Url="/cliente/:chave" Method="DELETE" Call="Deletar"  Cors="true"/>
        <Route Url="/cliente/:chave" Method="GET" Call="Pesquisar"  Cors="true"/>
        <Route Url="/cliente" Method="GET" Call="Listar"  Cors="true"/>
        <Route Url="/openapi" Method="GET" Call="OpenAPI" Cors="true"/>
        <Route Url="/test" Method="GET" Call="Test" Cors="true"/>
    </Routes>
}

Notez que nous avons le chemin (Url) et le verbe (Method) définis pour chaque appel (Call). Ainsi, le code qui rencontre l'API sait ce qu'il doit faire.

Cette structure de l'API REST ne sert pas seulement à acheminer les actions qui arrivent à l'API.

Elle sert également de base à la création du fichier Swagger de l'API, comme on peut le voir dans la documentation de la classe %REST.API, méthode GetWebRESTApplication: https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic....

Examinons maintenant comment générer cette documentation.

Commençons par publier notre API. Utilisons la même API que dans l'article https://community.intersystems.com/post/using-rest-api-flask-and-iam-intersystems-iris-part-1-rest-api

Il suffit de suivre les instructions et d'utiliser le code que l'article fournit pour avoir notre API publiée.

Une fois l'API publiée, nous inclurons un nouvel chemin dans URLMap et une nouvelle méthode dans notre code:

<Route Url="/openapi" Method="GET" Call="OpenAPI" Cors="true"/>

ClassMethod OpenAPI() As %Status
{
              Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }
   
    Write swagger.%ToJSON()

    Quit $$$OK
}

Incluez le nouvel chemin et la méthode qui lui est associée dans votre code API. Passons maintenant au test. Ouvrons Postman et appelons l'endpoint de génération de Swagger, qui est /openapi:

 

Ci-dessous, nous avons la définition complète du Swagger généré par notre appel:

{

"info": {

"title": "",

"description": "",

"version": "",

"x-ISC_Namespace": "DEMO"

},

"basePath": "/rest/servico",

"paths": {

"/customer": {

"post": {

"parameters": [

{

"name": "payloadBody",

"in": "body",

"description": "Request body contents",

 "required": false,

"schema": {

"type": "string"

}

}

],

"operationId": "Include",

"x-ISC_ServiceMethod": "Include",

 "x-ISC_CORS": true,

"responses": {

"default": {

"description": "(Unexpected Error)"

},

"200": {

"description": "(Expected Result)"

}

}

},

"get": {

"operationId": "List",

"x-ISC_ServiceMethod": "List",

 "x-ISC_CORS": true,

"responses": {

"default": {

"description": "(Unexpected Error)"

},

"200": {

"description": "(Expected Result)"

}

}

}

},

"/customer/{key}": {

"put": {

"parameters": [

{

"name": "key",

"in": "path",

 "required": true,

"type": "string"

},

{

"name": "payloadBody",

"in": "body",

"description": "Request body contents",

 "required": false,

"schema": {

"type": "string"

}

}

],

"operationId": "Change",

"x-ISC_ServiceMethod": "Change",

 "x-ISC_CORS": true,

"responses": {

"default": {

"description": "(Unexpected Error)"

},

"200": {

"description": "(Expected Result)"

}

}

},

"delete": {

"parameters": [

{

"name": "key",

"in": "path",

 "required": true,

"type": "string"

}

],

"operationId": "Delete",

"x-ISC_ServiceMethod": "Delete",

 "x-ISC_CORS": true,

"responses": {

"default": {

"description": "(Unexpected Error)"

},

"200": {

"description": "(Expected Result)"

}

}

},

"get": {

"parameters": [

{

"name": "key",

"in": "path",

 "required": true,

"type": "string"

}

],

"operationId": "Search",

"x-ISC_ServiceMethod": "Search",

 "x-ISC_CORS": true,

"responses": {

"default": {

"description": "(Unexpected Error)"

},

"200": {

"description": "(Expected Result)"

}

}

}

},

"/openapi": {

"get": {

"operationId": "OpenAPI",

"x-ISC_ServiceMethod": "OpenAPI",

 "x-ISC_CORS": true,

"responses": {

"default": {

"description": "(Unexpected Error)"

},

"200": {

"description": "(Expected Result)"

}

}

}

},

"/test": {

"get": {

"operationId": "Test",

"x-ISC_ServiceMethod": "Test",

 "x-ISC_CORS": true,

"responses": {

"default": {

"description": "(Unexpected Error)"

},

"200": {

"description": "(Expected Result)"

}

}

}

}

},

"Swagger": "2.0"

}

 

Le lien suivant nous permet d'accéder à la documentation d'Iris qui pointe vers un outil qui reçoit le résultat de notre appel API et le transforme en une interface conviviale pour la documentation et le test du service: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

Exécutons cette URL et fournissons le chemin pour récupérer la définition Swagger de notre API: https://swagger.io/tools/swagger-ui/

Remplaçons l'appel de démonstration sur la page (l'appel petstore) par notre appel: http://127.0.0.1/iris_iss/rest/servico/openapi et voyons l'écran avec la documentation Swagger générée:

En haut de l'appel, nous avons les informations de base de notre API:

Nous pouvons également naviguer dans les appels et consulter des informations importantes, comme dans l'appel POST pour inclure un nouvel utilisateur:

Mais, comme nous l'avons vu un peu plus haut, la définition Swagger est en réalité un fichier au format JSON  qui nous est disponible sous la forme d'un %DynamicObject (voir la documentation dans https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic....) après le ##class(%REST.API).GetWebRESTApplication() que nous effectuons dans notre méthode OpenAPI. De cette façon, en nous basant sur la définition de l'OpenAPI 2.0, nous pouvons inclure ou supprimer des renseignements de notre Swagger. Enrichissons, par exemple, les renseignements de base de l'API. Selon la définition de la version 2.0 (https://swagger.io/specification/v2/ ), les informations suivantes peuvent être mises à disposition:

 

Complétons alors les informations que nous pouvons rendre disponibles. Pour ce faire, nous allons modifier notre méthode OpenAPI, en incluant les informations de base, le protocole accepté, l'authentification et les données d'accès (hôte et basePath):

ClassMethod OpenAPI() As %Status
{
     Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }
    
    Set info = {
        "title": "Cliente",
        "description": "API de Manipulação de Cliente",
        "version": "1.00"
    }
    Do swagger.%Set("info", info)
    
    Do swagger.%Set("host", "127.0.0.1")
    Do swagger.%Set("basePath", "/iris_iss/rest/servico")

    Set schemes = []
    Do schemes.%Push("http")
    Do swagger.%Set("schemes", schemes)
             
    Set security = [{"basicAuth":[]}]
    Do swagger.%Set("security", security)

    Set securityDefinitions = {}
    Do securityDefinitions.%Set("basicAuth", {})
    Do securityDefinitions.basicAuth.%Set("type", "basic")
    Do swagger.%Set("securityDefinitions", securityDefinitions)
   
    Write swagger.%ToJSON()

    Quit $$$OK
}


Si nous appelons à nouveau la documentation Swagger sur la page d'aperçu, nous avons maintenant le résultat suivant:

 

Veillez à ce que notre documentation soit beaucoup plus complète, avec des informations plus détaillées sur l'API, telles que le mécanisme d'authentification (Basic Auth), le protocole accepté (HTTP) et les définitions de version, de description et d'URL.

Nous pouvons désormais enrichir la définition des appels en mettant la structure attendue dans les payloads et les exemples de données pour l'appel. Pour ce faire, superposons les informations dans les chemins pour "/client":

ClassMethod OpenAPI() As %Status
{
    Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }
    
    Set info = {
        "title": "Cliente",
        "description": "API de Manipulação de Cliente",
        "version": "1.00"
    }
    Do swagger.%Set("info", info)
    
    Do swagger.%Set("host", "127.0.0.1")
    Do swagger.%Set("basePath", "/iris_iss/rest/servico")

    Set schemes = []
    Do schemes.%Push("http")
    Do swagger.%Set("schemes", schemes)
             
    Set security = [{"basicAuth":[]}]
    Do swagger.%Set("security", security)

    Set securityDefinitions = {}
    Do securityDefinitions.%Set("basicAuth", {})
    Do securityDefinitions.basicAuth.%Set("type", "basic")
    Do swagger.%Set("securityDefinitions", securityDefinitions)
   
    Set incluirPesquisar = {
        "post": {
            "summary": "Criar um novo cliente",
            "description": "Recebe os dados de um cliente e cadastra no sistema.",
            "parameters": [
                {
                    "name": "body",
                    "in": "body",
                    "required": true,
                    "schema": {
                        "type": "object",
                        "properties": {
                            "nome": {
                                "type": "string",
                                "description": "Nome do cliente"
                            },
                            "idade": {
                                "type": "integer",
                                "description": "Idade do cliente"
                            }
                        },                              
                        "required": ["nome", "idade"],
                                           "example": {
                                                          "nome": "João Silva",
                                                          "idade": 30
                                           }
                    }
                }
            ],
            "responses": {
                          "default": {
                    "description": "Falha na chamada da API"
                },
                "200": {
                    "description": "Cliente criado com sucesso"
                },
                "406": {
                    "description": "Falha na inclusão do cliente"
                }                
            }
                            },
              "get": {
                    "summary": "Recupera todos os clientes",
                    "description": "Retorna a lista de clientes com suas informações.",
                    "responses": {
                                  "default": {
                    "description": "Falha na chamada da API"
                },
                      "200": {
                        "description": "Lista de clientes",
                        "schema": {
                          "type": "object",
                          "properties": {
                            "clientes": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "chave": {
                                    "type": "string",
                                    "description": "Chave única do cliente"
                                  },
                                  "nome": {
                                    "type": "string",
                                    "description": "Nome do cliente"
                                  },
                                  "idade": {
                                    "type": "integer",
                                    "description": "Idade do cliente"
                                  }
                                },
                                "required": ["chave", "nome", "idade"]
                              }
                            }
                          },
                          "example": {
                            "clientes": [
                              {
                                "chave": "1",
                                "nome": "Maria",
                                "idade": 35
                              },
                              {
                                "chave": "2",
                                "nome": "Julio",
                                "idade": 57
                              },
                              {
                                "chave": "4",
                                "nome": "Julia",
                                "idade": 25
                              },
                              {
                                "chave": "5",
                                "nome": "Julia",
                                "idade": 22
                              }
                            ]
                          }
                        }
                      }
                    }      
              }            
      }
    Do swagger.paths.%Set("/cliente", incluirPesquisar)
   
    Write swagger.%ToJSON()

    Quit $$$OK
}

En rappelant la page de documentation, nous pouvons maintenant voir les méthodes POST  pour inclure un client, et GET pour récupérer une liste:

Notez que nous avons déjà une explication de chacune des méthodes disponibles pour POST et GET qui ont été remplacées.

Notez que nous n'avons plus non plus les balises "x-ISC_ServiceMethod" and "x-ISC_Namespace" qui n'ont pas été incluses dans notre nouvelle définition.

En cliquant pour développer la méthode POST par exemple, nous disposons maintenant visuellement d'un exemple d'appel (Valeur d'exemple):

Et le format du payload de l'appel (Modèle):

Dans le cas de la récupération pour les utilisateurs, nous avons la définition de la réponse renvoyée avec un exemple:

Ainsi qu'avec le modèle prévu comme réponse:

 

Nous pouvons également tester les appels API à partir de la page de documentation elle-même. Pour cela, le bouton Try it out” (Essayez-le)  doit être disponible sur la page. Appuyez-le , puis appuyez sur Execute et vous verrez le résultat:

 

Les possibilités de l'approche Code-first  d'une API REST ouvrent de nombreuses possibilités de documentation, nous permettant de contrôler ce qui sera diffusé, en ajoutant ou en supprimant les informations que nous jugeons utiles dans le matériel publié.

La définition de l'OpenAPI est très complète et détaillée. Il existe une variété d'informations que nous pouvons inclure ou retirer, selon nos besoins ou ce que nous voulons y rendre disponible.

À très bientôt!

Discussion (0)0
Connectez-vous ou inscrivez-vous pour continuer