fetch_tours.rs in Rust
A real CLI: clap parses the flags, reqwest fetches, serde models the reply.
use std::error::Error;
use std::time::Duration;
use clap::Parser;
use serde::Deserialize;
/// Fetch the tour list for a language.
#[derive(Parser, Debug)]
#[command(name = "fetch-tours", version)]
struct Cli {
/// Language slug, such as rust or sql
lang: String,
/// Base URL of the server
#[arg(long, default_value = "https://typestar.io")]
base: String,
/// Only show tours with at least this many steps
#[arg(long, default_value_t = 0)]
min_steps: usize,
/// Print the raw JSON as well
#[arg(short, long)]
verbose: bool,
}
#[derive(Debug, Deserialize)]
struct Paths {
lang: String,
tours: Vec<Tour>,
}
#[derive(Debug, Deserialize)]
struct Tour {
slug: String,
title: String,
sections: Vec<Section>,
}
#[derive(Debug, Deserialize)]
struct Section {
title: String,
steps: Vec<Step>,
}
#[derive(Debug, Deserialize)]
struct Step {
idx: u32,
title: String,
}
impl Tour {
fn step_count(&self) -> usize {
self.sections.iter().map(|s| s.steps.len()).sum()
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(15))
.user_agent("fetch-tours/1.0")
.build()?;
let url = format!("{}/api/paths?lang={}", cli.base, cli.lang);
let response = client.get(&url).send().await?.error_for_status()?;
let body = response.text().await?;
if cli.verbose {
println!("{} bytes from {url}", body.len());
}
let paths: Paths = serde_json::from_str(&body)?;
println!("{} has {} tours", paths.lang, paths.tours.len());
for tour in paths.tours.iter().filter(|t| t.step_count() >= cli.min_steps) {
println!("{:<12} {:<32} {:>3} steps", tour.slug, tour.title,
tour.step_count());
if cli.verbose {
for section in &tour.sections {
println!(" {} ({})", section.title, section.steps.len());
}
}
}
let widest = paths.tours.iter().max_by_key(|t| t.step_count());
if let Some(tour) = widest {
println!("largest tour: {}", tour.title);
}
Ok(())
}
How it works
- The
Clistruct is the whole interface, help text included. - The models mirror the API response the tool consumes.
- Errors from three crates all funnel through
Box<dyn Error>.
Keywords and builtins used here
BoxCliOkPathsResultSectionSomeStepStringTourVecasyncawaitbooldynfnforifimplinletmainselfstep_countstructu32useusize
The run, in numbers
- Lines
- 93
- Characters to type
- 1932
- Tokens
- 486
- Three-star pace
- 115 tpm
At the three-star pace of 115 tokens a minute, this run takes about 254 seconds.
Step 1 of 1 in Encore, step 12 of 12 in CLI tools & HTTP clients.