Repository: njord-rs/njord (language: rust)

Issue:
Create custom field type: PrimaryKey
Just thinking of creating a custom field type called `PrimaryKey` to add to a field for example `id`: https://github.com/njord-rs/njord/blob/master/njord/tests/sqlite_test.rs#L15

Some example implementation:

```rust
pub struct PrimaryKey<T>(Option<T>);

impl<T> PrimaryKey<T> {
    pub fn new(value: Option<T>) -> Self {
        PrimaryKey(value)
    }

    pub fn get(&self) -> Option<&T> {
        self.0.as_ref()
    }
}

impl<T> Default for PrimaryKey<T> {
    fn default() -> Self {
        PrimaryKey(None)
    }
}
```

Usage example:

```rust
let table_row: User = User {
    id: PrimaryKey::default(),
    username: "mjovanc".to_string(),
    email: "mjovanc@icloud.com".to_string(),
    address: "Some Random Address 1".to_string(),
};
```

When setting it to ::default we are saying that we want the database to increment the id field automatically using autoincrement.


Discussion / hints:

Fix patch:
diff --git a/njord/db/insert.db b/njord/db/insert.db
index 90b42f12..165e3bc8 100644
Binary files a/njord/db/insert.db and b/njord/db/insert.db differ
diff --git a/njord/src/keys.rs b/njord/src/keys.rs
new file mode 100644
index 00000000..6fc4ae23
--- /dev/null
+++ b/njord/src/keys.rs
@@ -0,0 +1,76 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024,
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::{
+    convert::Infallible,
+    fmt::{Debug, Display},
+    str::FromStr,
+};
+
+#[derive(Debug)]
+pub struct PrimaryKey<T>(Option<T>);
+
+impl<T> PrimaryKey<T> {
+    pub fn new(value: Option<T>) -> Self {
+        PrimaryKey(value)
+    }
+
+    pub fn get(&self) -> Option<&T> {
+        self.0.as_ref()
+    }
+}
+
+impl<T> Default for PrimaryKey<T> {
+    fn default() -> Self {
+        PrimaryKey(None)
+    }
+}
+
+/// Implement the std::fmt::Display trait
+/// Used specifically for translating the primary key to the string representation
+impl<T: Debug> Display for PrimaryKey<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{:?}", self.get().unwrap())
+    }
+}
+
+/// Implement the std::str::FromStr trait
+/// Used specifically for translating the primary key from the string representation
+impl<T: Debug + FromStr> FromStr for PrimaryKey<T> {
+    type Err = Infallible;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.parse::<T>() {
+            Ok(value) => Ok(PrimaryKey(Some(value))),
+            Err(_) => Ok(PrimaryKey(None)),
+        }
+    }
+}
diff --git a/njord/src/lib.rs b/njord/src/lib.rs
index 8b3e2771..26a5d7a6 100644
--- a/njord/src/lib.rs
+++ b/njord/src/lib.rs
@@ -32,3 +32,4 @@ pub mod condition;
 pub mod sqlite;
 pub mod table;
 pub mod util;
+pub mod keys;


Test patch:
diff --git a/njord/tests/sqlite_test.rs b/njord/tests/sqlite_test.rs
index 9b9347cc..706b6032 100644
--- a/njord/tests/sqlite_test.rs
+++ b/njord/tests/sqlite_test.rs
@@ -1,6 +1,7 @@
 // integrations tests for sqlite
 
 use njord::condition::Condition;
+use njord::keys::PrimaryKey;
 use njord::sqlite::{self};
 use njord::table::Table;
 use njord_derive::Table;
@@ -12,7 +13,7 @@ use std::path::Path;
 #[derive(Table)]
 #[table_name = "users"]
 pub struct User {
-    id: usize, // TODO: should we create some sort of PrimaryKey<usize> ?
+    id: PrimaryKey<usize>,
     username: String,
     email: String,
     address: String,
@@ -21,14 +22,14 @@ pub struct User {
 #[derive(Table)]
 #[table_name = "categories"]
 pub struct Category {
-    id: usize,
+    id: PrimaryKey<usize>,
     name: String,
 }
 
 #[derive(Table)]
 #[table_name = "products"]
 pub struct Product {
-    id: usize,
+    id: PrimaryKey<usize>,
     name: String,
     description: String,
     price: f64,
@@ -67,7 +68,7 @@ fn insert_row() {
     let random_number: usize = rng.gen_range(0..max_usize / 2);
 
     let table_row: User = User {
-        id: random_number,
+        id: PrimaryKey::<usize>::new(Some(random_number)),
         username: "mjovanc".to_string(),
         email: "mjovanc@icloud.com".to_string(),
         address: "Some Random Address 1".to_string(),
@@ -95,7 +96,7 @@ fn update() {
     let condition = Condition::Eq("username".to_string(), "mjovanc".to_string());
 
     let table_row: User = User {
-        id: 0,
+        id: PrimaryKey::<usize>::new(Some(0)),
         username: "mjovanc".to_string(),
         email: "mjovanc@icloud.com".to_string(),
         address: "Some Random Address 1".to_string(),