CodeBlanks Logo Icon CodeBlanks

Error handling

Handle API errors with anyhow.

Error handling

For the user to fix a broken record, they must know where and why the error happened. The context that anyhow::Context would add in a real project, we build here with an error enum:

#[derive(Debug)]
enum KayitHatasi {
    AlanSayisi { no: usize, adet: usize },
    Id { no: usize, deger: String },
}

impl fmt::Display for KayitHatasi {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            KayitHatasi::AlanSayisi { no, adet } => write!(f, "kayıt {no}: 5 alan beklendi, {adet} geldi"),
            KayitHatasi::Id { no, deger } => write!(f, "kayıt {no}: geçersiz id: {deger}"),
        }
    }
}

A reader that returns errors

This time, instead of skipping a broken record, we stop at the first error; collecting into Result<Vec<_>, _> with collect does this in one line:

fn oku(veri: &str) -> Result<Vec<Yazi>, KayitHatasi> {
    veri.lines()
        .enumerate()
        .map(|(i, s)| kayit_coz(i + 1, s))
        .collect()
}

Practice Report a broken record

Write KayitHatasi (with Display), kayit_coz(no, s) and oku above. Print N yazı for two valid records and the error for x|a|b|c|true.

Starter code

main.rs
#[derive(Debug, Clone)]
struct Yazi {
    id: u32,
    konu: String,
    yazar: String,
    etiketler: Vec<String>,
    yayinda: bool,
}

fn main() {
    // buraya yaz
}

Sample solution

main.rs
use std::fmt;

#[derive(Debug, Clone)]
struct Yazi {
    id: u32,
    konu: String,
    yazar: String,
    etiketler: Vec<String>,
    yayinda: bool,
}

#[derive(Debug)]
enum KayitHatasi {
    AlanSayisi { no: usize, adet: usize },
    Id { no: usize, deger: String },
}

impl fmt::Display for KayitHatasi {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            KayitHatasi::AlanSayisi { no, adet } => write!(f, "kayıt {no}: 5 alan beklendi, {adet} geldi"),
            KayitHatasi::Id { no, deger } => write!(f, "kayıt {no}: geçersiz id: {deger}"),
        }
    }
}

fn kayit_coz(no: usize, s: &str) -> Result<Yazi, KayitHatasi> {
    let alanlar: Vec<&str> = s.split('|').collect();
    let [id, konu, yazar, etiket, yayinda] = alanlar[..] else {
        return Err(KayitHatasi::AlanSayisi { no, adet: alanlar.len() });
    };
    Ok(Yazi {
        id: id.parse().map_err(|_| KayitHatasi::Id { no, deger: id.to_string() })?,
        konu: konu.to_string(),
        yazar: yazar.to_string(),
        etiketler: etiket.split(',').map(String::from).collect(),
        yayinda: yayinda == "true",
    })
}

fn oku(veri: &str) -> Result<Vec<Yazi>, KayitHatasi> {
    veri.lines().enumerate().map(|(i, s)| kayit_coz(i + 1, s)).collect()
}

fn main() {
    match oku("1|Rust ile başlangıç|ece|rust|true\n2|Sahiplik|can|rust|false") {
        Ok(v) => println!("{} yazı", v.len()),
        Err(e) => println!("{}", e),
    }
    match oku("x|a|b|c|true") {
        Ok(v) => println!("{} yazı", v.len()),
        Err(e) => println!("{}", e),
    }
}

Expected output

2 yazı
kayıt 1: geçersiz id: x

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Where is the best place to add the record number to the error?

  • In the Display implementation
  • Nowhere
  • In the read loop iterating lines with enumerate Correct answer
  • Inside main

Explanation: Only the loop knows the number, so the number is passed as a parameter to the function creating the error.

2 Numbered iteration

Question
veri.lines()
    .____()
    .map(|(i, s)| kayit_coz(i + 1, s))
    .collect()
Answer
veri.lines()
    .enumerate()
    .map(|(i, s)| kayit_coz(i + 1, s))
    .collect()

Explanation: enumerate adds each line's position; used in the error.

3 Record and error

Term Meaning
x|a|b|c|true invalid id: x
1|a|b 5 fields expected, 3 given
(valid) Ok
First error Reading stops

All lessons in Intermediate