Skip to content

Update lib.rs #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 58 additions & 34 deletions src/icp_rust_boilerplate_backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#[macro_use]
extern crate serde;

use candid::{Decode, Encode};
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{BoundedStorable, Cell, DefaultMemoryImpl, StableBTreeMap, Storable};
use std::{borrow::Cow, cell::RefCell};

// Memory management types
type Memory = VirtualMemory<DefaultMemoryImpl>;
type IdCell = Cell<u64, Memory>;

// Struct representing a book in the library system
#[derive(candid::CandidType, Clone, Serialize, Deserialize, Default)]
struct Book {
id: u64,
Expand All @@ -18,6 +21,7 @@ struct Book {
borrower: Option<String>,
}

// Enum representing the genre of a book
#[derive(candid::CandidType, Clone, Serialize, Deserialize, Default, PartialEq)]
enum Genre {
#[default]
Expand All @@ -27,74 +31,89 @@ enum Genre {
Technology,
}

// Implement `Storable` for Book for stable storage encoding/decoding
impl Storable for Book {
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
fn to_bytes(&self) -> Cow<[u8]> {
Cow::Owned(Encode!(self).unwrap())
}

fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
fn from_bytes(bytes: Cow<[u8]>) -> Self {
Decode!(bytes.as_ref(), Self).unwrap()
}
}

// Implement `BoundedStorable` for size and fixed size definition
impl BoundedStorable for Book {
const MAX_SIZE: u32 = 1024;
const MAX_SIZE: u32 = 1024; // Maximum allowed size for a book
const IS_FIXED_SIZE: bool = false;
}

// Thread-local stable storage and ID counter
thread_local! {
static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> = RefCell::new(
MemoryManager::init(DefaultMemoryImpl::default())
);
);

static ID_COUNTER: RefCell<IdCell> = RefCell::new(
IdCell::init(MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(0))), 0)
.expect("Cannot create a counter")
);

static BOOK_STORAGE: RefCell<StableBTreeMap<u64, Book, Memory>> =
RefCell::new(StableBTreeMap::init(
MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(1)))
));
.expect("Cannot create a counter")
);

static BOOK_STORAGE: RefCell<StableBTreeMap<u64, Book, Memory>> = RefCell::new(
StableBTreeMap::init(
MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(1)))
)
);
}

// Payload for creating a new book
#[derive(candid::CandidType, Serialize, Deserialize)]
struct BookPayload {
title: String,
author: String,
genre: Genre,
}

// Query: Retrieve a book by ID
#[ic_cdk::query]
fn get_book(id: u64) -> Result<Book, Error> {
match _get_book(&id) {
Some(book) => Ok(book),
None => Err(Error::NotFound {
msg: format!("Book with id={} not found", id),
msg: format!("Book with ID={} not found", id),
}),
}
}

// Query: Retrieve all available books
#[ic_cdk::query]
fn get_available_books() -> Vec<Book> {
BOOK_STORAGE.with(|service| {
service
.borrow()
.iter()
.filter(|(_, book)| book.is_available)
.map(|(_, book)| book.clone())
.collect()
BOOK_STORAGE.with(|storage| {
storage
.borrow()
.iter()
.filter(|(_, book)| book.is_available)
.map(|(_, book)| book.clone())
.collect()
})
}

// Update: Add a new book to the library
#[ic_cdk::update]
fn add_book(payload: BookPayload) -> Result<Book, Error> {
// Validate input
if payload.title.trim().is_empty() || payload.author.trim().is_empty() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saran: Validasi input bisa lebih komprehensif

Pertimbangkan untuk menambahkan validasi batas atas untuk panjang judul dan penulis untuk memastikan mereka tidak melebihi batas yang wajar atau menyebabkan Buku melebihi MAX_SIZE.

Implementasi yang disarankan:

// Konstanta untuk validasi input
const MAX_TITLE_LENGTH: usize = 200;  // Panjang judul maksimum yang wajar
const MAX_AUTHOR_LENGTH: usize = 100;  // Panjang penulis maksimum yang wajar

// Pembaruan: Tambahkan buku baru ke perpustakaan
    // Validasi input
    let title = payload.title.trim();
    let author = payload.author.trim();

    if title.is_empty() || author.is_empty() {
        return Err(Error::InvalidOperation {
            msg: "Judul dan penulis tidak boleh kosong.".to_string(),
        });
    }

    if title.len() > MAX_TITLE_LENGTH {
        return Err(Error::InvalidOperation {
            msg: format!("Panjang judul melebihi maksimum {} karakter", MAX_TITLE_LENGTH),
        });
    }

    if author.len() > MAX_AUTHOR_LENGTH {
        return Err(Error::InvalidOperation {
            msg: format!("Panjang penulis melebihi maksimum {} karakter", MAX_AUTHOR_LENGTH),
        });
    }
Original comment in English

suggestion: Input validation could be more comprehensive

Consider adding upper bounds validation for title and author lengths to ensure they don't exceed reasonable limits or cause the Book to exceed MAX_SIZE.

Suggested implementation:

// Constants for input validation
const MAX_TITLE_LENGTH: usize = 200;  // Maximum reasonable title length
const MAX_AUTHOR_LENGTH: usize = 100;  // Maximum reasonable author length

// Update: Add a new book to the library
    // Validate input
    let title = payload.title.trim();
    let author = payload.author.trim();

    if title.is_empty() || author.is_empty() {
        return Err(Error::InvalidOperation {
            msg: "Title and author cannot be empty.".to_string(),
        });
    }

    if title.len() > MAX_TITLE_LENGTH {
        return Err(Error::InvalidOperation {
            msg: format!("Title length exceeds maximum of {} characters", MAX_TITLE_LENGTH),
        });
    }

    if author.len() > MAX_AUTHOR_LENGTH {
        return Err(Error::InvalidOperation {
            msg: format!("Author length exceeds maximum of {} characters", MAX_AUTHOR_LENGTH),
        });
    }

return Err(Error::InvalidOperation {
msg: "Title and author cannot be empty.".to_string(),
});
}

let id = ID_COUNTER
.with(|counter| {
let current_value = *counter.borrow().get();
counter.borrow_mut().set(current_value + 1)
})
.expect("cannot increment id counter");
.with(|counter| {
let current_value = *counter.borrow().get();
counter.borrow_mut().set(current_value + 1)
})
.expect("Cannot increment ID counter");

let book = Book {
id,
Expand All @@ -109,75 +128,80 @@ fn add_book(payload: BookPayload) -> Result<Book, Error> {
Ok(book)
}

// Update: Borrow a book by ID
#[ic_cdk::update]
fn borrow_book(book_id: u64) -> Result<Book, Error> {
match _get_book(&book_id) {
Some(mut book) => {
if !book.is_available {
return Err(Error::InvalidOperation {
msg: "Book is not available for borrowing".to_string(),
msg: "Book is not available for borrowing.".to_string(),
});
}

book.is_available = false;
book.borrower = Some(ic_cdk::caller().to_string());

do_insert_book(&book);
Ok(book)
}
None => Err(Error::NotFound {
msg: format!("Book with id={} not found", book_id),
msg: format!("Book with ID={} not found.", book_id),
}),
}
}

// Update: Return a borrowed book
#[ic_cdk::update]
fn return_book(book_id: u64) -> Result<Book, Error> {
match _get_book(&book_id) {
Some(mut book) => {
if book.is_available {
return Err(Error::InvalidOperation {
msg: "Book is already available".to_string(),
msg: "Book is already available.".to_string(),
});
}

book.is_available = true;
book.borrower = None;

do_insert_book(&book);
Ok(book)
}
None => Err(Error::NotFound {
msg: format!("Book with id={} not found", book_id),
msg: format!("Book with ID={} not found.", book_id),
}),
}
}

// Update: Delete a book by ID
#[ic_cdk::update]
fn delete_book(id: u64) -> Result<(), Error> {
match _get_book(&id) {
Some(_) => {
BOOK_STORAGE.with(|service| service.borrow_mut().remove(&id));
BOOK_STORAGE.with(|storage| storage.borrow_mut().remove(&id));
Ok(())
}
None => Err(Error::NotFound {
msg: format!("Book with id={} not found", id),
msg: format!("Book with ID={} not found.", id),
}),
}
}

// Helper: Retrieve a book by ID
fn _get_book(id: &u64) -> Option<Book> {
BOOK_STORAGE.with(|service| service.borrow().get(id))
BOOK_STORAGE.with(|storage| storage.borrow().get(id))
}

// Helper: Insert or update a book in storage
fn do_insert_book(book: &Book) {
BOOK_STORAGE.with(|service| service.borrow_mut().insert(book.id, book.clone()));
BOOK_STORAGE.with(|storage| storage.borrow_mut().insert(book.id, book.clone()));
}

// Error handling enum
#[derive(candid::CandidType, Deserialize, Serialize)]
enum Error {
NotFound { msg: String },
InvalidOperation { msg: String },
}

// Export candid interface
ic_cdk::export_candid!();