Tutorial: Build a Medical Records System with Rust

To create a simple medical record system in Rust using the iced GUI library, you need to follow several steps. We will cover setting up the project, creating the UI with iced, using an embedded database like sled for data storage, and ensuring data security and encryption.

Step 1: Setting Up the Project

First, create a new Rust project:

cargo new medical_record_system
cd medical_record_system

Add the necessary dependencies in Cargo.toml:

[dependencies]
iced = "0.6"
iced_native = "0.6"
iced_wgpu = "0.6"
sled = "0.34"
argon2 = "0.2.0"
aes = "0.7.4"
rand = "0.8.5"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"

Step 2: Define the Data Structures

Create a new file src/data.rs to hold the data structures:

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PatientProfile {
    pub first_name: String,
    pub last_name: String,
    pub address: String,
    pub job: String,
    pub age: u8,
    pub sex: String,
    pub allergies: String,
    pub family_history: String,
    pub medical_history: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MedicalRecord {
    pub event: String,
    pub date: String,
    pub title: String,
    pub description: String,
    pub note: String,
}

Step 3: Implementing Encryption

Create a new file src/encryption.rs to handle encryption and decryption of data:

use aes::Aes256;
use aes::cipher::{
    KeyIvInit,
    StreamCipher,
    StreamCipherSeek,
};
use rand::Rng;
use argon2::{self, Config};

type Aes256Ctr = ctr::Ctr128BE<Aes256>;

const KEY_SIZE: usize = 32;
const IV_SIZE: usize = 16;

pub fn encrypt(data: &[u8], key: &[u8], iv: &[u8]) -> Vec<u8> {
    let mut cipher = Aes256Ctr::new_var(key, iv).unwrap();
    let mut buffer = data.to_vec();
    cipher.apply_keystream(&mut buffer);
    buffer
}

pub fn decrypt(data: &[u8], key: &[u8], iv: &[u8]) -> Vec<u8> {
    let mut cipher = Aes256Ctr::new_var(key, iv).unwrap();
    let mut buffer = data.to_vec();
    cipher.apply_keystream(&mut buffer);
    buffer
}

pub fn generate_key(password: &str, salt: &str) -> Vec<u8> {
    let config = Config::default();
    argon2::hash_raw(password.as_bytes(), salt.as_bytes(), &config).unwrap()
}

Step 4: Integrate Database

Create a new file src/database.rs to manage the embedded database:

use sled::{Db, IVec};
use serde_json;
use crate::data::{PatientProfile, MedicalRecord};
use crate::encryption::{encrypt, decrypt};

pub struct Database {
    db: Db,
    key: Vec<u8>,
    iv: Vec<u8>,
}

impl Database {
    pub fn new(path: &str, key: Vec<u8>, iv: Vec<u8>) -> Self {
        let db = sled::open(path).unwrap();
        Database { db, key, iv }
    }

    pub fn insert_patient(&self, id: &str, profile: PatientProfile) {
        let serialized = serde_json::to_vec(&profile).unwrap();
        let encrypted = encrypt(&serialized, &self.key, &self.iv);
        self.db.insert(id, encrypted).unwrap();
    }

    pub fn get_patient(&self, id: &str) -> Option<PatientProfile> {
        if let Ok(Some(encrypted)) = self.db.get(id) {
            let decrypted = decrypt(&encrypted, &self.key, &self.iv);
            let profile: PatientProfile = serde_json::from_slice(&decrypted).unwrap();
            Some(profile)
        } else {
            None
        }
    }

    pub fn insert_record(&self, patient_id: &str, record: MedicalRecord) {
        let key = format!("{}_record", patient_id);
        let serialized = serde_json::to_vec(&record).unwrap();
        let encrypted = encrypt(&serialized, &self.key, &self.iv);
        self.db.insert(key, encrypted).unwrap();
    }

    pub fn get_records(&self, patient_id: &str) -> Vec<MedicalRecord> {
        let prefix = format!("{}_record", patient_id);
        let mut records = Vec::new();

        for item in self.db.scan_prefix(prefix) {
            let (_, encrypted) = item.unwrap();
            let decrypted = decrypt(&encrypted, &self.key, &self.iv);
            let record: MedicalRecord = serde_json::from_slice(&decrypted).unwrap();
            records.push(record);
        }

        records
    }
}

Step 5: Creating the UI

Edit src/main.rs to create the GUI using iced and integrate the database functionality:

mod data;
mod database;
mod encryption;

use iced::{
    button, executor, Align, Application, Button, Column, Command, Container, Element, Length, Row, Settings, Text,
    TextInput, text_input,
};
use database::Database;
use encryption::{generate_key, encrypt, decrypt};

pub fn main() -> iced::Result {
    MedicalRecordApp::run(Settings::default())
}

#[derive(Default)]
struct MedicalRecordApp {
    username: String,
    password: String,
    key: Option<Vec<u8>>,
    iv: [u8; 16],
    login_button: button::State,
    username_input: text_input::State,
    password_input: text_input::State,
    message: String,
}

#[derive(Debug, Clone)]
enum Message {
    UsernameChanged(String),
    PasswordChanged(String),
    Login,
}

impl Application for MedicalRecordApp {
    type Executor = executor::Default;
    type Message = Message;
    type Flags = ();

    fn new(_flags: ()) -> (Self, Command<Message>) {
        (Self::default(), Command::none())
    }

    fn title(&self) -> String {
        String::from("Medical Record System")
    }

    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            Message::UsernameChanged(username) => {
                self.username = username;
            }
            Message::PasswordChanged(password) => {
                self.password = password;
            }
            Message::Login => {
                let salt = "some_random_salt";
                let key = generate_key(&self.password, salt);
                self.key = Some(key);
                rand::thread_rng().fill(&mut self.iv);

                self.message = format!("Logged in as {}", self.username);
            }
        }
        Command::none()
    }

    fn view(&mut self) -> Element<Message> {
        let username_input = TextInput::new(
            &mut self.username_input,
            "Username",
            &self.username,
            Message::UsernameChanged,
        )
        .padding(10);

        let password_input = TextInput::new(
            &mut self.password_input,
            "Password",
            &self.password,
            Message::PasswordChanged,
        )
        .padding(10)
        .password();

        let login_button = Button::new(&mut self.login_button, Text::new("Login"))
            .padding(10)
            .on_press(Message::Login);

        let content = Column::new()
            .padding(20)
            .align_items(Align::Center)
            .push(username_input)
            .push(password_input)
            .push(login_button)
            .push(Text::new(&self.message));

        Container::new(content)
            .width(Length::Fill)
            .height(Length::Fill)
            .center_x()
            .center_y()
            .into()
    }
}

Step 6: Compile and Run

Compile and run the application:

cargo run

This setup includes a simple login system using username and password, with data encryption for patient profiles and medical records. Adjustments can be made for additional functionality as needed.








Open-source Apps

9,500+

Medical Apps

500+

Lists

450+

Dev. Resources

900+

Read more