Bye Bye Moore

PoCソルジャーな零細事業主が作業メモを残すブログ

練習用にアドレス帳みたいなのをつくる その2:ファイルに追加していく

機能を拡張して、ファイルに保存するようにします
知見を別の件で使うだろう事を考慮し、形式はJSONにしました

実際のところ

tomlファイル

[package]
name = "newproject"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4.5.2", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

スクリプト

use std::error::Error;
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
use std::fs::{OpenOptions, File};
use std::io::{Read, Write};

#[derive(Debug, Parser)] 
#[command(author, version, about)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
    Add {
        #[clap(value_parser)]
        name: String,
        #[clap(value_parser)]
        email: String,
        #[clap(value_parser)]
        age: u8,
    },
}

#[derive(Debug, Serialize, Deserialize)]
struct User {
    name: String,
    email: String,
    age: u8,
}

#[derive(Debug, Default, Serialize, Deserialize)]
struct UserDatabase {
    users: Vec<User>
}

impl UserDatabase {
    fn new() -> Self {
        UserDatabase { users: Vec::new() }
    }

    fn add_user(&mut self, user: User) -> Result<(), String> {
        // メールアドレスの重複チェック
        if self.users.iter().any(|u| u.email == user.email) {
            return Err("Email already exists".to_string());
        }
        self.users.push(user);
        Ok(())
    }

    fn save_to_file(&self) -> Result<(), Box<dyn Error>> {
        let json = serde_json::to_string_pretty(&self)?;
        let mut file = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open("users.json")?;
        file.write_all(json.as_bytes())?;
        Ok(())
    }

    fn load_from_file() -> Result<Self, Box<dyn Error>> {
        let mut file = match File::open("users.json") {
            Ok(file) => file,
            Err(_) => return Ok(UserDatabase::new()),
        };
        
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        
        let db: UserDatabase = serde_json::from_str(&contents)?;
        Ok(db)
    }
}

fn main() {
    let args = Cli::parse();
    // データベースの読み込み
    // ファイルが存在しない場合は新しいデータベースを作成(無名関数で処理を実装、errorの実体はe変数に格納)
    let mut db = UserDatabase::load_from_file().unwrap_or_else(|e| {
        println!("Failed to load database: {}", e);
        UserDatabase::new() // 新しいデータベースを作成
    });

    // コマンドの実行
    match args.command {    
        Commands::Add { name, email, age } => {
            let user = User {
                name: String::from(name),
                email: String::from(email),
                age,
            };

            match db.add_user(user) {
                Ok(_) => {
                    if let Err(e) = db.save_to_file() {
                        println!("Failed to save database: {}", e);
                    } else {
                        println!("User added and saved successfully");
                    }
                },
                Err(e) => println!("Failed to add user: {}", e),
            }

            // データベースの現在の状態を表示
            println!("Current database state: {:?}", db);
        }
    }   
}

実行結果

まずは普通に追加

$ cargo.exe run -- add "John Doe" "jonhdoe@example.jp" 38
     Running `target\debug\newproject.exe add "John Doe" jonhdoe@example.jp 38`
User added and saved successfully
Current database state: UserDatabase { users: [User { name: "John Doe", email: "jonhdoe@example.jp", age: 38 }] }

わざと同じ名前の人物を登録しても、メアドのバリデーションで弾かれる

$ cargo.exe run -- add "John Doe" "jonhdoe@example.jp" 38
     Running `target\debug\newproject.exe add "John Doe" jonhdoe@example.jp 38`
Failed to add user: Email already exists
Current database state: UserDatabase { users: [User { name: "John Doe", email: "jonhdoe@example.jp", age: 38 }] }

新しい人を追加

$ cargo.exe run -- add "Taro Tanaka" "taroT@example.jp" 38  
     Running `target\debug\newproject.exe add "Taro Tanaka" taroT@example.jp 38`
User added and saved successfully
Current database state: UserDatabase { users: [User { name: "John Doe", email: "jonhdoe@example.jp", age: 38 }, User { name: "Taro Tanaka", email: "taroT@example.jp", age: 38 }] }

以上の結果を、ターゲットのJSONファイルに書かれているか確認

$ cat .\users.json
{
  "users": [
    {
      "name": "John Doe",
      "email": "jonhdoe@example.jp",
      "age": 38
    },
    {
      "name": "Taro Tanaka",
      "email": "taroT@example.jp",
      "age": 38
    }
  ]
}

参考もと