From 03f09c933ed9b9a48e8eec9aa4a4039833760677 Mon Sep 17 00:00:00 2001 From: Vaishnav Sabari Girish Date: Fri, 13 Mar 2026 13:12:58 +0530 Subject: [PATCH] feat(add ns_error_t): Custom error type Signed-off-by: Vaishnav Sabari Girish --- Cargo.lock | 63 ++++++++++++++++++++ Cargo.toml | 3 +- README.md | 1 + crates/ns_error/Cargo.toml | 11 ++++ crates/ns_error/src/lib.rs | 53 +++++++++++++++++ crates/ns_io/Cargo.toml | 1 + crates/ns_io/src/input.rs | 114 +++++++++++++++++++++++-------------- examples/06_errors.c | 43 ++++++++++++++ include/ns_error.h | 40 +++++++++++++ include/ns_read.h | 10 ++-- 10 files changed, 290 insertions(+), 49 deletions(-) create mode 100644 crates/ns_error/Cargo.toml create mode 100644 crates/ns_error/src/lib.rs create mode 100644 examples/06_errors.c create mode 100644 include/ns_error.h diff --git a/Cargo.lock b/Cargo.lock index 5eb3fd4..e975866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,13 +2,76 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ns_error" +version = "0.1.7" +dependencies = [ + "thiserror", +] + [[package]] name = "ns_io" version = "0.1.7" dependencies = [ + "ns_error", "ns_string", ] [[package]] name = "ns_string" version = "0.1.7" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml index 2f7b812..3bcbe43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ resolver = "2" members = [ "crates/ns_io", - "crates/ns_string" + "crates/ns_string", + "crates/ns_error" # "crates/ns_data" ] diff --git a/README.md b/README.md index c1aeb33..e44a65f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ compiling the C examples, and linking everything together. - [x] Separate `print` and `println` functions - [x] Cargo Workspace Architecture - [x] User Input via `ns_read()` +- [x] Custom Error types (`ns_error_t`) - [ ] Printing Variables + Strings (Interpolation/Formatting) ## Quick Start diff --git a/crates/ns_error/Cargo.toml b/crates/ns_error/Cargo.toml new file mode 100644 index 0000000..51832f7 --- /dev/null +++ b/crates/ns_error/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ns_error" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] +thiserror = "2.0.18" + +[lib] +crate-type = ["lib", "staticlib", "cdylib"] diff --git a/crates/ns_error/src/lib.rs b/crates/ns_error/src/lib.rs new file mode 100644 index 0000000..0b13405 --- /dev/null +++ b/crates/ns_error/src/lib.rs @@ -0,0 +1,53 @@ +use std::os::raw::c_char; +use thiserror::Error; + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] +pub enum NsError { + #[error("Success")] + Success = 0, + + // Catch-all (Similar to python's "except Exception") + #[error("Unknown Error")] + Any = 1, + + // ns_io errors + #[error("I/O Error: Failed to read input")] + IoReadFailed = 10, + + #[error("I/O Error: Failed to write input")] + IoWriteFailed = 11, + + // Invalid input + #[error("I/O Error: Invalid Input format")] + InvalidInput = 12, + + //ns_string errors + #[error("String Error: Memory Allocation failed")] + StringAllocFailed = 20, + + #[error("String Error: Invalid UTF-8 sequence detected")] + StringInvalidUtf8 = 21, + + #[error("Error: Index out of bounds")] + IndexOutOfBounds = 22, +} + +// Helper functions +// This is added to help C easily print exactly what went wrong + +#[unsafe(no_mangle)] +pub extern "C" fn ns_error_message(err: NsError) -> *const c_char { + let msg = match err { + NsError::Success => c"Success", + NsError::Any => c"Unknown Error", + NsError::IoReadFailed => c"I/O Error: Failed to read input", + NsError::IoWriteFailed => c"I/O Error: Failed to write input", + NsError::InvalidInput => c"I/O Error: Invalid Input format", + NsError::StringAllocFailed => c"String Error: Memory allocation failed", + NsError::StringInvalidUtf8 => c"String Error: Invalid UTF-8 sequence detected", + NsError::IndexOutOfBounds => c"Error: Index Out of Bounds", + }; + + msg.as_ptr() +} diff --git a/crates/ns_io/Cargo.toml b/crates/ns_io/Cargo.toml index a743a43..47eaaca 100644 --- a/crates/ns_io/Cargo.toml +++ b/crates/ns_io/Cargo.toml @@ -10,4 +10,5 @@ authors.workspace = true crate-type = ["cdylib", "staticlib"] [dependencies] +ns_error = { path = "../ns_error" } ns_string = { path = "../ns_string" } diff --git a/crates/ns_io/src/input.rs b/crates/ns_io/src/input.rs index 993347d..6120f6a 100644 --- a/crates/ns_io/src/input.rs +++ b/crates/ns_io/src/input.rs @@ -1,74 +1,102 @@ +use ns_error::NsError; use std::ffi::{c_double, c_float, c_int}; use std::io::{self}; // Helper function to read a line from stdin safely -fn read_line_buffer() -> String { +fn read_line_buffer() -> Result { let mut buffer = String::new(); - // Ignore error for now (eg:Closed pipe) - let _ = io::stdin().read_line(&mut buffer); - buffer.trim().to_string() + // If reading from stdin completely fails (e.g., broken pipe), return the specific error + if io::stdin().read_line(&mut buffer).is_err() { + return Err(NsError::IoReadFailed); + } + + Ok(buffer.trim().to_string()) } -// Read Integer -#[unsafe(no_mangle)] +/// Read Integer +/// /// # Safety /// -/// This function reads an integer input -pub unsafe extern "C" fn ns_read_int(ptr: *mut c_int) { +/// This function is unsafe because it dereferences a raw pointer provided by C. +/// The caller must ensure that `ptr` is valid, properly aligned, and points to +/// initialized memory of type `c_int`. The function performs a null check, but +/// cannot guarantee the pointer is not dangling. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_read_int(ptr: *mut c_int) -> NsError { if ptr.is_null() { - return; + return NsError::Any; // Prevents segfaults, flags an error } - let input = read_line_buffer(); - - // Try to parse. If it fails, default to 0 - let val: c_int = input.parse().unwrap_or(0); - - // Unsafe: Write value to C memory address - unsafe { - *ptr = val; + match read_line_buffer() { + Ok(input) => { + // Match the parse result instead of blindly unwrapping! + match input.parse::() { + Ok(val) => { + unsafe { + *ptr = val; + } + NsError::Success + } + Err(_) => NsError::InvalidInput, + } + } + Err(e) => e, } } -// Read float -#[unsafe(no_mangle)] +/// Read float +/// /// # Safety /// -/// This function reads a floating point input -pub unsafe extern "C" fn ns_read_float(ptr: *mut c_float) { +/// This function is unsafe because it dereferences a raw pointer provided by C. +/// The caller must ensure that `ptr` is valid, properly aligned, and points to +/// initialized memory of type `c_float`. The function performs a null check, but +/// cannot guarantee the pointer is not dangling. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_read_float(ptr: *mut c_float) -> NsError { if ptr.is_null() { - return; + return NsError::Any; } - let input = read_line_buffer(); - - // Try to parse. If it fails, default to 0 - let val: c_float = input.parse().unwrap_or(0.0); - - // Unsafe: Write value to C memory address - unsafe { - *ptr = val; + match read_line_buffer() { + Ok(input) => match input.parse::() { + Ok(val) => { + unsafe { + *ptr = val; + } + NsError::Success + } + Err(_) => NsError::InvalidInput, + }, + Err(e) => e, } } -// Read double -#[unsafe(no_mangle)] +/// Read double +/// /// # Safety /// -/// This function reads a double input -pub unsafe extern "C" fn ns_read_double(ptr: *mut c_double) { +/// This function is unsafe because it dereferences a raw pointer provided by C. +/// The caller must ensure that `ptr` is valid, properly aligned, and points to +/// initialized memory of type `c_double`. The function performs a null check, but +/// cannot guarantee the pointer is not dangling. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_read_double(ptr: *mut c_double) -> NsError { if ptr.is_null() { - return; + return NsError::Any; } - let input = read_line_buffer(); - - // Try to parse. If it fails, default to 0 - let val: c_double = input.parse().unwrap_or(0.0); - - // Unsafe: Write value to C memory address - unsafe { - *ptr = val; + match read_line_buffer() { + Ok(input) => match input.parse::() { + Ok(val) => { + unsafe { + *ptr = val; + } + NsError::Success + } + Err(_) => NsError::InvalidInput, + }, + Err(e) => e, } } diff --git a/examples/06_errors.c b/examples/06_errors.c new file mode 100644 index 0000000..042cad1 --- /dev/null +++ b/examples/06_errors.c @@ -0,0 +1,43 @@ +#include "../include/ns.h" +#include "../include/ns_error.h" + +int main() { + ns_error_t err; + + int age; + + ns_println("----NextStd Error handling example---"); + ns_println("Try typing a valid number, or the text to see a safe fallback"); + ns_print("Enter your age: "); + + NS_TRY(err, ns_read(&age)) { + ns_print("Success, you entered: "); + ns_println(age); + } + NS_EXCEPT(err, NS_ERROR_INVALID_INPUT) { + ns_println("That is not a valid number! Please enter digits only."); + } + NS_EXCEPT(err, NS_ERROR_IO_READ) { + ns_println("Failed to read from standard input"); + } + NS_EXCEPT(err, NS_ERROR_ANY) { + ns_print("An error occurred: "); + ns_println(ns_error_message(err)); + } + + ns_println(""); + ns_println("----Testing Null pointer safety----"); + ns_println("Attempting to read a NULL pointer"); + + int* ptr = 0; + + NS_TRY(err, ns_read(ptr)) { + ns_println("CRITICAL FAILURE: This should not print!"); + } + NS_EXCEPT(err, NS_ERROR_ANY) { + ns_print("Safely caught the bad pointer! Error Message: "); + ns_println(ns_error_message(err)); + } + + return 0; +} diff --git a/include/ns_error.h b/include/ns_error.h new file mode 100644 index 0000000..26ad053 --- /dev/null +++ b/include/ns_error.h @@ -0,0 +1,40 @@ +#ifndef NS_ERROR_H +#define NS_ERROR_H + +#ifdef __cplusplus +extern "C" { +#endif + + typedef enum { + NS_SUCCESS = 0, + + // Catch all + NS_ERROR_ANY = 1, + + //ns_io errors + NS_ERROR_IO_READ = 10, + NS_ERROR_IO_WRITE = 11, + NS_ERROR_INVALID_INPUT = 12, + + // ns_string errors + NS_ERROR_STRING_ALLOC = 20, + NS_ERROR_STRING_UTF8 = 21, + + NS_ERROR_OUT_OF_BOUNDS = 22, + } ns_error_t; + + const char* ns_error_message(ns_error_t err); + +#define NS_TRY(err_var, expr) \ + if (((err_var) = (expr)) == NS_SUCCESS) + +#define NS_EXCEPT(err_var, err_type) \ + else if ((err_var) == (err_type) || (err_type) == NS_ERROR_ANY) + +#ifdef __cplusplus + +} + +#endif // __cplusplus + +#endif // !NS_ERROR_H diff --git a/include/ns_read.h b/include/ns_read.h index 16d1ac6..02bc494 100644 --- a/include/ns_read.h +++ b/include/ns_read.h @@ -1,17 +1,17 @@ #ifndef NS_READ_H #define NS_READ_H - +#include "ns_error.h" // Check if we are in C++ #ifdef __cplusplus extern "C" { #endif - // ----Reaing functions------ - void ns_read_int(int* ptr); - void ns_read_float(float* ptr); - void ns_read_double(double* ptr); + // ----Reading functions------ + ns_error_t ns_read_int(int* ptr); + ns_error_t ns_read_float(float* ptr); + ns_error_t ns_read_double(double* ptr); // Generic Read macro #define ns_read(x) _Generic((x), \