challange: 9
This commit is contained in:
parent
ef5b3820e2
commit
c8f3af6cf9
10
Cargo.toml
10
Cargo.toml
@ -5,14 +5,18 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.7.4", features = ["query"] }
|
axum = { version = "0.7.4", features = ["query"] }
|
||||||
cargo-manifest = "0.17.0"
|
|
||||||
serde = { version = "1.0.215", features = ["derive"] }
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
serde_json = "1.0.133"
|
serde_json = "1.0.133"
|
||||||
serde_yml = "0.0.12"
|
|
||||||
shuttle-axum = "0.49.0"
|
shuttle-axum = "0.49.0"
|
||||||
shuttle-runtime = "0.49.0"
|
shuttle-runtime = "0.49.0"
|
||||||
tokio = "1.28.2"
|
tokio = "1.28.2"
|
||||||
toml = "0.8.19"
|
cargo-manifest = { version = "0.17.0", optional = true }
|
||||||
|
serde_yml = { version = "0.0.12", optional = true }
|
||||||
|
toml = { version = "0.8.19", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum-test = "16.4.0"
|
axum-test = "16.4.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
task1-9 = ["cargo-manifest", "serde_yml", "toml"]
|
||||||
|
358
src/lib.rs
358
src/lib.rs
@ -1,14 +1,23 @@
|
|||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
use axum::routing::post;
|
#[cfg(feature = "task1-9")]
|
||||||
pub use routes::hello_world;
|
use routes::{
|
||||||
use routes::{hello_bird, ipv4_dest, ipv4_key, ipv6_dest, ipv6_key, manifest, minus_one};
|
hello_bird, hello_world, ipv4_dest, ipv4_key, ipv6_dest, ipv6_key, manifest, milk, minus_one,
|
||||||
|
refill, MilkFactory,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn router() -> axum::Router {
|
pub fn router() -> axum::Router {
|
||||||
|
#[cfg(feature = "task1-9")]
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
|
#[cfg(feature = "task1-9")]
|
||||||
|
use axum::routing::post;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
|
||||||
Router::new()
|
#[cfg(feature = "task1-9")]
|
||||||
|
let milk_factory = MilkFactory::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "task1-9")]
|
||||||
|
return Router::new()
|
||||||
.route("/hello_world", get(hello_world))
|
.route("/hello_world", get(hello_world))
|
||||||
.route("/-1/seek", get(minus_one))
|
.route("/-1/seek", get(minus_one))
|
||||||
.route("/2/dest", get(ipv4_dest))
|
.route("/2/dest", get(ipv4_dest))
|
||||||
@ -16,338 +25,11 @@ pub fn router() -> axum::Router {
|
|||||||
.route("/2/v6/dest", get(ipv6_dest))
|
.route("/2/v6/dest", get(ipv6_dest))
|
||||||
.route("/2/v6/key", get(ipv6_key))
|
.route("/2/v6/key", get(ipv6_key))
|
||||||
.route("/5/manifest", post(manifest))
|
.route("/5/manifest", post(manifest))
|
||||||
.route("/", get(hello_bird))
|
.route("/9/milk", post(milk))
|
||||||
}
|
.route("/9/refill", post(refill))
|
||||||
|
.with_state(milk_factory)
|
||||||
#[cfg(test)]
|
.route("/", get(hello_bird));
|
||||||
mod test {
|
|
||||||
use super::*;
|
// #[cfg(feature="task12")]
|
||||||
use axum::http::StatusCode;
|
Router::new()
|
||||||
use axum_test::TestServer;
|
|
||||||
|
|
||||||
fn test_server() -> TestServer {
|
|
||||||
TestServer::new(router()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_hello_world() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let response = server.get("/hello_world").await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("Hello, world!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_hello_bird() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let response = server.get("/").await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("Hello, bird!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_minus_one() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let response = server.get("/-1/seek").await;
|
|
||||||
response.assert_header("location", "https://www.youtube.com/watch?v=9Gc4QTqslN4");
|
|
||||||
response.assert_status(StatusCode::FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_ipv4_dest() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let response = server.get("/2/dest?from=10.0.0.0&key=1.2.3.255").await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("11.2.3.255");
|
|
||||||
|
|
||||||
let response = server.get("/2/dest?from=invalid-from&key=1.2.3.255").await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
|
|
||||||
let response = server.get("/2/dest?from=10.0.0.0&key=invalid-key").await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.get("/2/dest?from=128.128.33.0&key=255.0.255.33")
|
|
||||||
.await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("127.128.32.33");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_ipv4_key() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let response = server.get("/2/key?from=10.0.0.0&to=11.2.3.255").await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("1.2.3.255");
|
|
||||||
|
|
||||||
let response = server.get("/2/key?from=invalid-from&to=1.2.3.255").await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
|
|
||||||
let response = server.get("/2/key?from=10.0.0.0&to=invalid-to").await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.get("/2/key?from=128.128.33.0&to=127.128.32.33")
|
|
||||||
.await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("255.0.255.33");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_ipv6_dest() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let response = server.get("/2/v6/dest?from=fe80::1&key=5:6:7::3333").await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("fe85:6:7::3332");
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.get("/2/v6/dest?from=invalid-from&key=5:6:7::3333")
|
|
||||||
.await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
|
|
||||||
let response = server.get("/2/v6/dest?from=fe80::1&key=invalid-key").await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_ipv6_key() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.get("/2/v6/key?from=aaaa::aaaa&to=5555:ffff:c:0:0:c:1234:5555")
|
|
||||||
.await;
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text("ffff:ffff:c::c:1234:ffff");
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.get("/2/v6/dest?from=invalid-from&to=5:6:7::3333")
|
|
||||||
.await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
|
|
||||||
let response = server.get("/2/v6/dest?from=fe80::1&to=invalid-to").await;
|
|
||||||
response.assert_status_bad_request();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_1() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let want = "Toy car: 2\nLego brick: 230";
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "not-a-gift-order"
|
|
||||||
authors = ["Not Santa"]
|
|
||||||
keywords = ["Christmas 2024"]
|
|
||||||
|
|
||||||
[[package.metadata.orders]]
|
|
||||||
item = "Toy car"
|
|
||||||
quantity = 2
|
|
||||||
|
|
||||||
[[package.metadata.orders]]
|
|
||||||
item = "Lego brick"
|
|
||||||
quantity = 230"#;
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text(want);
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "coal-in-a-bowl"
|
|
||||||
authors = ["H4CK3R_13E7"]
|
|
||||||
keywords = ["Christmas 2024"]
|
|
||||||
|
|
||||||
[[package.metadata.orders]]
|
|
||||||
item = "Coal"
|
|
||||||
quantity = "Hahaha get rekt""#;
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
response.assert_status(StatusCode::NO_CONTENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_2() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = false
|
|
||||||
authors = ["Not Santa"]
|
|
||||||
keywords = ["Christmas 2024"]"#;
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
response.assert_status(StatusCode::BAD_REQUEST);
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "not-a-gift-order"
|
|
||||||
authors = ["Not Santa"]
|
|
||||||
keywords = ["Christmas 2024"]
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
incremental = "stonks""#;
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
response.assert_status(StatusCode::BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_3() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let want = "Toy car: 2\nLego brick: 230";
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "not-a-gift-order"
|
|
||||||
authors = ["Not Santa"]
|
|
||||||
keywords = ["Christmas 2024"]
|
|
||||||
|
|
||||||
[[package.metadata.orders]]
|
|
||||||
item = "Toy car"
|
|
||||||
quantity = 2
|
|
||||||
|
|
||||||
[[package.metadata.orders]]
|
|
||||||
item = "Lego brick"
|
|
||||||
quantity = 230"#;
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text(want);
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "not-a-gift-order"
|
|
||||||
authors = ["Not Santa"]
|
|
||||||
|
|
||||||
[[package.metadata.orders]]
|
|
||||||
item = "Toy car"
|
|
||||||
quantity = 2
|
|
||||||
|
|
||||||
[[package.metadata.orders]]
|
|
||||||
item = "Lego brick"
|
|
||||||
quantity = 230"#;
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
|
|
||||||
response.assert_status(StatusCode::BAD_REQUEST);
|
|
||||||
response.assert_text("Magic keyword not provided");
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "grass"
|
|
||||||
authors = ["A vegan cow"]
|
|
||||||
keywords = ["Moooooo"]"#;
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
response.assert_status(StatusCode::BAD_REQUEST);
|
|
||||||
response.assert_text("Magic keyword not provided");
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "grass"
|
|
||||||
authors = ["A vegan cow"]
|
|
||||||
keywords = ["Christmas 2024"]"#;
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/toml")
|
|
||||||
.await;
|
|
||||||
response.assert_status(StatusCode::NO_CONTENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_4() {
|
|
||||||
let server = test_server();
|
|
||||||
|
|
||||||
let payload = r#"[package]
|
|
||||||
name = "grass"
|
|
||||||
authors = ["A vegan cow"]
|
|
||||||
keywords = ["Christmas 2024"]"#;
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("text/html")
|
|
||||||
.await;
|
|
||||||
response.assert_status(StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
|
||||||
|
|
||||||
let want = "Toy train: 5";
|
|
||||||
|
|
||||||
let payload = r#"package:
|
|
||||||
name: big-chungus-sleigh
|
|
||||||
version: "2.0.24"
|
|
||||||
metadata:
|
|
||||||
orders:
|
|
||||||
- item: "Toy train"
|
|
||||||
quantity: 5
|
|
||||||
rust-version: "1.69"
|
|
||||||
keywords:
|
|
||||||
- "Christmas 2024""#;
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/yaml")
|
|
||||||
.await;
|
|
||||||
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text(want);
|
|
||||||
|
|
||||||
let want = "Toy train: 5";
|
|
||||||
|
|
||||||
let payload = r#"{
|
|
||||||
"package": {
|
|
||||||
"name": "big-chungus-sleigh",
|
|
||||||
"version": "2.0.24",
|
|
||||||
"metadata": {
|
|
||||||
"orders": [
|
|
||||||
{
|
|
||||||
"item": "Toy train",
|
|
||||||
"quantity": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rust-version": "1.69",
|
|
||||||
"keywords": [
|
|
||||||
"Christmas 2024"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let response = server
|
|
||||||
.post("/5/manifest")
|
|
||||||
.text(payload)
|
|
||||||
.content_type("application/json")
|
|
||||||
.await;
|
|
||||||
|
|
||||||
response.assert_status_ok();
|
|
||||||
response.assert_text(want);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
#![cfg(feature = "task1-9")]
|
||||||
mod hello_bird;
|
mod hello_bird;
|
||||||
mod hello_world;
|
mod hello_world;
|
||||||
mod minus_one;
|
mod minus_one;
|
||||||
mod task_five;
|
mod task_five;
|
||||||
|
mod task_nine;
|
||||||
mod task_two;
|
mod task_two;
|
||||||
|
|
||||||
pub use hello_bird::hello_bird;
|
pub use hello_bird::hello_bird;
|
||||||
@ -12,3 +14,5 @@ pub use task_two::ipv4_dest;
|
|||||||
pub use task_two::ipv4_key;
|
pub use task_two::ipv4_key;
|
||||||
pub use task_two::ipv6_dest;
|
pub use task_two::ipv6_dest;
|
||||||
pub use task_two::ipv6_key;
|
pub use task_two::ipv6_key;
|
||||||
|
|
||||||
|
pub use task_nine::{milk, refill, MilkFactory};
|
||||||
|
47
src/routes/task_nine/milk.rs
Normal file
47
src/routes/task_nine/milk.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::{header::CONTENT_TYPE, HeaderMap, StatusCode},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{MilkFactory, Unit};
|
||||||
|
|
||||||
|
#[allow(clippy::unused_async)]
|
||||||
|
pub async fn refill(State(milk_factory): State<MilkFactory>) -> impl IntoResponse {
|
||||||
|
milk_factory.magic_refill();
|
||||||
|
String::new().into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_async)]
|
||||||
|
pub async fn milk(
|
||||||
|
State(milk_factory): State<MilkFactory>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
payload: String,
|
||||||
|
) -> Result<String, impl IntoResponse> {
|
||||||
|
headers.get(CONTENT_TYPE).map_or_else(
|
||||||
|
|| match milk_factory.withdraw() {
|
||||||
|
Ok(message) => Ok(message.to_string()),
|
||||||
|
Err(message) => Err(message.into_response()),
|
||||||
|
},
|
||||||
|
|content_type| match content_type.to_str().unwrap() {
|
||||||
|
"application/json" => Unit::from_json(&payload).map_or_else(
|
||||||
|
|_| {
|
||||||
|
let _ = milk_factory.withdraw();
|
||||||
|
Err((StatusCode::BAD_REQUEST, "").into_response())
|
||||||
|
},
|
||||||
|
|unit| match unit {
|
||||||
|
Unit::Liters(_) | Unit::Gallons(_) | Unit::Litres(_) | Unit::Pints(_) => {
|
||||||
|
match milk_factory.withdraw() {
|
||||||
|
Ok(_) => Ok(unit.json().unwrap()),
|
||||||
|
Err(message) => Err(message.into_response()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => match milk_factory.withdraw() {
|
||||||
|
Ok(message) => Ok(message.to_string()),
|
||||||
|
Err(message) => Err(message.into_response()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
153
src/routes/task_nine/milk_factory.rs
Normal file
153
src/routes/task_nine/milk_factory.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
sync::{atomic::AtomicU32, Arc},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use axum::{http::StatusCode, response::IntoResponse};
|
||||||
|
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MilkFactory {
|
||||||
|
stock: Arc<AtomicU32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MilkFactory {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mf = Self {
|
||||||
|
stock: Arc::new(AtomicU32::new(5)),
|
||||||
|
};
|
||||||
|
mf.run();
|
||||||
|
mf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&self) {
|
||||||
|
thread::spawn({
|
||||||
|
let stock = self.stock.clone();
|
||||||
|
move || {
|
||||||
|
let mut start = std::time::Instant::now();
|
||||||
|
loop {
|
||||||
|
if start.elapsed().as_millis() >= 990 {
|
||||||
|
let current_stock = stock.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if current_stock < 5 {
|
||||||
|
stock.store(current_stock + 1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
start = std::time::Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn magic_refill(&self) {
|
||||||
|
self.stock.store(5, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn withdraw(&self) -> Result<MilkMessage, MilkMessage> {
|
||||||
|
let current_stock = self.stock.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if current_stock < 1 {
|
||||||
|
return Err(MilkMessage::NoMilkAvailable);
|
||||||
|
}
|
||||||
|
self.stock
|
||||||
|
.store(current_stock - 1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
Ok(MilkMessage::Withdraw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum MilkMessage {
|
||||||
|
Withdraw,
|
||||||
|
Refill,
|
||||||
|
NoMilkAvailable,
|
||||||
|
WithdrawUnit(Unit),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Copy, Clone, PartialEq)]
|
||||||
|
pub enum Unit {
|
||||||
|
#[serde(rename(deserialize = "liters", serialize = "gallons"))]
|
||||||
|
Liters(f32),
|
||||||
|
#[serde(rename(deserialize = "gallons", serialize = "liters"))]
|
||||||
|
Gallons(f32),
|
||||||
|
#[serde(rename(deserialize = "litres", serialize = "pints"))]
|
||||||
|
Litres(f32),
|
||||||
|
#[serde(rename(deserialize = "pints", serialize = "litres"))]
|
||||||
|
Pints(f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unit {
|
||||||
|
pub fn from_json(json: &str) -> Result<Self, serde_json::error::Error> {
|
||||||
|
serde_json::from_str(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json(self) -> Result<String, serde_json::error::Error> {
|
||||||
|
serde_json::to_string(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Unit {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut state = serializer.serialize_struct("Unit", 1)?;
|
||||||
|
match self {
|
||||||
|
Self::Liters(value) => {
|
||||||
|
state.serialize_field::<f32>("gallons", &(*value * 0.264_172_06))?;
|
||||||
|
}
|
||||||
|
Self::Gallons(value) => {
|
||||||
|
state.serialize_field::<f32>("liters", &(*value * 3.785_412))?;
|
||||||
|
}
|
||||||
|
Self::Litres(value) => {
|
||||||
|
state.serialize_field::<f32>("pints", &(*value * 1.759_754))?;
|
||||||
|
}
|
||||||
|
Self::Pints(value) => {
|
||||||
|
state.serialize_field::<f32>("litres", &(*value * 0.568_261))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unit() {
|
||||||
|
let unit = Unit::Liters(5.0);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&unit).unwrap(),
|
||||||
|
r#"{"gallons":1.3208603}"#
|
||||||
|
);
|
||||||
|
|
||||||
|
let unit = Unit::from_json(serde_json::json!({"liters": 5.0}).to_string().as_str()).unwrap();
|
||||||
|
assert_eq!(unit, Unit::Liters(5.0));
|
||||||
|
assert_eq!(unit.json().unwrap(), r#"{"gallons":1.3208603}"#);
|
||||||
|
|
||||||
|
let unit = Unit::from_json(
|
||||||
|
serde_json::json!({"gallons": -2.000_000_000_000_001})
|
||||||
|
.to_string()
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(unit, Unit::Gallons(-2.000_000_000_000_001));
|
||||||
|
assert_eq!(unit.json().unwrap(), r#"{"liters":-7.570824}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for MilkMessage {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Withdraw => writeln!(f, "Milk withdrawn"),
|
||||||
|
Self::Refill => writeln!(f, "Refilled milk"),
|
||||||
|
Self::NoMilkAvailable => writeln!(f, "No milk available"),
|
||||||
|
Self::WithdrawUnit(u) => write!(f, "{}", u.json().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for MilkMessage {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
match self {
|
||||||
|
Self::Withdraw | Self::WithdrawUnit(_) => (StatusCode::OK, self.to_string()),
|
||||||
|
Self::Refill => (StatusCode::OK, String::new()),
|
||||||
|
Self::NoMilkAvailable => (StatusCode::TOO_MANY_REQUESTS, self.to_string()),
|
||||||
|
}
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
6
src/routes/task_nine/mod.rs
Normal file
6
src/routes/task_nine/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
mod milk;
|
||||||
|
mod milk_factory;
|
||||||
|
|
||||||
|
pub use milk::{milk, refill};
|
||||||
|
|
||||||
|
pub use milk_factory::{MilkFactory, Unit};
|
38
tests/minus_one/main.rs
Normal file
38
tests/minus_one/main.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#[cfg(all(test, feature = "task1-9"))]
|
||||||
|
mod minus_one {
|
||||||
|
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_test::TestServer;
|
||||||
|
use itsscb_shuttlings_cch24::router;
|
||||||
|
|
||||||
|
fn test_server() -> TestServer {
|
||||||
|
TestServer::new(router()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_hello_world() {
|
||||||
|
let server = test_server();
|
||||||
|
|
||||||
|
let response = server.get("/hello_world").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Hello, world!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_hello_bird() {
|
||||||
|
let server = test_server();
|
||||||
|
|
||||||
|
let response = server.get("/").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Hello, bird!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_minus_one() {
|
||||||
|
let server = test_server();
|
||||||
|
|
||||||
|
let response = server.get("/-1/seek").await;
|
||||||
|
response.assert_header("location", "https://www.youtube.com/watch?v=9Gc4QTqslN4");
|
||||||
|
response.assert_status(StatusCode::FOUND);
|
||||||
|
}
|
||||||
|
}
|
121
tests/task_five/main.rs
Normal file
121
tests/task_five/main.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#[cfg(all(test, feature = "task1-9"))]
|
||||||
|
mod task_five {
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_test::TestServer;
|
||||||
|
use itsscb_shuttlings_cch24::router;
|
||||||
|
|
||||||
|
fn test_server() -> TestServer {
|
||||||
|
TestServer::new(router()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_milk() {
|
||||||
|
let sever = test_server();
|
||||||
|
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Milk withdrawn\n");
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Milk withdrawn\n");
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Milk withdrawn\n");
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Milk withdrawn\n");
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Milk withdrawn\n");
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
response.assert_status(StatusCode::TOO_MANY_REQUESTS);
|
||||||
|
|
||||||
|
let sever = test_server();
|
||||||
|
|
||||||
|
for i in 0..=10 {
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
match i {
|
||||||
|
0..=4 | 6 | 8 | 9 => {
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Milk withdrawn\n");
|
||||||
|
}
|
||||||
|
5 | 7 | 10 => {
|
||||||
|
response.assert_status(StatusCode::TOO_MANY_REQUESTS);
|
||||||
|
response.assert_text("No milk available\n");
|
||||||
|
match i {
|
||||||
|
5 => thread::sleep(Duration::from_secs(1)),
|
||||||
|
7 => thread::sleep(Duration::from_secs(2)),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
response.assert_status(StatusCode::SERVICE_UNAVAILABLE);
|
||||||
|
response.assert_text("No milk available\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sever = test_server();
|
||||||
|
|
||||||
|
let response = sever
|
||||||
|
.post("/9/milk")
|
||||||
|
.text(r#"{"liters":5}"#)
|
||||||
|
.content_type("application/json")
|
||||||
|
.await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text(r#"{"gallons":1.3208603}"#);
|
||||||
|
|
||||||
|
let sever = test_server();
|
||||||
|
|
||||||
|
let response = sever
|
||||||
|
.post("/9/milk")
|
||||||
|
.text(r#"{"gallons":5}"#)
|
||||||
|
.content_type("application/json")
|
||||||
|
.await;
|
||||||
|
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text(r#"{"liters":18.927061}"#);
|
||||||
|
|
||||||
|
let sever = test_server();
|
||||||
|
|
||||||
|
let response = sever
|
||||||
|
.post("/9/milk")
|
||||||
|
.text(r#"{"liters":1, "gallons":5}"#)
|
||||||
|
.content_type("application/json")
|
||||||
|
.await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
|
||||||
|
let response = sever
|
||||||
|
.post("/9/milk")
|
||||||
|
.text(r#"{"litres":2}"#)
|
||||||
|
.content_type("application/json")
|
||||||
|
.await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text(r#"{"pints":3.519508}"#);
|
||||||
|
|
||||||
|
let sever = test_server();
|
||||||
|
|
||||||
|
for i in 0..=11 {
|
||||||
|
let response = sever.post("/9/milk").await;
|
||||||
|
match i {
|
||||||
|
0..=4 | 6..=10 => {
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("Milk withdrawn\n");
|
||||||
|
}
|
||||||
|
5 | 11 => {
|
||||||
|
response.assert_status(StatusCode::TOO_MANY_REQUESTS);
|
||||||
|
response.assert_text("No milk available\n");
|
||||||
|
let response = sever.post("/9/refill").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
response.assert_status(StatusCode::SERVICE_UNAVAILABLE);
|
||||||
|
response.assert_text("No milk available\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
tests/task_two/main.rs
Normal file
87
tests/task_two/main.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#[cfg(all(test, feature = "task1-9"))]
|
||||||
|
mod task_two {
|
||||||
|
use axum_test::TestServer;
|
||||||
|
use itsscb_shuttlings_cch24::router;
|
||||||
|
|
||||||
|
fn test_server() -> TestServer {
|
||||||
|
TestServer::new(router()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ipv4_dest() {
|
||||||
|
let server = test_server();
|
||||||
|
|
||||||
|
let response = server.get("/2/dest?from=10.0.0.0&key=1.2.3.255").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("11.2.3.255");
|
||||||
|
|
||||||
|
let response = server.get("/2/dest?from=invalid-from&key=1.2.3.255").await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
|
||||||
|
let response = server.get("/2/dest?from=10.0.0.0&key=invalid-key").await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.get("/2/dest?from=128.128.33.0&key=255.0.255.33")
|
||||||
|
.await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("127.128.32.33");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ipv4_key() {
|
||||||
|
let server = test_server();
|
||||||
|
|
||||||
|
let response = server.get("/2/key?from=10.0.0.0&to=11.2.3.255").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("1.2.3.255");
|
||||||
|
|
||||||
|
let response = server.get("/2/key?from=invalid-from&to=1.2.3.255").await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
|
||||||
|
let response = server.get("/2/key?from=10.0.0.0&to=invalid-to").await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.get("/2/key?from=128.128.33.0&to=127.128.32.33")
|
||||||
|
.await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("255.0.255.33");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ipv6_dest() {
|
||||||
|
let server = test_server();
|
||||||
|
|
||||||
|
let response = server.get("/2/v6/dest?from=fe80::1&key=5:6:7::3333").await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("fe85:6:7::3332");
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.get("/2/v6/dest?from=invalid-from&key=5:6:7::3333")
|
||||||
|
.await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
|
||||||
|
let response = server.get("/2/v6/dest?from=fe80::1&key=invalid-key").await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ipv6_key() {
|
||||||
|
let server = test_server();
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.get("/2/v6/key?from=aaaa::aaaa&to=5555:ffff:c:0:0:c:1234:5555")
|
||||||
|
.await;
|
||||||
|
response.assert_status_ok();
|
||||||
|
response.assert_text("ffff:ffff:c::c:1234:ffff");
|
||||||
|
|
||||||
|
let response = server
|
||||||
|
.get("/2/v6/dest?from=invalid-from&to=5:6:7::3333")
|
||||||
|
.await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
|
||||||
|
let response = server.get("/2/v6/dest?from=fe80::1&to=invalid-to").await;
|
||||||
|
response.assert_status_bad_request();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user