CodeBlanks Logo Icon CodeBlanks

JSON config reader

Build a CLI that reads a config file with serde.

Final task: config reader

The last task builds a typical flow from real applications: read the configuration from JSON text, override it with command-line arguments, and report errors with clear messages. The real-project counterpart is serde_json + clap + anyhow:

#[derive(Deserialize)]
struct Ayar { ad: String, port: u16, hata_ayikla: bool }

let mut ayar: Ayar = serde_json::from_str(&metin).context("ayar okunamadı")?;
if let Some(p) = cli.port { ayar.port = p; }

In the exercise you'll write the reader and the override logic with the standard library.

Reading and overriding

The reader converts each key by its type and returns errors with context; the override step applies only the given arguments:

fn oku(json: &str) -> Result<Ayar, String> {
    // parse key by key, for port:
    // deger.parse().map_err(|_| format!("port geçersiz: {deger}"))?
}

fn ez(ayar: &mut Ayar, args: &[&str]) -> Result<(), String> {
    if let Some(i) = args.iter().position(|a| *a == "--port") {
        let s = args.get(i + 1).ok_or("--port değeri eksik")?;
        ayar.port = s.parse().map_err(|_| format!("port geçersiz: {s}"))?;
    }
    Ok(())
}

Practice Config reader

Write the oku and ez functions for Ayar { ad: String, port: u16, hata_ayikla: bool }. Read the text {"ad":"blog","port":3000,"hata_ayikla":false}, override with ["--port", "8080"] and print the values ad port hata_ayikla. Then read a text containing "port":"abc" and print the error as hata: ....

Starter code

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
struct Ayar {
    ad: String,
    port: u16,
    hata_ayikla: bool,
}

fn oku(json: &str) -> Result<Ayar, String> {
    let mut ad = None;
    let mut port = None;
    let mut hata_ayikla = None;
    for cift in json.trim().trim_start_matches('{').trim_end_matches('}').split(',') {
        let (a, d) = cift.split_once(':').ok_or("geçersiz çift")?;
        let deger = d.trim().trim_matches('"');
        match a.trim().trim_matches('"') {
            "ad" => ad = Some(deger.to_string()),
            "port" => port = Some(deger.parse().map_err(|_| format!("port geçersiz: {deger}"))?),
            "hata_ayikla" => hata_ayikla = Some(deger == "true"),
            k => return Err(format!("bilinmeyen anahtar: {k}")),
        }
    }
    Ok(Ayar {
        ad: ad.ok_or("ad eksik")?,
        port: port.ok_or("port eksik")?,
        hata_ayikla: hata_ayikla.unwrap_or(false),
    })
}

fn ez(ayar: &mut Ayar, args: &[&str]) -> Result<(), String> {
    if let Some(i) = args.iter().position(|a| *a == "--port") {
        let s = args.get(i + 1).ok_or("--port değeri eksik")?;
        ayar.port = s.parse().map_err(|_| format!("port geçersiz: {s}"))?;
    }
    Ok(())
}

fn main() {
    let mut a = oku(r#"{"ad":"blog","port":3000,"hata_ayikla":false}"#).unwrap();
    ez(&mut a, &["--port", "8080"]).unwrap();
    println!("{} {} {}", a.ad, a.port, a.hata_ayikla);
    match oku(r#"{"ad":"blog","port":"abc"}"#) {
        Ok(_) => println!("tamam"),
        Err(e) => println!("hata: {}", e),
    }
}

Expected output

blog 8080 false
hata: port geçersiz: abc

Questions and answers

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

1 Why is it a common rule for the command-line value to override the file?

  • It's faster
  • It isn't required
  • Temporary changes don't require editing the file Correct answer
  • The file can't be read

Explanation: The usual priority: argument > environment variable > file > default.

2 Change the setting

Question
fn ez(ayar: ____, args: &[&str]) -> Result<(), String>
Answer
fn ez(ayar: &mut Ayar, args: &[&str]) -> Result<(), String>

Explanation: Changing the setting in place needs &mut.

3 Input and result

Term Meaning
port 3000 + --port 8080 port 8080
port 3000 (no argument) port 3000
"port":"abc" port geçersiz: abc
--port (no value) --port value missing

All lessons in Intermediate