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.