Ora è arrivato il momento di creare le API REST con Rust. Il codice presentato il questa lezione completa quanto fatto nella precedente pertanto può essere realizzato come una sorta di sua evoluzione.
Recuperiamo il file mongo_database.rs
del codice mentre riscriveremo totalmente il main.rs
che qui servirà per avviare le API e completerà la parte di destinazione della migrazione.
Gestione delle dipendenze e avvio delle API REST
Per quanto riguarda le dipendenze, la relativa sezione del file Cargo.toml
è stata aggiornata nel seguente modo:
[dependencies]
warp = "0.3.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
mongodb = "3.2.3"
bson = "2.14"
Avvio delle API con Rust
Ecco il codice che avvia le API REST:
// importazione delle librerie
use warp::Filter;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
mod mongo_database;
use mongo_database::{Database, Libro};
#[derive(Debug)]
struct DatabaseError;
impl warp::reject::Reject for DatabaseError {}
// il codice è asincrono
#[tokio::main]
async fn main() {
// Connessione al database in locale. Verificare con propria installazione
let db = Database::connect("mongodb://localhost:27017", "biblioteca", "libri")
.await
.expect("Impossibile collegarsi al database! Errore...");
// componente di connessione al database MongoDB
let db = Arc::new(db);
// Definiamo la route POST /libri
let create_libro = warp::path("libri")
.and(warp::post())
.and(warp::body::json())
.and(with_db(db.clone()))
.and_then(crea_libro_handler);
println!("Server in ascolto all'indirizzo http://localhost:3030");
// attivazione del servizio in locale
warp::serve(create_libro)
.run(([127, 0, 0, 1], 3030))
.await;
}
// Gestione delle operazioni sui dati
async fn crea_libro_handler(libro: Libro, db: Arc<Database>) -> Result<impl warp::Reply, warp::Rejection> {
let libro_db = Libro {
id: None,
titolo: libro.titolo,
autore: libro.autore,
numero_pagine: libro.numero_pagine,
};
match db.crea_libro(libro_db).await {
Ok(id) => Ok(warp::reply::json(&format!("ID del nuovo documento: {}", id))),
Err(e) => {
eprintln!("ERRORE: {}", e);
Err(warp::reject::custom(DatabaseError))
}
}
}
// filtro per il collegamento tra database e API REST
fn with_db(db: Arc<Database>) -> impl Filter<Extract = (Arc<Database>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || db.clone())
}
Codice di definizione delle API
Si presti attenzione, per apprezzare Warp, a come sia configurato il codice di definizione delle API:
warp::path("libri")
.and(warp::post())
.and(warp::body::json())
.and(with_db(db.clone()))
.and_then(crea_libro_handler);
Viene indicato il tratto di percorso dell'URL e successivamente il metodo da usare (POST) per poi definire sia gli aspetti di conversione in JSON e, con le ultime due righe, vengono forniti gli strumenti per poter, rispettivamente, accedere al database e gestire la chiamata con il codice operativo.
L'utilizzo del filtro with_db
è una convenzione assai comune per fare in modo che una componente già pronta per l'interazione di un database venga "iniettata" nell'handler di gestione della chiamata ed essere pertanto disponibile per il suo utilizzo. Notiamo infatti che la funzione crea_libro_handler
se la trova già disponibile da poter sfruttare.
Test del codice Rust
Una volta avviato il server con un semplice comando cargo run
, avremo un servizio in attesa di ricevere dati da salvare nel database MongoDB. Per provarlo, si potranno eseguire operazioni con i client che si preferisce ma noi abbiamo usato il classico curl
, tool da riga di comando per l'interazione con servizi in HTTP. Questa la richiesta POST che abbiamo svolto:
curl -X POST http://localhost:3030/libri
-H "Content-Type: application/json"
-d '{"titolo":
"Progettare belle case",
"autore": "Andreina Rossi",
"numero_pagine": 1000}'
e questo il risultato che otteniamo a conferma del salvataggio dei dati:
ID del nuovo documento: 67fbe06db9eb08dd2d4bbc55
Procedendo ad una veloce prova con il client mongosh
, integrato in MongoDB, otteniamo la seguente conferma:
> db.libri.find()
[
{
_id: ObjectId('67fbe06db9eb08dd2d4bbc55'),
titolo: 'Progettare belle case',
autore: 'Andreina Rossi',
numero_pagine: 1000
}
]
La parte "server" della nostra sperimentazione è conclusa quindi. Possiamo pertanto dirigerci alla conclusione con il client MySQL/HTTP della prossima lezione su Rust.