Skip to content

Commit

Permalink
Merge pull request #61 from h1alexbel/17
Browse files Browse the repository at this point in the history
feat(#17): register_user, Storage redesigned to struct
  • Loading branch information
h1alexbel authored Jul 11, 2024
2 parents d09607b + 77b8765 commit 31636e6
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 35 deletions.
3 changes: 3 additions & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ 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"
log = { version = "0.4.21", features = [] }
env_logger = "0.11.3"
tempdir = "0.3.7"
tracing-subscriber = "0.3.18"
hamcrest = "0.1.5"
19 changes: 14 additions & 5 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,21 @@
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;

mod objects;
pub mod report;
mod routes;
mod xml;
#[allow(unused_imports)]
#[macro_use]
extern crate hamcrest;

#[derive(Default)]
pub struct Server {
Expand All @@ -46,8 +51,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 {
Expand All @@ -63,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(())
}
}
22 changes: 22 additions & 0 deletions server/src/objects/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
69 changes: 69 additions & 0 deletions server/src/objects/user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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::{debug, info};
use serde::{Deserialize, Serialize};
use serde_xml_rs::to_string;

#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub(crate) username: String,
}

impl User {
pub fn new(username: String) -> User {
User { username }
}
}

// @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.
// @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);
let xml = to_string(&self).unwrap();
debug!("XMLed user: {}", xml);
Ok(())
}
}

#[cfg(test)]
mod tests {
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_that!(jeff.username, is(equal_to(String::from(expected))));
Ok(())
}
}
9 changes: 3 additions & 6 deletions server/src/report/latex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(())
}
}
1 change: 1 addition & 0 deletions server/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
pub mod home;
pub mod register_user;
pub mod rs_err;
33 changes: 33 additions & 0 deletions server/src/routes/register_user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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)),
}
}
84 changes: 60 additions & 24 deletions server/src/xml/storage.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::fs::File;
use std::io::Write;

// The MIT License (MIT)
//
// Copyright (c) 2024 Aliaksei Bialiauski
Expand All @@ -19,42 +22,79 @@
// 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 std::fs::File;

use anyhow::Result;
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
#[derive(Default)]
#[allow(dead_code)]
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");
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(INIT_XML.as_bytes()) {
panic!("Failed to write initial content to '{}': {}", location, err);
}
Err(err) => {
panic!("fakehub storage failed to initialize in '{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 {
#[allow(dead_code)]
pub fn xml() -> Result<()> {
Ok(())
}
}

#[cfg(test)]
mod tests {
use std::fs;

use anyhow::Result;
use hamcrest::{equal_to, is, HamcrestMatcher};
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);
assert!(
path.exists(),
"storage file {:?} was not created, but should be",
storage
);
Storage::new(storage);
assert_that!(path.exists(), is(equal_to(true)));
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>\n<github><users/></github>\n</root>\n";
assert_that!(xml, is(equal_to(String::from(expected))));
Ok(())
}

Expand All @@ -63,12 +103,8 @@ mod tests {
let temp = TempDir::new("temp")?;
let path = temp.path().join("test.xml");
let storage = path.to_str();
touch_storage(storage);
assert!(
path.exists(),
"storage file {:?} was not created, but should be",
storage
);
Storage::new(storage);
assert_that!(path.exists(), is(equal_to(true)));
Ok(())
}
}

3 comments on commit 31636e6

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 31636e6 Jul 11, 2024

Choose a reason for hiding this comment

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

Puzzle 17-2c1e333d discovered in server/src/objects/user.rs) and submitted as #73. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 31636e6 Jul 11, 2024

Choose a reason for hiding this comment

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

Puzzle 17-e45e524d discovered in server/src/objects/user.rs) and submitted as #74. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 31636e6 Jul 11, 2024

Choose a reason for hiding this comment

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

Puzzle 17-1a69e44f discovered in server/src/xml/storage.rs) and submitted as #75. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

Please sign in to comment.