Skip to main content

rumtk_core/
core.rs

1/*
2 * rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 * This toolkit aims to be reliable, simple, performant, and standards compliant.
4 * Copyright (C) 2025  Luis M. Santos, M.D. <lsantos@medicalmasses.com>
5 * Copyright (C) 2025  MedicalMasses L.L.C. <contact@medicalmasses.com>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20use crate::strings::rumtk_format;
21use crate::strings::RUMString;
22use crate::types::RUMBuffer;
23use rand::{distr::Alphanumeric, Rng, RngExt};
24
25pub const DEFAULT_BUFFER_CHUNK_SIZE: usize = 1024;
26pub const DEFAULT_BUFFER_ITEM_COUNT: usize = 1024;
27
28pub type RUMError = RUMString;
29
30///
31/// Type used for propagating error messages.
32///
33pub type RUMResult<T> = Result<T, RUMError>;
34
35pub type RUMVec<T> = Vec<T>;
36
37pub fn is_unique<T: std::cmp::Eq + std::hash::Hash>(data: &Vec<T>) -> bool {
38    let mut keys = ahash::AHashSet::with_capacity(data.len());
39    for itm in data {
40        if !keys.insert(itm) {
41            return false;
42        }
43    }
44    true
45}
46
47///
48/// Take a requested index and the maximum size of the item container.
49/// Check if the index is valid and return an error if it is.
50/// The purpose of this function is to enable handling of out of bounds without triggering a panic.
51/// Also, add negative indices like Python does when doing a reverse search!
52///
53/// * If the index is 0, return Error
54/// * If the index is below 0, return the max - index iff max - index > 0
55/// * If the index is bigger than the defined max, return Error.
56/// * Otherwise, return the given index.
57///
58/// # Examples
59///
60/// ## Min
61/// ```
62/// use ::rumtk_core::core::clamp_index;
63/// use ::rumtk_core::strings::rumtk_format;
64/// let max: isize = 5;
65/// let i: isize = 1;
66/// let result = clamp_index(&i, &max).unwrap();
67/// assert_eq!(&1, &result, "{}", rumtk_format!("Expected to receive 0 but got {}", &result))
68/// ```
69///
70/// ## Max
71/// ```
72/// use ::rumtk_core::core::clamp_index;
73/// use ::rumtk_core::strings::rumtk_format;
74/// let max: isize = 5;
75/// let i: isize = 5;
76/// let result = clamp_index(&i, &max).unwrap();
77/// assert_eq!(&5, &result, "{}", rumtk_format!("Expected to receive 0 but got {}", &result))
78/// ```
79///
80/// ## Valid
81/// ```
82/// use ::rumtk_core::core::clamp_index;
83/// use ::rumtk_core::strings::rumtk_format;
84/// let max: isize = 5;
85/// let i: isize = 5;
86/// let result = clamp_index(&i, &max).unwrap();
87/// assert_eq!(&5, &result, "{}", rumtk_format!("Expected to receive 0 but got {}", &result))
88/// ```
89///
90/// ## Valid Negative Index (reverse lookup)
91/// ```
92/// use ::rumtk_core::core::clamp_index;
93/// use ::rumtk_core::strings::rumtk_format;
94/// let max: isize = 5;
95/// let i: isize = -1;
96/// let result = clamp_index(&i, &max).unwrap();
97/// assert_eq!(&5, &result, "{}", rumtk_format!("Expected to receive 0 but got {}", &result))
98/// ```
99#[inline(always)]
100pub fn clamp_index(given_indx: &isize, max_size: &isize) -> RUMResult<usize> {
101    let neg_max_indx = *max_size * -1;
102    if *given_indx == 0 {
103        return Err(rumtk_format!(
104            "Index {} is invalid! Use 1-indexed values if using positive indices.",
105            given_indx
106        ));
107    }
108
109    if *given_indx >= neg_max_indx && *given_indx < 0 {
110        return Ok((max_size + given_indx + 1) as usize);
111    }
112
113    if *given_indx > 0 && given_indx <= max_size {
114        return Ok(*given_indx as usize);
115    }
116
117    Err(rumtk_format!(
118        "Index {} is outside {} < x < {} boundary!",
119        given_indx,
120        neg_max_indx,
121        max_size
122    ))
123}
124
125///
126/// Convert slice of `&[u8]` to [RUMBuffer].
127///
128/// ## Example
129/// ```
130/// use rumtk_core::core::slice_to_buffer;
131/// use rumtk_core::types::RUMBuffer;
132///
133/// const expected: &str = "Hello World!";
134/// let buffer = RUMBuffer::from_static(expected.as_bytes());
135/// let result = slice_to_buffer(expected.as_bytes());
136///
137/// assert_eq!(result, buffer, "Slice to RUMBuffer conversion failed!");
138/// ```
139///
140pub fn slice_to_buffer(buffer: &[u8]) -> RUMBuffer {
141    RUMBuffer::copy_from_slice(buffer)
142}
143
144///
145/// Generates a new random buffer using the `rand` crate and wrapped inside a [RUMBuffer](RUMBuffer).
146/// 
147/// The buffer size can be adjusted via the turbofish method => `new_random_buffer::<10>()`.
148///
149/// ## Example
150///
151/// ```
152/// use rumtk_core::core::{new_random_buffer, DEFAULT_BUFFER_CHUNK_SIZE};
153///
154/// let buffer = new_random_buffer::<DEFAULT_BUFFER_CHUNK_SIZE>();
155///
156/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
157/// assert_eq!(buffer.len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
158/// ```
159///
160pub fn new_random_buffer<const N: usize>() -> [u8; N] {
161    let mut buffer = [0u8; N];
162    rand::fill(&mut buffer);
163    buffer
164}
165
166///
167/// Generates a new random buffer using the `rand` crate and wrapped inside a [RUMBuffer](RUMBuffer).
168///
169/// The buffer size can be adjusted via the turbofish method => `new_random_buffer::<10>()`.
170///
171/// ## Example
172///
173/// ```
174/// use rumtk_core::core::{new_random_buffer, DEFAULT_BUFFER_CHUNK_SIZE};
175///
176/// let buffer = new_random_buffer::<DEFAULT_BUFFER_CHUNK_SIZE>();
177///
178/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
179/// assert_eq!(buffer.len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
180/// ```
181///
182pub fn new_random_rumbuffer<const N: usize>() -> RUMBuffer {
183    slice_to_buffer(&new_random_buffer::<N>())
184}
185
186///
187/// Generates a new random string using the `rand` crate and wrapped inside a [RUMString](RUMString).
188///
189/// The buffer size can be adjusted via the turbofish method => `new_random_string_buffer::<10>()`.
190///
191/// ## Example
192///
193/// ```
194/// use rumtk_core::core::{new_random_string_buffer, DEFAULT_BUFFER_CHUNK_SIZE};
195///
196/// let buffer = new_random_string_buffer::<DEFAULT_BUFFER_CHUNK_SIZE>();
197///
198/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
199/// assert_eq!(buffer.len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
200/// ```
201///
202pub fn new_random_string_buffer<const N: usize>() -> RUMString {
203    rand::rng()
204        .sample_iter(&Alphanumeric)
205        .take(N) // Length of the string
206        .map(char::from)
207        .collect()
208}
209
210///
211/// Generates a new random set of [RUMString] using the `rand` crate.
212///
213/// The buffer size for each item can be adjusted via the turbofish method => `new_random_string_set::<10>()`.
214///
215/// ## Example
216///
217/// ```
218/// use rumtk_core::core::{new_random_string_set, DEFAULT_BUFFER_CHUNK_SIZE};
219///const item_count: usize = 5;
220/// 
221/// let buffer = new_random_string_set::<DEFAULT_BUFFER_CHUNK_SIZE>(item_count);
222///
223/// assert_eq!(buffer.is_empty(), false, "Function returned an empty random buffer which was unexpected!");
224/// assert_eq!(buffer.len(), item_count, "The new random buffer does not have the expected item count!");
225/// assert_eq!(buffer.get(0).unwrap().len(), DEFAULT_BUFFER_CHUNK_SIZE, "The new random buffer does not have the expected size!");
226/// ```
227///
228pub fn new_random_string_set<const N: usize>(item_count: usize) -> RUMVec<RUMString> {
229    let mut set = RUMVec::<RUMString>::with_capacity(item_count);
230    
231    for _ in 0..item_count {
232        set.push(new_random_string_buffer::<N>())
233    }
234    
235    set
236}