Open9

Rust 初心者が CLI 作る時のメモ

shikazukishikazuki

Reqwest

https://crates.io/crates/reqwest
以下のコードを実行を実行するには cargo add tokio -F full が必要

use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let resp = reqwest::get("https://httpbin.org/ip")
        .await?
        .json::<HashMap<String, String>>()
        .await?;
    println!("{resp:#?}");
    Ok(())
}

https://docs.rs/tokio/latest/tokio/index.html
tokio は以下の説明を確認
https://zenn.dev/woden/articles/9a6c3b26b89e0a

Proxy 設定

https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html
Proxy の basic 認証も可能

let proxy = reqwest::Proxy::https("http://localhost:1234")?
    .basic_auth("Aladdin", "open sesame");

ファイルダウンロード

https://zenn.dev/hpp/articles/76483c0d6237be

cookie_store を使う

fn create_proxy(c: &Config) -> reqwest::Result<Proxy> {
    let proxy = Proxy::https(&c.proxy_url)?.basic_auth(&c.proxy_user, &c.proxy_pass);
    Ok(proxy)
}

pub(crate) fn build_client(c: &Config) -> reqwest::Result<reqwest::Client> {
    let cookie_store = Arc::new(Jar::default());
    let proxy = create_proxy(c)?;
    let client = reqwest::Client::builder()
        .proxy(proxy)
        .cookie_store(true)
        .cookie_provider(Arc::clone(&cookie_store))
        .build()?;
    Ok(client)
shikazukishikazuki

serde

https://crates.io/crates/serde
derive を使いたいので追加しておく

cargo add serde -F derive

serialize と deserialize が簡単にできる
https://github.com/serde-rs/serde

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 1, y: 2 };

    // Convert the Point to a JSON string.
    let serialized = serde_json::to_string(&point).unwrap();

    // Prints serialized = {"x":1,"y":2}
    println!("serialized = {}", serialized);

    // Convert the JSON string back to a Point.
    let deserialized: Point = serde_json::from_str(&serialized).unwrap();

    // Prints deserialized = Point { x: 1, y: 2 }
    println!("deserialized = {:?}", deserialized);
}

rename や skip

https://serde.rs/attr-skip-serializing.html

#[derive(Serialize)]
struct Resource {
    // Always serialized.
    name: String,

    // Never serialized.
    #[serde(skip_serializing)]
    hash: String,

    // Use a method to decide whether the field should be skipped.
    #[serde(skip_serializing_if = "Map::is_empty")]
    metadata: Map<String, String>,

    #[serde(rename="camelCase")]
    camel_case: String,
}
shikazukishikazuki

seahorse

https://crates.io/crates/seahorse

コマンド や サブコマンド等を簡単に作れる

use seahorse::{App};
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let app = App::new(env!("CARGO_PKG_NAME"))
        .description(env!("CARGO_PKG_DESCRIPTION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli [args]")
        .action(|c| println!("Hello, {:?}", c.args));

    app.run(args);
}
shikazukishikazuki

dialoguer

https://docs.rs/dialoguer/latest/dialoguer/index.html

Input

一問一答できる

use dialoguer::Input;

fn main() {
    let name: String = Input::new()
        .with_prompt("Your name?")
        .interact_text()
        .unwrap();

    println!("Your name is: {}", name);
}

MultiSelect

あらかじめ items を用意しておいて、複数選択可能

use dialoguer::MultiSelect;

fn main() {
    let items = vec!["foo", "bar", "baz"];

    let selection = MultiSelect::new()
        .with_prompt("What do you choose?")
        .items(&items)
        .interact()
        .unwrap();

    println!("You chose:");

    for i in selection {
        println!("{}", items[i]);
    }
}

FuzzySelect

曖昧検索できる。ただし、選べるのは一つ

use dialoguer::FuzzySelect;

fn main() {
    let items = vec!["foo", "bar", "baz"];

    let selection = FuzzySelect::new()
        .with_prompt("What do you choose?")
        .items(&items)
        .interact()
        .unwrap();

    println!("You chose: {}", items[selection]);
}
shikazukishikazuki

thiserror

https://crates.io/crates/thiserror
外部 crate のエラーとかどうしようと思って利用

use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("data store disconnected")]
    Disconnect(#[from] io::Error),
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
    #[error("invalid header (expected {expected:?}, found {found:?})")]
    InvalidHeader {
        expected: String,
        found: String,
    },
    #[error("unknown data store error")]
    Unknown,
}

以下のようにすることが可能

  #[error("xx error")]
  LibraryError(#[from] xx::Error)