【Rust】rustでメールを送信する

use lettre:: {
    transport::smtp::authentication::Credentials, Message,
    message::{header, SinglePart},
    SmtpTransport, Transport, message::Mailbox, Address
};

#[tokio::main]
async fn main()  {
    
    let username = "hogehoge@gmail.com";
    let app_password = "**** **** **** ****";
    let smtp = "smtp.gmail.com";
    let email = Message::builder()
        .to(Mailbox::new(None, username.parse::<Address>().unwrap()))
        .from(Mailbox::new(None, username.parse::<Address>().unwrap()))
        .subject("題名: Rust Mail Test")
        .singlepart(
            SinglePart::builder()
                .header(header::ContentType::TEXT_PLAIN)
                .body(String::from("本文 Test"))
        )
        .unwrap();
    let credentials = Credentials::new(username.into(), app_password.into());
    let mailer = SmtpTransport::starttls_relay(smtp)
        .unwrap()
        .credentials(credentials)
        .build();
    let result = mailer.send(&email);
    println!("{:?}", result);
}

【Rust】カレンダーを作る【Algorithm】

fn leap_year(year: u32) -> bool {
    if year % 4 == 0 && year % 100 != 0 || year % 400 == 0 {
        return true;
    } 
    false
}

fn day_of_week(year: usize, month: usize) -> usize {
    let mut days_of_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

    if leap_year(year.try_into().unwrap()) {
        days_of_month[2] = 29;
    }

    let day = 1;
    let mut days = 0;

    for y in 1..year {
        if leap_year(y.try_into().unwrap()) {
            days += 366
        } else {
            days += 365
        }
    }

    for m in 1..month {
        days += days_of_month[m as usize]
    }

    days += day;
    return days % 7
}

fn main() {
    let days_of_week_name = ["日".to_string(), "月".to_string(),"火".to_string(),"水".to_string(),"木".to_string(),"金".to_string(),"土".to_string()];

    let mut days_of_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    let year = 2040;
    let month = 3;

    if leap_year(year.try_into().unwrap()) {
        days_of_month[2] = 29;
    }
    let first_day = day_of_week(year, month);

    println!("{}年 {}月", year, month);
    println!("日 月 火 水 木 金 土");
    let emp = " ".to_string();

    print!("{}", emp.repeat(first_day*3));
    for day in 1..(1 + days_of_month[month]) {
        print!("{:<2} ", day);
        if(day + first_day - 1) % 7 == 6 {
            print!("\n");
        }
    }
    print!("\n");
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/basic`
2040年 3月
日 月 火 水 木 金 土
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

これは中々面白い!!! そんなに難しくないけど、中々思いつかない!

【Rust】axumのlayer middlewareにstateを渡す方法

from_fn_with_state という書き方がある。
https://docs.rs/axum/latest/axum/middleware/fn.from_fn_with_state.html

    let app_state = model::connection_pool().await.unwrap();
    let app = Router::new()
        .route("/", get(handler))
        .layer(middleware::from_fn_with_state(app_state.clone(),auth_middleware))
        .layer(CsrfLayer::new(config))
        .with_state(app_state);
//

pub async fn auth_middleware(State(state): State<model::AppState>, req: Request,
    next: Next) -> impl IntoResponse {

    let res = next.run(req).await;
    res   
}

なるほどね、先回りしてよく作られてるw

【Rust】with_state(app_state)とCsrfConfigを共存させたい時

### ダメな例
with_stateを複数繋げるのは上手くいかない

 
    let config = CsrfConfig::default();
    let app = axum::Router::new()
        .merge(public_router)
        .merge(private_router)
        .with_state(config.clone())
        .with_state(app_state);
pub async fn handle_hoge(token: CsrfToken, State(state): State<model::AppState>)-> Response {

### 上手くいく例
csrfの方をstateではなく、layerで渡してあげる。

axum_csrf = {version = “0.11.0”,features = [“layer”]}

use axum_csrf::{CsrfToken, CsrfConfig, CsrfLayer};

    let config = CsrfConfig::default();

    let app = Router::new()
        .merge(public_router)
        .merge(private_router)
        .layer(CsrfLayer::new(config))
        .with_state(app_state);
pub async fn handle_hoge(token: CsrfToken, State(state): State<model::AppState>)-> Response {

なるほどね~

【Rust】Durationをmillisecondでcountする

.subsec_millis() でミリセカンドで取得できる。
.subsec_nanos() だとナノセカンドで値が大きすぎてしまう。

use std::{time};

    let now = time::Instant::now();
    let mut post_url = format!("http://www.jsontest.com/");
    let client = reqwest::Client::new();
    let resp = client.post(post_url)
        .header(reqwest::header::CONTENT_TYPE, "application/json")
        .json(&str)
        .send()
        .await
        .unwrap();
    println!("{}", now.elapsed().subsec_millis());

278

【Rust】anyhowを使ったエラーハンドリング

anyhow = “1.0.97”
https://docs.rs/anyhow/latest/anyhow/

anyhowを使うと、エラー処理が簡単にできるようになる。
anyhow::Errorはエラー型のラッパー、あらゆるエラー型からanyhow::Errorに変換できる。変換できる。
Resultにすると、?演算子で異なる複数の型のエラーを呼び出し元に移譲できる。
contextやwith_contextを使うと、Noneをanyhow::Errorとして移譲することも可能
anyhow::ResultがResultの型エイリアスとして定義されているので、use anyhow::Result;とすると、ResultをResultと書ける。

use anyhow::{ensure, Context, Result};
use std::fs;

#[tokio::main]
async fn main()  {

    let path_123 = "./data/temp/123.txt";
    println!("{}", with_unwrap(&path_123));
    
    // let path_abc = "./data/temp/abc.txt";
    // println!("{}", with_unwrap(&path_abc));

    let path_non_existent = "./data/to/non_existent.txt";
    println!("{}", with_unwrap(&path_non_existent));
}

fn with_unwrap(path: &str) -> i32 {
    let s = fs::read_to_string(path).unwrap();
    let i = s.parse::<i32>().unwrap();

    i * 10
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.76s
Running `target/debug/app`
1230
thread ‘main’ panicked at src/main.rs:16:30:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: “No such file or directory” }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
vagrant@vagrant:~/dev/rust/app$

エラーメッセージが、InvalidDigit、No such file or directoryでそれぞれ異なる。

#[tokio::main]
async fn main()  {

    let path_123 = "./data/temp/123.txt";
    println!("{}", with_unwrap(&path_123).unwrap());
    
    let path_abc = "./data/temp/abc.txt";
    println!("{:?}", with_unwrap(&path_abc).unwrap_err());

    let path_non_existent = "./data/to/non_existent.txt";
    println!("{:?}", with_unwrap(&path_non_existent).unwrap_err());
}

fn with_unwrap(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path)?;
    let i = s.parse::<i32>()?;

    Ok(i * 10)
}

1230
invalid digit found in string
No such file or directory (os error 2)

### anyhow::Contextでコンテキストを追加
with_contextは遅延評価でエラーの時のみ指定

fn with_unwrap(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
    let i = s.parse::<i32>()
            .with_context(|| format!("Failed to parse {s:?}"))?;

    Ok(i * 10)
}

1230
Failed to parse “abc”

Caused by:
invalid digit found in string
Failed to read “./data/to/non_existent.txt”

Caused by:
No such file or directory (os error 2)

### OptionをResultに変換
contextおよび with_contextはResultだけでなく、Optionからも呼べる。Noneも呼び出し元に移譲できる。

async fn main()  {

    let path_max = "data/temp/max.txt";
    fs::write(path_max, i32::MAX.to_string()).unwrap();
    println!("{:?}", with_anyhow_option(path_max).unwrap_err());

    let path_123 = "./data/temp/123.txt";
    println!("{}", with_unwrap(&path_123).unwrap());
    
    let path_abc = "./data/temp/abc.txt";
    println!("{:?}", with_unwrap(&path_abc).is_err());

    let path_non_existent = "./data/to/non_existent.txt";
    println!("{:?}", with_unwrap(&path_non_existent).is_err());
}

fn with_anyhow_option(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
    let i = s.parse::<i32>()
            .with_context(|| format!("Failed to parse {s:?}"))?;
    let i = i.checked_add(100).context("Overflow occurred")?;

    Ok(i)
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.86s
Running `target/debug/app`
Overflow occurred
1230
true
true

### マクロ(anyhow!, bail!, ensure!)で単発のエラーを返す
ensureで値に応じてエラーとすることもできる。

fn with_anyhow_macro(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
    let i = s.parse::<i32>()
            .with_context(|| format!("Failed to parse {s:?}"))?;
    let i = i.checked_add(100).context("Overflow occurred")?;

    ensure!(i >= 1000, "Value must be at least 1000, got {}", i);

    Ok(i)
}

match resultでの処理

    let path_123 = "./data/temp/123.txt";
    let result = with_anyhow_macro(&path_123);
    match result {
        Ok(n) => {
            println!("{}", n);
        }
        Err(error) => {
            println!("{}", error);
        }
    }

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.81s
Running `target/debug/app`
Value must be at least 1000, got 223

【JavaScript】ファイルの更新があれば、値を更新する

setTimeoutでループ処理ができる。
data.split(“\n”).lengthで、ファイルの行数を取得できる。

async function fetchData() {
  try {
    fetch('./data/names.txt')
    .then(response => response.text())
    .then(data => {
        console.log('テキストファイルの内容:');
        console.log(data.split("\n").length - 1);
        document.getElementById('target').textContent = data.split("\n").length - 1;
    })
    .catch(error => {
        console.error('エラー:', error);
    });
  } catch (error) {
    console.error('リクエストエラー:', error);
  }
  setTimeout(fetchData, 3000);
}
fetchData();

これだと、更新自体はできますが、データをpublicな場所には置きたくないですね…

【Rust】シャミアの秘密計算(SSS)をRustで動かしてみる

private keyを複数のシェアに分割して、それをrestoreして元のprivate keyに戻すということは割と簡単にできそうではある。

shamirsecretsharing = “0.1.5”

use shamirsecretsharing::*;

async fn main()  {

    let private_key = "i3HdSBYb5fmuD4RxAf77VnYbyAy6b8ab5LEddfQLM2VaYrUUFNNp6Pk22dsTAibj".to_string();
    let data = private_key.into_bytes();
    println!("{:?}", data);

    let count = 3;
    let treshold = 2;
    let mut shares = create_shares(&data, count, treshold).unwrap();
    
    for share in shares.clone() {
        print_base_encode(share);
    }

    let restored = combine_shares(&shares.clone()).unwrap();
    println!("{:?}", String::from_utf8(restored.unwrap()).unwrap());
}

fn print_base_encode(s: Vec<u8>) {
    // println!("{:?}", s.clone());
    let base64_encoded = base64::encode(&s);
    println!("base64 encoded: {}", base64_encoded);
    // println!("{:?}", BASE64_STANDARD.decode(base64_encoded).unwrap());
}

base64 encoded: AZv7eBLYhiBxgjpD3kI0YbLnnC88DnTWPghszvPWHLt4D+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=

base64 encoded: AhM+urNxZnzJsekDjygSpCqresvZz1Kg/zFE8/zdbLkWD+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=

base64 encoded: A2t9DSUWz0ihoFHKSQ7556tm0Zdzebl7SSZcEfkttU7FD+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=
“i3HdSBYb5fmuD4RxAf77VnYbyAy6b8ab5LEddfQLM2VaYrUUFNNp6Pk22dsTAibj”

【Python】シャミアの秘密計算

import random
from typing import List, Tuple

def generate_coefficients(secret: int, threshold: int) -> List[int]:
    coefficients = [secret]
    for _ in range(threshold - 1):
        coefficients.append(random.randint(1, 256))
    return coefficients

def create_shares(
    secret: int, total_shares: int, threshold: int
) -> List[Tuple[int, int]]: 
    coefficients = generate_coefficients(secret, threshold)
    shares = []
    for x in range(1, total_shares + 1):
        y = sum(coeff * (x**exp) for exp, coeff in enumerate(coefficients))
        shares.append((x, y))
    return shares

def reconstruct_secret(shares: List[Tuple[int, int]], threshold: int) -> int:
    def _lagrange_interpolation(x: int, x_s: List[int], y_s: List[int]) -> int:
        def _basis(j: int) -> int:
            num = 1
            den = 1
            for m in range(len(x_s)):
                if m != j:
                    num *= x - x_s[m]
                    den *= x_s[j] - x_s[m]
            return num // den

        result = 0
        for j in range(len(y_s)):
            result += y_s[j] * _basis(j)
        return result

    x_s, y_s = zip(*shares)
    return _lagrange_interpolation(0, x_s, y_s)

if __name__ == "__main__":
    secret = 2732
    total_shares = 5
    threshold = 2

    shares = create_shares(secret, total_shares, threshold)
    print("shares:", shares)

    selected_shares = shares[:threshold]
    print(zip(*selected_shares))
    x_s, y_s = zip(*selected_shares)
    print(x_s, y_s)
    recovered_secret = reconstruct_secret(selected_shares, threshold)
    print("Recovered Secret:", recovered_secret)

$ python3 test.py
shares: [(1, 2961), (2, 3190), (3, 3419), (4, 3648), (5, 3877)]

(1, 2) (2961, 3190)
Recovered Secret: 2732

【Rust】reqwestによるPostのレスポンスタイムの差

幾つかのサイトにデータをPostして、レスポンスタイムに差があるかを調査する。

    let now = time::Instant::now();
    let mut post_url = format!("http://www.jsontest.com/");
    let client = reqwest::Client::new();
    let resp = client.post(post_url)
        .header(reqwest::header::CONTENT_TYPE, "application/json")
        .json(&str)
        .send()
        .await
        .unwrap();
    println!("{:?}", resp);
    println!("{:?}", now.elapsed());

↓レスポンスタイムの差

同じサーバ内
67.997892ms
76.798063ms
73.945108ms

http://httpbin.org/post
626.023117ms
560.575466ms
1.050126063s

https://dummyjson.com/posts
695.071869ms
825.34323ms
676.196368ms

http://www.jsontest.com/
229.820077ms
256.854971ms
203.667686ms

当たり前だが、同じサーバ内のレスポンスは早い。
それ以外でも、サイトごとによって、レスポンスタイムが早いものと遅いものの違いが生じている。
なるほど、なかなか面白いね。