Utilisation d'une crate externe

Stocker les todos en JSON avec serde

Ce programme, même si minime, est lancé. Mais on peut toujours l'améliorer un peu. Venant du monde de JavaScript, l'auteur à décidé qu'à la place d'un fichier texte brut, il voulais stocker les valeurs dans un fichier JSON.

Nous allons prendre cette opportunité pour voir comment installer et utiliser un paquet depuis la communauté open source de Rust en passant par crates.io

Installer serde

Pour installer un nouveau paquet comme serde dans notre projet, ouvrons le fichier Cargo.toml. Dans le bas, vous devriez voir un champ [dependencies]: Ajoutez simplement la ligne suivante au fichier:

[dependencies]
serde_json = "1.0.60"

Et c'est tout. La prochaine fois que cargo va compiler notre programme, il va aussi télécharger et inclure le nouveau paquet avec notre code.

Mettre à jour Todo::new

Le premier endroit où nous voulons utiliser serde est lorsque l'on cherche à lire le fichier base de donnée. Maintenant, à la place de lire un fichier ".txt", on veut lire un fichier JSON.

Dans le bloc impl, mettons à jour la fonction new:

// inside Todo impl block

fn new() -> Result<Todo, std::io::Error> {
    // open db.json
    let f = std::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .read(true)
        .open("db.json")?;
    // serialize json as HashMap
    match serde_json::from_reader(f) {
        Ok(map) => Ok(Todo { map }),
        Err(e) if e.is_eof() => Ok(Todo {
            map: HashMap::new(),
        }),
        Err(e) => panic!("An error occurred: {}", e),
    }
}

Les changements notables ici sont:

let f = std::fs::OpenOptions::new()

Plus de liaison mut f pour le fichier OpenOption, car nous n'avons plus besoin d'allouer manuellement le contenu dans une String comme auparavant. Serde s'en chargera pour nous.

open("db.json")?;

On mets à jour notre extension de fichier en tant que db.json.

match serde_json::from_reader(f) {

serde_json::from_reader désérialize le fichier pour nous.

Ok(map => Ok(Todo { map }))

Il intérfére avec le type de retour de map et va tenter de convertir notre JSON en un HashMap compatible. Si tout va bien on retourne notre struct Todo comme avant.

Err(e) if e.is_eof() => Ok(Todo {
    map: HashMap::new(),
}),

C'est un match guard qui nous permet d'affiner le comportement de l'expression match. Si serde retourne une erreur EOF (end of file) prématuré, cela signifie que le fichier est totalement vide (par exemple au premier lancement, ou si nous supprimons le fichier). Dans ce cas on récupère de cette erreur et on retourne un HashMap vide.

Err(e) => panic!("An error occurred: {}", e),

Pour toutes les autres erreurs, on quitte immédiatement le programme.

Mettre à jour Todo.save

L'autre emplacement où nous voulons utiliser serde est là où l'on sauvegarde notre map en tant que JSON. Pour faire cela, mettez à jour la méthode save dans le bloc impl pour devenir:

// inside Todo impl block
fn save(self) -> Result<(), Box<dyn std::error::Error>> {
    // open db.json
    let f = std::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .open("db.json")?;
    // write to file with serde
    serde_json::to_writer_pretty(f, &self.map)?;
    Ok(())
}

Comme auparavant, voyons ce qui change ici:

fn save(self) -> Result<(), Box<dyn std::error::Error>> {

Cette fois on renvoie une Box contenant une implémentation d'erreur générique de Rust. Pour faire simple, une box est un pointeur vers une allocation en mémoire. Puisque nous pouvons retourner soit une erreur du système lors de l'ouverture du fichier, soit une erreur serde lors de sa conversion, nous ne savons pas vraiment laquelle des deux notre fonction peut renvoyer. Par conséquent, nous renvoyons un pointeur vers l'erreur possible, au lieu de l'erreur elle-même, afin que l'appelant puisse les gérer.

let f = std::fs::OpenOptions::new()

Comme pour new, plus de liaison mut f pour le fichier OpenOption, car nous n'avons plus besoin d'allouer manuellement le contenu dans une String.

open("db.json")?;

On mets évidemment à jour le nom du fichier en db.json pour rester cohérent.

serde_json::to_writer_pretty(f, &self.map)?;

Enfin, nous laissons serde faire le gros du travail et écrire notre HashMap sous forme de fichier JSON (pretty-printed).

Note: N'oubliez pas de retirer use std::io::Read; et use std::str::FromStr; au début du fichier, nous n'en avons plus besoin.

Et voilà.

Maintenant, vous pouvez lancer votre programme et vérifier la sortie enregistrée dans le fichier. Si tout s'est bien passé, vous devriez voir vos tâches enregistrées au format JSON.