Rand

  • rand - 随机数生成器

Serde

serde是一个处理数据结构的序列化和反序列化的框架,serde_json则是用来处理 JSON 的库

::: tip 序列化和反序列化 序列化是将 Rust 数据结构转换为其他格式,而反序列化则是将其他格式的数据转换回 Rust 数据结构 :::

可以快速地使用json!构建一个 JSON 对象

use serde_json::json;

fn main() {
  let p = json!({
    "name": "JQiue",
    "age": 24,
    "hobbies": ["sleep", "harmonica"]
  })
  println!("{}", p["hobbies"][0]);
}

使用from_str从一个字符串中解析成 JSON 对象

fn main() {
  let str = r#"{
      "name": "Ush2a",
      "age":25
  }"#;
  let v: Value = serde_json::from_str(str).unwrap();
}

::: tip 如果访问了不存在的属性咋办?

serde_json 对未类型化的 JSON 提供了一个递归枚举用来表示serde_json::Value

enum Value {
  Null,
  Bool(bool),
  Number(Number),
  String(String),
  Array(Vec<Value>),
  Object(Map<String, Value>),
}

:::

serde_json 也能将 JSON 映射成 Rust 数据结构,这需要提前定义结构体

use serde::{Deserialize, Serialize};
use serde_json;

#[derive(Serialize, Deserialize)]
struct Mobile {
  processor: String,
  memory: u8,
  // 指定默认值
  #[serde(default)]
  display: Display,
  storage: u32,
  battery: u32,
  price: u32,
}

#[derive(Debug, Serialize, Deserialize)]
struct Display {
  dpi: u32,
  hz: u32,
  technology: String,
}

// 实现默认值
impl Default for Display {
  fn default() -> Self {
    Display {
      dpi: 0,
      hz: 0,
      technology: "".to_string(),
    }
  }
}

fn main() {
  let mut mobiles: Vec<Mobile> = Vec::new();
  let mobiles_json_str = fs::read_to_string("mobiles.json").unwrap();
  mobiles = serde_json::from_str(&mobiles_json_str).unwrap();
}

属性

属性分为三类:

  • 容器属性 - 适用于结构体和枚举
  • 变体属性 - 适用于枚举变体
  • 字段属性 - 适用于结构体字段或者枚举变体的字段
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]  // <-- 这是一个容器属性
struct S {
  #[serde(default)]  // <-- 这是一个字段属性
  f: i32,
}

#[derive(Serialize, Deserialize)]
#[serde(rename = "e")]  // <-- 这也是一个容器属性
enum E {
  #[serde(rename = "a")]  // <-- 这是一个变体属性
  A(String),
}
  • #[serde(rename_all = "...")] - 重命名所有字段以符合给定的命名约定,比如:lowercaseUPPERCASEPascalCasecamelCasesnake_case
  • #[serde(rename = "name")] - 序列化和反序列化此字段时使用给定的名称而不是其 Rust 名称。这对于将字段序列化为驼峰式或序列化名称为 Rust 保留关键字的字段很有用
  • #[serde(alias = "name")] - 从给定名称反序列化此字段。可以重复指定同一字段的多个可能名称
  • #[serde(default)] - 如果反序列化时值不存在,则使用Default::default()
  • #[serde(default = "path")] - 如果反序列化时值不存在,则调用一个必须是该fn() -> T形式的函数
  • #[serde(skip)] - 跳过此字段,不要序列化和反序列化
  • #[serde(skip_serializing_if = "path")] - 调用一个函数以确定是否跳过序列化此字段。给定的函数必须为fn(&T) -> bool,比如skip_serializing_if = "Option::is_none"将跳过字段为None的序列化

#[serde(default)] 可以对一个字段设置默认值

#[derive(Deserialize, Debug)]
pub struct Config {
  #[serde(default = "default_workers")] // 调用 default_workers 函数设置默认值
  pub workers: usize,
  pub host: String,
  pub port: u16,
  pub database_url: String,
}

fn default_workers() -> usize {
  1
}

Rust-embed

rust-embed 用于在发布期间在编译时将文件加载到 Rust 二进制文件中,并在开发期间从 fs 加载文件

#[derive(RustEmbed)]
#[folder = "examples/public/"]
#[prefix = "prefix/"]
struct Asset;

fn main() {
  let index_html = Asset::get("prefix/index.html").unwrap();
  println!("{:?}", std::str::from_utf8(index_html.data.as_ref()));

  for file in Asset::iter() {
    println!("{}", file.as_ref());
  }
}

Lettre

Lettre 是一个功能强大的邮件库


Reqwest

比较知名的 HTTP 请求库

GET 请求

let body = reqwest::get("https://www.rust-lang.org")
    .await?
    .text()
    .await?;

POST 请求

use reqwest::Client;

let client = Client::new();

let res = client.post("http://httpbin.org/post")
  .body("the exact body that is sent")
  .send()
  .await?;

发送 JSON 必须开启jsonfeatures

let mut map = HashMap::new();
map.insert("username", "admin");
map.insert("password", "admin");
let data = client
  .post(url + "/user/login")
  .json(&map)
  .send()
  .await?
  .text()
  .await?;

到目前为止,都是基于异步的请求,reqwest 提供了堵塞的请求客户端,必须开启blockingfeatures

use reqwest::blocking;

let data = blocking::get(format!("{url}/ping"))?.text()?;
println!("{data}");

Jsonwebtoken

用于生成和验证 jwt 的库

[dependencies]
jsonwebtoken = "9.2.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0"
time = "0.3.34"
use jsonwebtoken::errors::{Error, ErrorKind};
use jsonwebtoken::{
    decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation,
};
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};

mod jwt_numeric_date {
    //! Custom serialization of OffsetDateTime to conform with the JWT spec (RFC 7519 section 2, "Numeric Date")
    use serde::{self, Deserialize, Deserializer, Serializer};
    use time::OffsetDateTime;

    /// Serializes an OffsetDateTime to a Unix timestamp (milliseconds since 1970/1/1T00:00:00T)
    pub fn serialize<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let timestamp = date.unix_timestamp();
        serializer.serialize_i64(timestamp)
    }

    /// Attempts to deserialize an i64 and use as a Unix timestamp
    pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
    where
        D: Deserializer<'de>,
    {
        OffsetDateTime::from_unix_timestamp(i64::deserialize(deserializer)?)
            .map_err(|_| serde::de::Error::custom("invalid Unix timestamp value"))
    }
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Data {
    user_id: String,
    is_plus: i8,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Claims {
    data: Data,
    #[serde(with = "jwt_numeric_date")]
    iat: OffsetDateTime,
    #[serde(with = "jwt_numeric_date")]
    exp: OffsetDateTime,
}

impl Claims {
    pub fn new(data: Data, iat: OffsetDateTime, exp: OffsetDateTime) -> Self {
        // normalize the timestamps by stripping of microseconds
        let iat = iat
            .date()
            .with_hms_milli(iat.hour(), iat.minute(), iat.second(), 0)
            .unwrap()
            .assume_utc();
        let exp = exp
            .date()
            .with_hms_milli(exp.hour(), exp.minute(), exp.second(), 0)
            .unwrap()
            .assume_utc();

        Self { data, iat, exp }
    }
}

// 签发
fn sign(payload: Claims, key: String) -> String {
    let header = Header::default();
    let key = EncodingKey::from_secret(key.as_ref());
    match encode(&header, &payload, &key) {
        Ok(token) => token,
        Err(error) => panic!("{error}"),
    }
}

// 验证
fn verify(token: String, key: String) -> Result<TokenData<Claims>, Error> {
    let key = DecodingKey::from_secret(key.as_ref());
    let validation = Validation::new(Algorithm::HS256);

    match decode::<Claims>(&token, &key, &validation) {
        Ok(c) => Ok(c),
        Err(err) => match *err.kind() {
            ErrorKind::InvalidToken => Err(Error::from(ErrorKind::InvalidToken)), // Example on how to handle a specific error
            ErrorKind::InvalidIssuer => Err(Error::from(ErrorKind::InvalidIssuer)), // Example on how to handle a specific error
            _ => Err(err),
        },
    }
}

fn main() {
    let key = "aurora";
    let iat = OffsetDateTime::now_utc();
    let exp = iat + Duration::days(7);
    let data = Data {
        user_id: "foo_id".to_string(),
        is_plus: 1,
    };
    let my_claims = Claims::new(data, iat, exp);

    let token = sign(my_claims, key.to_string());

    match verify(token, key.to_string()) {
        Ok(res) => {
            println!("{:?}", res.claims.data);
            println!("{:?}", res.header);
        }
        Err(_) => {
            println!("验证失败")
        }
    }
}

Chrono

Chrono 是时间库的首选,Chrono 提供了一个DateTime类型来表示时区中的日期和时间,必须依赖Timezone对象进行构造,该对象定义了本地日期如何转换为 UTC 日期以及反向转换,有三种实现:

  • Utc 指定的是 UTC 时区,这是最高效的
  • Local 指定系统本地时区
  • FixedOffset 指定任意固定时区
use chrono::Utc;

// 获取当前 UTC 时间
let now: chrono::DateTime<Utc> = Utc::now();
println!("Current time: {}", now);

// 解析字符串格式的时间
let date_time_str = "2023-04-25 12:34:56";
let date_time: DateTime<Utc> = DateTime::parse_from_str(date_time_str, "%Y-%m-%d %H:%M:%S")
    .unwrap()
    .with_timezone(&Utc);
println!("Parsed time: {}", date_time);

// 获取本地时区时间
let local: DateTime<Local> = Local::now();

// 格式化时间
let formatted = date_time.format("%Y-%m-%d %H:%M:%S").to_string();

let start = Utc::now();
// 执行某些操作
let end = Utc::now();
let duration = end - start;
println!("Operation took: {:?}", duration);

// 计算时间差
let date_time: DateTime<Utc> = DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc);

// 转换为时间戳
let timestamp: i64 = date_time.timestamp();

// 从时间戳创建
let date_time: DateTime<Utc> = DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc);

日期计算

let tomorrow = date_time.checked_add_days(1).unwrap();
let last_month = date_time.checked_sub_months(1).unwrap();

NaiveDateTime 是一个不带有时区的日期和时间组合

Regex

regex 是事实上的标准正则表达式库,非常快,但不支持更花哨的功能,如回溯

use regex::Regex;

// 基本匹配
let re = Regex::new(r"foo").unwrap();
assert!(re.is_match("foo bar"));

// 捕获分组
let re = Regex::new(r"(\w+) (\w+)").unwrap();
let captures = re.captures("hello world").unwrap();
assert_eq!(captures.get(1).unwrap().as_str(), "hello");
assert_eq!(captures.get(2).unwrap().as_str(), "world");

// 替换
let re = Regex::new(r"foo").unwrap();
let text = re.replace("foo bar foo", "baz");
assert_eq!(text, "baz bar baz");

// 迭代匹配
let re = Regex::new(r"\w+").unwrap();
for capture in re.captures_iter("foo bar baz") {
  println!("{}", capture.get(0).unwrap().as_str());
}

// 缓存

static REF_RE: Regex = Regex::new(r"foo").unwrap();

fn is_foo(text: &str) -> bool {
    REF_RE.is_match(text)
}

Anyhow

anyhow 主要解决以下问题:

  • 简化错误处理:anyhow 提供了一个通用的错误类型 anyhow::Error,它可以包装几乎任何类型的错误。这使得在函数返回值中使用Result<T, anyhow::Error>变得非常方便
  • 动态错误:anyhow::Error 是一个动态错误类型,可以在运行时存储任何实现了 std::error::Error trait 的错误
  • 错误上下文:anyhow 提供了 .context() 方法,允许为错误添加额外的上下文信息
  • 适合应用程序级别使用:anyhow 特别适合在应用程序的顶层使用,当可能需要处理多种不同类型的错误

Thiserror

thiserror 主要解决以下问题:

  • 自定义错误类型:thiserror 提供了一个派生宏,使得创建自定义错误类型变得简单
  • 静态错误:与 anyhow 不同,thiserror 创建的是静态类型的错误,这意味着它们在编译时就确定了类型
  • 错误消息模板:thiserror 允许你为每种错误类型定义自定义的错误消息模板
  • 适合库级别使用:thiserror 当需要定义明确的错误类型

Tonic

gRPC 的 Rust 实现,高性能,开源,为移动设备与 HTTP/2 准备的通用 RPC 框架

Cron

一个 cron 表达式解析器和调度资源管理器

let expression = "* 0 8 * * *";
let schedule = Schedule::from_str(expression).unwrap();
println!("Upcoming fire times:");
for datetime in schedule.upcoming(Utc).take(10) {
  println!("-> {}", datetime);
}

Instant_akismet

这是一个 akismet 的 client 实现

let akismet_client = AkismetClient::new(
  String::from("https://exampleblog.com"), // The URL for your blog
  "86f1r1r5ea50".to_owned(),               // Your Akismet API key
  reqwest::Client::new(),                  // Reqwest client to use for requests
  AkismetOptions::default(),               // AkismetOptions config
);
akismet_client.verify_key().await?;
let comment = Comment::new(&akismet_client.blog, "127.0.0.1")
  .comment_author("viagra-test-123")
  .comment_author_email("akismet-guaranteed-spam@example.com")
  .comment_content("test");
let is_spam = akismet_client.check_comment(comment).await?;
println!("{:#?}", is_spam);

Image

Rusty-tesseract

let img = Image::from_path("images/excuse_me.png").unwrap();
let default_args = Args::default();
let output = rusty_tesseract::image_to_string(&img, &default_args)
  .unwrap()
  .replace("", "");
println!("The String output is: {:?}", output);

console

用于构建控制台用户界面的库,它提供颜色、样式和终端操作

dialoguer

用于在命令行中提示用户的库,它支持各种提示类型,例如选择、输入和确认

indicatif

用于在命令行中显示进度条的库,它支持各种进度条样式和功能,例如消息和速度估计

let total_size = 10000;
let pb = ProgressBar::new(total_size);
pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
    .unwrap()
    .progress_chars("#>-"));
pb.set_position(current_position);
pb.finish_with_message("downloaded");

Clap

用于解析命令行参数的库,它允许你定义应用程序接受的参数,并自动生成帮助消息

Tower

Tower 是一个用于构建“服务”的库,这些服务是模块化和可重用的组件,在 Tower 中服务是一个核心概念,可以想象成一个处理任何请求并响应的组件

在 Tower 一个ServiceTrait的定义如下:

pub trait Service<Request> {
    // 这个类型表示服务处理请求时可能出现的错误。
    type Error;

    // 这个类型表示服务返回的响应。
    type Response;


    type Future: Future<Output = Result<Self::Response, Self::Error>>;

    fn poll_ready(
        &mut self,
        cx: &mut Context<'_>,
    ) -> Poll<Result<(), Self::Error>>;

    // 一个异步函数,它接受一个请求并返回一个 Future。
    fn call(&mut self, req: Request) -> Self::Future;

}

RustFS + S3

因为 RustFS 兼容 S3,所以可以直接使用 S3 SDK

aws-sdk-s3 = "1.108.0"
aws-config = { version = "1.1.7", features = ["behavior-version-latest"] }
pub struct Config {
    pub region: String,
    pub access_key_id: String,
    pub secret_access_key: String,
    pub endpoint_url: String,
}

impl Config {
    pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
        let region = "us-east-1".to_string();
        let access_key_id = "access_key".to_string();
        let secret_access_key = "secret".to_string();
        let endpoint_url = "http://endpoint_url".to_string();

        Ok(Config {
            region,
            access_key_id,
            secret_access_key,
            endpoint_url,
        })
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>>{
    let config = Config::from_env()?;
    let credentials = Credentials::new(
        config.access_key_id,
        config.secret_access_key,
        None,
        None,
        "rustfs",
    );
    let region = Region::new(config.region);
    let endpoint_url = config.endpoint_url;
    let shard_config = aws_config::defaults(BehaviorVersion::latest())
        .region(region)
        .credentials_provider(credentials)
        .endpoint_url(endpoint_url)
        .load()
        .await;
    let rustfs_client = Client::new(&shard_config);
    // 获取 buckets
    let buckets = rustfs_client.list_buckets().send().await?;
    // 获取所有的对象
    let objects = rustfs_client
        .list_objects_v2()
        .bucket("bucket")
        .send()
        .await?;
    // 获取指定对象
    let mut object = rustfs_client
        .get_object()
        .bucket("bucket")
        .key("object_key")
        .send()
        .await?;
    // 下载对象到本地
    let mut file = File::create("./path/file")?;
    while let Some(bytes) = object.body.try_next().await? {
        let bytes_len = bytes.len();
        file.write_all(&bytes)?;
    }
    Ok(())
}