OpenAPI
Definirea schemei serviciilor.
Introducere
OpenAPI este o specificație care standardizează serviciile oferite de API-urile REST, ajută la înțelegerea modului în care sunt definite serviciile, cum ar fi adresa, inputurile și outputurile.
Permite generarea unei interfețe de testare și generarea automată de clienți pentru integrarea cu alte tehnologii.
Mai multe despre OpenAPI.
Activare și Configurare
Pentru a activa și configura OpenAPI în aplicația dvs. din Netuno, este necesar să editați fișierul de configurare al aplicației aferent mediului pe care îl utilizați, cum ar fi:
📂 config/_development.json📂 config/_production.json
Introduceți și ajustați următorii parametri:
...
"openapi": {
"host": "http://aplicatia-mea.local.netu.no:9000",
"basePath": "/services",
"schemes": [ "http" ],
"servers": [
{
"url": "http://aplicatia-mea.local.netu.no:9000/services",
"description": "Dezvoltare Locală"
}
]
},
...
Asigurați-vă că setările au sens; în exemplul de mai sus este pentru mediul de development.
În producție, este recomandat să utilizați
https.
_openapi.json în Servicii
În rădăcina serviciilor, configurați fișierul _openapi.json.
Calea completă din cadrul aplicației este:
📂 server/services/_openapi.json
Conținutul acestui fișier va fi ca în acest exemplu:
{
"info": {
"title": "Aplicația Mea",
"description": "API REST al Aplicației Mele, utilizați cu moderație.",
"version": "1.0"
},
"components": {
"securitySchemes": {
"BearerAuth": {
"type": "http",
"scheme": "bearer"
}
}
}
}
Ajustați titlul și descrierea la ceea ce are sens în cazul dvs.
Input
Prin configurarea fișierelor schema de input, Netuno poate realiza validarea parametrilor primiți comparându-i cu regulile JSON Schema.
Mai multe despre JSON Schema.
Dacă apare vreo eroare de validare, execuția serviciului este împiedicată și este afișată o eroare în log-ul Netuno, iar consumatorului serviciului i se returnează codul de stare HTTP 400 (Bad Request).
Pentru a configura regulile parametrilor de input, este necesar să creați un fișier cu extensia in.json lângă fișierul de cod al serviciului, cu același nume.
- Serviciul
detaliu.jsar trebui să aibă fișieruldetaliu.in.json. - Serviciul
detaliu.post.jsar trebui să aibă fișieruldetaliu.post.in.json. - Serviciul
detaliu/post.jsar trebui să aibă fișieruldetaliu/post.in.json.
Exemplu de conținut al in.json:
{
"tags":[
"servicii de produs"
],
"summary": "Detaliu Produs",
"description": "Sunt obținute toate câmpurile de detaliu ale produsului specificat.",
"type": "object",
"properties": {
"uid": {
"type": "uid"
}
},
"required": [
"uid"
]
}
Netuno acceptă câteva tipuri suplimentare care nu sunt acceptate de JSON Schema, cum ar fi string-not-empty, array-not-empty, id și uid (sau uuid, sau chiar guid).
Mai multe despre această specificație la JSON Schema Learn.
Output
Prin configurarea fișierelor schema de output, Netuno poate realiza validarea parametrilor trimiși comparându-i cu regulile JSON Schema.
Mai multe despre JSON Schema.
Dacă apare vreo eroare de validare, outputul este trimis totuși consumatorului serviciului, însă mesajele de eroare de validare sunt afișate în log-ul Netuno.
Pentru a configura regulile parametrilor de output, este necesar să creați un fișier cu extensia out.200.json lângă fișierul de cod al serviciului, cu același nume ca serviciul.
Unde 200 este codul de stare HTTP care apare în răspuns; pentru a accepta mai multe coduri de stare HTTP, este suficient să adăugați mai multe fișiere JSON cu numărul diferit.
Câteva exemple:
- Serviciul
salveaza.jsar trebui să aibă fișierulsalveaza.out.200.json. - Serviciul
salveaza.post.jsar trebui să aibă fișierulsalveaza.post.out.200.json. - Serviciul
salveaza/post.jsar trebui să aibă fișierulsalveaza/post.out.200.json.
Exemplu de conținut al out.200.json:
{
"type": "object",
"properties": {
"rezultat": {
"type": "boolean"
}
},
"required": [
"rezultat"
]
}
Netuno acceptă câteva tipuri suplimentare care nu sunt acceptate de JSON Schema, cum ar fi string-not-empty, array-not-empty, id și uid (sau uuid, sau chiar guid).
Mai multe despre această specificație la JSON Schema Learn.
Import de Scheme
Netuno acceptă importul de scheme, ceea ce permite reutilizarea unei părți din schemă în mai multe JSON-uri.
La importul schemelor se evită repetarea codului.
Creați folderul _schema în rădăcina folderului serviciilor, la:
📂 server/services/_schema
Acum, în configurația JSON, este posibil să importați scheme cu parametrul _schema în JSON, de exemplu:
{
"summary": "Ștergere Produse",
"description": "Permite eliminarea definitivă a produselor.",
"_schema": "get/doar-cod.in"
}
Aceasta înseamnă că va fi importat fișierul _schema/get/doar-cod.in.json, unde se află parametrul _schema.
Specificație și Teste
Pentru a genera specificația completă a tuturor serviciilor care acceptă OpenAPI cu JSON Schema, accesați de exemplu:
Va fi afișat un JSON cu întreaga specificație.
Pentru testare, copiați întregul JSON generat.
Deschideți Editorul Swagger: https://editor.swagger.io/
Ștergeți conținutul existent din partea stângă, lipiți JSON-ul și răspundeți afirmativ la întrebarea privind conversia din JSON în YAML.
Cu interfața generată în partea dreaptă, este posibil să înțelegeți mai bine toate serviciile disponibile și să efectuați teste.
De asemenea, permite generarea automată de clienți pentru alte tehnologii.
Programatic
În folderul _schema putem utiliza fișiere .js care vor fi executate odată cu generarea definiției OpenAPI, dar și în procesul de validare a inputului și outputului serviciilor.
Prin programarea schemei putem genera validări dinamice, de exemplu:
- Se acceptă doar un cod care există în baza de date.
- Cu
UIDse obține înregistrarea din baza de date și se partajează cu serviciile. - Dacă este
POSTsauPUT, se generează un tip de schemă diferit de cel generat în cazulDELETE.
Exemplu cu Listă de Valori
Imaginați-vă că avem un serviciu care primește codul unei anumite categorii.
Nu putem primi orice valoare ca și cod, deoarece poate fi o valoare care nu corespunde unui cod valid de categorie.
Astfel, putem restricționa codurile acceptate doar la cele înregistrate în baza de date.
Se creează schema în: server/services/_schema/categorie/coduri.js
const dbCategorii = _db.query("SELECT cod FROM categorie")
const listaCoduri = _val.list()
for (const dbCategorie of dbCategorii) {
listaCoduri.add(dbCategorie.getString("cod"))
}
_dataSchema
.set('type', 'string')
.set(
'enum',
listaCoduri
)
În
listaCodurivor fi conținute toate codurile de categorii înregistrate în baza de date.
În serviciu putem avea următorul JSON care utilizează schema categorie/coduri:
{
"summary": "Listează Produsele din Categorie",
"description": "Obține toate produsele asociate unei anumite categorii.",
"type": "object",
"properties": {
"categorie": {
"type": "object",
"properties": {
"cod": {
"_schema": "categorie/coduri"
}
},
"required": [
"cod"
]
}
},
"required": [
"categorie"
]
}
Astfel reușim să garantăm că serviciul se execută doar dacă codul este valid.
Exemplu cu Obiect Global
Imaginați-vă că avem mai multe servicii care primesc UID-ul clientului și, în fiecare serviciu, trebuie să accesăm baza de date pentru a obține datele clientului asociate cu UID-ul său.
Putem centraliza acest lucru într-o schemă programată care, prin UID-ul clientului primit, obține datele clientului din baza de date și le stochează într-un obiect global accesibil din codul serviciilor.
Astfel, datele clientului sunt întotdeauna încărcate global și automat atunci când primim un UID de client.
Creăm o schemă programată în: server/services/_schema/client.js
const client = _req.getValues('client', _val.map())
if (!_service.isGeneratingOpenAPIDefinition()) {
const dbClient = _db.get('client', client.getString("uid"))
if (dbClient == null) {
_header.status(404)
_out.json(
_val.map()
.set('error', true)
.set('code', 'client-negasit')
)
_service.cancel()
}
_val.global().set('client', dbClient)
}
_dataSchema.set('type', 'object')
.set(
'properties',
_val.map()
.set(
'uid',
_val.map()
.set('type', 'uid')
)
)
.set(
'required',
_val.list()
.add('uid')
)
Cu
_service.isGeneratingOpenAPIDefinition()putem verifica dacă este generarea definiției OpenAPI; altfel, este o execuție a unei cereri HTTP.Dacă
UID-ul clientului nu este găsit, atunci se returnează 404 și se întrerupe execuția serviciului.Observați că utilizăm
_val.global(), care permite partajarea datelor global între toate fișierele de cod executate în procesarea cererii HTTP.
În continuarea exemplului, avem un serviciu care listează cumpărăturile efectuate de client.
Avem validarea de input pentru serviciu în: server/services/client/cumparaturi/post.in.json
{
"summary": "Listă de Cumpărături",
"description": "Listează cumpărăturile efectuate de client.",
"type": "object",
"properties": {
"client": {
"_schema": "client"
}
},
"required": [
"client"
]
}
Observați că valoarea
"_schema"a clientului este numele fișierului de cod pe care l-am programat anterior în schemă.
Iar în codul serviciului cumpărăturilor clienților în: server/services/client/cumparaturi/post.js
const dbClient = _val.global().getValues('client')
...
Prin _val.global() obținem datele clientului din baza de date care au fost încărcate automat de codul schemei.
Exemplu cu Metodă HTTP
Putem returna o schemă diferită pentru fiecare tip de metodă HTTP.
De exemplu, putem genera o schemă prin cod pentru operațiile de creare, editare sau eliminare a porturilor de rețea:
- Dacă este POST, realizează operația de creare, deci nu conține UID, dar solicită numele și numărul portului.
- Dacă este PUT, realizează operația de editare, deci trebuie să solicite UID-ul, numele și numărul portului de rețea.
- Când este PUT sau DELETE, are nevoie de UID pentru a realiza operațiile respective de editare și eliminare.
const proprietati = _val.map()
const obligatorii = _val.list()
if (_dataSchema.isMethod('PUT') || _dataSchema.isMethod('DELETE')) {
proprietati.set(
'uid',
_val.map()
.set('type', 'uid')
)
obligatorii.add('uid')
}
if (!_dataSchema.isMethod('DELETE')) {
proprietati
.set(
'nume',
_val.map()
.set('type', 'string-not-empty')
.set('pattern', '^[a-zA-Z0-9-_\\.]{1,16}$')
)
.set(
'port',
_val.map()
.set('type', 'integer')
.set('minimum', portMinim)
.set('maximum', portMaxim)
)
obligatorii
.add('nume')
.add('port')
}
_dataSchema.set('type', 'object')
.set(
'properties',
proprietati
)
.set(
'required',
obligatorii
)
Videoclipuri
Urmează câteva videoclipuri exemplificative despre cum să dezvoltați API-ul integrat cu OpenAPI:
Concluzie
Cu suportul OpenAPI putem crea reguli de validare folosind JSON Schema, ceea ce oferă următoarele avantaje:
- Se asigură o securitate mai mare a datelor injectate în servicii.
- Alertă în
logdacă outputul serviciului nu este conform specificației. - Generează cu ușurință o interfață de navigare în servicii și de testare.