From a78b559307d7d9573f953dbc277ce45cb1395828 Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Wed, 10 Jul 2024 18:46:06 +0300
Subject: [PATCH 01/10] feat(#17): register_user handler, tracing-subscriber
 crate

---
 server/Cargo.toml                  |  1 +
 server/src/lib.rs                  | 13 ++++++---
 server/src/objects/mod.rs          | 22 ++++++++++++++++
 server/src/objects/user.rs         | 42 ++++++++++++++++++++++++++++++
 server/src/routes/register_user.rs | 35 +++++++++++++++++++++++++
 5 files changed, 109 insertions(+), 4 deletions(-)
 create mode 100644 server/src/objects/mod.rs
 create mode 100644 server/src/objects/user.rs
 create mode 100644 server/src/routes/register_user.rs

diff --git a/server/Cargo.toml b/server/Cargo.toml
index edd3b8d2..015821f2 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -44,3 +44,4 @@ axum = "0.7.5"
 log = { version = "0.4.21", features = [] }
 env_logger = "0.11.3"
 tempdir = "0.3.7"
+tracing-subscriber = "0.3.18"
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 10bcefd1..fba2fbc9 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -22,14 +22,16 @@
 use std::io;
 
 use anyhow::Result;
-use axum::routing::get;
+use axum::routing::{get, post};
 use axum::Router;
 use tokio::net::TcpListener;
 
 use crate::routes::home;
-use crate::xml::storage::touch_storage;
+use crate::routes::register_user::{register_user};
+use crate::xml::storage::Storage;
 
 pub mod report;
+mod objects;
 mod routes;
 mod xml;
 
@@ -46,8 +48,11 @@ impl Server {
 
 impl Server {
     pub async fn start(self) -> Result<()> {
-        touch_storage(Some("fakehub.xml"));
-        let app: Router = Router::new().route("/", get(home::home));
+        tracing_subscriber::fmt::init();
+        Storage::new(Some("fakehub.xml"));
+        let app: Router = Router::new()
+            .route("/", get(home::home))
+            .route("/users", post(register_user));
         let addr: String = format!("0.0.0.0:{}", self.port);
         let started: io::Result<TcpListener> = TcpListener::bind(addr.clone()).await;
         match started {
diff --git a/server/src/objects/mod.rs b/server/src/objects/mod.rs
new file mode 100644
index 00000000..06c393b7
--- /dev/null
+++ b/server/src/objects/mod.rs
@@ -0,0 +1,22 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2024 Aliaksei Bialiauski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+pub mod user;
diff --git a/server/src/objects/user.rs b/server/src/objects/user.rs
new file mode 100644
index 00000000..2155498f
--- /dev/null
+++ b/server/src/objects/user.rs
@@ -0,0 +1,42 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2024 Aliaksei Bialiauski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+use anyhow::Result;
+use log::info;
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+pub struct User {
+    pub(crate) username: String,
+}
+
+impl User {
+    pub fn new(username: String) -> User {
+        User { username }
+    }
+}
+
+impl User {
+    pub async fn save(self) -> Result<()> {
+        info!("registering user @{}", self.username);
+        Ok(())
+    }
+}
diff --git a/server/src/routes/register_user.rs b/server/src/routes/register_user.rs
new file mode 100644
index 00000000..7a30051e
--- /dev/null
+++ b/server/src/routes/register_user.rs
@@ -0,0 +1,35 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2024 Aliaksei Bialiauski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+use axum::http::StatusCode;
+use axum::Json;
+
+use crate::objects::user::User;
+
+pub async fn register_user(Json(payload): Json<User>) -> Result<StatusCode, String> {
+    let user = User::new(payload.username.clone());
+    match user.save().await {
+        Ok(_) => Ok(StatusCode::CREATED),
+        Err(e) => Err(
+            format!("Can't register {}: {}", payload.username, e),
+        ),
+    }
+}

From 40a32a23728e4527df1a2334553a84fa6a463d93 Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Wed, 10 Jul 2024 18:46:28 +0300
Subject: [PATCH 02/10] feat(#17): pub mod

---
 server/src/routes/mod.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs
index dc2b5115..36cc97c9 100644
--- a/server/src/routes/mod.rs
+++ b/server/src/routes/mod.rs
@@ -21,3 +21,4 @@
 // SOFTWARE.
 pub mod home;
 pub mod rs_err;
+pub mod register_user;

From b8b9336b5b61df7b895b3683c934b9669a623f5b Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Wed, 10 Jul 2024 18:46:47 +0300
Subject: [PATCH 03/10] feat(#17): storage redesigned to struct

---
 server/src/xml/storage.rs | 73 +++++++++++++++++++++++++++++++--------
 1 file changed, 58 insertions(+), 15 deletions(-)

diff --git a/server/src/xml/storage.rs b/server/src/xml/storage.rs
index 165c57ff..dea008e0 100644
--- a/server/src/xml/storage.rs
+++ b/server/src/xml/storage.rs
@@ -19,37 +19,63 @@
 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
+use anyhow::Result;
 use std::fs::File;
-
+use std::io::Write;
 use log::info;
 
-pub fn touch_storage(path: Option<&str>) -> File {
-    let location = path.unwrap_or("fakehub.xml");
-    info!("Initializing XML storage: {location}");
-    match File::create(location) {
-        Ok(file) => {
-            info!("'{location}' initialized");
-            file
-        }
-        Err(err) => {
-            panic!("fakehub storage failed to initialize in '{location}': {err}")
+#[derive(Default)]
+pub struct Storage {
+    pub(crate) path: String,
+}
+
+impl Storage {
+    pub fn new(path: Option<&str>) -> Storage {
+        let location = path.unwrap_or("fakehub.xml");
+        info!("Initializing XML storage: {location}");
+        let mut file = match File::create(location) {
+            Ok(file) => {
+                file
+            }
+            Err(err) => {
+                panic!("fakehub storage failed to initialize in '{location}': {err}");
+            }
+        };
+        if let Err(err) = file.write_all(
+            "<root>\
+             <github><users/></github>\
+            </root>".as_bytes()
+        ) {
+            panic!("Failed to write initial content to '{}': {}", location, err);
         }
+        info!("'{}' initialized", location);
+        Storage { path: String::from(location) }
+    }
+}
+
+// @todo #17:35min Implement #xml function in Storage.
+//  This function should return full XML storage has at the moment. #xml
+//  function should be thread-safe, as it intended to be used concurrently.
+//  Don't forget to create a unit tests related to #xml function.
+impl Storage {
+    pub fn xml() -> Result<()> {
+        Ok(())
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use std::fs;
     use anyhow::Result;
     use tempdir::TempDir;
-
-    use crate::xml::storage::touch_storage;
+    use crate::xml::storage::Storage;
 
     #[test]
     fn creates_xml_storage() -> Result<()> {
         let temp = TempDir::new("temp")?;
         let path = temp.path().join("fakehub.xml");
         let storage = path.to_str();
-        touch_storage(storage);
+        Storage::new(storage);
         assert!(
             path.exists(),
             "storage file {:?} was not created, but should be",
@@ -58,12 +84,29 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn reads_initial_content() -> Result<()>{
+        let temp = TempDir::new("temp")?;
+        let path = temp.path().join("fakehub.xml");
+        Storage::new(path.to_str());
+        let xml = fs::read_to_string(path).unwrap();
+        let expected = "<root><github><users/></github></root>";
+        assert_eq!(
+            xml,
+            expected,
+            "Received initial XML {} does not match with expected {}",
+            xml,
+            expected
+        );
+        Ok(())
+    }
+
     #[test]
     fn creates_xml_storage_with_different_name() -> Result<()> {
         let temp = TempDir::new("temp")?;
         let path = temp.path().join("test.xml");
         let storage = path.to_str();
-        touch_storage(storage);
+        Storage::new(storage);
         assert!(
             path.exists(),
             "storage file {:?} was not created, but should be",

From 5e0c434b1e9b7a69558fc01347a7f5981cbc8406 Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Wed, 10 Jul 2024 19:04:42 +0300
Subject: [PATCH 04/10] feat(#17): serde_xml_rs, puzzle for #save

---
 server/Cargo.toml          |  1 +
 server/src/objects/user.rs | 36 +++++++++++++++++++++++++++++++++---
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/server/Cargo.toml b/server/Cargo.toml
index 015821f2..a9eb42e1 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -38,6 +38,7 @@ path = "src/lib.rs"
 openapi = { path = "../github-mirror" }
 anyhow = "1.0.86"
 serde = { version = "1.0.203", features = ["derive"] }
+serde-xml-rs = "0.6.0"
 serde_json = "1.0.117"
 tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros", "fs"] }
 axum = "0.7.5"
diff --git a/server/src/objects/user.rs b/server/src/objects/user.rs
index 2155498f..e7732ce6 100644
--- a/server/src/objects/user.rs
+++ b/server/src/objects/user.rs
@@ -20,10 +20,11 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 use anyhow::Result;
-use log::info;
-use serde::Deserialize;
+use log::{debug, info};
+use serde::{Deserialize, Serialize};
+use serde_xml_rs::to_string;
 
-#[derive(Debug, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct User {
     pub(crate) username: String,
 }
@@ -34,9 +35,38 @@ impl User {
     }
 }
 
+// @todo #17:40min Apply XMLed user to the <users/> node in storage.
+//  We should apply XMLed user to the <users> XML node in storage. First we
+//  need to check that user with provided name does not exist, and only then
+//  apply it to the storage. Keep in mind that application function in the
+//  storage should be thread-safe (as well as #xml function). Don't forget to
+//  create unit tests that prove that.
 impl User {
     pub async fn save(self) -> Result<()> {
         info!("registering user @{}", self.username);
+        let xml = to_string(&self).unwrap();
+        debug!("XMLed user: {}", xml);
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use anyhow::Result;
+    use crate::objects::user::User;
+
+    #[test]
+    fn returns_username() -> Result<()> {
+        let expected = "jeff";
+        let jeff = User::new(String::from(expected));
+        assert_eq!(
+            jeff.username,
+            expected,
+            "Username {} from user: {:?} does not match with expected {}",
+            jeff.username,
+            jeff,
+            expected
+        );
         Ok(())
     }
 }

From 2ee37bf18dd5011caa25de8687253b82c2ed2abd Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Wed, 10 Jul 2024 19:12:20 +0300
Subject: [PATCH 05/10] feat(#17): clean for fmt

---
 server/src/lib.rs                  |  4 ++--
 server/src/objects/user.rs         |  8 +++-----
 server/src/routes/mod.rs           |  2 +-
 server/src/routes/register_user.rs |  4 +---
 server/src/xml/storage.rs          | 26 ++++++++++++++------------
 5 files changed, 21 insertions(+), 23 deletions(-)

diff --git a/server/src/lib.rs b/server/src/lib.rs
index fba2fbc9..ecb41aa5 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -27,11 +27,11 @@ use axum::Router;
 use tokio::net::TcpListener;
 
 use crate::routes::home;
-use crate::routes::register_user::{register_user};
+use crate::routes::register_user::register_user;
 use crate::xml::storage::Storage;
 
-pub mod report;
 mod objects;
+pub mod report;
 mod routes;
 mod xml;
 
diff --git a/server/src/objects/user.rs b/server/src/objects/user.rs
index e7732ce6..799cc64c 100644
--- a/server/src/objects/user.rs
+++ b/server/src/objects/user.rs
@@ -53,6 +53,7 @@ impl User {
 #[cfg(test)]
 mod tests {
     use anyhow::Result;
+
     use crate::objects::user::User;
 
     #[test]
@@ -60,12 +61,9 @@ mod tests {
         let expected = "jeff";
         let jeff = User::new(String::from(expected));
         assert_eq!(
-            jeff.username,
-            expected,
+            jeff.username, expected,
             "Username {} from user: {:?} does not match with expected {}",
-            jeff.username,
-            jeff,
-            expected
+            jeff.username, jeff, expected
         );
         Ok(())
     }
diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs
index 36cc97c9..1e466dc4 100644
--- a/server/src/routes/mod.rs
+++ b/server/src/routes/mod.rs
@@ -20,5 +20,5 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 pub mod home;
-pub mod rs_err;
 pub mod register_user;
+pub mod rs_err;
diff --git a/server/src/routes/register_user.rs b/server/src/routes/register_user.rs
index 7a30051e..ae5edbd4 100644
--- a/server/src/routes/register_user.rs
+++ b/server/src/routes/register_user.rs
@@ -28,8 +28,6 @@ pub async fn register_user(Json(payload): Json<User>) -> Result<StatusCode, Stri
     let user = User::new(payload.username.clone());
     match user.save().await {
         Ok(_) => Ok(StatusCode::CREATED),
-        Err(e) => Err(
-            format!("Can't register {}: {}", payload.username, e),
-        ),
+        Err(e) => Err(format!("Can't register {}: {}", payload.username, e)),
     }
 }
diff --git a/server/src/xml/storage.rs b/server/src/xml/storage.rs
index dea008e0..785d0d55 100644
--- a/server/src/xml/storage.rs
+++ b/server/src/xml/storage.rs
@@ -1,3 +1,6 @@
+use std::fs::File;
+use std::io::Write;
+
 // The MIT License (MIT)
 //
 // Copyright (c) 2024 Aliaksei Bialiauski
@@ -20,8 +23,6 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 use anyhow::Result;
-use std::fs::File;
-use std::io::Write;
 use log::info;
 
 #[derive(Default)]
@@ -34,9 +35,7 @@ impl Storage {
         let location = path.unwrap_or("fakehub.xml");
         info!("Initializing XML storage: {location}");
         let mut file = match File::create(location) {
-            Ok(file) => {
-                file
-            }
+            Ok(file) => file,
             Err(err) => {
                 panic!("fakehub storage failed to initialize in '{location}': {err}");
             }
@@ -44,12 +43,15 @@ impl Storage {
         if let Err(err) = file.write_all(
             "<root>\
              <github><users/></github>\
-            </root>".as_bytes()
+            </root>"
+                .as_bytes(),
         ) {
             panic!("Failed to write initial content to '{}': {}", location, err);
         }
         info!("'{}' initialized", location);
-        Storage { path: String::from(location) }
+        Storage {
+            path: String::from(location),
+        }
     }
 }
 
@@ -66,8 +68,10 @@ impl Storage {
 #[cfg(test)]
 mod tests {
     use std::fs;
+
     use anyhow::Result;
     use tempdir::TempDir;
+
     use crate::xml::storage::Storage;
 
     #[test]
@@ -85,18 +89,16 @@ mod tests {
     }
 
     #[test]
-    fn reads_initial_content() -> Result<()>{
+    fn reads_initial_content() -> Result<()> {
         let temp = TempDir::new("temp")?;
         let path = temp.path().join("fakehub.xml");
         Storage::new(path.to_str());
         let xml = fs::read_to_string(path).unwrap();
         let expected = "<root><github><users/></github></root>";
         assert_eq!(
-            xml,
-            expected,
+            xml, expected,
             "Received initial XML {} does not match with expected {}",
-            xml,
-            expected
+            xml, expected
         );
         Ok(())
     }

From 698d8194a4beedc1a0ad54b209a6299d8de13b4c Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Wed, 10 Jul 2024 19:19:43 +0300
Subject: [PATCH 06/10] feat(#17): allow dead code

---
 server/src/xml/storage.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/server/src/xml/storage.rs b/server/src/xml/storage.rs
index 785d0d55..9f8c3e9f 100644
--- a/server/src/xml/storage.rs
+++ b/server/src/xml/storage.rs
@@ -26,6 +26,7 @@ use anyhow::Result;
 use log::info;
 
 #[derive(Default)]
+#[allow(dead_code)]
 pub struct Storage {
     pub(crate) path: String,
 }
@@ -60,6 +61,7 @@ impl Storage {
 //  function should be thread-safe, as it intended to be used concurrently.
 //  Don't forget to create a unit tests related to #xml function.
 impl Storage {
+    #[allow(dead_code)]
     pub fn xml() -> Result<()> {
         Ok(())
     }

From 2a866974bb1792f5f0fa67b2724e414176e26650 Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Thu, 11 Jul 2024 09:41:39 +0300
Subject: [PATCH 07/10] feat(#17): INIT_XML const

---
 server/src/xml/storage.rs | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/server/src/xml/storage.rs b/server/src/xml/storage.rs
index 9f8c3e9f..afc1588b 100644
--- a/server/src/xml/storage.rs
+++ b/server/src/xml/storage.rs
@@ -31,6 +31,10 @@ pub struct Storage {
     pub(crate) path: String,
 }
 
+const INIT_XML: &str = "<root>\
+                        <github><users/></github>\
+                        </root>";
+
 impl Storage {
     pub fn new(path: Option<&str>) -> Storage {
         let location = path.unwrap_or("fakehub.xml");
@@ -41,12 +45,7 @@ impl Storage {
                 panic!("fakehub storage failed to initialize in '{location}': {err}");
             }
         };
-        if let Err(err) = file.write_all(
-            "<root>\
-             <github><users/></github>\
-            </root>"
-                .as_bytes(),
-        ) {
+        if let Err(err) = file.write_all(INIT_XML.as_bytes()) {
             panic!("Failed to write initial content to '{}': {}", location, err);
         }
         info!("'{}' initialized", location);

From 2bb8df344dc44248a9434e6f0c0930de4e08d353 Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Thu, 11 Jul 2024 10:15:18 +0300
Subject: [PATCH 08/10] feat(#17): hamcrest

---
 server/Cargo.toml          |  1 +
 server/src/lib.rs          |  6 +++++-
 server/src/objects/user.rs | 10 +++-------
 server/src/report/latex.rs |  9 +++------
 server/src/xml/storage.rs  | 19 ++++---------------
 5 files changed, 16 insertions(+), 29 deletions(-)

diff --git a/server/Cargo.toml b/server/Cargo.toml
index a9eb42e1..2b128160 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -46,3 +46,4 @@ log = { version = "0.4.21", features = [] }
 env_logger = "0.11.3"
 tempdir = "0.3.7"
 tracing-subscriber = "0.3.18"
+hamcrest = "0.1.5"
diff --git a/server/src/lib.rs b/server/src/lib.rs
index ecb41aa5..c0a1b08f 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -34,6 +34,9 @@ mod objects;
 pub mod report;
 mod routes;
 mod xml;
+#[allow(unused_imports)]
+#[macro_use]
+extern crate hamcrest;
 
 #[derive(Default)]
 pub struct Server {
@@ -68,11 +71,12 @@ impl Server {
 #[cfg(test)]
 mod tests {
     use anyhow::Result;
+    use hamcrest::{equal_to, is, HamcrestMatcher};
 
     #[test]
     fn creates_the_server() -> Result<()> {
         let server = crate::Server::new(1234);
-        assert_eq!(server.port, 1234);
+        assert_that!(server.port, is(equal_to(1234)));
         Ok(())
     }
 }
diff --git a/server/src/objects/user.rs b/server/src/objects/user.rs
index 799cc64c..1c19363a 100644
--- a/server/src/objects/user.rs
+++ b/server/src/objects/user.rs
@@ -52,19 +52,15 @@ impl User {
 
 #[cfg(test)]
 mod tests {
-    use anyhow::Result;
-
     use crate::objects::user::User;
+    use anyhow::Result;
+    use hamcrest::{equal_to, is, HamcrestMatcher};
 
     #[test]
     fn returns_username() -> Result<()> {
         let expected = "jeff";
         let jeff = User::new(String::from(expected));
-        assert_eq!(
-            jeff.username, expected,
-            "Username {} from user: {:?} does not match with expected {}",
-            jeff.username, jeff, expected
-        );
+        assert_that!(jeff.username, is(equal_to(String::from(expected))));
         Ok(())
     }
 }
diff --git a/server/src/report/latex.rs b/server/src/report/latex.rs
index 74148575..ea45db29 100644
--- a/server/src/report/latex.rs
+++ b/server/src/report/latex.rs
@@ -45,9 +45,9 @@ pub fn template(path: &str) -> String {
 
 #[cfg(test)]
 mod tests {
-    use anyhow::Result;
-
     use crate::report::latex::template;
+    use anyhow::Result;
+    use hamcrest::{equal_to, is, HamcrestMatcher};
 
     #[test]
     // @todo #41:60min Add support of @ExtendsWith from JUnit in order to pass expected as test parameter.
@@ -64,10 +64,7 @@ mod tests {
 \tbd{History: TBD}
 \end{document}
 ";
-        assert_eq!(
-            content, expected,
-            "Template content '{content}' does not match with '{expected}'"
-        );
+        assert_that!(content, is(equal_to(String::from(expected))));
         Ok(())
     }
 }
diff --git a/server/src/xml/storage.rs b/server/src/xml/storage.rs
index afc1588b..b01958a7 100644
--- a/server/src/xml/storage.rs
+++ b/server/src/xml/storage.rs
@@ -71,6 +71,7 @@ mod tests {
     use std::fs;
 
     use anyhow::Result;
+    use hamcrest::{equal_to, is, HamcrestMatcher};
     use tempdir::TempDir;
 
     use crate::xml::storage::Storage;
@@ -81,11 +82,7 @@ mod tests {
         let path = temp.path().join("fakehub.xml");
         let storage = path.to_str();
         Storage::new(storage);
-        assert!(
-            path.exists(),
-            "storage file {:?} was not created, but should be",
-            storage
-        );
+        assert_that!(path.exists(), is(equal_to(true)));
         Ok(())
     }
 
@@ -96,11 +93,7 @@ mod tests {
         Storage::new(path.to_str());
         let xml = fs::read_to_string(path).unwrap();
         let expected = "<root><github><users/></github></root>";
-        assert_eq!(
-            xml, expected,
-            "Received initial XML {} does not match with expected {}",
-            xml, expected
-        );
+        assert_that!(xml, is(equal_to(String::from(expected))));
         Ok(())
     }
 
@@ -110,11 +103,7 @@ mod tests {
         let path = temp.path().join("test.xml");
         let storage = path.to_str();
         Storage::new(storage);
-        assert!(
-            path.exists(),
-            "storage file {:?} was not created, but should be",
-            storage
-        );
+        assert_that!(path.exists(), is(equal_to(true)));
         Ok(())
     }
 }

From 49bea5cd1e492c5ef1f3f145d12a5bf4e6a79e8a Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Thu, 11 Jul 2024 11:32:30 +0300
Subject: [PATCH 09/10] feat(#17): clippy reject unwrap puzzle

---
 server/src/objects/user.rs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/server/src/objects/user.rs b/server/src/objects/user.rs
index 1c19363a..4519d4a4 100644
--- a/server/src/objects/user.rs
+++ b/server/src/objects/user.rs
@@ -41,6 +41,9 @@ impl User {
 //  apply it to the storage. Keep in mind that application function in the
 //  storage should be thread-safe (as well as #xml function). Don't forget to
 //  create unit tests that prove that.
+// @todo #17:30min Configure clippy to reject code with #unwrap().
+//  We should prohibit to use #unwrap() function in our code. Let's configure
+//  clippy tool in the respective manner and get rid of all #unwrap() calls.
 impl User {
     pub async fn save(self) -> Result<()> {
         info!("registering user @{}", self.username);

From 77b876511d9595c27ce6e3a85122076541ef8f94 Mon Sep 17 00:00:00 2001
From: h1alexbel <aliaksei.bialiauski@hey.com>
Date: Thu, 11 Jul 2024 11:35:01 +0300
Subject: [PATCH 10/10] feat(#17): multiline string

---
 server/src/xml/storage.rs | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/server/src/xml/storage.rs b/server/src/xml/storage.rs
index b01958a7..939b4c7d 100644
--- a/server/src/xml/storage.rs
+++ b/server/src/xml/storage.rs
@@ -31,9 +31,10 @@ pub struct Storage {
     pub(crate) path: String,
 }
 
-const INIT_XML: &str = "<root>\
-                        <github><users/></github>\
-                        </root>";
+const INIT_XML: &str = "<root>
+<github><users/></github>
+</root>
+";
 
 impl Storage {
     pub fn new(path: Option<&str>) -> Storage {
@@ -92,7 +93,7 @@ mod tests {
         let path = temp.path().join("fakehub.xml");
         Storage::new(path.to_str());
         let xml = fs::read_to_string(path).unwrap();
-        let expected = "<root><github><users/></github></root>";
+        let expected = "<root>\n<github><users/></github>\n</root>\n";
         assert_that!(xml, is(equal_to(String::from(expected))));
         Ok(())
     }